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