blob: 2359f6c377f0b6be8c8bc5cd9e2ec363fee1901d [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/loader/idleness_detector.h"
#include "base/check.h"
#include "base/time/default_tick_clock.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame.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/page/page.h"
#include "third_party/blink/renderer/core/paint/first_meaningful_paint_detector.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
namespace blink {
constexpr base::TimeDelta IdlenessDetector::kNetworkQuietWindow;
constexpr base::TimeDelta IdlenessDetector::kNetworkQuietWatchdog;
void IdlenessDetector::Shutdown() {
Stop();
local_frame_ = nullptr;
}
void IdlenessDetector::WillCommitLoad() {
in_network_2_quiet_period_ = false;
in_network_0_quiet_period_ = false;
network_2_quiet_ = base::TimeTicks();
network_0_quiet_ = base::TimeTicks();
network_2_quiet_start_time_ = base::TimeTicks();
network_0_quiet_start_time_ = base::TimeTicks();
}
void IdlenessDetector::DomContentLoadedEventFired() {
if (!local_frame_)
return;
if (!task_observer_added_) {
Thread::Current()->AddTaskTimeObserver(this);
task_observer_added_ = true;
}
in_network_2_quiet_period_ = true;
in_network_0_quiet_period_ = true;
network_2_quiet_ = base::TimeTicks();
network_0_quiet_ = base::TimeTicks();
OnDidLoadResource();
}
void IdlenessDetector::OnWillSendRequest(ResourceFetcher* fetcher) {
// If |fetcher| is not the current fetcher of the Document, then that means
// it's a new navigation, bail out in this case since it shouldn't affect the
// current idleness of the local frame.
if (!local_frame_ || fetcher != local_frame_->GetDocument()->Fetcher())
return;
// When OnWillSendRequest is called, the new loader hasn't been added to the
// fetcher, thus we need to add 1 as the total request count.
int request_count = fetcher->ActiveRequestCount() + 1;
// If we are above the allowed number of active requests, reset timers.
if (in_network_2_quiet_period_ && request_count > 2)
network_2_quiet_ = base::TimeTicks();
if (in_network_0_quiet_period_ && request_count > 0)
network_0_quiet_ = base::TimeTicks();
}
// This function is called when the number of active connections is decreased.
// Note that the number of active connections doesn't decrease monotonically.
void IdlenessDetector::OnDidLoadResource() {
if (!local_frame_)
return;
// Document finishes parsing after DomContentLoadedEventEnd is fired,
// check the status in order to avoid false signals.
if (!local_frame_->GetDocument()->HasFinishedParsing())
return;
// If we already reported quiet time, bail out.
if (!in_network_0_quiet_period_ && !in_network_2_quiet_period_)
return;
int request_count =
local_frame_->GetDocument()->Fetcher()->ActiveRequestCount();
// If we did not achieve either 0 or 2 active connections, bail out.
if (request_count > 2)
return;
base::TimeTicks timestamp = clock_->NowTicks();
// Arriving at =2 updates the quiet_2 base timestamp.
// Arriving at <2 sets the quiet_2 base timestamp only if
// it was not already set.
if (request_count == 2 && in_network_2_quiet_period_) {
network_2_quiet_ = timestamp;
network_2_quiet_start_time_ = timestamp;
} else if (request_count < 2 && in_network_2_quiet_period_ &&
network_2_quiet_.is_null()) {
network_2_quiet_ = timestamp;
network_2_quiet_start_time_ = timestamp;
}
if (request_count == 0 && in_network_0_quiet_period_) {
network_0_quiet_ = timestamp;
network_0_quiet_start_time_ = timestamp;
}
if (!network_quiet_timer_.IsActive()) {
network_quiet_timer_.StartOneShot(kNetworkQuietWatchdog, FROM_HERE);
}
}
base::TimeTicks IdlenessDetector::GetNetworkAlmostIdleTime() {
return network_2_quiet_start_time_;
}
bool IdlenessDetector::NetworkIsAlmostIdle() {
if (in_network_2_quiet_period_)
return false;
if (!network_2_quiet_.is_null())
return false;
if (network_2_quiet_start_time_.is_null())
return false;
base::TimeTicks current_time = base::TimeTicks::Now();
if (current_time - network_2_quiet_start_time_ <= network_quiet_window_)
return false;
return true;
}
base::TimeTicks IdlenessDetector::GetNetworkIdleTime() {
return network_0_quiet_start_time_;
}
void IdlenessDetector::WillProcessTask(base::TimeTicks start_time) {
// If we have idle time and we are network_quiet_window_ seconds past it, emit
// idle signals.
DocumentLoader* loader = local_frame_->Loader().GetDocumentLoader();
if (in_network_2_quiet_period_ && !network_2_quiet_.is_null() &&
start_time - network_2_quiet_ > network_quiet_window_) {
probe::LifecycleEvent(
local_frame_, loader, "networkAlmostIdle",
network_2_quiet_start_time_.since_origin().InSecondsF());
DCHECK(local_frame_->GetDocument());
if (auto* document_resource_coordinator =
local_frame_->GetDocument()->GetResourceCoordinator()) {
document_resource_coordinator->SetNetworkAlmostIdle();
}
if (WebServiceWorkerNetworkProvider* service_worker_network_provider =
loader->GetServiceWorkerNetworkProvider()) {
service_worker_network_provider->DispatchNetworkQuiet();
}
FirstMeaningfulPaintDetector::From(*local_frame_->GetDocument())
.OnNetwork2Quiet();
if (local_frame_->IsMainFrame()) {
if (Page* page = local_frame_->GetPage()) {
if (PageScheduler* scheduler = page->GetPageScheduler())
scheduler->OnLocalMainFrameNetworkAlmostIdle();
}
}
in_network_2_quiet_period_ = false;
network_2_quiet_ = base::TimeTicks();
}
if (in_network_0_quiet_period_ && !network_0_quiet_.is_null() &&
start_time - network_0_quiet_ > network_quiet_window_) {
probe::LifecycleEvent(
local_frame_, loader, "networkIdle",
network_0_quiet_start_time_.since_origin().InSecondsF());
in_network_0_quiet_period_ = false;
network_0_quiet_ = base::TimeTicks();
}
if (!in_network_0_quiet_period_ && !in_network_2_quiet_period_)
Stop();
}
void IdlenessDetector::DidProcessTask(base::TimeTicks start_time,
base::TimeTicks end_time) {
// Shift idle timestamps with the duration of the task, we were not idle.
if (in_network_2_quiet_period_ && !network_2_quiet_.is_null())
network_2_quiet_ += end_time - start_time;
if (in_network_0_quiet_period_ && !network_0_quiet_.is_null())
network_0_quiet_ += end_time - start_time;
}
IdlenessDetector::IdlenessDetector(LocalFrame* local_frame,
const base::TickClock* clock)
: local_frame_(local_frame),
task_observer_added_(false),
clock_(clock),
network_quiet_timer_(
local_frame->GetTaskRunner(TaskType::kInternalLoading),
this,
&IdlenessDetector::NetworkQuietTimerFired) {
if (local_frame->GetSettings()) {
network_quiet_window_ = base::TimeDelta::FromSecondsD(
local_frame->GetSettings()->GetNetworkQuietTimeout());
}
}
void IdlenessDetector::Stop() {
network_quiet_timer_.Stop();
if (!task_observer_added_)
return;
Thread::Current()->RemoveTaskTimeObserver(this);
task_observer_added_ = false;
}
void IdlenessDetector::NetworkQuietTimerFired(TimerBase*) {
// TODO(lpy) Reduce the number of timers.
if ((in_network_0_quiet_period_ && !network_0_quiet_.is_null()) ||
(in_network_2_quiet_period_ && !network_2_quiet_.is_null())) {
network_quiet_timer_.StartOneShot(kNetworkQuietWatchdog, FROM_HERE);
}
}
void IdlenessDetector::Trace(Visitor* visitor) const {
visitor->Trace(local_frame_);
visitor->Trace(network_quiet_timer_);
}
} // namespace blink