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 "InspectorInstrumentation.h" 35#include "Logging.h" 36#include "ResourceResponse.h" 37#include <wtf/text/CString.h> 38#include <wtf/CurrentTime.h> 39 40using std::min; 41 42namespace WebCore { 43 44// Always start progress at initialProgressValue. This helps provide feedback as 45// soon as a load starts. 46static const double initialProgressValue = 0.1; 47 48// Similarly, always leave space at the end. This helps show the user that we're not done 49// until we're done. 50static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue 51 52static const int progressItemDefaultEstimatedLength = 1024 * 16; 53 54// Check if the load is progressing this often. 55static const double progressHeartbeatInterval = 0.1; 56// How many heartbeats must pass without progress before deciding the load is currently stalled. 57static const unsigned loadStalledHeartbeatCount = 4; 58// How many bytes are required between heartbeats to consider it progress. 59static const unsigned minumumBytesPerHeartbeatForProgress = 1024; 60 61struct ProgressItem { 62 WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED; 63public: 64 ProgressItem(long long length) 65 : bytesReceived(0) 66 , estimatedLength(length) { } 67 68 long long bytesReceived; 69 long long estimatedLength; 70}; 71 72unsigned long ProgressTracker::s_uniqueIdentifier = 0; 73 74ProgressTracker::ProgressTracker() 75 : m_totalPageAndResourceBytesToLoad(0) 76 , m_totalBytesReceived(0) 77 , m_lastNotifiedProgressValue(0) 78 , m_lastNotifiedProgressTime(0) 79 , m_progressNotificationInterval(0.02) 80 , m_progressNotificationTimeInterval(0.1) 81 , m_finalProgressChangedSent(false) 82 , m_progressValue(0) 83 , m_numProgressTrackedFrames(0) 84 , m_progressHeartbeatTimer(this, &ProgressTracker::progressHeartbeatTimerFired) 85 , m_heartbeatsWithNoProgress(0) 86 , m_totalBytesReceivedBeforePreviousHeartbeat(0) 87{ 88} 89 90ProgressTracker::~ProgressTracker() 91{ 92} 93 94PassOwnPtr<ProgressTracker> ProgressTracker::create() 95{ 96 return adoptPtr(new ProgressTracker); 97} 98 99double ProgressTracker::estimatedProgress() const 100{ 101 return m_progressValue; 102} 103 104void ProgressTracker::reset() 105{ 106 m_progressItems.clear(); 107 108 m_totalPageAndResourceBytesToLoad = 0; 109 m_totalBytesReceived = 0; 110 m_progressValue = 0; 111 m_lastNotifiedProgressValue = 0; 112 m_lastNotifiedProgressTime = 0; 113 m_finalProgressChangedSent = false; 114 m_numProgressTrackedFrames = 0; 115 m_originatingProgressFrame = 0; 116 117 m_heartbeatsWithNoProgress = 0; 118 m_totalBytesReceivedBeforePreviousHeartbeat = 0; 119 m_progressHeartbeatTimer.stop(); 120} 121 122void ProgressTracker::progressStarted(Frame* frame) 123{ 124 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()); 125 126 frame->loader()->client()->willChangeEstimatedProgress(); 127 128 if (m_numProgressTrackedFrames == 0 || m_originatingProgressFrame == frame) { 129 reset(); 130 m_progressValue = initialProgressValue; 131 m_originatingProgressFrame = frame; 132 133 m_progressHeartbeatTimer.startRepeating(progressHeartbeatInterval); 134 m_originatingProgressFrame->loader()->loadProgressingStatusChanged(); 135 136 m_originatingProgressFrame->loader()->client()->postProgressStartedNotification(); 137 } 138 m_numProgressTrackedFrames++; 139 140 frame->loader()->client()->didChangeEstimatedProgress(); 141 InspectorInstrumentation::frameStartedLoading(frame); 142} 143 144void ProgressTracker::progressCompleted(Frame* frame) 145{ 146 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()); 147 148 if (m_numProgressTrackedFrames <= 0) 149 return; 150 151 frame->loader()->client()->willChangeEstimatedProgress(); 152 153 m_numProgressTrackedFrames--; 154 if (!m_numProgressTrackedFrames || m_originatingProgressFrame == frame) 155 finalProgressComplete(); 156 157 frame->loader()->client()->didChangeEstimatedProgress(); 158} 159 160void ProgressTracker::finalProgressComplete() 161{ 162 LOG(Progress, "Final progress complete (%p)", this); 163 164 RefPtr<Frame> frame = m_originatingProgressFrame.release(); 165 166 // Before resetting progress value be sure to send client a least one notification 167 // with final progress value. 168 if (!m_finalProgressChangedSent) { 169 m_progressValue = 1; 170 frame->loader()->client()->postProgressEstimateChangedNotification(); 171 } 172 173 reset(); 174 175 frame->loader()->client()->setMainFrameDocumentReady(true); 176 frame->loader()->client()->postProgressFinishedNotification(); 177 frame->loader()->loadProgressingStatusChanged(); 178 179 InspectorInstrumentation::frameStoppedLoading(frame.get()); 180} 181 182void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) 183{ 184 LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d, originating frame %p", this, m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); 185 186 if (m_numProgressTrackedFrames <= 0) 187 return; 188 189 long long estimatedLength = response.expectedContentLength(); 190 if (estimatedLength < 0) 191 estimatedLength = progressItemDefaultEstimatedLength; 192 193 m_totalPageAndResourceBytesToLoad += estimatedLength; 194 195 if (ProgressItem* item = m_progressItems.get(identifier)) { 196 item->bytesReceived = 0; 197 item->estimatedLength = estimatedLength; 198 } else 199 m_progressItems.set(identifier, adoptPtr(new ProgressItem(estimatedLength))); 200} 201 202void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) 203{ 204 ProgressItem* item = m_progressItems.get(identifier); 205 206 // FIXME: Can this ever happen? 207 if (!item) 208 return; 209 210 RefPtr<Frame> frame = m_originatingProgressFrame; 211 212 frame->loader()->client()->willChangeEstimatedProgress(); 213 214 unsigned bytesReceived = length; 215 double increment, percentOfRemainingBytes; 216 long long remainingBytes, estimatedBytesForPendingRequests; 217 218 item->bytesReceived += bytesReceived; 219 if (item->bytesReceived > item->estimatedLength) { 220 m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); 221 item->estimatedLength = item->bytesReceived * 2; 222 } 223 224 int numPendingOrLoadingRequests = frame->loader()->numPendingOrLoadingRequests(true); 225 estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; 226 remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); 227 if (remainingBytes > 0) // Prevent divide by 0. 228 percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; 229 else 230 percentOfRemainingBytes = 1.0; 231 232 // For documents that use WebCore's layout system, treat first layout as the half-way point. 233 // FIXME: The hasHTMLView function is a sort of roundabout way of asking "do you use WebCore's layout system". 234 bool useClampedMaxProgress = frame->loader()->client()->hasHTMLView() 235 && !frame->loader()->stateMachine()->firstLayoutDone(); 236 double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; 237 increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; 238 m_progressValue += increment; 239 m_progressValue = min(m_progressValue, maxProgressValue); 240 ASSERT(m_progressValue >= initialProgressValue); 241 242 m_totalBytesReceived += bytesReceived; 243 244 double now = currentTime(); 245 double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; 246 247 LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d", this, m_progressValue, m_numProgressTrackedFrames); 248 double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; 249 if ((notificationProgressDelta >= m_progressNotificationInterval || 250 notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) && 251 m_numProgressTrackedFrames > 0) { 252 if (!m_finalProgressChangedSent) { 253 if (m_progressValue == 1) 254 m_finalProgressChangedSent = true; 255 256 frame->loader()->client()->postProgressEstimateChangedNotification(); 257 258 m_lastNotifiedProgressValue = m_progressValue; 259 m_lastNotifiedProgressTime = now; 260 } 261 } 262 263 frame->loader()->client()->didChangeEstimatedProgress(); 264} 265 266void ProgressTracker::completeProgress(unsigned long identifier) 267{ 268 ProgressItem* item = m_progressItems.get(identifier); 269 270 // This can happen if a load fails without receiving any response data. 271 if (!item) 272 return; 273 274 // Adjust the total expected bytes to account for any overage/underage. 275 long long delta = item->bytesReceived - item->estimatedLength; 276 m_totalPageAndResourceBytesToLoad += delta; 277 278 m_progressItems.remove(identifier); 279} 280 281unsigned long ProgressTracker::createUniqueIdentifier() 282{ 283 return ++s_uniqueIdentifier; 284} 285 286bool ProgressTracker::isMainLoadProgressing() const 287{ 288 if (!m_originatingProgressFrame) 289 return false; 290 // See if the load originated from a subframe. 291 if (m_originatingProgressFrame->tree()->parent()) 292 return false; 293 return m_progressValue && m_progressValue < finalProgressValue && m_heartbeatsWithNoProgress < loadStalledHeartbeatCount; 294} 295 296void ProgressTracker::progressHeartbeatTimerFired(Timer<ProgressTracker>*) 297{ 298 if (m_totalBytesReceived < m_totalBytesReceivedBeforePreviousHeartbeat + minumumBytesPerHeartbeatForProgress) 299 ++m_heartbeatsWithNoProgress; 300 else 301 m_heartbeatsWithNoProgress = 0; 302 303 m_totalBytesReceivedBeforePreviousHeartbeat = m_totalBytesReceived; 304 305 if (m_originatingProgressFrame) 306 m_originatingProgressFrame->loader()->loadProgressingStatusChanged(); 307 308 if (m_progressValue >= finalProgressValue) 309 m_progressHeartbeatTimer.stop(); 310} 311 312} 313