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