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