diff --git a/dom/abort/AbortFollower.h b/dom/abort/AbortFollower.h index 959996039074..8c2509781a1a 100644 --- a/dom/abort/AbortFollower.h +++ b/dom/abort/AbortFollower.h @@ -44,6 +44,11 @@ class AbortFollower : public nsISupports { WeakPtr mFollowingSignal; }; +/* + * AbortSignalImpl is a minimal implementation without an associated global + * and without event dispatching, those are added in AbortSignal. + * See Bug 1478101 + */ class AbortSignalImpl : public nsISupports, public SupportsWeakPtr { public: explicit AbortSignalImpl(bool aAborted, JS::Handle aReason); @@ -67,6 +72,8 @@ class AbortSignalImpl : public nsISupports, public SupportsWeakPtr { virtual ~AbortSignalImpl() { UnlinkFollowers(); } + void SetAborted(JS::Handle aReason); + JS::Heap mReason; private: diff --git a/dom/abort/AbortSignal.cpp b/dom/abort/AbortSignal.cpp index 01d0ac947fca..de5f24c080ec 100644 --- a/dom/abort/AbortSignal.cpp +++ b/dom/abort/AbortSignal.cpp @@ -49,8 +49,7 @@ void AbortSignalImpl::SignalAbort(JS::Handle aReason) { } // Step 2. - mAborted = true; - mReason = aReason; + SetAborted(aReason); // Step 3. // When there are multiple followers, the follower removal algorithm @@ -66,6 +65,11 @@ void AbortSignalImpl::SignalAbort(JS::Handle aReason) { UnlinkFollowers(); } +void AbortSignalImpl::SetAborted(JS::Handle aReason) { + mAborted = true; + mReason = aReason; +} + void AbortSignalImpl::Traverse(AbortSignalImpl* aSignal, nsCycleCollectionTraversalCallback& cb) { ImplCycleCollectionTraverse(cb, aSignal->mFollowers, "mFollowers", 0); @@ -109,11 +113,13 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal, DOMEventTargetHelper) AbortSignalImpl::Traverse(static_cast(tmp), cb); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDependentSignals) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal, DOMEventTargetHelper) AbortSignalImpl::Unlink(static_cast(tmp)); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDependentSignals) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal) @@ -129,7 +135,9 @@ NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper) AbortSignal::AbortSignal(nsIGlobalObject* aGlobalObject, bool aAborted, JS::Handle aReason) - : DOMEventTargetHelper(aGlobalObject), AbortSignalImpl(aAborted, aReason) { + : DOMEventTargetHelper(aGlobalObject), + AbortSignalImpl(aAborted, aReason), + mDependent(false) { mozilla::HoldJSObjects(this); } @@ -250,6 +258,62 @@ already_AddRefed AbortSignal::Timeout(GlobalObject& aGlobal, return signal.forget(); } +// https://dom.spec.whatwg.org/#create-a-dependent-abort-signal +already_AddRefed AbortSignal::Any( + GlobalObject& aGlobal, + const Sequence>& aSignals) { + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + + // Step 1. Let resultSignal be a new object implementing AbortSignal using + // realm + RefPtr resultSignal = + new AbortSignal(global, false, JS::UndefinedHandleValue); + + // Step 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 (const auto& signal : aSignals) { + if (signal->Aborted()) { + JS::Rooted reason(RootingCx(), signal->RawReason()); + resultSignal->SetAborted(reason); + return resultSignal.forget(); + } + } + + // Step 3. Set resultSignal's dependent to true + resultSignal->mDependent = true; + + // Step 4. For each signal of signals + for (const auto& signal : aSignals) { + if (!signal->Dependent()) { + // Step 4.1. If signal is not dependent, make resultSignal dependent on it + resultSignal->MakeDependentOn(signal); + } else { + // Step 4.2. Otherwise, make resultSignal dependent on its source signals + for (const auto& sourceSignal : signal->mSourceSignals) { + MOZ_ASSERT(!sourceSignal->Aborted() && !sourceSignal->Dependent()); + resultSignal->MakeDependentOn(sourceSignal); + } + } + } + + // Step 5. Return resultSignal. + return resultSignal.forget(); +} + +void AbortSignal::MakeDependentOn(AbortSignal* aSignal) { + MOZ_ASSERT(mDependent); + MOZ_ASSERT(aSignal); + // append only if not already contained in list + // https://infra.spec.whatwg.org/#set-append + if (!mSourceSignals.Contains(aSignal)) { + mSourceSignals.AppendElement(aSignal); + } + if (!aSignal->mDependentSignals.Contains(this)) { + aSignal->mDependentSignals.AppendElement(this); + } +} + // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted void AbortSignal::ThrowIfAborted(JSContext* aCx, ErrorResult& aRv) { aRv.MightThrowJSException(); @@ -271,7 +335,7 @@ void AbortSignal::SignalAbort(JS::Handle aReason) { // Steps 1-4. AbortSignalImpl::SignalAbort(aReason); - // Step 5. + // Step 5. Fire an event named abort at this signal EventInit init; init.mBubbles = false; init.mCancelable = false; @@ -280,6 +344,14 @@ void AbortSignal::SignalAbort(JS::Handle aReason) { event->SetTrusted(true); DispatchEvent(*event); + + // Step 6. Abort dependentSignals of this signal + for (const auto& dependant : mDependentSignals) { + MOZ_ASSERT(dependant->mSourceSignals.Contains(this)); + dependant->SignalAbort(aReason); + } + // clear dependent signals so that they might be garbage collected + mDependentSignals.Clear(); } void AbortSignal::RunAbortAlgorithm() { @@ -287,6 +359,8 @@ void AbortSignal::RunAbortAlgorithm() { SignalAbort(reason); } +bool AbortSignal::Dependent() const { return mDependent; } + AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); } // AbortFollower diff --git a/dom/abort/AbortSignal.h b/dom/abort/AbortSignal.h index 45c0390477e7..b5732cb022b0 100644 --- a/dom/abort/AbortSignal.h +++ b/dom/abort/AbortSignal.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_AbortSignal_h #define mozilla_dom_AbortSignal_h +#include "mozilla/RefPtr.h" #include "mozilla/dom/AbortFollower.h" #include "mozilla/DOMEventTargetHelper.h" @@ -47,6 +48,10 @@ class AbortSignal : public DOMEventTargetHelper, uint64_t aMilliseconds, ErrorResult& aRv); + static already_AddRefed Any( + GlobalObject& aGlobal, + const Sequence>& aSignals); + void ThrowIfAborted(JSContext* aCx, ErrorResult& aRv); // AbortSignalImpl @@ -57,8 +62,17 @@ class AbortSignal : public DOMEventTargetHelper, virtual bool IsTaskSignal() const { return false; } + bool Dependent() const; + protected: ~AbortSignal(); + + void MakeDependentOn(AbortSignal* aSignal); + + nsTArray> mSourceSignals; + nsTArray> mDependentSignals; + + bool mDependent; }; } // namespace mozilla::dom diff --git a/dom/webidl/AbortSignal.webidl b/dom/webidl/AbortSignal.webidl index 5a87bc482cbd..78734ed4eda6 100644 --- a/dom/webidl/AbortSignal.webidl +++ b/dom/webidl/AbortSignal.webidl @@ -12,6 +12,7 @@ interface AbortSignal : EventTarget { [NewObject] static AbortSignal abort(optional any reason); [Exposed=(Window,Worker), NewObject, Throws] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds); + [NewObject] static AbortSignal _any(sequence signals); readonly attribute boolean aborted; readonly attribute any reason; diff --git a/testing/web-platform/meta/dom/abort/abort-signal-any.any.js.ini b/testing/web-platform/meta/dom/abort/abort-signal-any.any.js.ini deleted file mode 100644 index 54d7d2262a5d..000000000000 --- a/testing/web-platform/meta/dom/abort/abort-signal-any.any.js.ini +++ /dev/null @@ -1,62 +0,0 @@ -[abort-signal-any.any.html] - [AbortSignal.any() works with an empty array of signals] - expected: FAIL - - [AbortSignal.any() follows a single signal (using AbortController)] - expected: FAIL - - [AbortSignal.any() follows multiple signals (using AbortController)] - expected: FAIL - - [AbortSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)] - expected: FAIL - - [AbortSignal.any() can be passed the same signal more than once (using AbortController)] - expected: FAIL - - [AbortSignal.any() uses the first instance of a duplicate signal (using AbortController)] - expected: FAIL - - [AbortSignal.any() signals are composable (using AbortController)] - expected: FAIL - - [AbortSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)] - expected: FAIL - - [AbortSignal.any() works with intermediate signals (using AbortController)] - expected: FAIL - - [Abort events for AbortSignal.any() signals fire in the right order (using AbortController)] - expected: FAIL - - -[abort-signal-any.any.worker.html] - [AbortSignal.any() works with an empty array of signals] - expected: FAIL - - [AbortSignal.any() follows a single signal (using AbortController)] - expected: FAIL - - [AbortSignal.any() follows multiple signals (using AbortController)] - expected: FAIL - - [AbortSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)] - expected: FAIL - - [AbortSignal.any() can be passed the same signal more than once (using AbortController)] - expected: FAIL - - [AbortSignal.any() uses the first instance of a duplicate signal (using AbortController)] - expected: FAIL - - [AbortSignal.any() signals are composable (using AbortController)] - expected: FAIL - - [AbortSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)] - expected: FAIL - - [AbortSignal.any() works with intermediate signals (using AbortController)] - expected: FAIL - - [Abort events for AbortSignal.any() signals fire in the right order (using AbortController)] - expected: FAIL diff --git a/testing/web-platform/meta/dom/idlharness-shadowrealm.window.js.ini b/testing/web-platform/meta/dom/idlharness-shadowrealm.window.js.ini index 3ea473980160..ab43fe3dca7d 100644 --- a/testing/web-platform/meta/dom/idlharness-shadowrealm.window.js.ini +++ b/testing/web-platform/meta/dom/idlharness-shadowrealm.window.js.ini @@ -140,6 +140,3 @@ [AbortController interface: operation abort(optional any)] expected: FAIL - - [AbortSignal interface: operation any(sequence)] - expected: FAIL diff --git a/testing/web-platform/meta/dom/idlharness.any.js.ini b/testing/web-platform/meta/dom/idlharness.any.js.ini deleted file mode 100644 index 6b8d439adf0d..000000000000 --- a/testing/web-platform/meta/dom/idlharness.any.js.ini +++ /dev/null @@ -1,28 +0,0 @@ -[idlharness.any.sharedworker.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [AbortSignal interface: operation any(sequence)] - expected: FAIL - - [AbortSignal interface: calling any(sequence) on new AbortController().signal with too few arguments must throw TypeError] - expected: FAIL - - -[idlharness.any.serviceworker.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [AbortSignal interface: operation any(sequence)] - expected: FAIL - - [AbortSignal interface: calling any(sequence) on new AbortController().signal with too few arguments must throw TypeError] - expected: FAIL - - -[idlharness.any.worker.html] - expected: - if (os == "android") and fission: [TIMEOUT, OK] - [AbortSignal interface: operation any(sequence)] - expected: FAIL - - [AbortSignal interface: calling any(sequence) on new AbortController().signal with too few arguments must throw TypeError] - expected: FAIL diff --git a/testing/web-platform/meta/dom/idlharness.window.js.ini b/testing/web-platform/meta/dom/idlharness.window.js.ini index 4e268f968e59..a99b5832194d 100644 --- a/testing/web-platform/meta/dom/idlharness.window.js.ini +++ b/testing/web-platform/meta/dom/idlharness.window.js.ini @@ -5,9 +5,4 @@ [idlharness.window.html?exclude=Node] expected: if (os == "android") and fission: [OK, TIMEOUT] - [AbortSignal interface: operation any(sequence)] - expected: FAIL - - [AbortSignal interface: calling any(sequence) on new AbortController().signal with too few arguments must throw TypeError] - expected: FAIL diff --git a/testing/web-platform/meta/scheduler/task-signal-any-abort.tentative.any.js.ini b/testing/web-platform/meta/scheduler/task-signal-any-abort.tentative.any.js.ini deleted file mode 100644 index b6060bd41b34..000000000000 --- a/testing/web-platform/meta/scheduler/task-signal-any-abort.tentative.any.js.ini +++ /dev/null @@ -1,236 +0,0 @@ -[task-signal-any-abort.tentative.any.serviceworker.html] - expected: - if (os == "win") and not debug and (processor == "x86_64"): [OK, TIMEOUT] - [TaskSignal.any() works with an empty array of signals] - expected: FAIL - - [TaskSignal.any() follows a single signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using AbortController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using AbortController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using AbortController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows a single signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using TaskController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using TaskController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using TaskController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using TaskController)] - expected: FAIL - - -[task-signal-any-abort.tentative.any.sharedworker.html] - [TaskSignal.any() works with an empty array of signals] - expected: FAIL - - [TaskSignal.any() follows a single signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using AbortController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using AbortController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using AbortController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows a single signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using TaskController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using TaskController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using TaskController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using TaskController)] - expected: FAIL - - -[task-signal-any-abort.tentative.any.worker.html] - [TaskSignal.any() works with an empty array of signals] - expected: FAIL - - [TaskSignal.any() follows a single signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using AbortController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using AbortController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using AbortController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows a single signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using TaskController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using TaskController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using TaskController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using TaskController)] - expected: FAIL - - -[task-signal-any-abort.tentative.any.html] - [TaskSignal.any() works with an empty array of signals] - expected: FAIL - - [TaskSignal.any() follows a single signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using AbortController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using AbortController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using AbortController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using AbortController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using AbortController)] - expected: FAIL - - [TaskSignal.any() follows a single signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() follows multiple signals (using TaskController)] - expected: FAIL - - [TaskSignal.any() returns an aborted signal if passed an aborted signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() can be passed the same signal more than once (using TaskController)] - expected: FAIL - - [TaskSignal.any() uses the first instance of a duplicate signal (using TaskController)] - expected: FAIL - - [TaskSignal.any() signals are composable (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with signals returned by AbortSignal.timeout() (using TaskController)] - expected: FAIL - - [TaskSignal.any() works with intermediate signals (using TaskController)] - expected: FAIL - - [Abort events for TaskSignal.any() signals fire in the right order (using TaskController)] - expected: FAIL diff --git a/testing/web-platform/meta/scheduler/task-signal-any-priority.tentative.any.js.ini b/testing/web-platform/meta/scheduler/task-signal-any-priority.tentative.any.js.ini index ceb8959b7d83..4b844a75a408 100644 --- a/testing/web-platform/meta/scheduler/task-signal-any-priority.tentative.any.js.ini +++ b/testing/web-platform/meta/scheduler/task-signal-any-priority.tentative.any.js.ini @@ -20,9 +20,6 @@ [Priority change propagates to multiple dependent signals in the right order] expected: FAIL - [TaskSignal.any() does not propagate abort when not given dependent abort signals] - expected: FAIL - [TaskSignal.any() propagates abort and priority] expected: FAIL @@ -55,9 +52,6 @@ [Priority change propagates to multiple dependent signals in the right order] expected: FAIL - [TaskSignal.any() does not propagate abort when not given dependent abort signals] - expected: FAIL - [TaskSignal.any() propagates abort and priority] expected: FAIL @@ -90,9 +84,6 @@ [Priority change propagates to multiple dependent signals in the right order] expected: FAIL - [TaskSignal.any() does not propagate abort when not given dependent abort signals] - expected: FAIL - [TaskSignal.any() propagates abort and priority] expected: FAIL @@ -125,9 +116,6 @@ [Priority change propagates to multiple dependent signals in the right order] expected: FAIL - [TaskSignal.any() does not propagate abort when not given dependent abort signals] - expected: FAIL - [TaskSignal.any() propagates abort and priority] expected: FAIL