blob: e002692640f02b309a7cd84c97ec9654dcd7f736 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// 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/modules/scheduler/dom_scheduler.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_scheduler_post_task_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_scheduler_post_task_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_scheduler_yield_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_abortsignal_schedulersignalinherit.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/scheduler/dom_task.h"
#include "third_party/blink/renderer/modules/scheduler/dom_task_continuation.h"
#include "third_party/blink/renderer/modules/scheduler/dom_task_signal.h"
#include "third_party/blink/renderer/modules/scheduler/script_wrappable_task_state.h"
#include "third_party/blink/renderer/platform/bindings/enumeration_base.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_queue_type.h"
#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_task_queue.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
const char DOMScheduler::kSupplementName[] = "DOMScheduler";
DOMScheduler* DOMScheduler::scheduler(ExecutionContext& context) {
DOMScheduler* scheduler =
Supplement<ExecutionContext>::From<DOMScheduler>(context);
if (!scheduler) {
scheduler = MakeGarbageCollected<DOMScheduler>(&context);
Supplement<ExecutionContext>::ProvideTo(context, scheduler);
}
return scheduler;
}
DOMScheduler::DOMScheduler(ExecutionContext* context)
: ExecutionContextLifecycleObserver(context),
Supplement<ExecutionContext>(*context),
fixed_priority_task_signals_(kWebSchedulingPriorityCount) {
if (context->IsContextDestroyed()) {
return;
}
CHECK(context->GetScheduler());
CreateFixedPriorityTaskQueues(context, WebSchedulingQueueType::kTaskQueue,
fixed_priority_task_queues_);
if (RuntimeEnabledFeatures::SchedulerYieldEnabled(context)) {
CreateFixedPriorityTaskQueues(context,
WebSchedulingQueueType::kContinuationQueue,
fixed_priority_continuation_queues_);
}
}
void DOMScheduler::ContextDestroyed() {
fixed_priority_task_queues_.clear();
signal_to_task_queue_map_.clear();
}
void DOMScheduler::Trace(Visitor* visitor) const {
visitor->Trace(fixed_priority_task_queues_);
visitor->Trace(fixed_priority_continuation_queues_);
visitor->Trace(fixed_priority_task_signals_);
visitor->Trace(signal_to_task_queue_map_);
visitor->Trace(signal_to_continuation_queue_map_);
ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
Supplement<ExecutionContext>::Trace(visitor);
}
ScriptPromise DOMScheduler::postTask(
ScriptState* script_state,
V8SchedulerPostTaskCallback* callback_function,
SchedulerPostTaskOptions* options,
ExceptionState& exception_state) {
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
// The bindings layer implicitly converts thrown exceptions in
// promise-returning functions to promise rejections.
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Current window is detached");
return ScriptPromise();
}
auto* task_signal = GetTaskSignalFromOptions(
script_state, exception_state, options->getSignalOr(nullptr),
options->hasPriority()
? AtomicString(IDLEnumAsString(options->priority()))
: g_null_atom);
if (exception_state.HadException()) {
// The given signal was aborted.
return ScriptPromise();
}
CHECK(task_signal);
auto* task_queue =
GetTaskQueue(task_signal, WebSchedulingQueueType::kTaskQueue);
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(
script_state, exception_state.GetContext());
MakeGarbageCollected<DOMTask>(resolver, callback_function, task_signal,
task_queue,
base::Milliseconds(options->delay()));
return resolver->Promise();
}
ScriptPromise DOMScheduler::yield(ScriptState* script_state,
SchedulerYieldOptions* options,
ExceptionState& exception_state) {
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Current window is detached");
return ScriptPromise();
}
// Abort and priority can be inherited together or separately. Abort
// inheritance only depends on the signal option. Signal inheritance implies
// priority inheritance, but can be overridden by specifying a fixed
// priority.
absl::variant<AbortSignal*, InheritOption> signal_option(nullptr);
if (options->hasSignal()) {
if (options->signal()->IsSchedulerSignalInherit()) {
// {signal: "inherit"}
signal_option = InheritOption::kInherit;
} else {
// {signal: signalObject}
signal_option = options->signal()->GetAsAbortSignal();
}
}
absl::variant<AtomicString, InheritOption> priority_option(g_null_atom);
if ((options->hasPriority() && options->priority() == "inherit")) {
// {priority: "inherit"}
priority_option = InheritOption::kInherit;
} else if (!options->hasPriority() &&
absl::holds_alternative<InheritOption>(signal_option)) {
// {signal: "inherit"} with no priority override.
priority_option = InheritOption::kInherit;
} else if (options->hasPriority()) {
// Priority override.
priority_option = AtomicString(IDLEnumAsString(options->priority()));
}
auto* task_signal = GetTaskSignalFromOptions(script_state, exception_state,
signal_option, priority_option);
if (exception_state.HadException()) {
// The given or inherited signal was aborted.
return ScriptPromise();
}
CHECK(task_signal);
auto* task_queue =
GetTaskQueue(task_signal, WebSchedulingQueueType::kContinuationQueue);
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(
script_state, exception_state.GetContext());
MakeGarbageCollected<DOMTaskContinuation>(resolver, task_signal, task_queue);
return resolver->Promise();
}
scheduler::TaskAttributionIdType DOMScheduler::taskId(
ScriptState* script_state) {
ThreadScheduler* scheduler = ThreadScheduler::Current();
DCHECK(scheduler);
DCHECK(scheduler->GetTaskAttributionTracker());
absl::optional<scheduler::TaskAttributionId> task_id =
scheduler->GetTaskAttributionTracker()->RunningTaskAttributionId(
script_state);
// task_id cannot be unset here, as a task has presumably already ran in order
// for this API call to be called.
DCHECK(task_id);
return task_id.value().value();
}
AtomicString DOMScheduler::isAncestor(
ScriptState* script_state,
scheduler::TaskAttributionIdType parentId) {
scheduler::TaskAttributionTracker::AncestorStatus status =
scheduler::TaskAttributionTracker::AncestorStatus::kNotAncestor;
ThreadScheduler* scheduler = ThreadScheduler::Current();
DCHECK(scheduler);
scheduler::TaskAttributionTracker* tracker =
scheduler->GetTaskAttributionTracker();
DCHECK(tracker);
status =
tracker->IsAncestor(script_state, scheduler::TaskAttributionId(parentId));
switch (status) {
case scheduler::TaskAttributionTracker::AncestorStatus::kAncestor:
return AtomicString("ancestor");
case scheduler::TaskAttributionTracker::AncestorStatus::kNotAncestor:
return AtomicString("not ancestor");
case scheduler::TaskAttributionTracker::AncestorStatus::kUnknown:
return AtomicString("unknown");
}
NOTREACHED();
return AtomicString("not reached");
}
void DOMScheduler::CreateFixedPriorityTaskQueues(
ExecutionContext* context,
WebSchedulingQueueType queue_type,
FixedPriorityTaskQueueVector& task_queues) {
FrameOrWorkerScheduler* scheduler = context->GetScheduler();
for (size_t i = 0; i < kWebSchedulingPriorityCount; i++) {
auto priority = static_cast<WebSchedulingPriority>(i);
std::unique_ptr<WebSchedulingTaskQueue> task_queue =
scheduler->CreateWebSchedulingTaskQueue(queue_type, priority);
task_queues.push_back(
MakeGarbageCollected<DOMTaskQueue>(std::move(task_queue), priority));
}
}
DOMScheduler::DOMTaskQueue* DOMScheduler::CreateDynamicPriorityTaskQueue(
DOMTaskSignal* signal,
WebSchedulingQueueType queue_type) {
FrameOrWorkerScheduler* scheduler = GetExecutionContext()->GetScheduler();
CHECK(scheduler);
WebSchedulingPriority priority =
WebSchedulingPriorityFromString(signal->priority());
std::unique_ptr<WebSchedulingTaskQueue> task_queue =
scheduler->CreateWebSchedulingTaskQueue(queue_type, priority);
CHECK(task_queue);
auto* dom_task_queue =
MakeGarbageCollected<DOMTaskQueue>(std::move(task_queue), priority);
auto* handle = signal->AddPriorityChangeAlgorithm(WTF::BindRepeating(
&DOMScheduler::OnPriorityChange, WrapWeakPersistent(this),
WrapWeakPersistent(signal), WrapWeakPersistent(dom_task_queue)));
dom_task_queue->SetPriorityChangeHandle(handle);
return dom_task_queue;
}
DOMTaskSignal* DOMScheduler::GetTaskSignalFromOptions(
ScriptState* script_state,
ExceptionState& exception_state,
absl::variant<AbortSignal*, InheritOption> signal_option,
absl::variant<AtomicString, InheritOption> priority_option) {
// `inherited_signal` will be null if no inheritance was specified or there's
// nothing to inherit, e.g. yielding from a non-postTask task.
// Note: `inherited_signal` will be the one from the original task, i.e. it
// doesn't get reset by continuations.
DOMTaskSignal* inherited_signal = nullptr;
if (absl::holds_alternative<InheritOption>(signal_option) ||
absl::holds_alternative<InheritOption>(priority_option)) {
CHECK(RuntimeEnabledFeatures::SchedulerYieldEnabled(
ExecutionContext::From(script_state)));
if (auto* inherited_state =
ScriptWrappableTaskState::GetCurrent(script_state)) {
inherited_signal = inherited_state->GetSignal();
}
}
AbortSignal* abort_source =
absl::holds_alternative<AbortSignal*>(signal_option)
? absl::get<AbortSignal*>(signal_option)
: inherited_signal;
// Short-circuit things now that we know if `abort_source` is aborted.
if (abort_source && abort_source->aborted()) {
exception_state.RethrowV8Exception(
ToV8Traits<IDLAny>::ToV8(script_state,
abort_source->reason(script_state))
.ToLocalChecked());
return nullptr;
}
DOMTaskSignal* priority_source = nullptr;
if (absl::holds_alternative<InheritOption>(priority_option)) {
priority_source = inherited_signal;
} else if (absl::get<AtomicString>(priority_option) != g_null_atom) {
// The priority option overrides the signal for priority.
priority_source = GetFixedPriorityTaskSignal(
script_state, WebSchedulingPriorityFromString(
absl::get<AtomicString>(priority_option)));
} else if (IsA<DOMTaskSignal>(absl::get<AbortSignal*>(signal_option))) {
priority_source = To<DOMTaskSignal>(absl::get<AbortSignal*>(signal_option));
}
// `priority_source` is null if there was nothing to inherit or no signal or
// priority was specified.
if (!priority_source) {
priority_source =
GetFixedPriorityTaskSignal(script_state, kDefaultPriority);
}
// The priority and abort sources are the same non-null task signal, so use
// that signal.
if (priority_source == abort_source) {
return priority_source;
}
// `priority_source` is already settled for abort and priority, and there is
// no abort source to combine with. Use `priority_source` rather than creating
// a new one.
if (priority_source->HasFixedPriority() && !priority_source->CanAbort() &&
(!abort_source || !abort_source->CanAbort())) {
return priority_source;
}
// Otherwise there are separate priority and abort sources. Create a
// composite signal from the sources and use that.
if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
HeapVector<Member<AbortSignal>> abort_source_signals;
if (abort_source) {
abort_source_signals.push_back(abort_source);
}
return MakeGarbageCollected<DOMTaskSignal>(
script_state, priority_source->priority(), priority_source,
abort_source_signals);
} else {
// Fall back to use Follow if composition isn't enabled (kill switch path).
CHECK(priority_source->HasFixedPriority());
CHECK_EQ(priority_source->GetSignalType(),
AbortSignal::SignalType::kInternal);
// `priority_source` wasn't returned earlier because internal signals are
// never settled. Even though an abort algorithm will be added, it's safe
// to just use this signal.
if (!abort_source) {
return priority_source;
}
auto* result_signal = DOMTaskSignal::CreateFixedPriorityTaskSignal(
script_state, priority_source->priority());
result_signal->Follow(script_state, abort_source);
return result_signal;
}
}
DOMTaskSignal* DOMScheduler::GetFixedPriorityTaskSignal(
ScriptState* script_state,
WebSchedulingPriority priority) {
wtf_size_t index = static_cast<wtf_size_t>(priority);
if (!fixed_priority_task_signals_[index]) {
auto* signal = DOMTaskSignal::CreateFixedPriorityTaskSignal(
script_state, WebSchedulingPriorityToString(priority));
CHECK(signal->HasFixedPriority());
fixed_priority_task_signals_[index] = signal;
}
return fixed_priority_task_signals_[index];
}
DOMScheduler::DOMTaskQueue* DOMScheduler::GetTaskQueue(
DOMTaskSignal* task_signal,
WebSchedulingQueueType queue_type) {
if (task_signal->HasFixedPriority()) {
auto priority = WebSchedulingPriorityFromString(task_signal->priority());
return queue_type == WebSchedulingQueueType::kTaskQueue
? fixed_priority_task_queues_[static_cast<wtf_size_t>(priority)]
: fixed_priority_continuation_queues_[static_cast<wtf_size_t>(
priority)];
} else {
SignalToTaskQueueMap& queue_map =
queue_type == WebSchedulingQueueType::kTaskQueue
? signal_to_task_queue_map_
: signal_to_continuation_queue_map_;
if (queue_map.Contains(task_signal)) {
return queue_map.at(task_signal);
}
// We haven't seen this task signal before, so create a task queue for it.
auto* dom_task_queue =
CreateDynamicPriorityTaskQueue(task_signal, queue_type);
queue_map.insert(task_signal, dom_task_queue);
return dom_task_queue;
}
}
void DOMScheduler::OnPriorityChange(DOMTaskSignal* signal,
DOMTaskQueue* task_queue) {
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
return;
}
DCHECK(signal);
task_queue->SetPriority(WebSchedulingPriorityFromString(signal->priority()));
}
DOMScheduler::DOMTaskQueue::DOMTaskQueue(
std::unique_ptr<WebSchedulingTaskQueue> task_queue,
WebSchedulingPriority priority)
: web_scheduling_task_queue_(std::move(task_queue)),
task_runner_(web_scheduling_task_queue_->GetTaskRunner()),
priority_(priority) {
DCHECK(task_runner_);
}
void DOMScheduler::DOMTaskQueue::Trace(Visitor* visitor) const {
visitor->Trace(priority_change_handle_);
}
void DOMScheduler::DOMTaskQueue::SetPriority(WebSchedulingPriority priority) {
if (priority_ == priority)
return;
web_scheduling_task_queue_->SetPriority(priority);
priority_ = priority;
}
DOMScheduler::DOMTaskQueue::~DOMTaskQueue() = default;
} // namespace blink