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