Bug 1830781 - Implement AbortSignal.any() r=saschanaz,webidl,smaug

Differential Revision: https://phabricator.services.mozilla.com/D198011
This commit is contained in:
Vincent Hilla 2024-01-23 08:43:07 +00:00
parent 250f71b842
commit fe5461ab38
10 changed files with 100 additions and 350 deletions

View File

@ -44,6 +44,11 @@ class AbortFollower : public nsISupports {
WeakPtr<AbortSignalImpl> 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<JS::Value> aReason);
@ -67,6 +72,8 @@ class AbortSignalImpl : public nsISupports, public SupportsWeakPtr {
virtual ~AbortSignalImpl() { UnlinkFollowers(); }
void SetAborted(JS::Handle<JS::Value> aReason);
JS::Heap<JS::Value> mReason;
private:

View File

@ -49,8 +49,7 @@ void AbortSignalImpl::SignalAbort(JS::Handle<JS::Value> 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<JS::Value> aReason) {
UnlinkFollowers();
}
void AbortSignalImpl::SetAborted(JS::Handle<JS::Value> 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<AbortSignalImpl*>(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<AbortSignalImpl*>(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<JS::Value> aReason)
: DOMEventTargetHelper(aGlobalObject), AbortSignalImpl(aAborted, aReason) {
: DOMEventTargetHelper(aGlobalObject),
AbortSignalImpl(aAborted, aReason),
mDependent(false) {
mozilla::HoldJSObjects(this);
}
@ -250,6 +258,62 @@ already_AddRefed<AbortSignal> AbortSignal::Timeout(GlobalObject& aGlobal,
return signal.forget();
}
// https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
already_AddRefed<AbortSignal> AbortSignal::Any(
GlobalObject& aGlobal,
const Sequence<OwningNonNull<AbortSignal>>& aSignals) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
// Step 1. Let resultSignal be a new object implementing AbortSignal using
// realm
RefPtr<AbortSignal> 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<JS::Value> 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<JS::Value> 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<JS::Value> 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

View File

@ -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<AbortSignal> Any(
GlobalObject& aGlobal,
const Sequence<OwningNonNull<AbortSignal>>& 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<WeakPtr<AbortSignal>> mSourceSignals;
nsTArray<RefPtr<AbortSignal>> mDependentSignals;
bool mDependent;
};
} // namespace mozilla::dom

View File

@ -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<AbortSignal> signals);
readonly attribute boolean aborted;
readonly attribute any reason;

View File

@ -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

View File

@ -140,6 +140,3 @@
[AbortController interface: operation abort(optional any)]
expected: FAIL
[AbortSignal interface: operation any(sequence<AbortSignal>)]
expected: FAIL

View File

@ -1,28 +0,0 @@
[idlharness.any.sharedworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[AbortSignal interface: operation any(sequence<AbortSignal>)]
expected: FAIL
[AbortSignal interface: calling any(sequence<AbortSignal>) 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<AbortSignal>)]
expected: FAIL
[AbortSignal interface: calling any(sequence<AbortSignal>) 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<AbortSignal>)]
expected: FAIL
[AbortSignal interface: calling any(sequence<AbortSignal>) on new AbortController().signal with too few arguments must throw TypeError]
expected: FAIL

View File

@ -5,9 +5,4 @@
[idlharness.window.html?exclude=Node]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[AbortSignal interface: operation any(sequence<AbortSignal>)]
expected: FAIL
[AbortSignal interface: calling any(sequence<AbortSignal>) on new AbortController().signal with too few arguments must throw TypeError]
expected: FAIL

View File

@ -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

View File

@ -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