| // Copyright 2015 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/platform/scheduler/main_thread/frame_scheduler_impl.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/blame_context.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/platform/blame_context.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/common/features.h" |
| #include "third_party/blink/renderer/platform/scheduler/common/throttling/budget_pool.h" |
| #include "third_party/blink/renderer/platform/scheduler/common/tracing_helper.h" |
| #include "third_party/blink/renderer/platform/scheduler/main_thread/auto_advancing_virtual_time_domain.h" |
| #include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h" |
| #include "third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.h" |
| #include "third_party/blink/renderer/platform/scheduler/main_thread/page_visibility_state.h" |
| #include "third_party/blink/renderer/platform/scheduler/main_thread/resource_loading_task_runner_handle_impl.h" |
| #include "third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.h" |
| #include "third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.h" |
| |
| namespace blink { |
| |
| namespace scheduler { |
| |
| using base::sequence_manager::TaskQueue; |
| using QueueTraits = MainThreadTaskQueue::QueueTraits; |
| |
| namespace { |
| |
| const char* VisibilityStateToString(bool is_visible) { |
| if (is_visible) { |
| return "visible"; |
| } else { |
| return "hidden"; |
| } |
| } |
| |
| const char* PausedStateToString(bool is_paused) { |
| if (is_paused) { |
| return "paused"; |
| } else { |
| return "running"; |
| } |
| } |
| |
| const char* FrozenStateToString(bool is_frozen) { |
| if (is_frozen) { |
| return "frozen"; |
| } else { |
| return "running"; |
| } |
| } |
| |
| const char* KeepActiveStateToString(bool keep_active) { |
| if (keep_active) { |
| return "keep_active"; |
| } else { |
| return "no_keep_active"; |
| } |
| } |
| |
| // Used to update the priority of task_queue. Note that this function is |
| // used for queues associated with a frame. |
| void UpdatePriority(MainThreadTaskQueue* task_queue) { |
| if (!task_queue) |
| return; |
| |
| FrameSchedulerImpl* frame_scheduler = task_queue->GetFrameScheduler(); |
| DCHECK(frame_scheduler); |
| task_queue->SetQueuePriority(frame_scheduler->ComputePriority(task_queue)); |
| } |
| |
| // Extract a substring from |source| from [start to end), trimming leading |
| // whitespace. |
| std::string ExtractAndTrimString(std::string source, size_t start, size_t end) { |
| DCHECK(start < source.length()); |
| DCHECK(end <= source.length()); |
| DCHECK(start <= end); |
| // Trim whitespace |
| while (start < end && source[start] == ' ') |
| ++start; |
| if (start < end) |
| return source.substr(start, end - start); |
| return ""; |
| } |
| |
| std::set<std::string> TaskTypesFromFieldTrialParam(const char* param) { |
| std::set<std::string> result; |
| std::string task_type_list = base::GetFieldTrialParamValueByFeature( |
| kThrottleAndFreezeTaskTypes, param); |
| if (!task_type_list.length()) |
| return result; |
| // Extract the individual names, separated by ",". |
| size_t pos = 0, start = 0; |
| while ((pos = task_type_list.find(',', start)) != std::string::npos) { |
| std::string task_type = ExtractAndTrimString(task_type_list, start, pos); |
| // Not valid to start with "," or have ",," in the list. |
| DCHECK(task_type.length()); |
| result.insert(task_type); |
| start = pos + 1; |
| } |
| // Handle the last or only task type name. |
| std::string task_type = |
| ExtractAndTrimString(task_type_list, start, task_type_list.length()); |
| DCHECK(task_type.length()); |
| result.insert(task_type); |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| FrameSchedulerImpl::ActiveConnectionHandleImpl::ActiveConnectionHandleImpl( |
| FrameSchedulerImpl* frame_scheduler) |
| : frame_scheduler_(frame_scheduler->GetWeakPtr()) { |
| frame_scheduler->DidOpenActiveConnection(); |
| } |
| |
| FrameSchedulerImpl::ActiveConnectionHandleImpl::~ActiveConnectionHandleImpl() { |
| if (frame_scheduler_) { |
| static_cast<FrameSchedulerImpl*>(frame_scheduler_.get()) |
| ->DidCloseActiveConnection(); |
| } |
| } |
| |
| FrameSchedulerImpl::PauseSubresourceLoadingHandleImpl:: |
| PauseSubresourceLoadingHandleImpl( |
| base::WeakPtr<FrameSchedulerImpl> frame_scheduler) |
| : frame_scheduler_(std::move(frame_scheduler)) { |
| DCHECK(frame_scheduler_); |
| frame_scheduler_->AddPauseSubresourceLoadingHandle(); |
| } |
| |
| FrameSchedulerImpl::PauseSubresourceLoadingHandleImpl:: |
| ~PauseSubresourceLoadingHandleImpl() { |
| if (frame_scheduler_) |
| frame_scheduler_->RemovePauseSubresourceLoadingHandle(); |
| } |
| |
| std::unique_ptr<FrameSchedulerImpl> FrameSchedulerImpl::Create( |
| PageSchedulerImpl* parent_page_scheduler, |
| FrameScheduler::Delegate* delegate, |
| base::trace_event::BlameContext* blame_context, |
| FrameScheduler::FrameType frame_type) { |
| std::unique_ptr<FrameSchedulerImpl> frame_scheduler(new FrameSchedulerImpl( |
| parent_page_scheduler->GetMainThreadScheduler(), parent_page_scheduler, |
| delegate, blame_context, frame_type)); |
| parent_page_scheduler->RegisterFrameSchedulerImpl(frame_scheduler.get()); |
| return frame_scheduler; |
| } |
| |
| FrameSchedulerImpl::FrameSchedulerImpl( |
| MainThreadSchedulerImpl* main_thread_scheduler, |
| PageSchedulerImpl* parent_page_scheduler, |
| FrameScheduler::Delegate* delegate, |
| base::trace_event::BlameContext* blame_context, |
| FrameScheduler::FrameType frame_type) |
| : frame_type_(frame_type), |
| is_ad_frame_(false), |
| main_thread_scheduler_(main_thread_scheduler), |
| parent_page_scheduler_(parent_page_scheduler), |
| delegate_(delegate), |
| blame_context_(blame_context), |
| throttling_state_(SchedulingLifecycleState::kNotThrottled), |
| frame_visible_(true, |
| "FrameScheduler.FrameVisible", |
| this, |
| &tracing_controller_, |
| VisibilityStateToString), |
| frame_paused_(false, |
| "FrameScheduler.FramePaused", |
| this, |
| &tracing_controller_, |
| PausedStateToString), |
| frame_origin_type_(frame_type == FrameType::kMainFrame |
| ? FrameOriginType::kMainFrame |
| : FrameOriginType::kSameOriginFrame, |
| "FrameScheduler.Origin", |
| this, |
| &tracing_controller_, |
| FrameOriginTypeToString), |
| subresource_loading_paused_(false, |
| "FrameScheduler.SubResourceLoadingPaused", |
| this, |
| &tracing_controller_, |
| PausedStateToString), |
| url_tracer_("FrameScheduler.URL", this), |
| task_queues_throttled_(false, |
| "FrameScheduler.TaskQueuesThrottled", |
| this, |
| &tracing_controller_, |
| YesNoStateToString), |
| active_connection_count_(0), |
| subresource_loading_pause_count_(0u), |
| has_active_connection_(false, |
| "FrameScheduler.HasActiveConnection", |
| this, |
| &tracing_controller_, |
| YesNoStateToString), |
| page_frozen_for_tracing_( |
| parent_page_scheduler_ ? parent_page_scheduler_->IsFrozen() : true, |
| "FrameScheduler.PageFrozen", |
| this, |
| &tracing_controller_, |
| FrozenStateToString), |
| page_visibility_for_tracing_( |
| parent_page_scheduler_ && parent_page_scheduler_->IsPageVisible() |
| ? PageVisibilityState::kVisible |
| : PageVisibilityState::kHidden, |
| "FrameScheduler.PageVisibility", |
| this, |
| &tracing_controller_, |
| PageVisibilityStateToString), |
| page_keep_active_for_tracing_( |
| parent_page_scheduler_ ? parent_page_scheduler_->KeepActive() : false, |
| "FrameScheduler.KeepActive", |
| this, |
| &tracing_controller_, |
| KeepActiveStateToString), |
| weak_factory_(this) { |
| frame_task_queue_controller_.reset( |
| new FrameTaskQueueController(main_thread_scheduler_, this, this)); |
| } |
| |
| FrameSchedulerImpl::FrameSchedulerImpl() |
| : FrameSchedulerImpl(nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| FrameType::kSubframe) {} |
| |
| namespace { |
| |
| void CleanUpQueue(MainThreadTaskQueue* queue) { |
| DCHECK(queue); |
| |
| queue->DetachFromMainThreadScheduler(); |
| queue->DetachFromFrameScheduler(); |
| queue->SetBlameContext(nullptr); |
| queue->SetQueuePriority(TaskQueue::QueuePriority::kLowPriority); |
| } |
| |
| } // namespace |
| |
| FrameSchedulerImpl::~FrameSchedulerImpl() { |
| weak_factory_.InvalidateWeakPtrs(); |
| |
| for (const auto& task_queue_and_voter : |
| frame_task_queue_controller_->GetAllTaskQueuesAndVoters()) { |
| if (task_queue_and_voter.first->CanBeThrottled()) { |
| RemoveThrottleableQueueFromBackgroundCPUTimeBudgetPool( |
| task_queue_and_voter.first); |
| } |
| CleanUpQueue(task_queue_and_voter.first); |
| } |
| |
| if (parent_page_scheduler_) { |
| parent_page_scheduler_->Unregister(this); |
| |
| if (has_active_connection()) |
| parent_page_scheduler_->OnConnectionUpdated(); |
| } |
| } |
| |
| void FrameSchedulerImpl::DetachFromPageScheduler() { |
| for (const auto& task_queue_and_voter : |
| frame_task_queue_controller_->GetAllTaskQueuesAndVoters()) { |
| if (task_queue_and_voter.first->CanBeThrottled()) { |
| RemoveThrottleableQueueFromBackgroundCPUTimeBudgetPool( |
| task_queue_and_voter.first); |
| } |
| } |
| |
| parent_page_scheduler_ = nullptr; |
| } |
| |
| void FrameSchedulerImpl::RemoveThrottleableQueueFromBackgroundCPUTimeBudgetPool( |
| MainThreadTaskQueue* task_queue) { |
| DCHECK(task_queue); |
| DCHECK(task_queue->CanBeThrottled()); |
| |
| if (!parent_page_scheduler_) |
| return; |
| |
| CPUTimeBudgetPool* time_budget_pool = |
| parent_page_scheduler_->BackgroundCPUTimeBudgetPool(); |
| |
| if (!time_budget_pool) |
| return; |
| |
| // On tests, the scheduler helper might already be shut down and tick is not |
| // available. |
| base::TimeTicks now; |
| if (main_thread_scheduler_->tick_clock()) |
| now = main_thread_scheduler_->tick_clock()->NowTicks(); |
| else |
| now = base::TimeTicks::Now(); |
| time_budget_pool->RemoveQueue(now, task_queue); |
| } |
| |
| void FrameSchedulerImpl::SetFrameVisible(bool frame_visible) { |
| DCHECK(parent_page_scheduler_); |
| if (frame_visible_ == frame_visible) |
| return; |
| UMA_HISTOGRAM_BOOLEAN("RendererScheduler.IPC.FrameVisibility", frame_visible); |
| frame_visible_ = frame_visible; |
| UpdatePolicy(); |
| } |
| |
| bool FrameSchedulerImpl::IsFrameVisible() const { |
| return frame_visible_; |
| } |
| |
| void FrameSchedulerImpl::SetCrossOrigin(bool cross_origin) { |
| DCHECK(parent_page_scheduler_); |
| if (frame_origin_type_ == FrameOriginType::kMainFrame) { |
| DCHECK(!cross_origin); |
| return; |
| } |
| if (cross_origin) { |
| frame_origin_type_ = FrameOriginType::kCrossOriginFrame; |
| } else { |
| frame_origin_type_ = FrameOriginType::kSameOriginFrame; |
| } |
| UpdatePolicy(); |
| } |
| |
| void FrameSchedulerImpl::SetIsAdFrame() { |
| is_ad_frame_ = true; |
| UpdatePolicy(); |
| } |
| |
| bool FrameSchedulerImpl::IsAdFrame() const { |
| return is_ad_frame_; |
| } |
| |
| bool FrameSchedulerImpl::IsCrossOrigin() const { |
| return frame_origin_type_ == FrameOriginType::kCrossOriginFrame; |
| } |
| |
| void FrameSchedulerImpl::TraceUrlChange(const String& url) { |
| url_tracer_.TraceString(url); |
| } |
| |
| FrameScheduler::FrameType FrameSchedulerImpl::GetFrameType() const { |
| return frame_type_; |
| } |
| |
| void FrameSchedulerImpl::InitializeTaskTypeQueueTraitsMap( |
| FrameTaskTypeToQueueTraitsArray& frame_task_types_to_queue_traits) { |
| DCHECK_EQ(frame_task_types_to_queue_traits.size(), |
| static_cast<size_t>(TaskType::kCount)); |
| // Using std set and strings here because field trial parameters are std |
| // strings, and we cannot use WTF strings as Blink is not yet initialized. |
| std::set<std::string> throttleable_task_type_names; |
| std::set<std::string> freezable_task_type_names; |
| if (base::FeatureList::IsEnabled(kThrottleAndFreezeTaskTypes)) { |
| throttleable_task_type_names = |
| TaskTypesFromFieldTrialParam(kThrottleableTaskTypesListParam); |
| freezable_task_type_names = |
| TaskTypesFromFieldTrialParam(kFreezableTaskTypesListParam); |
| } |
| for (size_t i = 0; i < static_cast<size_t>(TaskType::kCount); i++) { |
| TaskType type = static_cast<TaskType>(i); |
| base::Optional<QueueTraits> queue_traits = |
| CreateQueueTraitsForTaskType(type); |
| if (queue_traits && (throttleable_task_type_names.size() || |
| freezable_task_type_names.size())) { |
| const char* task_type_name = TaskTypeNames::TaskTypeToString(type); |
| if (throttleable_task_type_names.erase(task_type_name)) |
| queue_traits->SetCanBeThrottled(true); |
| if (freezable_task_type_names.erase(task_type_name)) |
| queue_traits->SetCanBeFrozen(true); |
| } |
| frame_task_types_to_queue_traits[i] = queue_traits; |
| } |
| // Protect against configuration errors. |
| DCHECK(throttleable_task_type_names.empty()); |
| DCHECK(freezable_task_type_names.empty()); |
| } |
| |
| // static |
| base::Optional<QueueTraits> FrameSchedulerImpl::CreateQueueTraitsForTaskType( |
| TaskType type) { |
| // TODO(haraken): Optimize the mapping from TaskTypes to task runners. |
| switch (type) { |
| case TaskType::kJavascriptTimer: |
| return ThrottleableTaskQueueTraits(); |
| case TaskType::kInternalLoading: |
| case TaskType::kNetworking: |
| case TaskType::kNetworkingWithURLLoaderAnnotation: |
| case TaskType::kNetworkingControl: |
| // Loading task queues are handled separately. |
| return base::nullopt; |
| // Throttling following tasks may break existing web pages, so tentatively |
| // these are unthrottled. |
| // TODO(nhiroki): Throttle them again after we're convinced that it's safe |
| // or provide a mechanism that web pages can opt-out it if throttling is not |
| // desirable. |
| case TaskType::kDatabaseAccess: |
| case TaskType::kDOMManipulation: |
| case TaskType::kHistoryTraversal: |
| case TaskType::kEmbed: |
| case TaskType::kCanvasBlobSerialization: |
| case TaskType::kRemoteEvent: |
| case TaskType::kWebSocket: |
| case TaskType::kMicrotask: |
| case TaskType::kUnshippedPortMessage: |
| case TaskType::kFileReading: |
| case TaskType::kPresentation: |
| case TaskType::kSensor: |
| case TaskType::kPerformanceTimeline: |
| case TaskType::kWebGL: |
| case TaskType::kIdleTask: |
| case TaskType::kInternalDefault: |
| case TaskType::kMiscPlatformAPI: |
| // TODO(altimin): Move appropriate tasks to throttleable task queue. |
| return DeferrableTaskQueueTraits(); |
| // PostedMessage can be used for navigation, so we shouldn't defer it |
| // when expecting a user gesture. |
| case TaskType::kPostedMessage: |
| case TaskType::kWorkerAnimation: |
| // UserInteraction tasks should be run even when expecting a user gesture. |
| case TaskType::kUserInteraction: |
| // Media events should not be deferred to ensure that media playback is |
| // smooth. |
| case TaskType::kMediaElementEvent: |
| case TaskType::kInternalWebCrypto: |
| case TaskType::kInternalIndexedDB: |
| case TaskType::kInternalMedia: |
| case TaskType::kInternalMediaRealTime: |
| case TaskType::kInternalUserInteraction: |
| case TaskType::kInternalIntersectionObserver: |
| return PausableTaskQueueTraits(); |
| case TaskType::kInternalIPC: |
| // The TaskType of Inspector tasks needs to be unpausable because they need |
| // to run even on a paused page. |
| case TaskType::kInternalInspector: |
| // The TaskType of worker tasks needs to be unpausable (in addition to |
| // unthrottled and undeferred) not to prevent service workers that may |
| // control browser navigation on multiple tabs. |
| case TaskType::kInternalWorker: |
| // Some tasks in the tests need to run when objects are paused e.g. to hook |
| // when recovering from debugger JavaScript statetment. |
| case TaskType::kInternalTest: |
| return UnpausableTaskQueueTraits(); |
| case TaskType::kInternalTranslation: |
| return ForegroundOnlyTaskQueueTraits(); |
| case TaskType::kDeprecatedNone: |
| case TaskType::kMainThreadTaskQueueV8: |
| case TaskType::kMainThreadTaskQueueCompositor: |
| case TaskType::kMainThreadTaskQueueDefault: |
| case TaskType::kMainThreadTaskQueueInput: |
| case TaskType::kMainThreadTaskQueueIdle: |
| case TaskType::kMainThreadTaskQueueIPC: |
| case TaskType::kMainThreadTaskQueueControl: |
| case TaskType::kMainThreadTaskQueueCleanup: |
| case TaskType::kCompositorThreadTaskQueueDefault: |
| case TaskType::kCompositorThreadTaskQueueInput: |
| case TaskType::kWorkerThreadTaskQueueDefault: |
| case TaskType::kWorkerThreadTaskQueueV8: |
| case TaskType::kWorkerThreadTaskQueueCompositor: |
| case TaskType::kExperimentalWebSchedulingUserInteraction: |
| case TaskType::kExperimentalWebSchedulingBestEffort: |
| case TaskType::kCount: |
| // Not a valid frame-level TaskType. |
| return base::nullopt; |
| } |
| // This method is called for all values between 0 and kCount. TaskType, |
| // however, has numbering gaps, so even though all enumerated TaskTypes are |
| // handled in the switch and return a value, we fall through for some values |
| // of |type|. |
| return base::nullopt; |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> FrameSchedulerImpl::GetTaskRunner( |
| TaskType type) { |
| scoped_refptr<MainThreadTaskQueue> task_queue = GetTaskQueue(type); |
| DCHECK(task_queue); |
| return task_queue->CreateTaskRunner(type); |
| } |
| |
| scoped_refptr<MainThreadTaskQueue> FrameSchedulerImpl::GetTaskQueue( |
| TaskType type) { |
| switch (type) { |
| case TaskType::kInternalLoading: |
| case TaskType::kNetworking: |
| case TaskType::kNetworkingWithURLLoaderAnnotation: |
| return frame_task_queue_controller_->LoadingTaskQueue(); |
| case TaskType::kNetworkingControl: |
| return frame_task_queue_controller_->LoadingControlTaskQueue(); |
| case TaskType::kInternalInspector: |
| return frame_task_queue_controller_->InspectorTaskQueue(); |
| case TaskType::kExperimentalWebSchedulingUserInteraction: |
| return frame_task_queue_controller_->ExperimentalWebSchedulingTaskQueue( |
| FrameTaskQueueController::WebSchedulingTaskQueueType:: |
| kWebSchedulingUserVisiblePriority); |
| case TaskType::kExperimentalWebSchedulingBestEffort: |
| return frame_task_queue_controller_->ExperimentalWebSchedulingTaskQueue( |
| FrameTaskQueueController::WebSchedulingTaskQueueType:: |
| kWebSchedulingBestEffortPriority); |
| default: |
| // Non-loading task queue. |
| DCHECK_LT(static_cast<size_t>(type), |
| main_thread_scheduler_->scheduling_settings() |
| .frame_task_types_to_queue_traits.size()); |
| base::Optional<QueueTraits> queue_traits = |
| main_thread_scheduler_->scheduling_settings() |
| .frame_task_types_to_queue_traits[static_cast<size_t>(type)]; |
| // We don't have a QueueTraits mapping for |task_type| if it is not a |
| // frame-level task type. |
| DCHECK(queue_traits); |
| return frame_task_queue_controller_->NonLoadingTaskQueue( |
| queue_traits.value()); |
| } |
| } |
| |
| std::unique_ptr<WebResourceLoadingTaskRunnerHandle> |
| FrameSchedulerImpl::CreateResourceLoadingTaskRunnerHandle() { |
| return CreateResourceLoadingTaskRunnerHandleImpl(); |
| } |
| |
| std::unique_ptr<ResourceLoadingTaskRunnerHandleImpl> |
| FrameSchedulerImpl::CreateResourceLoadingTaskRunnerHandleImpl() { |
| if (main_thread_scheduler_->scheduling_settings() |
| .use_resource_fetch_priority || |
| (parent_page_scheduler_->IsLoading() && |
| main_thread_scheduler_->scheduling_settings() |
| .use_resource_priorities_only_during_loading)) { |
| scoped_refptr<MainThreadTaskQueue> task_queue = |
| frame_task_queue_controller_->NewResourceLoadingTaskQueue(); |
| resource_loading_task_queue_priorities_.insert( |
| task_queue, task_queue->GetQueuePriority()); |
| return ResourceLoadingTaskRunnerHandleImpl::WrapTaskRunner(task_queue); |
| } |
| |
| return ResourceLoadingTaskRunnerHandleImpl::WrapTaskRunner( |
| frame_task_queue_controller_->LoadingTaskQueue()); |
| } |
| |
| void FrameSchedulerImpl::DidChangeResourceLoadingPriority( |
| scoped_refptr<MainThreadTaskQueue> task_queue, |
| net::RequestPriority priority) { |
| // This check is done since in some cases (when kUseResourceFetchPriority |
| // feature isn't enabled) we use the loading task queue for resource loading |
| // and the priority of this queue shouldn't be affected by resource |
| // priorities. |
| auto queue_priority_pair = |
| resource_loading_task_queue_priorities_.find(task_queue); |
| if (queue_priority_pair != resource_loading_task_queue_priorities_.end()) { |
| task_queue->SetNetRequestPriority(priority); |
| queue_priority_pair->value = main_thread_scheduler_->scheduling_settings() |
| .net_to_blink_priority[priority]; |
| auto* voter = |
| frame_task_queue_controller_->GetQueueEnabledVoter(task_queue); |
| UpdateQueuePolicy(task_queue.get(), voter); |
| } |
| } |
| |
| void FrameSchedulerImpl::OnShutdownResourceLoadingTaskQueue( |
| scoped_refptr<MainThreadTaskQueue> task_queue) { |
| // This check is done since in some cases (when kUseResourceFetchPriority |
| // feature isn't enabled) we use the loading task queue for resource loading, |
| // and the lifetime of this queue isn't bound to one resource. |
| auto iter = resource_loading_task_queue_priorities_.find(task_queue); |
| if (iter != resource_loading_task_queue_priorities_.end()) { |
| resource_loading_task_queue_priorities_.erase(iter); |
| bool removed = frame_task_queue_controller_->RemoveResourceLoadingTaskQueue( |
| task_queue); |
| DCHECK(removed); |
| CleanUpQueue(task_queue.get()); |
| } |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> |
| FrameSchedulerImpl::ControlTaskRunner() { |
| DCHECK(parent_page_scheduler_); |
| return main_thread_scheduler_->ControlTaskRunner(); |
| } |
| |
| blink::PageScheduler* FrameSchedulerImpl::GetPageScheduler() const { |
| return parent_page_scheduler_; |
| } |
| |
| void FrameSchedulerImpl::DidStartProvisionalLoad(bool is_main_frame) { |
| main_thread_scheduler_->DidStartProvisionalLoad(is_main_frame); |
| } |
| |
| void FrameSchedulerImpl::DidCommitProvisionalLoad( |
| bool is_web_history_inert_commit, |
| bool is_reload, |
| bool is_main_frame) { |
| main_thread_scheduler_->DidCommitProvisionalLoad(is_web_history_inert_commit, |
| is_reload, is_main_frame); |
| } |
| |
| WebScopedVirtualTimePauser FrameSchedulerImpl::CreateWebScopedVirtualTimePauser( |
| const WTF::String& name, |
| WebScopedVirtualTimePauser::VirtualTaskDuration duration) { |
| return WebScopedVirtualTimePauser(main_thread_scheduler_, duration, name); |
| } |
| |
| void FrameSchedulerImpl::DidOpenActiveConnection() { |
| ++active_connection_count_; |
| has_active_connection_ = static_cast<bool>(active_connection_count_); |
| if (parent_page_scheduler_) |
| parent_page_scheduler_->OnConnectionUpdated(); |
| } |
| |
| void FrameSchedulerImpl::DidCloseActiveConnection() { |
| DCHECK_GT(active_connection_count_, 0); |
| --active_connection_count_; |
| has_active_connection_ = static_cast<bool>(active_connection_count_); |
| if (parent_page_scheduler_) |
| parent_page_scheduler_->OnConnectionUpdated(); |
| } |
| |
| void FrameSchedulerImpl::AsValueInto( |
| base::trace_event::TracedValue* state) const { |
| state->SetBoolean("frame_visible", frame_visible_); |
| state->SetBoolean("page_visible", parent_page_scheduler_->IsPageVisible()); |
| state->SetBoolean("cross_origin", IsCrossOrigin()); |
| state->SetString("frame_type", |
| frame_type_ == FrameScheduler::FrameType::kMainFrame |
| ? "MainFrame" |
| : "Subframe"); |
| state->SetBoolean( |
| "disable_background_timer_throttling", |
| !RuntimeEnabledFeatures::TimerThrottlingForBackgroundTabsEnabled()); |
| |
| state->BeginDictionary("frame_task_queue_controller"); |
| frame_task_queue_controller_->AsValueInto(state); |
| state->EndDictionary(); |
| |
| if (blame_context_) { |
| state->BeginDictionary("blame_context"); |
| state->SetString( |
| "id_ref", |
| PointerToString(reinterpret_cast<void*>(blame_context_->id()))); |
| state->SetString("scope", blame_context_->scope()); |
| state->EndDictionary(); |
| } |
| } |
| |
| void FrameSchedulerImpl::SetPageVisibilityForTracing( |
| PageVisibilityState page_visibility) { |
| page_visibility_for_tracing_ = page_visibility; |
| } |
| |
| bool FrameSchedulerImpl::IsPageVisible() const { |
| return parent_page_scheduler_ ? parent_page_scheduler_->IsPageVisible() |
| : true; |
| } |
| |
| bool FrameSchedulerImpl::IsAudioPlaying() const { |
| return parent_page_scheduler_ ? parent_page_scheduler_->IsAudioPlaying() |
| : false; |
| } |
| |
| void FrameSchedulerImpl::SetPaused(bool frame_paused) { |
| DCHECK(parent_page_scheduler_); |
| if (frame_paused_ == frame_paused) |
| return; |
| |
| frame_paused_ = frame_paused; |
| UpdatePolicy(); |
| } |
| |
| void FrameSchedulerImpl::SetPageFrozenForTracing(bool frozen) { |
| page_frozen_for_tracing_ = frozen; |
| } |
| |
| void FrameSchedulerImpl::SetPageKeepActiveForTracing(bool keep_active) { |
| page_keep_active_for_tracing_ = keep_active; |
| } |
| |
| void FrameSchedulerImpl::UpdatePolicy() { |
| bool task_queues_were_throttled = task_queues_throttled_; |
| task_queues_throttled_ = ShouldThrottleTaskQueues(); |
| |
| for (const auto& task_queue_and_voter : |
| frame_task_queue_controller_->GetAllTaskQueuesAndVoters()) { |
| UpdateQueuePolicy(task_queue_and_voter.first, task_queue_and_voter.second); |
| if (task_queues_were_throttled != task_queues_throttled_) { |
| UpdateTaskQueueThrottling(task_queue_and_voter.first, |
| task_queues_throttled_); |
| } |
| } |
| |
| NotifyLifecycleObservers(); |
| } |
| |
| void FrameSchedulerImpl::UpdateQueuePolicy( |
| MainThreadTaskQueue* queue, |
| TaskQueue::QueueEnabledVoter* voter) { |
| DCHECK(queue); |
| UpdatePriority(queue); |
| if (!voter) |
| return; |
| DCHECK(parent_page_scheduler_); |
| bool queue_disabled = false; |
| queue_disabled |= frame_paused_ && queue->CanBePaused(); |
| // Per-frame freezable task queues will be frozen after 5 mins in background |
| // on Android, and if the browser freezes the page in the background. They |
| // will be resumed when the page is visible. |
| bool queue_frozen = |
| parent_page_scheduler_->IsFrozen() && queue->CanBeFrozen(); |
| // Override freezing if keep-active is true. |
| if (queue_frozen && !queue->FreezeWhenKeepActive()) |
| queue_frozen = !parent_page_scheduler_->KeepActive(); |
| queue_disabled |= queue_frozen; |
| // Per-frame freezable queues of tasks which are specified as getting frozen |
| // immediately when their frame becomes invisible get frozen. They will be |
| // resumed when the frame becomes visible again. |
| queue_disabled |= !frame_visible_ && !queue->CanRunInBackground(); |
| voter->SetQueueEnabled(!queue_disabled); |
| } |
| |
| SchedulingLifecycleState FrameSchedulerImpl::CalculateLifecycleState( |
| ObserverType type) const { |
| // Detached frames are not throttled. |
| if (!parent_page_scheduler_) |
| return SchedulingLifecycleState::kNotThrottled; |
| |
| if (parent_page_scheduler_->IsFrozen() && |
| !parent_page_scheduler_->KeepActive()) { |
| DCHECK(!parent_page_scheduler_->IsPageVisible()); |
| return SchedulingLifecycleState::kStopped; |
| } |
| if (subresource_loading_paused_ && type == ObserverType::kLoader) |
| return SchedulingLifecycleState::kStopped; |
| if (type == ObserverType::kLoader && |
| parent_page_scheduler_->HasActiveConnection()) { |
| return SchedulingLifecycleState::kNotThrottled; |
| } |
| if (parent_page_scheduler_->IsThrottled()) |
| return SchedulingLifecycleState::kThrottled; |
| if (!parent_page_scheduler_->IsPageVisible()) |
| return SchedulingLifecycleState::kHidden; |
| return SchedulingLifecycleState::kNotThrottled; |
| } |
| |
| void FrameSchedulerImpl::OnFirstMeaningfulPaint() { |
| main_thread_scheduler_->OnFirstMeaningfulPaint(); |
| } |
| |
| std::unique_ptr<FrameScheduler::ActiveConnectionHandle> |
| FrameSchedulerImpl::OnActiveConnectionCreated() { |
| return std::make_unique<FrameSchedulerImpl::ActiveConnectionHandleImpl>(this); |
| } |
| |
| bool FrameSchedulerImpl::ShouldThrottleTaskQueues() const { |
| if (!RuntimeEnabledFeatures::TimerThrottlingForBackgroundTabsEnabled()) |
| return false; |
| if (parent_page_scheduler_ && parent_page_scheduler_->IsAudioPlaying()) |
| return false; |
| if (!parent_page_scheduler_->IsPageVisible()) |
| return true; |
| return RuntimeEnabledFeatures::TimerThrottlingForHiddenFramesEnabled() && |
| !frame_visible_ && IsCrossOrigin(); |
| } |
| |
| void FrameSchedulerImpl::UpdateTaskQueueThrottling( |
| MainThreadTaskQueue* task_queue, |
| bool should_throttle) { |
| if (!task_queue->CanBeThrottled()) |
| return; |
| if (should_throttle) { |
| main_thread_scheduler_->task_queue_throttler()->IncreaseThrottleRefCount( |
| task_queue); |
| } else { |
| main_thread_scheduler_->task_queue_throttler()->DecreaseThrottleRefCount( |
| task_queue); |
| } |
| } |
| |
| bool FrameSchedulerImpl::IsExemptFromBudgetBasedThrottling() const { |
| return has_active_connection(); |
| } |
| |
| TaskQueue::QueuePriority FrameSchedulerImpl::ComputePriority( |
| MainThreadTaskQueue* task_queue) const { |
| DCHECK(task_queue); |
| |
| FrameScheduler* frame_scheduler = task_queue->GetFrameScheduler(); |
| |
| // Checks the task queue is associated with this frame scheduler. |
| DCHECK_EQ(frame_scheduler, this); |
| |
| auto queue_priority_pair = resource_loading_task_queue_priorities_.find( |
| base::WrapRefCounted(task_queue)); |
| if (queue_priority_pair != resource_loading_task_queue_priorities_.end()) { |
| return queue_priority_pair->value; |
| } |
| |
| base::Optional<TaskQueue::QueuePriority> fixed_priority = |
| task_queue->FixedPriority(); |
| |
| if (fixed_priority) |
| return fixed_priority.value(); |
| |
| // A hidden page with no audio. |
| if (parent_page_scheduler_->IsBackgrounded()) { |
| if (main_thread_scheduler_->scheduling_settings() |
| .low_priority_background_page) |
| return TaskQueue::QueuePriority::kLowPriority; |
| |
| if (main_thread_scheduler_->scheduling_settings() |
| .best_effort_background_page) |
| return TaskQueue::QueuePriority::kBestEffortPriority; |
| } |
| |
| // If the page is loading or if the priority experiments should take place at |
| // all times. |
| if (parent_page_scheduler_->IsLoading() || |
| !main_thread_scheduler_->scheduling_settings() |
| .use_frame_priorities_only_during_loading) { |
| // Low priority feature enabled for hidden frame. |
| if (main_thread_scheduler_->scheduling_settings() |
| .low_priority_hidden_frame && |
| !IsFrameVisible()) |
| return TaskQueue::QueuePriority::kLowPriority; |
| |
| bool is_subframe = GetFrameType() == FrameScheduler::FrameType::kSubframe; |
| bool is_throttleable_task_queue = |
| task_queue->queue_type() == |
| MainThreadTaskQueue::QueueType::kFrameThrottleable; |
| |
| // Low priority feature enabled for sub-frame. |
| if (main_thread_scheduler_->scheduling_settings().low_priority_subframe && |
| is_subframe) |
| return TaskQueue::QueuePriority::kLowPriority; |
| |
| // Low priority feature enabled for sub-frame throttleable task queues. |
| if (main_thread_scheduler_->scheduling_settings() |
| .low_priority_subframe_throttleable && |
| is_subframe && is_throttleable_task_queue) |
| return TaskQueue::QueuePriority::kLowPriority; |
| |
| // Low priority feature enabled for throttleable task queues. |
| if (main_thread_scheduler_->scheduling_settings() |
| .low_priority_throttleable && |
| is_throttleable_task_queue) |
| return TaskQueue::QueuePriority::kLowPriority; |
| } |
| |
| // Ad frame experiment. |
| if (IsAdFrame() && (parent_page_scheduler_->IsLoading() || |
| !main_thread_scheduler_->scheduling_settings() |
| .use_adframe_priorities_only_during_loading)) { |
| if (main_thread_scheduler_->scheduling_settings().low_priority_ad_frame) { |
| return TaskQueue::QueuePriority::kLowPriority; |
| } |
| |
| if (main_thread_scheduler_->scheduling_settings().best_effort_ad_frame) { |
| return TaskQueue::QueuePriority::kBestEffortPriority; |
| } |
| } |
| |
| // Frame origin type experiment. |
| if (IsCrossOrigin()) { |
| if (main_thread_scheduler_->scheduling_settings() |
| .low_priority_cross_origin || |
| (main_thread_scheduler_->scheduling_settings() |
| .low_priority_cross_origin_only_during_loading && |
| parent_page_scheduler_->IsLoading())) { |
| return TaskQueue::QueuePriority::kLowPriority; |
| } |
| } |
| |
| if (task_queue->queue_type() == |
| MainThreadTaskQueue::QueueType::kWebSchedulingUserInteraction) { |
| return TaskQueue::QueuePriority::kNormalPriority; |
| } |
| |
| if (task_queue->queue_type() == |
| MainThreadTaskQueue::QueueType::kWebSchedulingBestEffort) { |
| return TaskQueue::QueuePriority::kLowPriority; |
| } |
| |
| return task_queue->queue_type() == |
| MainThreadTaskQueue::QueueType::kFrameLoadingControl |
| ? TaskQueue::QueuePriority::kHighPriority |
| : TaskQueue::QueuePriority::kNormalPriority; |
| } |
| |
| std::unique_ptr<blink::mojom::blink::PauseSubresourceLoadingHandle> |
| FrameSchedulerImpl::GetPauseSubresourceLoadingHandle() { |
| return std::make_unique<PauseSubresourceLoadingHandleImpl>( |
| weak_factory_.GetWeakPtr()); |
| } |
| |
| void FrameSchedulerImpl::AddPauseSubresourceLoadingHandle() { |
| ++subresource_loading_pause_count_; |
| if (subresource_loading_pause_count_ != 1) { |
| DCHECK(subresource_loading_paused_); |
| return; |
| } |
| |
| DCHECK(!subresource_loading_paused_); |
| subresource_loading_paused_ = true; |
| UpdatePolicy(); |
| } |
| |
| void FrameSchedulerImpl::RemovePauseSubresourceLoadingHandle() { |
| DCHECK_LT(0u, subresource_loading_pause_count_); |
| --subresource_loading_pause_count_; |
| DCHECK(subresource_loading_paused_); |
| if (subresource_loading_pause_count_ == 0) { |
| subresource_loading_paused_ = false; |
| UpdatePolicy(); |
| } |
| } |
| |
| ukm::UkmRecorder* FrameSchedulerImpl::GetUkmRecorder() { |
| if (!delegate_) |
| return nullptr; |
| return delegate_->GetUkmRecorder(); |
| } |
| |
| ukm::SourceId FrameSchedulerImpl::GetUkmSourceId() { |
| if (!delegate_) |
| return ukm::kInvalidSourceId; |
| return delegate_->GetUkmSourceId(); |
| } |
| |
| void FrameSchedulerImpl::OnTaskQueueCreated( |
| MainThreadTaskQueue* task_queue, |
| base::sequence_manager::TaskQueue::QueueEnabledVoter* voter) { |
| DCHECK(parent_page_scheduler_); |
| |
| task_queue->SetBlameContext(blame_context_); |
| UpdateQueuePolicy(task_queue, voter); |
| |
| if (task_queue->CanBeThrottled()) { |
| CPUTimeBudgetPool* time_budget_pool = |
| parent_page_scheduler_->BackgroundCPUTimeBudgetPool(); |
| if (time_budget_pool) { |
| time_budget_pool->AddQueue( |
| main_thread_scheduler_->tick_clock()->NowTicks(), task_queue); |
| } |
| if (task_queues_throttled_) { |
| UpdateTaskQueueThrottling(task_queue, true); |
| } |
| } |
| } |
| |
| // static |
| MainThreadTaskQueue::QueueTraits |
| FrameSchedulerImpl::ThrottleableTaskQueueTraits() { |
| return QueueTraits() |
| .SetCanBeThrottled(true) |
| .SetCanBeFrozen(true) |
| .SetCanBeDeferred(true) |
| .SetCanBePaused(true); |
| } |
| |
| // static |
| MainThreadTaskQueue::QueueTraits |
| FrameSchedulerImpl::DeferrableTaskQueueTraits() { |
| return QueueTraits() |
| .SetCanBeDeferred(true) |
| .SetCanBeFrozen(base::FeatureList::IsEnabled( |
| blink::features::kStopNonTimersInBackground)) |
| .SetCanBePaused(true); |
| } |
| |
| // static |
| MainThreadTaskQueue::QueueTraits FrameSchedulerImpl::PausableTaskQueueTraits() { |
| return QueueTraits() |
| .SetCanBeFrozen(base::FeatureList::IsEnabled( |
| blink::features::kStopNonTimersInBackground)) |
| .SetCanBePaused(true); |
| } |
| |
| // static |
| MainThreadTaskQueue::QueueTraits |
| FrameSchedulerImpl::UnpausableTaskQueueTraits() { |
| return QueueTraits(); |
| } |
| |
| MainThreadTaskQueue::QueueTraits |
| FrameSchedulerImpl::ForegroundOnlyTaskQueueTraits() { |
| return ThrottleableTaskQueueTraits().SetCanRunInBackground(false); |
| } |
| |
| } // namespace scheduler |
| } // namespace blink |