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 WebCore { 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 ProgressTracker::ProgressTracker(LocalFrame* frame) 66 : m_frame(frame) 67 , m_inProgress(false) 68 , m_totalPageAndResourceBytesToLoad(0) 69 , m_totalBytesReceived(0) 70 , m_lastNotifiedProgressValue(0) 71 , m_lastNotifiedProgressTime(0) 72 , m_progressNotificationInterval(0.02) 73 , m_progressNotificationTimeInterval(0.1) 74 , m_finalProgressChangedSent(false) 75 , m_progressValue(0) 76 { 77 } 78 79 ProgressTracker::~ProgressTracker() 80 { 81 if (m_inProgress) 82 progressCompleted(); 83 } 84 85 PassOwnPtr<ProgressTracker> ProgressTracker::create(LocalFrame* frame) 86 { 87 return adoptPtr(new ProgressTracker(frame)); 88 } 89 90 double ProgressTracker::estimatedProgress() const 91 { 92 return m_progressValue; 93 } 94 95 void ProgressTracker::reset() 96 { 97 m_progressItems.clear(); 98 99 m_totalPageAndResourceBytesToLoad = 0; 100 m_totalBytesReceived = 0; 101 m_progressValue = 0; 102 m_lastNotifiedProgressValue = 0; 103 m_lastNotifiedProgressTime = 0; 104 m_finalProgressChangedSent = false; 105 } 106 107 void ProgressTracker::progressStarted() 108 { 109 if (!m_inProgress) { 110 reset(); 111 m_progressValue = initialProgressValue; 112 m_frame->loader().client()->didStartLoading(NavigationToDifferentDocument); 113 } 114 m_inProgress = true; 115 InspectorInstrumentation::frameStartedLoading(m_frame); 116 } 117 118 void ProgressTracker::progressCompleted() 119 { 120 ASSERT(m_inProgress); 121 m_inProgress = false; 122 if (!m_finalProgressChangedSent) { 123 m_progressValue = 1; 124 m_frame->loader().client()->progressEstimateChanged(m_progressValue); 125 } 126 reset(); 127 m_frame->loader().client()->didStopLoading(); 128 InspectorInstrumentation::frameStoppedLoading(m_frame); 129 } 130 131 void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) 132 { 133 if (!m_inProgress) 134 return; 135 136 long long estimatedLength = response.expectedContentLength(); 137 if (estimatedLength < 0) 138 estimatedLength = progressItemDefaultEstimatedLength; 139 140 m_totalPageAndResourceBytesToLoad += estimatedLength; 141 142 if (ProgressItem* item = m_progressItems.get(identifier)) { 143 item->bytesReceived = 0; 144 item->estimatedLength = estimatedLength; 145 } else 146 m_progressItems.set(identifier, adoptPtr(new ProgressItem(estimatedLength))); 147 } 148 149 void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) 150 { 151 ProgressItem* item = m_progressItems.get(identifier); 152 153 // FIXME: Can this ever happen? 154 if (!item) 155 return; 156 157 unsigned bytesReceived = length; 158 double increment, percentOfRemainingBytes; 159 long long remainingBytes, estimatedBytesForPendingRequests; 160 161 item->bytesReceived += bytesReceived; 162 if (item->bytesReceived > item->estimatedLength) { 163 m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); 164 item->estimatedLength = item->bytesReceived * 2; 165 } 166 167 int numPendingOrLoadingRequests = m_frame->document()->fetcher()->requestCount(); 168 estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; 169 remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); 170 if (remainingBytes > 0) // Prevent divide by 0. 171 percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; 172 else 173 percentOfRemainingBytes = 1.0; 174 175 // For documents that use WebCore's layout system, treat first layout as the half-way point. 176 bool useClampedMaxProgress = !m_frame->view()->didFirstLayout(); 177 double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; 178 increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; 179 m_progressValue += increment; 180 m_progressValue = min(m_progressValue, maxProgressValue); 181 ASSERT(m_progressValue >= initialProgressValue); 182 183 m_totalBytesReceived += bytesReceived; 184 185 double now = currentTime(); 186 double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; 187 188 double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; 189 if (notificationProgressDelta >= m_progressNotificationInterval || notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) { 190 if (!m_finalProgressChangedSent) { 191 if (m_progressValue == 1) 192 m_finalProgressChangedSent = true; 193 194 m_frame->loader().client()->progressEstimateChanged(m_progressValue); 195 196 m_lastNotifiedProgressValue = m_progressValue; 197 m_lastNotifiedProgressTime = now; 198 } 199 } 200 } 201 202 void ProgressTracker::completeProgress(unsigned long identifier) 203 { 204 ProgressItem* item = m_progressItems.get(identifier); 205 206 // This can happen if a load fails without receiving any response data. 207 if (!item) 208 return; 209 210 // Adjust the total expected bytes to account for any overage/underage. 211 long long delta = item->bytesReceived - item->estimatedLength; 212 m_totalPageAndResourceBytesToLoad += delta; 213 214 m_progressItems.remove(identifier); 215 } 216 217 } 218