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 "core/loader/ProgressTracker.h" 28 29#include "core/fetch/ResourceFetcher.h" 30#include "core/frame/FrameView.h" 31#include "core/frame/LocalFrame.h" 32#include "core/inspector/InspectorInstrumentation.h" 33#include "core/loader/FrameLoader.h" 34#include "core/loader/FrameLoaderClient.h" 35#include "platform/Logging.h" 36#include "platform/network/ResourceResponse.h" 37#include "wtf/CurrentTime.h" 38#include "wtf/text/CString.h" 39 40using std::min; 41 42namespace blink { 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 54struct ProgressItem { 55 WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED; 56public: 57 ProgressItem(long long length) 58 : bytesReceived(0) 59 , estimatedLength(length) { } 60 61 long long bytesReceived; 62 long long estimatedLength; 63}; 64 65PassOwnPtrWillBeRawPtr<ProgressTracker> ProgressTracker::create(LocalFrame* frame) 66{ 67 return adoptPtrWillBeNoop(new ProgressTracker(frame)); 68} 69 70ProgressTracker::ProgressTracker(LocalFrame* frame) 71 : m_frame(frame) 72 , m_inProgress(false) 73 , m_totalPageAndResourceBytesToLoad(0) 74 , m_totalBytesReceived(0) 75 , m_lastNotifiedProgressValue(0) 76 , m_lastNotifiedProgressTime(0) 77 , m_progressNotificationInterval(0.02) 78 , m_progressNotificationTimeInterval(0.1) 79 , m_finalProgressChangedSent(false) 80 , m_progressValue(0) 81{ 82} 83 84ProgressTracker::~ProgressTracker() 85{ 86 ASSERT(!m_inProgress); 87} 88 89void ProgressTracker::trace(Visitor* visitor) 90{ 91 visitor->trace(m_frame); 92} 93 94void ProgressTracker::dispose() 95{ 96 if (m_inProgress) 97 progressCompleted(); 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 = 0; 114 m_finalProgressChangedSent = false; 115} 116 117void ProgressTracker::progressStarted() 118{ 119 if (!m_inProgress) { 120 reset(); 121 m_progressValue = initialProgressValue; 122 m_frame->loader().client()->didStartLoading(NavigationToDifferentDocument); 123 } 124 m_inProgress = true; 125 InspectorInstrumentation::frameStartedLoading(m_frame); 126} 127 128void ProgressTracker::progressCompleted() 129{ 130 ASSERT(m_inProgress); 131 m_inProgress = false; 132 if (!m_finalProgressChangedSent) { 133 m_progressValue = 1; 134 m_frame->loader().client()->progressEstimateChanged(m_progressValue); 135 } 136 reset(); 137 m_frame->loader().client()->didStopLoading(); 138 InspectorInstrumentation::frameStoppedLoading(m_frame); 139} 140 141void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) 142{ 143 if (!m_inProgress) 144 return; 145 146 long long estimatedLength = response.expectedContentLength(); 147 if (estimatedLength < 0) 148 estimatedLength = progressItemDefaultEstimatedLength; 149 150 m_totalPageAndResourceBytesToLoad += estimatedLength; 151 152 if (ProgressItem* item = m_progressItems.get(identifier)) { 153 item->bytesReceived = 0; 154 item->estimatedLength = estimatedLength; 155 } else 156 m_progressItems.set(identifier, adoptPtr(new ProgressItem(estimatedLength))); 157} 158 159void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) 160{ 161 ProgressItem* item = m_progressItems.get(identifier); 162 163 // FIXME: Can this ever happen? 164 if (!item) 165 return; 166 167 unsigned bytesReceived = length; 168 double increment, percentOfRemainingBytes; 169 long long remainingBytes, estimatedBytesForPendingRequests; 170 171 item->bytesReceived += bytesReceived; 172 if (item->bytesReceived > item->estimatedLength) { 173 m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); 174 item->estimatedLength = item->bytesReceived * 2; 175 } 176 177 int numPendingOrLoadingRequests = m_frame->document()->fetcher()->requestCount(); 178 estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; 179 remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); 180 if (remainingBytes > 0) // Prevent divide by 0. 181 percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; 182 else 183 percentOfRemainingBytes = 1.0; 184 185 // For documents that use WebCore's layout system, treat first layout as the half-way point. 186 bool useClampedMaxProgress = !m_frame->view()->didFirstLayout(); 187 double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; 188 increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; 189 m_progressValue += increment; 190 m_progressValue = min(m_progressValue, maxProgressValue); 191 ASSERT(m_progressValue >= initialProgressValue); 192 193 m_totalBytesReceived += bytesReceived; 194 195 double now = currentTime(); 196 double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; 197 198 double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; 199 if (notificationProgressDelta >= m_progressNotificationInterval || notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) { 200 if (!m_finalProgressChangedSent) { 201 if (m_progressValue == 1) 202 m_finalProgressChangedSent = true; 203 204 m_frame->loader().client()->progressEstimateChanged(m_progressValue); 205 206 m_lastNotifiedProgressValue = m_progressValue; 207 m_lastNotifiedProgressTime = now; 208 } 209 } 210} 211 212void ProgressTracker::completeProgress(unsigned long identifier) 213{ 214 ProgressItem* item = m_progressItems.get(identifier); 215 216 // This can happen if a load fails without receiving any response data. 217 if (!item) 218 return; 219 220 // Adjust the total expected bytes to account for any overage/underage. 221 long long delta = item->bytesReceived - item->estimatedLength; 222 m_totalPageAndResourceBytesToLoad += delta; 223 224 m_progressItems.remove(identifier); 225} 226 227} 228