| // 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/core/dom/scripted_idle_task_controller.h" |
| |
| #include "base/location.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/core/dom/idle_request_options.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/platform/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/blink/renderer/platform/wtf/ref_counted.h" |
| #include "third_party/blink/renderer/platform/wtf/time.h" |
| |
| namespace blink { |
| |
| namespace internal { |
| |
| class IdleRequestCallbackWrapper |
| : public RefCounted<IdleRequestCallbackWrapper> { |
| public: |
| static scoped_refptr<IdleRequestCallbackWrapper> Create( |
| ScriptedIdleTaskController::CallbackId id, |
| ScriptedIdleTaskController* controller) { |
| return base::AdoptRef(new IdleRequestCallbackWrapper(id, controller)); |
| } |
| virtual ~IdleRequestCallbackWrapper() = default; |
| |
| static void IdleTaskFired( |
| scoped_refptr<IdleRequestCallbackWrapper> callback_wrapper, |
| TimeTicks deadline) { |
| if (ScriptedIdleTaskController* controller = |
| callback_wrapper->Controller()) { |
| // If we are going to yield immediately, reschedule the callback for |
| // later. |
| if (ThreadScheduler::Current()->ShouldYieldForHighPriorityWork()) { |
| controller->ScheduleCallback(std::move(callback_wrapper), |
| /* timeout_millis */ 0); |
| return; |
| } |
| controller->CallbackFired(callback_wrapper->Id(), deadline, |
| IdleDeadline::CallbackType::kCalledWhenIdle); |
| } |
| callback_wrapper->Cancel(); |
| } |
| |
| static void TimeoutFired( |
| scoped_refptr<IdleRequestCallbackWrapper> callback_wrapper) { |
| if (ScriptedIdleTaskController* controller = |
| callback_wrapper->Controller()) { |
| controller->CallbackFired(callback_wrapper->Id(), CurrentTimeTicks(), |
| IdleDeadline::CallbackType::kCalledByTimeout); |
| } |
| callback_wrapper->Cancel(); |
| } |
| |
| void Cancel() { controller_ = nullptr; } |
| |
| ScriptedIdleTaskController::CallbackId Id() const { return id_; } |
| ScriptedIdleTaskController* Controller() const { return controller_; } |
| |
| private: |
| IdleRequestCallbackWrapper(ScriptedIdleTaskController::CallbackId id, |
| ScriptedIdleTaskController* controller) |
| : id_(id), controller_(controller) {} |
| |
| ScriptedIdleTaskController::CallbackId id_; |
| WeakPersistent<ScriptedIdleTaskController> controller_; |
| }; |
| |
| } // namespace internal |
| |
| ScriptedIdleTaskController::V8IdleTask::V8IdleTask( |
| V8IdleRequestCallback* callback) |
| : callback_(callback) {} |
| |
| void ScriptedIdleTaskController::V8IdleTask::Trace(blink::Visitor* visitor) { |
| visitor->Trace(callback_); |
| ScriptedIdleTaskController::IdleTask::Trace(visitor); |
| } |
| |
| void ScriptedIdleTaskController::V8IdleTask::invoke(IdleDeadline* deadline) { |
| callback_->InvokeAndReportException(nullptr, deadline); |
| } |
| |
| ScriptedIdleTaskController::ScriptedIdleTaskController( |
| ExecutionContext* context) |
| : PausableObject(context), |
| scheduler_(ThreadScheduler::Current()), |
| next_callback_id_(0), |
| paused_(false) { |
| PauseIfNeeded(); |
| } |
| |
| ScriptedIdleTaskController::~ScriptedIdleTaskController() = default; |
| |
| void ScriptedIdleTaskController::Trace(blink::Visitor* visitor) { |
| visitor->Trace(idle_tasks_); |
| PausableObject::Trace(visitor); |
| } |
| |
| int ScriptedIdleTaskController::NextCallbackId() { |
| while (true) { |
| ++next_callback_id_; |
| |
| if (!IsValidCallbackId(next_callback_id_)) |
| next_callback_id_ = 1; |
| |
| if (!idle_tasks_.Contains(next_callback_id_)) |
| return next_callback_id_; |
| } |
| } |
| |
| ScriptedIdleTaskController::CallbackId |
| ScriptedIdleTaskController::RegisterCallback( |
| IdleTask* idle_task, |
| const IdleRequestOptions* options) { |
| DCHECK(idle_task); |
| |
| CallbackId id = NextCallbackId(); |
| idle_tasks_.Set(id, idle_task); |
| long long timeout_millis = options->timeout(); |
| |
| probe::AsyncTaskScheduled(GetExecutionContext(), "requestIdleCallback", |
| idle_task); |
| |
| scoped_refptr<internal::IdleRequestCallbackWrapper> callback_wrapper = |
| internal::IdleRequestCallbackWrapper::Create(id, this); |
| ScheduleCallback(std::move(callback_wrapper), timeout_millis); |
| TRACE_EVENT_INSTANT1("devtools.timeline", "RequestIdleCallback", |
| TRACE_EVENT_SCOPE_THREAD, "data", |
| inspector_idle_callback_request_event::Data( |
| GetExecutionContext(), id, timeout_millis)); |
| return id; |
| } |
| |
| void ScriptedIdleTaskController::ScheduleCallback( |
| scoped_refptr<internal::IdleRequestCallbackWrapper> callback_wrapper, |
| long long timeout_millis) { |
| scheduler_->PostIdleTask( |
| FROM_HERE, WTF::Bind(&internal::IdleRequestCallbackWrapper::IdleTaskFired, |
| callback_wrapper)); |
| if (timeout_millis > 0) { |
| GetExecutionContext() |
| ->GetTaskRunner(TaskType::kIdleTask) |
| ->PostDelayedTask( |
| FROM_HERE, |
| WTF::Bind(&internal::IdleRequestCallbackWrapper::TimeoutFired, |
| callback_wrapper), |
| TimeDelta::FromMilliseconds(timeout_millis)); |
| } |
| } |
| |
| void ScriptedIdleTaskController::CancelCallback(CallbackId id) { |
| TRACE_EVENT_INSTANT1( |
| "devtools.timeline", "CancelIdleCallback", TRACE_EVENT_SCOPE_THREAD, |
| "data", |
| inspector_idle_callback_cancel_event::Data(GetExecutionContext(), id)); |
| if (!IsValidCallbackId(id)) |
| return; |
| |
| idle_tasks_.erase(id); |
| } |
| |
| void ScriptedIdleTaskController::CallbackFired( |
| CallbackId id, |
| TimeTicks deadline, |
| IdleDeadline::CallbackType callback_type) { |
| if (!idle_tasks_.Contains(id)) |
| return; |
| |
| if (paused_) { |
| if (callback_type == IdleDeadline::CallbackType::kCalledByTimeout) { |
| // Queue for execution when we are resumed. |
| pending_timeouts_.push_back(id); |
| } |
| // Just drop callbacks called while suspended, these will be reposted on the |
| // idle task queue when we are resumed. |
| return; |
| } |
| |
| RunCallback(id, deadline, callback_type); |
| } |
| |
| void ScriptedIdleTaskController::RunCallback( |
| CallbackId id, |
| TimeTicks deadline, |
| IdleDeadline::CallbackType callback_type) { |
| DCHECK(!paused_); |
| |
| // Keep the idle task in |idle_tasks_| so that it's still wrapper-traced. |
| // TODO(https://crbug.com/796145): Remove this hack once on-stack objects |
| // get supported by either of wrapper-tracing or unified GC. |
| auto idle_task_iter = idle_tasks_.find(id); |
| if (idle_task_iter == idle_tasks_.end()) |
| return; |
| IdleTask* idle_task = idle_task_iter->value; |
| DCHECK(idle_task); |
| |
| TimeDelta allotted_time = |
| std::max(deadline - CurrentTimeTicks(), TimeDelta()); |
| |
| probe::AsyncTask async_task(GetExecutionContext(), idle_task); |
| probe::UserCallback probe(GetExecutionContext(), "requestIdleCallback", |
| AtomicString(), true); |
| |
| TRACE_EVENT1( |
| "devtools.timeline", "FireIdleCallback", "data", |
| inspector_idle_callback_fire_event::Data( |
| GetExecutionContext(), id, allotted_time.InMillisecondsF(), |
| callback_type == IdleDeadline::CallbackType::kCalledByTimeout)); |
| idle_task->invoke(IdleDeadline::Create(deadline, callback_type)); |
| |
| // Finally there is no need to keep the idle task alive. |
| // |
| // Do not use the iterator because the idle task might update |idle_tasks_|. |
| idle_tasks_.erase(id); |
| } |
| |
| void ScriptedIdleTaskController::ContextDestroyed(ExecutionContext*) { |
| idle_tasks_.clear(); |
| } |
| |
| void ScriptedIdleTaskController::Pause() { |
| paused_ = true; |
| } |
| |
| void ScriptedIdleTaskController::Unpause() { |
| DCHECK(paused_); |
| paused_ = false; |
| |
| // Run any pending timeouts. |
| Vector<CallbackId> pending_timeouts; |
| pending_timeouts_.swap(pending_timeouts); |
| for (auto& id : pending_timeouts) |
| RunCallback(id, CurrentTimeTicks(), |
| IdleDeadline::CallbackType::kCalledByTimeout); |
| |
| // Repost idle tasks for any remaining callbacks. |
| for (auto& idle_task : idle_tasks_) { |
| scoped_refptr<internal::IdleRequestCallbackWrapper> callback_wrapper = |
| internal::IdleRequestCallbackWrapper::Create(idle_task.key, this); |
| scheduler_->PostIdleTask( |
| FROM_HERE, |
| WTF::Bind(&internal::IdleRequestCallbackWrapper::IdleTaskFired, |
| callback_wrapper)); |
| } |
| } |
| |
| } // namespace blink |