1 /* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "ProgressTracker.h" 28 29 #include "DocumentLoader.h" 30 #include "Frame.h" 31 #include "FrameLoader.h" 32 #include "FrameLoaderStateMachine.h" 33 #include "FrameLoaderClient.h" 34 #include "Logging.h" 35 #include "ResourceResponse.h" 36 #include <wtf/text/CString.h> 37 #include <wtf/CurrentTime.h> 38 39 using std::min; 40 41 namespace WebCore { 42 43 // Always start progress at initialProgressValue. This helps provide feedback as 44 // soon as a load starts. 45 static const double initialProgressValue = 0.1; 46 47 // Similarly, always leave space at the end. This helps show the user that we're not done 48 // until we're done. 49 static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue 50 51 static const int progressItemDefaultEstimatedLength = 1024 * 16; 52 53 struct ProgressItem { 54 WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED; 55 public: 56 ProgressItem(long long length) 57 : bytesReceived(0) 58 , estimatedLength(length) { } 59 60 long long bytesReceived; 61 long long estimatedLength; 62 }; 63 64 unsigned long ProgressTracker::s_uniqueIdentifier = 0; 65 66 ProgressTracker::ProgressTracker() 67 : m_totalPageAndResourceBytesToLoad(0) 68 , m_totalBytesReceived(0) 69 , m_lastNotifiedProgressValue(0) 70 , m_lastNotifiedProgressTime(0) 71 , m_progressNotificationInterval(0.02) 72 , m_progressNotificationTimeInterval(0.1) 73 , m_finalProgressChangedSent(false) 74 , m_progressValue(0) 75 , m_numProgressTrackedFrames(0) 76 { 77 } 78 79 ProgressTracker::~ProgressTracker() 80 { 81 deleteAllValues(m_progressItems); 82 } 83 84 double ProgressTracker::estimatedProgress() const 85 { 86 return m_progressValue; 87 } 88 89 void ProgressTracker::reset() 90 { 91 deleteAllValues(m_progressItems); 92 m_progressItems.clear(); 93 94 m_totalPageAndResourceBytesToLoad = 0; 95 m_totalBytesReceived = 0; 96 m_progressValue = 0; 97 m_lastNotifiedProgressValue = 0; 98 m_lastNotifiedProgressTime = 0; 99 m_finalProgressChangedSent = false; 100 m_numProgressTrackedFrames = 0; 101 m_originatingProgressFrame = 0; 102 } 103 104 void ProgressTracker::progressStarted(Frame* frame) 105 { 106 LOG(Progress, "Progress started (%p) - frame %p(\"%s\"), value %f, tracked frames %d, originating frame %p", this, frame, frame->tree()->uniqueName().string().utf8().data(), m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); 107 108 frame->loader()->client()->willChangeEstimatedProgress(); 109 110 if (m_numProgressTrackedFrames == 0 || m_originatingProgressFrame == frame) { 111 reset(); 112 m_progressValue = initialProgressValue; 113 m_originatingProgressFrame = frame; 114 115 m_originatingProgressFrame->loader()->client()->postProgressStartedNotification(); 116 } 117 m_numProgressTrackedFrames++; 118 119 frame->loader()->client()->didChangeEstimatedProgress(); 120 } 121 122 void ProgressTracker::progressCompleted(Frame* frame) 123 { 124 LOG(Progress, "Progress completed (%p) - frame %p(\"%s\"), value %f, tracked frames %d, originating frame %p", this, frame, frame->tree()->uniqueName().string().utf8().data(), m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); 125 126 if (m_numProgressTrackedFrames <= 0) 127 return; 128 129 frame->loader()->client()->willChangeEstimatedProgress(); 130 131 m_numProgressTrackedFrames--; 132 if (m_numProgressTrackedFrames == 0 || 133 (frame == m_originatingProgressFrame && m_numProgressTrackedFrames != 0)) 134 finalProgressComplete(); 135 136 frame->loader()->client()->didChangeEstimatedProgress(); 137 } 138 139 void ProgressTracker::finalProgressComplete() 140 { 141 LOG(Progress, "Final progress complete (%p)", this); 142 143 RefPtr<Frame> frame = m_originatingProgressFrame.release(); 144 145 // Before resetting progress value be sure to send client a least one notification 146 // with final progress value. 147 if (!m_finalProgressChangedSent) { 148 m_progressValue = 1; 149 frame->loader()->client()->postProgressEstimateChangedNotification(); 150 } 151 152 reset(); 153 154 frame->loader()->client()->setMainFrameDocumentReady(true); 155 frame->loader()->client()->postProgressFinishedNotification(); 156 } 157 158 void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) 159 { 160 LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d, originating frame %p", this, m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); 161 162 if (m_numProgressTrackedFrames <= 0) 163 return; 164 165 long long estimatedLength = response.expectedContentLength(); 166 if (estimatedLength < 0) 167 estimatedLength = progressItemDefaultEstimatedLength; 168 169 m_totalPageAndResourceBytesToLoad += estimatedLength; 170 171 if (ProgressItem* item = m_progressItems.get(identifier)) { 172 item->bytesReceived = 0; 173 item->estimatedLength = estimatedLength; 174 } else 175 m_progressItems.set(identifier, adoptPtr(new ProgressItem(estimatedLength)).leakPtr()); 176 } 177 178 void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) 179 { 180 ProgressItem* item = m_progressItems.get(identifier); 181 182 // FIXME: Can this ever happen? 183 if (!item) 184 return; 185 186 RefPtr<Frame> frame = m_originatingProgressFrame; 187 188 frame->loader()->client()->willChangeEstimatedProgress(); 189 190 unsigned bytesReceived = length; 191 double increment, percentOfRemainingBytes; 192 long long remainingBytes, estimatedBytesForPendingRequests; 193 194 item->bytesReceived += bytesReceived; 195 if (item->bytesReceived > item->estimatedLength) { 196 m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); 197 item->estimatedLength = item->bytesReceived * 2; 198 } 199 200 int numPendingOrLoadingRequests = frame->loader()->numPendingOrLoadingRequests(true); 201 estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; 202 remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); 203 if (remainingBytes > 0) // Prevent divide by 0. 204 percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; 205 else 206 percentOfRemainingBytes = 1.0; 207 208 // For documents that use WebCore's layout system, treat first layout as the half-way point. 209 // FIXME: The hasHTMLView function is a sort of roundabout way of asking "do you use WebCore's layout system". 210 bool useClampedMaxProgress = frame->loader()->client()->hasHTMLView() 211 && !frame->loader()->stateMachine()->firstLayoutDone(); 212 double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; 213 increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; 214 m_progressValue += increment; 215 m_progressValue = min(m_progressValue, maxProgressValue); 216 ASSERT(m_progressValue >= initialProgressValue); 217 218 m_totalBytesReceived += bytesReceived; 219 220 double now = currentTime(); 221 double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; 222 223 LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d", this, m_progressValue, m_numProgressTrackedFrames); 224 double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; 225 if ((notificationProgressDelta >= m_progressNotificationInterval || 226 notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) && 227 m_numProgressTrackedFrames > 0) { 228 if (!m_finalProgressChangedSent) { 229 if (m_progressValue == 1) 230 m_finalProgressChangedSent = true; 231 232 frame->loader()->client()->postProgressEstimateChangedNotification(); 233 234 m_lastNotifiedProgressValue = m_progressValue; 235 m_lastNotifiedProgressTime = now; 236 } 237 } 238 239 frame->loader()->client()->didChangeEstimatedProgress(); 240 } 241 242 void ProgressTracker::completeProgress(unsigned long identifier) 243 { 244 ProgressItem* item = m_progressItems.get(identifier); 245 246 // This can happen if a load fails without receiving any response data. 247 if (!item) 248 return; 249 250 // Adjust the total expected bytes to account for any overage/underage. 251 long long delta = item->bytesReceived - item->estimatedLength; 252 m_totalPageAndResourceBytesToLoad += delta; 253 item->estimatedLength = item->bytesReceived; 254 255 m_progressItems.remove(identifier); 256 delete item; 257 } 258 259 unsigned long ProgressTracker::createUniqueIdentifier() 260 { 261 return ++s_uniqueIdentifier; 262 } 263 264 265 } 266