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