ProgressTracker.cpp [plain text]
#include "config.h"
#include "ProgressTracker.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderStateMachine.h"
#include "FrameLoaderClient.h"
#include "InspectorInstrumentation.h"
#include "Logging.h"
#include "MainFrame.h"
#include "ProgressTrackerClient.h"
#include "ResourceResponse.h"
#include <wtf/text/CString.h>
#include <wtf/CurrentTime.h>
namespace WebCore {
static const double initialProgressValue = 0.1;
static const double finalProgressValue = 0.9;
static const int progressItemDefaultEstimatedLength = 1024 * 16;
static const double progressHeartbeatInterval = 0.1;
static const unsigned loadStalledHeartbeatCount = 4;
static const unsigned minumumBytesPerHeartbeatForProgress = 1024;
static const std::chrono::milliseconds progressNotificationTimeInterval = std::chrono::milliseconds(200);
struct ProgressItem {
WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED;
public:
ProgressItem(long long length)
: bytesReceived(0)
, estimatedLength(length)
{
}
long long bytesReceived;
long long estimatedLength;
};
unsigned long ProgressTracker::s_uniqueIdentifier = 0;
ProgressTracker::ProgressTracker(ProgressTrackerClient& client)
: m_client(client)
, m_totalPageAndResourceBytesToLoad(0)
, m_totalBytesReceived(0)
, m_lastNotifiedProgressValue(0)
, m_finalProgressChangedSent(false)
, m_progressValue(0)
, m_numProgressTrackedFrames(0)
, m_progressHeartbeatTimer(*this, &ProgressTracker::progressHeartbeatTimerFired)
, m_heartbeatsWithNoProgress(0)
, m_totalBytesReceivedBeforePreviousHeartbeat(0)
, m_isMainLoad(false)
{
}
ProgressTracker::~ProgressTracker()
{
m_client.progressTrackerDestroyed();
}
double ProgressTracker::estimatedProgress() const
{
return m_progressValue;
}
void ProgressTracker::reset()
{
m_progressItems.clear();
m_totalPageAndResourceBytesToLoad = 0;
m_totalBytesReceived = 0;
m_progressValue = 0;
m_lastNotifiedProgressValue = 0;
m_lastNotifiedProgressTime = std::chrono::steady_clock::time_point();
m_finalProgressChangedSent = false;
m_numProgressTrackedFrames = 0;
m_originatingProgressFrame = nullptr;
m_heartbeatsWithNoProgress = 0;
m_totalBytesReceivedBeforePreviousHeartbeat = 0;
m_progressHeartbeatTimer.stop();
}
void ProgressTracker::progressStarted(Frame& frame)
{
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());
m_client.willChangeEstimatedProgress();
if (!m_numProgressTrackedFrames || m_originatingProgressFrame == &frame) {
reset();
m_progressValue = initialProgressValue;
m_originatingProgressFrame = &frame;
m_progressHeartbeatTimer.startRepeating(progressHeartbeatInterval);
m_originatingProgressFrame->loader().loadProgressingStatusChanged();
bool isMainFrame = !m_originatingProgressFrame->tree().parent();
auto elapsedTimeSinceMainLoadComplete = std::chrono::steady_clock::now() - m_mainLoadCompletionTime;
static const auto subframePartOfMainLoadThreshold = std::chrono::seconds(1);
m_isMainLoad = isMainFrame || elapsedTimeSinceMainLoadComplete < subframePartOfMainLoadThreshold;
m_client.progressStarted(*m_originatingProgressFrame);
}
m_numProgressTrackedFrames++;
m_client.didChangeEstimatedProgress();
InspectorInstrumentation::frameStartedLoading(frame);
}
void ProgressTracker::progressCompleted(Frame& frame)
{
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());
if (m_numProgressTrackedFrames <= 0)
return;
m_client.willChangeEstimatedProgress();
m_numProgressTrackedFrames--;
if (!m_numProgressTrackedFrames || m_originatingProgressFrame == &frame)
finalProgressComplete();
m_client.didChangeEstimatedProgress();
}
void ProgressTracker::finalProgressComplete()
{
LOG(Progress, "Final progress complete (%p)", this);
RefPtr<Frame> frame = m_originatingProgressFrame.release();
if (!m_finalProgressChangedSent) {
m_progressValue = 1;
m_client.progressEstimateChanged(*frame);
}
reset();
if (m_isMainLoad)
m_mainLoadCompletionTime = std::chrono::steady_clock::now();
frame->loader().client().setMainFrameDocumentReady(true);
m_client.progressFinished(*frame);
frame->loader().loadProgressingStatusChanged();
InspectorInstrumentation::frameStoppedLoading(*frame);
}
void ProgressTracker::incrementProgress(unsigned long identifier, const ResourceResponse& response)
{
LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d, originating frame %p", this, m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get());
if (m_numProgressTrackedFrames <= 0)
return;
long long estimatedLength = response.expectedContentLength();
if (estimatedLength < 0)
estimatedLength = progressItemDefaultEstimatedLength;
m_totalPageAndResourceBytesToLoad += estimatedLength;
auto& item = m_progressItems.add(identifier, nullptr).iterator->value;
if (!item) {
item = std::make_unique<ProgressItem>(estimatedLength);
return;
}
item->bytesReceived = 0;
item->estimatedLength = estimatedLength;
}
void ProgressTracker::incrementProgress(unsigned long identifier, unsigned bytesReceived)
{
ProgressItem* item = m_progressItems.get(identifier);
if (!item)
return;
RefPtr<Frame> frame = m_originatingProgressFrame;
m_client.willChangeEstimatedProgress();
double increment, percentOfRemainingBytes;
long long remainingBytes, estimatedBytesForPendingRequests;
item->bytesReceived += bytesReceived;
if (item->bytesReceived > item->estimatedLength) {
m_totalPageAndResourceBytesToLoad += ((item->bytesReceived * 2) - item->estimatedLength);
item->estimatedLength = item->bytesReceived * 2;
}
int numPendingOrLoadingRequests = frame->loader().numPendingOrLoadingRequests(true);
estimatedBytesForPendingRequests = static_cast<long long>(progressItemDefaultEstimatedLength) * numPendingOrLoadingRequests;
remainingBytes = ((m_totalPageAndResourceBytesToLoad + estimatedBytesForPendingRequests) - m_totalBytesReceived);
if (remainingBytes > 0) percentOfRemainingBytes = (double)bytesReceived / (double)remainingBytes;
else
percentOfRemainingBytes = 1.0;
bool useClampedMaxProgress = frame->loader().client().hasHTMLView()
&& !frame->loader().stateMachine().firstLayoutDone();
double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue;
increment = (maxProgressValue - m_progressValue) * percentOfRemainingBytes;
m_progressValue += increment;
m_progressValue = std::min(m_progressValue, maxProgressValue);
ASSERT(m_progressValue >= initialProgressValue);
m_totalBytesReceived += bytesReceived;
auto now = std::chrono::steady_clock::now();
auto notifiedProgressTimeDelta = now - m_lastNotifiedProgressTime;
LOG(Progress, "Progress incremented (%p) - value %f, tracked frames %d", this, m_progressValue, m_numProgressTrackedFrames);
if ((notifiedProgressTimeDelta >= progressNotificationTimeInterval || m_progressValue == 1) && m_numProgressTrackedFrames > 0) {
if (!m_finalProgressChangedSent) {
if (m_progressValue == 1)
m_finalProgressChangedSent = true;
m_client.progressEstimateChanged(*frame);
m_lastNotifiedProgressValue = m_progressValue;
m_lastNotifiedProgressTime = now;
}
}
m_client.didChangeEstimatedProgress();
}
void ProgressTracker::completeProgress(unsigned long identifier)
{
auto it = m_progressItems.find(identifier);
if (it == m_progressItems.end())
return;
ProgressItem& item = *it->value;
long long delta = item.bytesReceived - item.estimatedLength;
m_totalPageAndResourceBytesToLoad += delta;
m_progressItems.remove(it);
}
unsigned long ProgressTracker::createUniqueIdentifier()
{
return ++s_uniqueIdentifier;
}
bool ProgressTracker::isMainLoadProgressing() const
{
if (!m_originatingProgressFrame)
return false;
if (!m_isMainLoad)
return false;
return m_progressValue && m_progressValue < finalProgressValue && m_heartbeatsWithNoProgress < loadStalledHeartbeatCount;
}
void ProgressTracker::progressHeartbeatTimerFired()
{
if (m_totalBytesReceived < m_totalBytesReceivedBeforePreviousHeartbeat + minumumBytesPerHeartbeatForProgress)
++m_heartbeatsWithNoProgress;
else
m_heartbeatsWithNoProgress = 0;
m_totalBytesReceivedBeforePreviousHeartbeat = m_totalBytesReceived;
if (m_originatingProgressFrame)
m_originatingProgressFrame->loader().loadProgressingStatusChanged();
if (m_progressValue >= finalProgressValue)
m_progressHeartbeatTimer.stop();
}
}