Home | History | Annotate | Download | only in loader
      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 
     40 using std::min;
     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 struct ProgressItem {
     55     WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED;
     56 public:
     57     ProgressItem(long long length)
     58         : bytesReceived(0)
     59         , estimatedLength(length) { }
     60 
     61     long long bytesReceived;
     62     long long estimatedLength;
     63 };
     64 
     65 ProgressTracker::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 
     79 ProgressTracker::~ProgressTracker()
     80 {
     81     if (m_inProgress)
     82         progressCompleted();
     83 }
     84 
     85 PassOwnPtr<ProgressTracker> ProgressTracker::create(LocalFrame* frame)
     86 {
     87     return adoptPtr(new ProgressTracker(frame));
     88 }
     89 
     90 double ProgressTracker::estimatedProgress() const
     91 {
     92     return m_progressValue;
     93 }
     94 
     95 void 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 
    107 void 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 
    118 void 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 
    131 void 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 
    149 void 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 
    202 void 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