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 40 using std::min; 41 42 namespace blink { 43 44 // Always start progress at initialProgressValue. This helps provide feedback as 45 // soon as a load starts. 46 static 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. 50 static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue 51 52 static const int progressItemDefaultEstimatedLength = 1024 * 16; 53 54 struct ProgressItem { 55 WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED; 56 public: 57 ProgressItem(long long length) 58 : bytesReceived(0) 59 , estimatedLength(length) { } 60 61 long long bytesReceived; 62 long long estimatedLength; 63 }; 64 65 PassOwnPtrWillBeRawPtr<ProgressTracker> ProgressTracker::create(LocalFrame* frame) 66 { 67 return adoptPtrWillBeNoop(new ProgressTracker(frame)); 68 } 69 70 ProgressTracker::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 84 ProgressTracker::~ProgressTracker() 85 { 86 ASSERT(!m_inProgress); 87 } 88 89 void ProgressTracker::trace(Visitor* visitor) 90 { 91 visitor->trace(m_frame); 92 } 93 94 void ProgressTracker::dispose() 95 { 96 if (m_inProgress) 97 progressCompleted(); 98 } 99 100 double ProgressTracker::estimatedProgress() const 101 { 102 return m_progressValue; 103 } 104 105 void 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 117 void 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 128 void 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 141 void 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 159 void 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 212 void 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