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 "FrameLoaderClient.h" 33 #include "ResourceResponse.h" 34 #include <wtf/CurrentTime.h> 35 36 using std::min; 37 38 namespace WebCore { 39 40 // Always start progress at initialProgressValue. This helps provide feedback as 41 // soon as a load starts. 42 static const double initialProgressValue = 0.1; 43 44 // Similarly, always leave space at the end. This helps show the user that we're not done 45 // until we're done. 46 static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue 47 48 static const int progressItemDefaultEstimatedLength = 1024 * 16; 49 50 struct ProgressItem : Noncopyable { 51 ProgressItem(long long length) 52 : bytesReceived(0) 53 , estimatedLength(length) { } 54 55 long long bytesReceived; 56 long long estimatedLength; 57 }; 58 59 ProgressTracker::ProgressTracker() 60 : m_uniqueIdentifier(0) 61 , m_totalPageAndResourceBytesToLoad(0) 62 , m_totalBytesReceived(0) 63 , m_lastNotifiedProgressValue(0) 64 , m_lastNotifiedProgressTime(0) 65 , m_progressNotificationInterval(0.02) 66 , m_progressNotificationTimeInterval(0.1) 67 , m_finalProgressChangedSent(false) 68 , m_progressValue(0) 69 , m_numProgressTrackedFrames(0) 70 { 71 } 72 73 ProgressTracker::~ProgressTracker() 74 { 75 deleteAllValues(m_progressItems); 76 } 77 78 double ProgressTracker::estimatedProgress() const 79 { 80 return m_progressValue; 81 } 82 83 void ProgressTracker::reset() 84 { 85 deleteAllValues(m_progressItems); 86 m_progressItems.clear(); 87 88 m_totalPageAndResourceBytesToLoad = 0; 89 m_totalBytesReceived = 0; 90 m_progressValue = 0; 91 m_lastNotifiedProgressValue = 0; 92 m_lastNotifiedProgressTime = 0; 93 m_finalProgressChangedSent = false; 94 m_numProgressTrackedFrames = 0; 95 m_originatingProgressFrame = 0; 96 } 97 98 void ProgressTracker::progressStarted(Frame* frame) 99 { 100 // LOG (Progress, "frame %p(%@), _private->numProgressTrackedFrames %d, _private->originatingProgressFrame %p", frame, [frame name], _private->numProgressTrackedFrames, _private->originatingProgressFrame); 101 102 frame->loader()->client()->willChangeEstimatedProgress(); 103 104 if (m_numProgressTrackedFrames == 0 || m_originatingProgressFrame == frame) { 105 reset(); 106 m_progressValue = initialProgressValue; 107 m_originatingProgressFrame = frame; 108 109 m_originatingProgressFrame->loader()->client()->postProgressStartedNotification(); 110 } 111 m_numProgressTrackedFrames++; 112 113 frame->loader()->client()->didChangeEstimatedProgress(); 114 } 115 116 void ProgressTracker::progressCompleted(Frame* frame) 117 { 118 // LOG (Progress, "frame %p(%@), _private->numProgressTrackedFrames %d, _private->originatingProgressFrame %p", frame, [frame name], _private->numProgressTrackedFrames, _private->originatingProgressFrame); 119 120 if (m_numProgressTrackedFrames <= 0) 121 return; 122 123 frame->loader()->client()->willChangeEstimatedProgress(); 124 125 m_numProgressTrackedFrames--; 126 if (m_numProgressTrackedFrames == 0 || 127 (frame == m_originatingProgressFrame && m_numProgressTrackedFrames != 0)) 128 finalProgressComplete(); 129 130 frame->loader()->client()->didChangeEstimatedProgress(); 131 } 132 133 void ProgressTracker::finalProgressComplete() 134 { 135 // LOG (Progress, ""); 136 137 RefPtr<Frame> frame = m_originatingProgressFrame.release(); 138 139 // Before resetting progress value be sure to send client a least one notification 140 // with final progress value. 141 if (!m_finalProgressChangedSent) { 142 m_progressValue = 1; 143 frame->loader()->client()->postProgressEstimateChangedNotification(); 144 } 145 146 reset(); 147 148 frame->loader()->client()->setMainFrameDocumentReady(true); 149 frame->loader()->client()->postProgressFinishedNotification(); 150 } 151 152 void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response) 153 { 154 // LOG (Progress, "_private->numProgressTrackedFrames %d, _private->originatingProgressFrame %p", _private->numProgressTrackedFrames, _private->originatingProgressFrame); 155 156 if (m_numProgressTrackedFrames <= 0) 157 return; 158 159 long long estimatedLength = response.expectedContentLength(); 160 if (estimatedLength < 0) 161 estimatedLength = progressItemDefaultEstimatedLength; 162 163 m_totalPageAndResourceBytesToLoad += estimatedLength; 164 165 if (ProgressItem* item = m_progressItems.get(identifier)) { 166 item->bytesReceived = 0; 167 item->estimatedLength = estimatedLength; 168 } else 169 m_progressItems.set(identifier, new ProgressItem(estimatedLength)); 170 } 171 172 void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) 173 { 174 ProgressItem* item = m_progressItems.get(identifier); 175 176 // FIXME: Can this ever happen? 177 if (!item) 178 return; 179 180 RefPtr<Frame> frame = m_originatingProgressFrame; 181 182 frame->loader()->client()->willChangeEstimatedProgress(); 183 184 unsigned bytesReceived = length; 185 double increment, percentOfRemainingBytes; 186 long long remainingBytes, estimatedBytesForPendingRequests; 187 188 item->bytesReceived += bytesReceived; 189 if (item->bytesReceived > item->estimatedLength) { 190 m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength); 191 item->estimatedLength = item->bytesReceived * 2; 192 } 193 194 int numPendingOrLoadingRequests = frame->loader()->numPendingOrLoadingRequests(true); 195 estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests; 196 remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived); 197 if (remainingBytes > 0) // Prevent divide by 0. 198 percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes; 199 else 200 percentOfRemainingBytes = 1.0; 201 202 // For documents that use WebCore's layout system, treat first layout as the half-way point. 203 // FIXME: The hasHTMLView function is a sort of roundabout way of asking "do you use WebCore's layout system". 204 bool useClampedMaxProgress = frame->loader()->client()->hasHTMLView() 205 && !frame->loader()->firstLayoutDone(); 206 double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; 207 increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes; 208 m_progressValue += increment; 209 m_progressValue = min(m_progressValue, maxProgressValue); 210 ASSERT(m_progressValue >= initialProgressValue); 211 212 m_totalBytesReceived += bytesReceived; 213 214 double now = currentTime(); 215 double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime; 216 217 // LOG (Progress, "_private->progressValue %g, _private->numProgressTrackedFrames %d", _private->progressValue, _private->numProgressTrackedFrames); 218 double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue; 219 if ((notificationProgressDelta >= m_progressNotificationInterval || 220 notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) && 221 m_numProgressTrackedFrames > 0) { 222 if (!m_finalProgressChangedSent) { 223 if (m_progressValue == 1) 224 m_finalProgressChangedSent = true; 225 226 frame->loader()->client()->postProgressEstimateChangedNotification(); 227 228 m_lastNotifiedProgressValue = m_progressValue; 229 m_lastNotifiedProgressTime = now; 230 } 231 } 232 233 frame->loader()->client()->didChangeEstimatedProgress(); 234 } 235 236 void ProgressTracker::completeProgress(unsigned long identifier) 237 { 238 ProgressItem* item = m_progressItems.get(identifier); 239 240 // FIXME: Can this happen? 241 if (!item) 242 return; 243 244 // Adjust the total expected bytes to account for any overage/underage. 245 long long delta = item->bytesReceived - item->estimatedLength; 246 m_totalPageAndResourceBytesToLoad += delta; 247 item->estimatedLength = item->bytesReceived; 248 249 m_progressItems.remove(identifier); 250 delete item; 251 } 252 253 unsigned long ProgressTracker::createUniqueIdentifier() 254 { 255 return ++m_uniqueIdentifier; 256 } 257 258 259 } 260