diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any-expected.txt index 25877db979fa2..88d765cab544c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any-expected.txt @@ -9,4 +9,7 @@ PASS AbortSignal.any() signals are composable (using AbortController) PASS AbortSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController) PASS AbortSignal.any() works with intermediate signals (using AbortController) PASS Abort events for AbortSignal.any() signals fire in the right order (using AbortController) +PASS Dependent signals for AbortSignal.any() are marked aborted before abort events fire (using AbortController) +PASS Dependent signals for AbortSignal.any() are aborted correctly for reentrant aborts (using AbortController) +PASS Dependent signals for AbortSignal.any() are aborted with correct timing (using AbortController) diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any.worker-expected.txt index 25877db979fa2..88d765cab544c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any.worker-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/abort/abort-signal-any.any.worker-expected.txt @@ -9,4 +9,7 @@ PASS AbortSignal.any() signals are composable (using AbortController) PASS AbortSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController) PASS AbortSignal.any() works with intermediate signals (using AbortController) PASS Abort events for AbortSignal.any() signals fire in the right order (using AbortController) +PASS Dependent signals for AbortSignal.any() are marked aborted before abort events fire (using AbortController) +PASS Dependent signals for AbortSignal.any() are aborted correctly for reentrant aborts (using AbortController) +PASS Dependent signals for AbortSignal.any() are aborted with correct timing (using AbortController) diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/abort/resources/abort-signal-any-tests.js b/LayoutTests/imported/w3c/web-platform-tests/dom/abort/resources/abort-signal-any-tests.js index 66e4141eaccb0..b0834342581d4 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/abort/resources/abort-signal-any-tests.js +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/abort/resources/abort-signal-any-tests.js @@ -182,4 +182,70 @@ function abortSignalAnyTests(signalInterface, controllerInterface) { controller.abort(); assert_equals(result, "01234"); }, `Abort events for ${desc} signals fire in the right order ${suffix}`); + + test(t => { + const controller = new controllerInterface(); + const signal1 = signalInterface.any([controller.signal]); + const signal2 = signalInterface.any([signal1]); + let eventFired = false; + + controller.signal.addEventListener('abort', () => { + const signal3 = signalInterface.any([signal2]); + assert_true(controller.signal.aborted); + assert_true(signal1.aborted); + assert_true(signal2.aborted); + assert_true(signal3.aborted); + eventFired = true; + }); + + controller.abort(); + assert_true(eventFired, "event fired"); + }, `Dependent signals for ${desc} are marked aborted before abort events fire ${suffix}`); + + test(t => { + const controller1 = new controllerInterface(); + const controller2 = new controllerInterface(); + const signal = signalInterface.any([controller1.signal, controller2.signal]); + let count = 0; + + controller1.signal.addEventListener('abort', () => { + controller2.abort("reason 2"); + }); + + signal.addEventListener('abort', () => { + count++; + }); + + controller1.abort("reason 1"); + assert_equals(count, 1); + assert_true(signal.aborted); + assert_equals(signal.reason, "reason 1"); + }, `Dependent signals for ${desc} are aborted correctly for reentrant aborts ${suffix}`); + + promise_test(async t => { + const controller1 = new controllerInterface(); + const controller2 = new controllerInterface(); + const signal = signalInterface.any([controller1.signal, controller2.signal]); + + const results = []; + + controller1.signal.addEventListener('abort', () => results.push('controller1 aborted')); + controller2.signal.addEventListener('abort', () => results.push('controller2 aborted')); + signal.addEventListener('abort', () => results.push('signal.any aborted')); + + controller1.abort(); + + // After a single task, assert that everything has happened correctly. + await new Promise(resolve => { + t.step_timeout(resolve); + }); + + assert_true(signal.aborted, ".any signal is aborted"); + assert_true(controller1.signal.aborted, "controller1.signal is aborted"); + assert_false(controller2.signal.aborted, "controller2.signal is NOT aborted"); + assert_array_equals(results, [ + "controller1 aborted", + "signal.any aborted", + ], "Events are fired in the right ordered"); + }, `Dependent signals for ${desc} are aborted with correct timing ${suffix}`); } diff --git a/Source/WebCore/dom/AbortSignal.cpp b/Source/WebCore/dom/AbortSignal.cpp index 9c56ddce9335d..f5b39ec9cc725 100644 --- a/Source/WebCore/dom/AbortSignal.cpp +++ b/Source/WebCore/dom/AbortSignal.cpp @@ -79,7 +79,9 @@ Ref AbortSignal::any(ScriptExecutionContext& context, const Vector< { Ref resultSignal = AbortSignal::create(&context); - auto abortedSignalIndex = signals.findIf([](auto& signal) { return signal->aborted(); }); + auto abortedSignalIndex = signals.findIf([](auto& signal) { + return signal->aborted(); + }); if (abortedSignalIndex != notFound) { resultSignal->signalAbort(signals[abortedSignalIndex]->reason().getValue()); return resultSignal; @@ -124,10 +126,31 @@ void AbortSignal::addDependentSignal(AbortSignal& signal) void AbortSignal::signalAbort(JSC::JSValue reason) { // 1. If signal's aborted flag is set, then return. - if (m_aborted) + if (aborted()) return; - + // 2. Set signal’s aborted flag. + markAborted(reason); + + Vector> dependentSignalsToAbort = { }; + + for (Ref dependentSignal : std::exchange(m_dependentSignals, { })) { + if (!dependentSignal->aborted()) { + dependentSignal->markAborted(reason); + dependentSignalsToAbort.append(dependentSignal); + } + } + + // 5. Run the abort steps + runAbortSteps(); + + // 6. For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal. + for (auto& dependentSignal : dependentSignalsToAbort) + dependentSignal->runAbortSteps(); +} + +void AbortSignal::markAborted(JSC::JSValue reason) +{ m_aborted = true; m_sourceSignals.clear(); @@ -135,16 +158,21 @@ void AbortSignal::signalAbort(JSC::JSValue reason) // https://bugs.webkit.org/show_bug.cgi?id=236353 ASSERT(reason); m_reason.setWeakly(reason); +} +void AbortSignal::runAbortSteps() +{ + auto reason = m_reason.getValue(); + ASSERT(reason); + + // 1. For each algorithm of signal's abort algorithms: run algorithm. + // 2. Empty signal’s abort algorithms. (std::exchange empties) auto algorithms = std::exchange(m_algorithms, { }); for (auto& algorithm : algorithms) algorithm.second(reason); - // 5. Fire an event named abort at signal. + // 3. Fire an event named abort at signal. dispatchEvent(Event::create(eventNames().abortEvent, Event::CanBubble::No, Event::IsCancelable::No)); - - for (Ref dependentSignal : std::exchange(m_dependentSignals, { })) - dependentSignal->signalAbort(reason); } // https://dom.spec.whatwg.org/#abortsignal-follow diff --git a/Source/WebCore/dom/AbortSignal.h b/Source/WebCore/dom/AbortSignal.h index 57930f5045b51..15f8552da836d 100644 --- a/Source/WebCore/dom/AbortSignal.h +++ b/Source/WebCore/dom/AbortSignal.h @@ -88,13 +88,16 @@ class AbortSignal final : public RefCounted, public EventTarget, pr void addSourceSignal(AbortSignal&); void addDependentSignal(AbortSignal&); + void markAborted(JSC::JSValue); + void runAbortSteps(); + // EventTarget. enum EventTargetInterfaceType eventTargetInterface() const final { return EventTargetInterfaceType::AbortSignal; } ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); } void refEventTarget() final { ref(); } void derefEventTarget() final { deref(); } void eventListenersDidChange() final; - + Vector> m_algorithms; WeakPtr m_followingSignal; AbortSignalSet m_sourceSignals; @@ -110,4 +113,3 @@ class AbortSignal final : public RefCounted, public EventTarget, pr WebCoreOpaqueRoot root(AbortSignal*); } // namespace WebCore -