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 39using std::min; 40 41namespace WebCore { 42 43// Always start progress at initialProgressValue. This helps provide feedback as 44// soon as a load starts. 45static 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. 49static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue 50 51static const int progressItemDefaultEstimatedLength = 1024 * 16; 52 53struct ProgressItem { 54 WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED; 55public: 56 ProgressItem(long long length) 57 : bytesReceived(0) 58 , estimatedLength(length) { } 59 60 long long bytesReceived; 61 long long estimatedLength; 62}; 63 64unsigned long ProgressTracker::s_uniqueIdentifier = 0; 65 66ProgressTracker::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 79ProgressTracker::~ProgressTracker() 80{ 81 deleteAllValues(m_progressItems); 82} 83 84double ProgressTracker::estimatedProgress() const 85{ 86 return m_progressValue; 87} 88 89void 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 104void 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 122void 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 139void 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 158void 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 178void 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 242void 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 259unsigned long ProgressTracker::createUniqueIdentifier() 260{ 261 return ++s_uniqueIdentifier; 262} 263 264 265} 266