CTTE Timer and DeferrableOneShotTimer
[WebKit-https.git] / Source / WebCore / loader / ProgressTracker.cpp
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 "FrameLoaderStateMachine.h"
33 #include "FrameLoaderClient.h"
34 #include "InspectorInstrumentation.h"
35 #include "Logging.h"
36 #include "ProgressTrackerClient.h"
37 #include "ResourceResponse.h"
38 #include <wtf/text/CString.h>
39 #include <wtf/CurrentTime.h>
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 // Check if the load is progressing this often.
54 static const double progressHeartbeatInterval = 0.1;
55
56 // How many heartbeats must pass without progress before deciding the load is currently stalled.
57 static const unsigned loadStalledHeartbeatCount = 4;
58
59 // How many bytes are required between heartbeats to consider it progress.
60 static const unsigned minumumBytesPerHeartbeatForProgress = 1024;
61
62 struct ProgressItem {
63     WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED;
64 public:
65     ProgressItem(long long length) 
66         : bytesReceived(0)
67         , estimatedLength(length)
68     {
69     }
70     
71     long long bytesReceived;
72     long long estimatedLength;
73 };
74
75 unsigned long ProgressTracker::s_uniqueIdentifier = 0;
76
77 ProgressTracker::ProgressTracker(ProgressTrackerClient& client)
78     : m_client(client)
79     , m_totalPageAndResourceBytesToLoad(0)
80     , m_totalBytesReceived(0)
81     , m_lastNotifiedProgressValue(0)
82     , m_lastNotifiedProgressTime(0)
83     , m_progressNotificationInterval(0.02)
84     , m_progressNotificationTimeInterval(0.1)
85     , m_finalProgressChangedSent(false)
86     , m_progressValue(0)
87     , m_numProgressTrackedFrames(0)
88     , m_progressHeartbeatTimer(this, &ProgressTracker::progressHeartbeatTimerFired)
89     , m_heartbeatsWithNoProgress(0)
90     , m_totalBytesReceivedBeforePreviousHeartbeat(0)
91 {
92 }
93
94 ProgressTracker::~ProgressTracker()
95 {
96     // FIXME: Uncomment this once we've transitioned to custom progress tracker clients.
97     // m_client.progressTrackerDestroyed();
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     m_numProgressTrackedFrames = 0;
116     m_originatingProgressFrame = 0;
117
118     m_heartbeatsWithNoProgress = 0;
119     m_totalBytesReceivedBeforePreviousHeartbeat = 0;
120     m_progressHeartbeatTimer.stop();
121 }
122
123 void ProgressTracker::progressStarted(Frame& frame)
124 {
125     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());
126
127     m_client.willChangeEstimatedProgress();
128     
129     if (!m_numProgressTrackedFrames || m_originatingProgressFrame == &frame) {
130         reset();
131         m_progressValue = initialProgressValue;
132         m_originatingProgressFrame = &frame;
133
134         m_progressHeartbeatTimer.startRepeating(progressHeartbeatInterval);
135         m_originatingProgressFrame->loader().loadProgressingStatusChanged();
136
137         m_client.progressStarted(*m_originatingProgressFrame);
138     }
139     m_numProgressTrackedFrames++;
140
141     m_client.didChangeEstimatedProgress();
142     InspectorInstrumentation::frameStartedLoading(frame);
143 }
144
145 void ProgressTracker::progressCompleted(Frame& frame)
146 {
147     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());
148     
149     if (m_numProgressTrackedFrames <= 0)
150         return;
151     
152     m_client.willChangeEstimatedProgress();
153         
154     m_numProgressTrackedFrames--;
155     if (!m_numProgressTrackedFrames || m_originatingProgressFrame == &frame)
156         finalProgressComplete();
157     
158     m_client.didChangeEstimatedProgress();
159 }
160
161 void ProgressTracker::finalProgressComplete()
162 {
163     LOG(Progress, "Final progress complete (%p)", this);
164     
165     RefPtr<Frame> frame = m_originatingProgressFrame.release();
166     
167     // Before resetting progress value be sure to send client a least one notification
168     // with final progress value.
169     if (!m_finalProgressChangedSent) {
170         m_progressValue = 1;
171         m_client.progressEstimateChanged(*frame);
172     }
173
174     reset();
175
176     frame->loader().client().setMainFrameDocumentReady(true);
177     m_client.progressFinished(*frame);
178     frame->loader().loadProgressingStatusChanged();
179
180     InspectorInstrumentation::frameStoppedLoading(*frame);
181 }
182
183 void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response)
184 {
185     LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d, originating frame %p", this, m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get());
186
187     if (m_numProgressTrackedFrames <= 0)
188         return;
189     
190     long long estimatedLength = response.expectedContentLength();
191     if (estimatedLength < 0)
192         estimatedLength = progressItemDefaultEstimatedLength;
193     
194     m_totalPageAndResourceBytesToLoad += estimatedLength;
195
196     auto& item = m_progressItems.add(identifier, nullptr).iterator->value;
197     if (!item) {
198         item = std::make_unique<ProgressItem>(estimatedLength);
199         return;
200     }
201     
202     item->bytesReceived = 0;
203     item->estimatedLength = estimatedLength;
204 }
205
206 void ProgressTracker::incrementProgress(unsigned long identifier, unsigned bytesReceived)
207 {
208     ProgressItem* item = m_progressItems.get(identifier);
209     
210     // FIXME: Can this ever happen?
211     if (!item)
212         return;
213
214     RefPtr<Frame> frame = m_originatingProgressFrame;
215     
216     m_client.willChangeEstimatedProgress();
217     
218     double increment, percentOfRemainingBytes;
219     long long remainingBytes, estimatedBytesForPendingRequests;
220     
221     item->bytesReceived += bytesReceived;
222     if (item->bytesReceived > item->estimatedLength) {
223         m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength);
224         item->estimatedLength = item->bytesReceived * 2;
225     }
226     
227     int numPendingOrLoadingRequests = frame->loader().numPendingOrLoadingRequests(true);
228     estimatedBytesForPendingRequests = progressItemDefaultEstimatedLength * numPendingOrLoadingRequests;
229     remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived);
230     if (remainingBytes > 0)  // Prevent divide by 0.
231         percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes;
232     else
233         percentOfRemainingBytes = 1.0;
234     
235     // For documents that use WebCore's layout system, treat first layout as the half-way point.
236     // FIXME: The hasHTMLView function is a sort of roundabout way of asking "do you use WebCore's layout system".
237     bool useClampedMaxProgress = frame->loader().client().hasHTMLView()
238         && !frame->loader().stateMachine()->firstLayoutDone();
239     double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue;
240     increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes;
241     m_progressValue += increment;
242     m_progressValue = std::min(m_progressValue, maxProgressValue);
243     ASSERT(m_progressValue >= initialProgressValue);
244     
245     m_totalBytesReceived += bytesReceived;
246     
247     double now = monotonicallyIncreasingTime();
248     double notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime;
249     
250     LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d", this, m_progressValue, m_numProgressTrackedFrames);
251     double notificationProgressDelta = m_progressValue - m_lastNotifiedProgressValue;
252     if ((notificationProgressDelta >= m_progressNotificationInterval ||
253          notifiedProgressTimeDelta >= m_progressNotificationTimeInterval) &&
254         m_numProgressTrackedFrames > 0) {
255         if (!m_finalProgressChangedSent) {
256             if (m_progressValue == 1)
257                 m_finalProgressChangedSent = true;
258             
259             m_client.progressEstimateChanged(*frame);
260
261             m_lastNotifiedProgressValue = m_progressValue;
262             m_lastNotifiedProgressTime = now;
263         }
264     }
265     
266     m_client.didChangeEstimatedProgress();
267 }
268
269 void ProgressTracker::completeProgress(unsigned long identifier)
270 {
271     auto it = m_progressItems.find(identifier);
272
273     // This can happen if a load fails without receiving any response data.
274     if (it == m_progressItems.end())
275         return;
276
277     ProgressItem& item = *it->value;
278     
279     // Adjust the total expected bytes to account for any overage/underage.
280     long long delta = item.bytesReceived - item.estimatedLength;
281     m_totalPageAndResourceBytesToLoad += delta;
282
283     m_progressItems.remove(it);
284 }
285
286 unsigned long ProgressTracker::createUniqueIdentifier()
287 {
288     return ++s_uniqueIdentifier;
289 }
290
291 bool ProgressTracker::isMainLoadProgressing() const
292 {
293     if (!m_originatingProgressFrame)
294         return false;
295     // See if the load originated from a subframe.
296     if (m_originatingProgressFrame->tree().parent())
297         return false;
298     return m_progressValue && m_progressValue < finalProgressValue && m_heartbeatsWithNoProgress < loadStalledHeartbeatCount;
299 }
300
301 void ProgressTracker::progressHeartbeatTimerFired(Timer<ProgressTracker>&)
302 {
303     if (m_totalBytesReceived < m_totalBytesReceivedBeforePreviousHeartbeat + minumumBytesPerHeartbeatForProgress)
304         ++m_heartbeatsWithNoProgress;
305     else
306         m_heartbeatsWithNoProgress = 0;
307
308     m_totalBytesReceivedBeforePreviousHeartbeat = m_totalBytesReceived;
309
310     if (m_originatingProgressFrame)
311         m_originatingProgressFrame->loader().loadProgressingStatusChanged();
312
313     if (m_progressValue >= finalProgressValue)
314         m_progressHeartbeatTimer.stop();
315 }
316
317 }