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/frame/Frame.h" 30 #include "core/frame/FrameView.h" 31 #include "core/inspector/InspectorInstrumentation.h" 32 #include "core/loader/FrameLoader.h" 33 #include "core/loader/FrameLoaderClient.h" 34 #include "platform/Logging.h" 35 #include "platform/network/ResourceResponse.h" 36 #include "wtf/CurrentTime.h" 37 #include "wtf/text/CString.h" 38 39 using std::min; 40 41 namespace WebCore { 42 43 // Always start progress at initialProgressValue. This helps provide feedback as 44 // soon as a load starts. 45 static 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. 49 static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue 50 51 static const int progressItemDefaultEstimatedLength = 1024 * 16; 52 53 struct ProgressItem { 54 WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED; 55 public: 56 ProgressItem(long long length) 57 : bytesReceived(0) 58 , estimatedLength(length) { } 59 60 long long bytesReceived; 61 long long estimatedLength; 62 }; 63 64 ProgressTracker::ProgressTracker() 65 : m_totalPageAndResourceBytesToLoad(0) 66 , m_totalBytesReceived(0) 67 , m_lastNotifiedProgressValue(0) 68 , m_lastNotifiedProgressTime(0) 69 , m_progressNotificationInterval(0.02) 70 , m_progressNotificationTimeInterval(0.1) 71 , m_finalProgressChangedSent(false) 72 , m_progressValue(0) 73 , m_numProgressTrackedFrames(0) 74 { 75 } 76 77 ProgressTracker::~ProgressTracker() 78 { 79 } 80 81 PassOwnPtr<ProgressTracker> ProgressTracker::create() 82 { 83 return adoptPtr(new ProgressTracker); 84 } 85 86 double ProgressTracker::estimatedProgress() const 87 { 88 return m_progressValue; 89 } 90 91 void ProgressTracker::reset() 92 { 93 m_progressItems.clear(); 94 95 m_totalPageAndResourceBytesToLoad = 0; 96 m_totalBytesReceived = 0; 97 m_progressValue = 0; 98 m_lastNotifiedProgressValue = 0; 99 m_lastNotifiedProgressTime = 0; 100 m_finalProgressChangedSent = false; 101 m_numProgressTrackedFrames = 0; 102 m_originatingProgressFrame = 0; 103 } 104 105 void ProgressTracker::progressStarted(Frame* frame) 106 { 107 WTF_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()); 108 109 if (m_numProgressTrackedFrames == 0 || m_originatingProgressFrame == frame) { 110 reset(); 111 m_progressValue = initialProgressValue; 112 m_originatingProgressFrame = frame; 113 114 m_originatingProgressFrame->loader().client()->postProgressStartedNotification(); 115 } 116 m_numProgressTrackedFrames++; 117 InspectorInstrumentation::frameStartedLoading(frame); 118 } 119 120 void ProgressTracker::progressCompleted(Frame* frame) 121 { 122 WTF_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()); 123 124 if (m_numProgressTrackedFrames <= 0) 125 return; 126 m_numProgressTrackedFrames--; 127 if (!m_numProgressTrackedFrames || m_originatingProgressFrame == frame) 128 finalProgressComplete(); 129 } 130 131 void ProgressTracker::finalProgressComplete() 132 { 133 WTF_LOG(Progress, "Final progress complete (%p)", this); 134 135 RefPtr<Frame> frame = m_originatingProgressFrame.release(); 136 137 // Before resetting progress value be sure to send client a least one notification 138 // with final progress value. 139 if (!m_finalProgressChangedSent) { 140 m_progressValue = 1; 141 frame->loader().client()->postProgressEstimateChangedNotification(); 142 } 143 144 reset(); 145 frame->loader().client()->postProgressFinishedNotification(); 146 InspectorInstrumentation::frameStoppedLoading(frame.get()); 147 } 148 149 void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) 150 { 151 WTF_LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d, originating frame %p", this, m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); 152 153 if (m_numProgressTrackedFrames <= 0) 154 return; 155 156 long long estimatedLength = response.expectedContentLength(); 157 if (estimatedLength < 0) 158 estimatedLength = progressItemDefaultEstimatedLength; 159 160 m_totalPageAndResourceBytesToLoad += estimatedLength; 161 162 if (ProgressItem* item = m_progressItems.get(identifier)) { 163 item->bytesReceived = 0; 164 item->estimatedLength = estimatedLength; 165 } else 166 m_progressItems.set(identifier, adoptPtr(new ProgressItem(estimatedLength))); 167 } 168 169 void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) 170 { 171 ProgressItem* item = m_progressItems.get(identifier); 172 173 // FIXME: Can this ever happen? 174 if (!item) 175 return; 176 177 RefPtr<Frame> frame = m_originatingProgressFrame; 178 179 unsigned bytesReceived = length; 180 double increment, percentOfRemainingBytes; 181 long long remainingBytes, estimatedBytesForPendingRequests; 182 183 item->bytesReceived += bytesReceived; 184 if (item->bytesReceived > item->estimatedLength) { 185 m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); 186 item->estimatedLength = item->bytesReceived * 2; 187 } 188 189 int numPendingOrLoadingRequests = frame->loader().numPendingOrLoadingRequests(true); 190 estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; 191 remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); 192 if (remainingBytes > 0) // Prevent divide by 0. 193 percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; 194 else 195 percentOfRemainingBytes = 1.0; 196 197 // For documents that use WebCore's layout system, treat first layout as the half-way point. 198 bool useClampedMaxProgress = !frame->view()->didFirstLayout(); 199 double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; 200 increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; 201 m_progressValue += increment; 202 m_progressValue = min(m_progressValue, maxProgressValue); 203 ASSERT(m_progressValue >= initialProgressValue); 204 205 m_totalBytesReceived += bytesReceived; 206 207 double now = currentTime(); 208 double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; 209 210 WTF_LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d", this, m_progressValue, m_numProgressTrackedFrames); 211 double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; 212 if ((notificationProgressDelta >= m_progressNotificationInterval || 213 notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) && 214 m_numProgressTrackedFrames > 0) { 215 if (!m_finalProgressChangedSent) { 216 if (m_progressValue == 1) 217 m_finalProgressChangedSent = true; 218 219 frame->loader().client()->postProgressEstimateChangedNotification(); 220 221 m_lastNotifiedProgressValue = m_progressValue; 222 m_lastNotifiedProgressTime = now; 223 } 224 } 225 } 226 227 void ProgressTracker::completeProgress(unsigned long identifier) 228 { 229 ProgressItem* item = m_progressItems.get(identifier); 230 231 // This can happen if a load fails without receiving any response data. 232 if (!item) 233 return; 234 235 // Adjust the total expected bytes to account for any overage/underage. 236 long long delta = item->bytesReceived - item->estimatedLength; 237 m_totalPageAndResourceBytesToLoad += delta; 238 239 m_progressItems.remove(identifier); 240 } 241 242 } 243