blob: 622b8268f87250de37a93e945ae1750323aede2c [file] [log] [blame]
// Copyright 2018 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/core/dom/abort_signal.h"
#include <optional>
#include <utility>
#include "base/check_deref.h"
#include "base/functional/callback.h"
#include "base/functional/function_ref.h"
#include "base/time/time.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/core/dom/abort_signal_composition_manager.h"
#include "third_party/blink/renderer/core/dom/abort_signal_composition_type.h"
#include "third_party/blink/renderer/core/dom/abort_signal_registry.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/event_target_names.h"
#include "third_party/blink/renderer/core/event_type_names.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_linked_hash_set.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
namespace {
class OnceCallbackAlgorithm final : public AbortSignal::Algorithm {
public:
explicit OnceCallbackAlgorithm(base::OnceClosure callback)
: callback_(std::move(callback)) {}
~OnceCallbackAlgorithm() override = default;
void Run() override { std::move(callback_).Run(); }
private:
base::OnceClosure callback_;
};
} // namespace
AbortSignal::AbortSignal(ExecutionContext* execution_context)
: execution_context_(execution_context),
signal_type_(SignalType::kComposite) {
InitializeCompositeSignal(HeapVector<Member<AbortSignal>>());
}
AbortSignal::AbortSignal(ExecutionContext* execution_context,
SignalType signal_type)
: execution_context_(execution_context),
signal_type_(signal_type),
composition_manager_(MakeGarbageCollected<SourceSignalCompositionManager>(
*this,
AbortSignalCompositionType::kAbort)) {
DCHECK_NE(signal_type, SignalType::kComposite);
}
AbortSignal::AbortSignal(ScriptState* script_state,
const HeapVector<Member<AbortSignal>>& source_signals)
: execution_context_(ExecutionContext::From(script_state)),
signal_type_(SignalType::kComposite) {
// If any of the signals are aborted, skip the linking and just abort this
// signal.
for (auto& source : source_signals) {
CHECK(source.Get());
if (source->aborted()) {
abort_reason_ = source->reason(script_state);
break;
}
}
InitializeCompositeSignal(aborted() ? HeapVector<Member<AbortSignal>>()
: source_signals);
}
void AbortSignal::InitializeCompositeSignal(
const HeapVector<Member<AbortSignal>>& source_signals) {
CHECK_EQ(signal_type_, SignalType::kComposite);
composition_manager_ =
MakeGarbageCollected<DependentSignalCompositionManager>(
*this, AbortSignalCompositionType::kAbort, source_signals);
// Ensure the registry isn't created during GC, e.g. during an abort
// controller's prefinalizer.
AbortSignalRegistry::From(CHECK_DEREF(execution_context_.Get()));
}
AbortSignal::~AbortSignal() = default;
// static
AbortSignal* AbortSignal::abort(ScriptState* script_state) {
v8::Local<v8::Value> dom_exception = V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kAbortError,
"signal is aborted without reason");
CHECK(!dom_exception.IsEmpty());
ScriptValue reason(script_state->GetIsolate(), dom_exception);
return abort(script_state, reason);
}
// static
AbortSignal* AbortSignal::abort(ScriptState* script_state, ScriptValue reason) {
DCHECK(!reason.IsEmpty());
AbortSignal* signal = MakeGarbageCollected<AbortSignal>(
ExecutionContext::From(script_state), SignalType::kAborted);
signal->abort_reason_ = reason;
signal->composition_manager_->Settle();
return signal;
}
// static
AbortSignal* AbortSignal::any(ScriptState* script_state,
HeapVector<Member<AbortSignal>> signals) {
return MakeGarbageCollected<AbortSignal>(script_state, signals);
}
// static
AbortSignal* AbortSignal::timeout(ScriptState* script_state,
uint64_t milliseconds) {
ExecutionContext* context = ExecutionContext::From(script_state);
AbortSignal* signal =
MakeGarbageCollected<AbortSignal>(context, SignalType::kTimeout);
// The spec requires us to use the timer task source, but there are a few
// timer task sources due to our throttling implementation. We match
// setTimeout for immediate timeouts, but use the high-nesting task type for
// all positive timeouts so they are eligible for throttling (i.e. no
// nesting-level exception).
TaskType task_type = milliseconds == 0
? TaskType::kJavascriptTimerImmediate
: TaskType::kJavascriptTimerDelayedHighNesting;
// `signal` needs to be held with a strong reference to keep it alive in case
// there are or will be event handlers attached.
context->GetTaskRunner(task_type)->PostDelayedTask(
FROM_HERE,
WTF::BindOnce(&AbortSignal::AbortTimeoutFired, WrapPersistent(signal),
WrapPersistent(script_state)),
base::Milliseconds(milliseconds));
return signal;
}
void AbortSignal::AbortTimeoutFired(ScriptState* script_state) {
if (GetExecutionContext()->IsContextDestroyed() ||
!script_state->ContextIsValid()) {
return;
}
ScriptState::Scope scope(script_state);
auto* isolate = script_state->GetIsolate();
v8::Local<v8::Value> reason = V8ThrowDOMException::CreateOrEmpty(
isolate, DOMExceptionCode::kTimeoutError, "signal timed out");
SignalAbort(script_state, ScriptValue(isolate, reason), SignalAbortPassKey());
}
ScriptValue AbortSignal::reason(ScriptState* script_state) const {
DCHECK(script_state->GetIsolate()->InContext());
if (abort_reason_.IsEmpty()) {
return ScriptValue(script_state->GetIsolate(),
v8::Undefined(script_state->GetIsolate()));
}
return abort_reason_;
}
void AbortSignal::throwIfAborted(ScriptState* script_state,
ExceptionState& exception_state) const {
if (!aborted())
return;
exception_state.RethrowV8Exception(reason(script_state).V8Value());
}
const AtomicString& AbortSignal::InterfaceName() const {
return event_target_names::kAbortSignal;
}
ExecutionContext* AbortSignal::GetExecutionContext() const {
return execution_context_.Get();
}
AbortSignal::AlgorithmHandle* AbortSignal::AddAlgorithm(Algorithm* algorithm) {
if (aborted() || composition_manager_->IsSettled()) {
return nullptr;
}
auto* handle = MakeGarbageCollected<AlgorithmHandle>(algorithm, this);
CHECK(!abort_algorithms_.Contains(handle));
// This always appends since `handle` is not already in the collection.
abort_algorithms_.insert(handle);
return handle;
}
AbortSignal::AlgorithmHandle* AbortSignal::AddAlgorithm(
base::OnceClosure algorithm) {
if (aborted() || composition_manager_->IsSettled()) {
return nullptr;
}
auto* callback_algorithm =
MakeGarbageCollected<OnceCallbackAlgorithm>(std::move(algorithm));
auto* handle =
MakeGarbageCollected<AlgorithmHandle>(callback_algorithm, this);
CHECK(!abort_algorithms_.Contains(handle));
// This always appends since `handle` is not already in the collection.
abort_algorithms_.insert(handle);
return handle;
}
void AbortSignal::RemoveAlgorithm(AlgorithmHandle* handle) {
if (aborted() || composition_manager_->IsSettled()) {
return;
}
abort_algorithms_.erase(handle);
}
void AbortSignal::SignalAbort(ScriptState* script_state,
ScriptValue reason,
SignalAbortPassKey) {
DCHECK(!reason.IsEmpty());
if (aborted())
return;
if (reason.IsUndefined()) {
abort_reason_ = ScriptValue(
script_state->GetIsolate(),
V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kAbortError,
"signal is aborted with undefined reason"));
} else {
abort_reason_ = reason;
}
for (AbortSignal::AlgorithmHandle* handle : abort_algorithms_) {
CHECK(handle);
CHECK(handle->GetAlgorithm());
handle->GetAlgorithm()->Run();
}
DispatchEvent(*Event::Create(event_type_names::kAbort));
DCHECK(composition_manager_);
// Dependent signals are linked directly to source signals, so the abort
// only gets propagated for source signals.
if (auto* source_signal_manager = DynamicTo<SourceSignalCompositionManager>(
composition_manager_.Get())) {
// This is safe against reentrancy because new dependents are not added to
// already aborted signals.
for (auto& signal : source_signal_manager->GetDependentSignals()) {
CHECK(signal.Get());
signal->SignalAbort(script_state, abort_reason_, SignalAbortPassKey());
}
}
composition_manager_->Settle();
}
void AbortSignal::Trace(Visitor* visitor) const {
visitor->Trace(abort_reason_);
visitor->Trace(execution_context_);
visitor->Trace(abort_algorithms_);
visitor->Trace(composition_manager_);
EventTarget::Trace(visitor);
}
AbortSignalCompositionManager* AbortSignal::GetCompositionManager(
AbortSignalCompositionType type) {
if (type == AbortSignalCompositionType::kAbort) {
return composition_manager_.Get();
}
return nullptr;
}
void AbortSignal::DetachFromController() {
if (aborted()) {
return;
}
composition_manager_->Settle();
}
void AbortSignal::OnSignalSettled(AbortSignalCompositionType type) {
if (type == AbortSignalCompositionType::kAbort) {
abort_algorithms_.clear();
}
if (signal_type_ == SignalType::kComposite) {
InvokeRegistryCallback([&](AbortSignalRegistry& registry) {
registry.UnregisterSignal(*this, type);
});
}
}
bool AbortSignal::CanAbort() const {
if (aborted()) {
return false;
}
return !composition_manager_->IsSettled();
}
void AbortSignal::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTarget::AddedEventListener(event_type, registered_listener);
OnEventListenerAddedOrRemoved(event_type, AddRemoveType::kAdded);
}
void AbortSignal::RemovedEventListener(
const AtomicString& event_type,
const RegisteredEventListener& registered_listener) {
EventTarget::RemovedEventListener(event_type, registered_listener);
OnEventListenerAddedOrRemoved(event_type, AddRemoveType::kRemoved);
}
void AbortSignal::InvokeRegistryCallback(
base::FunctionRef<void(AbortSignalRegistry&)> callback) {
CHECK_EQ(signal_type_, SignalType::kComposite);
callback(*AbortSignalRegistry::From(*GetExecutionContext()));
}
void AbortSignal::OnEventListenerAddedOrRemoved(const AtomicString& event_type,
AddRemoveType add_or_remove) {
if (signal_type_ != SignalType::kComposite) {
return;
}
std::optional<AbortSignalCompositionType> composition_type;
if (event_type == event_type_names::kAbort) {
composition_type = AbortSignalCompositionType::kAbort;
} else if (event_type == event_type_names::kPrioritychange) {
composition_type = AbortSignalCompositionType::kPriority;
} else {
return;
}
if (IsSettledFor(*composition_type)) {
// Signals are unregistered when they're settled for `composition_type`
// since the event will no longer be propagated. In that case, the signal
// doesn't need to be unregistered on removal, and it shouldn't be
// registered on adding a listener, since that could leak it.
return;
}
if (add_or_remove == AddRemoveType::kRemoved &&
HasEventListeners(event_type)) {
// Unsettled composite signals need to be kept alive while they have active
// event listeners for `event_type`, so only unregister the signal if
// removing the last one.
return;
}
// `manager` will be null if this signal doesn't handle composition for
// `composition_type`.
if (GetCompositionManager(*composition_type)) {
InvokeRegistryCallback([&](AbortSignalRegistry& registry) {
switch (add_or_remove) {
case AddRemoveType::kAdded:
registry.RegisterSignal(*this, *composition_type);
break;
case AddRemoveType::kRemoved:
registry.UnregisterSignal(*this, *composition_type);
break;
}
});
}
}
bool AbortSignal::IsSettledFor(
AbortSignalCompositionType composition_type) const {
return composition_type == AbortSignalCompositionType::kAbort &&
composition_manager_->IsSettled();
}
AbortSignal::AlgorithmHandle::AlgorithmHandle(AbortSignal::Algorithm* algorithm,
AbortSignal* signal)
: algorithm_(algorithm), signal_(signal) {
CHECK(algorithm_);
CHECK(signal_);
}
AbortSignal::AlgorithmHandle::~AlgorithmHandle() = default;
void AbortSignal::AlgorithmHandle::Trace(Visitor* visitor) const {
visitor->Trace(algorithm_);
visitor->Trace(signal_);
}
} // namespace blink