// 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