blob: 1e0088c45e1aec2ff5cfe5aa4e26ed68013baae9 [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/loader/progress_tracker.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/paint/paint_timing.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/cstring.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
// Always start progress at initialProgressValue. This helps provide feedback as
// soon as a load starts.
static const double kInitialProgressValue = 0.1;
static const int kProgressItemDefaultEstimatedLength = 1024 * 1024;
static const double kProgressNotificationInterval = 0.02;
static const double kProgressNotificationTimeInterval = 0.1;
struct ProgressItem {
USING_FAST_MALLOC(ProgressItem);
public:
explicit ProgressItem(long long length)
: bytes_received(0), estimated_length(length) {}
long long bytes_received;
long long estimated_length;
DISALLOW_COPY_AND_ASSIGN(ProgressItem);
};
ProgressTracker* ProgressTracker::Create(LocalFrame* frame) {
return MakeGarbageCollected<ProgressTracker>(frame);
}
ProgressTracker::ProgressTracker(LocalFrame* frame)
: frame_(frame),
last_notified_progress_value_(0),
last_notified_progress_time_(0),
finished_parsing_(false),
did_first_contentful_paint_(false),
progress_value_(0) {}
ProgressTracker::~ProgressTracker() = default;
void ProgressTracker::Trace(blink::Visitor* visitor) {
visitor->Trace(frame_);
}
void ProgressTracker::Dispose() {
if (frame_->IsLoading())
ProgressCompleted();
DCHECK(!frame_->IsLoading());
}
double ProgressTracker::EstimatedProgress() const {
return progress_value_;
}
void ProgressTracker::Reset() {
progress_items_.clear();
progress_value_ = 0;
last_notified_progress_value_ = 0;
last_notified_progress_time_ = 0;
finished_parsing_ = false;
did_first_contentful_paint_ = false;
}
LocalFrameClient* ProgressTracker::GetLocalFrameClient() const {
return frame_->Client();
}
void ProgressTracker::ProgressStarted() {
Reset();
progress_value_ = kInitialProgressValue;
if (!frame_->IsLoading()) {
GetLocalFrameClient()->DidStartLoading();
frame_->SetIsLoading(true);
probe::frameStartedLoading(frame_);
}
}
void ProgressTracker::ProgressCompleted() {
DCHECK(frame_->IsLoading());
frame_->SetIsLoading(false);
SendFinalProgress();
Reset();
GetLocalFrameClient()->DidStopLoading();
probe::frameStoppedLoading(frame_);
}
void ProgressTracker::FinishedParsing() {
finished_parsing_ = true;
MaybeSendProgress();
}
void ProgressTracker::DidFirstContentfulPaint() {
did_first_contentful_paint_ = true;
MaybeSendProgress();
}
void ProgressTracker::SendFinalProgress() {
if (progress_value_ == 1)
return;
progress_value_ = 1;
GetLocalFrameClient()->ProgressEstimateChanged(progress_value_);
}
void ProgressTracker::WillStartLoading(unsigned long identifier,
ResourceLoadPriority priority) {
if (!frame_->IsLoading())
return;
if (HaveParsedAndPainted() || priority < ResourceLoadPriority::kHigh)
return;
progress_items_.Set(identifier, std::make_unique<ProgressItem>(
kProgressItemDefaultEstimatedLength));
}
void ProgressTracker::IncrementProgress(unsigned long identifier,
const ResourceResponse& response) {
ProgressItem* item = progress_items_.at(identifier);
if (!item)
return;
long long estimated_length = response.ExpectedContentLength();
if (estimated_length < 0)
estimated_length = kProgressItemDefaultEstimatedLength;
item->bytes_received = 0;
item->estimated_length = estimated_length;
}
void ProgressTracker::IncrementProgress(unsigned long identifier,
size_t length) {
ProgressItem* item = progress_items_.at(identifier);
if (!item)
return;
item->bytes_received += length;
if (item->bytes_received > item->estimated_length)
item->estimated_length = item->bytes_received * 2;
MaybeSendProgress();
}
bool ProgressTracker::HaveParsedAndPainted() {
return finished_parsing_ && did_first_contentful_paint_;
}
void ProgressTracker::MaybeSendProgress() {
if (!frame_->IsLoading())
return;
progress_value_ = kInitialProgressValue + 0.1; // +0.1 for committing
if (finished_parsing_)
progress_value_ += 0.1;
if (did_first_contentful_paint_)
progress_value_ += 0.1;
long long bytes_received = 0;
long long estimated_bytes_for_pending_requests = 0;
for (const auto& progress_item : progress_items_) {
bytes_received += progress_item.value->bytes_received;
estimated_bytes_for_pending_requests +=
progress_item.value->estimated_length;
}
DCHECK_GE(estimated_bytes_for_pending_requests, 0);
DCHECK_GE(estimated_bytes_for_pending_requests, bytes_received);
if (HaveParsedAndPainted() &&
estimated_bytes_for_pending_requests == bytes_received) {
SendFinalProgress();
return;
}
double percent_of_bytes_received =
!estimated_bytes_for_pending_requests
? 1.0
: (double)bytes_received /
(double)estimated_bytes_for_pending_requests;
progress_value_ += percent_of_bytes_received / 2;
DCHECK_GE(progress_value_, kInitialProgressValue);
// Always leave space at the end. This helps show the user that we're not
// done until we're done.
DCHECK_LE(progress_value_, 0.9);
if (progress_value_ < last_notified_progress_value_)
return;
double now = CurrentTime();
double notified_progress_time_delta = now - last_notified_progress_time_;
double notification_progress_delta =
progress_value_ - last_notified_progress_value_;
if (notification_progress_delta >= kProgressNotificationInterval ||
notified_progress_time_delta >= kProgressNotificationTimeInterval) {
GetLocalFrameClient()->ProgressEstimateChanged(progress_value_);
last_notified_progress_value_ = progress_value_;
last_notified_progress_time_ = now;
}
}
void ProgressTracker::CompleteProgress(unsigned long identifier) {
ProgressItem* item = progress_items_.at(identifier);
if (!item)
return;
item->estimated_length = item->bytes_received;
MaybeSendProgress();
}
} // namespace blink