diff --git a/Tests/LibWeb/Text/expected/abortsignal-any.txt b/Tests/LibWeb/Text/expected/abortsignal-any.txt new file mode 100644 index 00000000000..4e88fb3ab77 --- /dev/null +++ b/Tests/LibWeb/Text/expected/abortsignal-any.txt @@ -0,0 +1,39 @@ +Signal 0 aborted: false +Signal 1 aborted: false +Signal 2 aborted: false +Signal 3 aborted: false +Signal 4 aborted: false +Abort callback for signal: 0. Reason: Test AbortController 1 +Abort callback for signal: 1. Reason: Test AbortController 1 +Abort callback for signal: 3. Reason: Test AbortController 1 +Abort callback for signal: 4. Reason: Test AbortController 1 +Signal 0 aborted: true +Signal 1 aborted: true +Signal 2 aborted: false +Signal 3 aborted: true +Signal 4 aborted: true +Signal 0 aborted: false +Signal 1 aborted: false +Signal 2 aborted: false +Signal 3 aborted: false +Signal 4 aborted: false +Abort callback for signal: 1. Reason: Test AbortController 2 +Abort callback for signal: 2. Reason: Test AbortController 2 +Abort callback for signal: 3. Reason: Test AbortController 2 +Abort callback for signal: 4. Reason: Test AbortController 2 +Signal 0 aborted: false +Signal 1 aborted: true +Signal 2 aborted: true +Signal 3 aborted: true +Signal 4 aborted: true +Signal 0 aborted: false +Signal 1 aborted: false +Signal 2 aborted: false +Signal 3 aborted: false +Signal 4 aborted: false +Abort callback for signal: 4. Reason: Test AbortController 3 +Signal 0 aborted: false +Signal 1 aborted: false +Signal 2 aborted: false +Signal 3 aborted: false +Signal 4 aborted: true diff --git a/Tests/LibWeb/Text/input/abortsignal-any.html b/Tests/LibWeb/Text/input/abortsignal-any.html new file mode 100644 index 00000000000..b76dc8ebffb --- /dev/null +++ b/Tests/LibWeb/Text/input/abortsignal-any.html @@ -0,0 +1,52 @@ + + diff --git a/Userland/Libraries/LibWeb/DOM/AbortSignal.cpp b/Userland/Libraries/LibWeb/DOM/AbortSignal.cpp index 6852cc783dc..0b5df2fb26c 100644 --- a/Userland/Libraries/LibWeb/DOM/AbortSignal.cpp +++ b/Userland/Libraries/LibWeb/DOM/AbortSignal.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Luke Wilde + * Copyright (c) 2024, Tim Ledbetter * * SPDX-License-Identifier: BSD-2-Clause */ @@ -67,6 +68,10 @@ void AbortSignal::signal_abort(JS::Value reason) auto abort_event = Event::create(realm(), HTML::EventNames::abort); abort_event->set_is_trusted(true); dispatch_event(abort_event); + + // 6. For each dependentSignal of signal’s dependent signals, signal abort on dependentSignal with signal’s abort reason. + for (auto const& dependent_signal : m_dependent_signals) + dependent_signal->signal_abort(reason); } void AbortSignal::set_onabort(WebIDL::CallbackType* event_handler) @@ -95,6 +100,12 @@ void AbortSignal::visit_edges(JS::Cell::Visitor& visitor) visitor.visit(m_abort_reason); for (auto& algorithm : m_abort_algorithms) visitor.visit(algorithm); + + for (auto& source_signal : m_source_signals) + visitor.visit(source_signal); + + for (auto& dependent_signal : m_dependent_signals) + visitor.visit(dependent_signal); } // https://dom.spec.whatwg.org/#abortsignal-follow @@ -162,4 +173,74 @@ WebIDL::ExceptionOr> AbortSignal::timeout(JS::VM& return signal; } +// https://dom.spec.whatwg.org/#dom-abortsignal-any +WebIDL::ExceptionOr> AbortSignal::any(JS::VM& vm, JS::Value signals) +{ + Vector> signals_list; + auto iterator_record = TRY(get_iterator(vm, signals, JS::IteratorHint::Sync)); + while (true) { + auto next = TRY(iterator_step_value(vm, iterator_record)); + if (!next.has_value()) + break; + + auto value = next.release_value(); + if (!value.is_object() || !is(value.as_object())) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "AbortSignal"); + + auto& signal = static_cast(value.as_object()); + signals_list.append(JS::make_handle(signal)); + } + + // The static any(signals) method steps are to return the result of creating a dependent abort signal from signals using AbortSignal and the current realm. + return create_dependent_abort_signal(*vm.current_realm(), signals_list); +} + +// https://dom.spec.whatwg.org/#create-a-dependent-abort-signal +WebIDL::ExceptionOr> AbortSignal::create_dependent_abort_signal(JS::Realm& realm, Vector> const& signals) +{ + // 1. Let resultSignal be a new object implementing signalInterface using realm. + auto result_signal = TRY(construct_impl(realm)); + + // 2. For each signal of signals: if signal is aborted, then set resultSignal’s abort reason to signal’s abort reason and return resultSignal. + for (auto const& signal : signals) { + if (signal->aborted()) { + result_signal->set_reason(signal->reason()); + return result_signal; + } + } + + // 3. Set resultSignal’s dependent to true. + result_signal->set_dependent(true); + + // 4. For each signal of signals: + for (auto const& signal : signals) { + // 1. If signal’s dependent is false, then: + if (!signal->dependent()) { + // 1. Append signal to resultSignal’s source signals. + result_signal->append_source_signal({ signal }); + + // 2. Append resultSignal to signal’s dependent signals. + signal->append_dependent_signal(result_signal); + } + // 2. Otherwise, for each sourceSignal of signal’s source signals: + else { + for (auto const& source_signal : signal->source_signals()) { + // 1. Assert: sourceSignal is not aborted and not dependent. + VERIFY(source_signal); + VERIFY(!source_signal->aborted()); + VERIFY(!source_signal->dependent()); + + // 2. Append sourceSignal to resultSignal’s source signals. + result_signal->append_source_signal(source_signal); + + // 3. Append resultSignal to sourceSignal’s dependent signals. + source_signal->append_dependent_signal(result_signal); + } + } + } + + // 5. Return resultSignal + return result_signal; +} + } diff --git a/Userland/Libraries/LibWeb/DOM/AbortSignal.h b/Userland/Libraries/LibWeb/DOM/AbortSignal.h index 7e71de8b5e2..7fb0ad016bf 100644 --- a/Userland/Libraries/LibWeb/DOM/AbortSignal.h +++ b/Userland/Libraries/LibWeb/DOM/AbortSignal.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Luke Wilde + * Copyright (c) 2024, Tim Ledbetter * * SPDX-License-Identifier: BSD-2-Clause */ @@ -46,6 +47,7 @@ public: static WebIDL::ExceptionOr> abort(JS::VM&, JS::Value reason); static WebIDL::ExceptionOr> timeout(JS::VM&, Web::WebIDL::UnsignedLongLong milliseconds); + static WebIDL::ExceptionOr> any(JS::VM&, JS::Value signals); private: explicit AbortSignal(JS::Realm&); @@ -53,6 +55,16 @@ private: virtual void initialize(JS::Realm&) override; virtual void visit_edges(JS::Cell::Visitor&) override; + static WebIDL::ExceptionOr> create_dependent_abort_signal(JS::Realm&, Vector> const&); + + bool dependent() const { return m_dependent; } + void set_dependent(bool dependent) { m_dependent = dependent; } + + Vector> source_signals() const { return m_source_signals; } + + void append_source_signal(JS::GCPtr source_signal) { m_source_signals.append(source_signal); } + void append_dependent_signal(JS::GCPtr dependent_signal) { m_dependent_signals.append(dependent_signal); } + // https://dom.spec.whatwg.org/#abortsignal-abort-reason // An AbortSignal object has an associated abort reason, which is a JavaScript value. It is undefined unless specified otherwise. JS::Value m_abort_reason { JS::js_undefined() }; @@ -60,6 +72,18 @@ private: // https://dom.spec.whatwg.org/#abortsignal-abort-algorithms // FIXME: This should be a set. Vector>> m_abort_algorithms; + + // https://dom.spec.whatwg.org/#abortsignal-source-signals + // An AbortSignal object has associated source signals (a weak set of AbortSignal objects that the object is dependent on for its aborted state), which is initially empty. + Vector> m_source_signals; + + // https://dom.spec.whatwg.org/#abortsignal-dependent-signals + // An AbortSignal object has associated dependent signals (a weak set of AbortSignal objects that are dependent on the object for their aborted state), which is initially empty. + Vector> m_dependent_signals; + + // https://dom.spec.whatwg.org/#abortsignal-dependent + // An AbortSignal object has a dependent (a boolean), which is initially false. + bool m_dependent { false }; }; } diff --git a/Userland/Libraries/LibWeb/DOM/AbortSignal.idl b/Userland/Libraries/LibWeb/DOM/AbortSignal.idl index 9e515a9b9c1..b168cb5e0bd 100644 --- a/Userland/Libraries/LibWeb/DOM/AbortSignal.idl +++ b/Userland/Libraries/LibWeb/DOM/AbortSignal.idl @@ -6,7 +6,8 @@ interface AbortSignal : EventTarget { [NewObject] static AbortSignal abort(optional any reason); [Exposed=(Window,Worker), NewObject] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds); - // FIXME: [NewObject] static AbortSignal _any(sequence signals); + // FIXME: Argument should be of type: sequence. + [NewObject] static AbortSignal _any(any signals); readonly attribute boolean aborted; readonly attribute any reason;