1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AbortSignal.h"
9 #include "mozilla/dom/AbortSignalBinding.h"
10 #include "mozilla/dom/DOMException.h"
11 #include "mozilla/dom/Event.h"
12 #include "mozilla/dom/EventBinding.h"
13 #include "mozilla/dom/TimeoutHandler.h"
14 #include "mozilla/dom/TimeoutManager.h"
15 #include "mozilla/dom/ToJSValue.h"
16 #include "mozilla/dom/WorkerPrivate.h"
17 #include "mozilla/RefPtr.h"
18 #include "nsCycleCollectionParticipant.h"
19 #include "nsPIDOMWindow.h"
21 namespace mozilla::dom
{
24 // ----------------------------------------------------------------------------
26 AbortSignalImpl::AbortSignalImpl(bool aAborted
, JS::Handle
<JS::Value
> aReason
)
27 : mReason(aReason
), mAborted(aAborted
) {
28 MOZ_ASSERT_IF(!mReason
.isUndefined(), mAborted
);
31 bool AbortSignalImpl::Aborted() const { return mAborted
; }
33 void AbortSignalImpl::GetReason(JSContext
* aCx
,
34 JS::MutableHandle
<JS::Value
> aReason
) {
38 MaybeAssignAbortError(aCx
);
42 JS::Value
AbortSignalImpl::RawReason() const { return mReason
.get(); }
44 // https://dom.spec.whatwg.org/#abortsignal-signal-abort steps 1-4
45 void AbortSignalImpl::SignalAbort(JS::Handle
<JS::Value
> aReason
) {
55 // When there are multiple followers, the follower removal algorithm
56 // https://dom.spec.whatwg.org/#abortsignal-remove could be invoked in an
57 // earlier algorithm to remove a later algorithm, so |mFollowers| must be a
58 // |nsTObserverArray| to defend against mutation.
59 for (RefPtr
<AbortFollower
>& follower
: mFollowers
.ForwardRange()) {
60 MOZ_ASSERT(follower
->mFollowingSignal
== this);
61 follower
->RunAbortAlgorithm();
68 void AbortSignalImpl::SetAborted(JS::Handle
<JS::Value
> aReason
) {
73 void AbortSignalImpl::Traverse(AbortSignalImpl
* aSignal
,
74 nsCycleCollectionTraversalCallback
& cb
) {
75 ImplCycleCollectionTraverse(cb
, aSignal
->mFollowers
, "mFollowers", 0);
78 void AbortSignalImpl::Unlink(AbortSignalImpl
* aSignal
) {
79 aSignal
->mReason
.setUndefined();
80 aSignal
->UnlinkFollowers();
83 void AbortSignalImpl::MaybeAssignAbortError(JSContext
* aCx
) {
85 if (!mReason
.isUndefined()) {
89 JS::Rooted
<JS::Value
> exception(aCx
);
90 RefPtr
<DOMException
> dom
= DOMException::Create(NS_ERROR_DOM_ABORT_ERR
);
92 if (NS_WARN_IF(!ToJSValue(aCx
, dom
, &exception
))) {
96 mReason
.set(exception
);
99 void AbortSignalImpl::UnlinkFollowers() {
100 // Manually unlink all followers before destructing the array, or otherwise
101 // the array will be accessed by Unfollow() while being destructed.
102 for (RefPtr
<AbortFollower
>& follower
: mFollowers
.ForwardRange()) {
103 follower
->mFollowingSignal
= nullptr;
109 // ----------------------------------------------------------------------------
111 NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal
)
113 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal
,
114 DOMEventTargetHelper
)
115 AbortSignalImpl::Traverse(static_cast<AbortSignalImpl
*>(tmp
), cb
);
116 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDependentSignals
)
117 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
119 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal
,
120 DOMEventTargetHelper
)
121 AbortSignalImpl::Unlink(static_cast<AbortSignalImpl
*>(tmp
));
122 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDependentSignals
)
123 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
125 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal
)
126 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
128 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AbortSignal
,
129 DOMEventTargetHelper
)
130 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason
)
131 NS_IMPL_CYCLE_COLLECTION_TRACE_END
133 NS_IMPL_ADDREF_INHERITED(AbortSignal
, DOMEventTargetHelper
)
134 NS_IMPL_RELEASE_INHERITED(AbortSignal
, DOMEventTargetHelper
)
136 AbortSignal::AbortSignal(nsIGlobalObject
* aGlobalObject
, bool aAborted
,
137 JS::Handle
<JS::Value
> aReason
)
138 : DOMEventTargetHelper(aGlobalObject
),
139 AbortSignalImpl(aAborted
, aReason
),
141 mozilla::HoldJSObjects(this);
144 JSObject
* AbortSignal::WrapObject(JSContext
* aCx
,
145 JS::Handle
<JSObject
*> aGivenProto
) {
146 return AbortSignal_Binding::Wrap(aCx
, this, aGivenProto
);
149 already_AddRefed
<AbortSignal
> AbortSignal::Abort(
150 GlobalObject
& aGlobal
, JS::Handle
<JS::Value
> aReason
) {
151 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
153 RefPtr
<AbortSignal
> abortSignal
= new AbortSignal(global
, true, aReason
);
154 return abortSignal
.forget();
157 class AbortSignalTimeoutHandler final
: public TimeoutHandler
{
159 AbortSignalTimeoutHandler(JSContext
* aCx
, AbortSignal
* aSignal
)
160 : TimeoutHandler(aCx
), mSignal(aSignal
) {}
162 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
163 NS_DECL_CYCLE_COLLECTION_CLASS(AbortSignalTimeoutHandler
)
165 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
167 MOZ_CAN_RUN_SCRIPT
bool Call(const char* /* unused */) override
{
169 if (NS_WARN_IF(!jsapi
.Init(mSignal
->GetParentObject()))) {
170 // (false is only for setInterval, see
171 // nsGlobalWindowInner::RunTimeoutHandler)
175 // Step 1. Queue a global task on the timer task source given global to
176 // signal abort given signal and a new "TimeoutError" DOMException.
177 JS::Rooted
<JS::Value
> exception(jsapi
.cx());
178 RefPtr
<DOMException
> dom
= DOMException::Create(NS_ERROR_DOM_TIMEOUT_ERR
);
179 if (NS_WARN_IF(!ToJSValue(jsapi
.cx(), dom
, &exception
))) {
183 mSignal
->SignalAbort(exception
);
188 ~AbortSignalTimeoutHandler() override
= default;
190 RefPtr
<AbortSignal
> mSignal
;
193 NS_IMPL_CYCLE_COLLECTION(AbortSignalTimeoutHandler
, mSignal
)
194 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortSignalTimeoutHandler
)
195 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalTimeoutHandler
)
196 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignalTimeoutHandler
)
197 NS_INTERFACE_MAP_ENTRY(nsISupports
)
200 static void SetTimeoutForGlobal(GlobalObject
& aGlobal
, TimeoutHandler
& aHandler
,
201 int32_t timeout
, ErrorResult
& aRv
) {
202 if (NS_IsMainThread()) {
203 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
204 do_QueryInterface(aGlobal
.GetAsSupports());
206 aRv
.ThrowInvalidStateError("Could not find window.");
211 nsresult rv
= innerWindow
->TimeoutManager().SetTimeout(
212 &aHandler
, timeout
, /* aIsInterval */ false,
213 Timeout::Reason::eAbortSignalTimeout
, &handle
);
219 WorkerPrivate
* workerPrivate
=
220 GetWorkerPrivateFromContext(aGlobal
.Context());
221 workerPrivate
->SetTimeout(aGlobal
.Context(), &aHandler
, timeout
,
222 /* aIsInterval */ false,
223 Timeout::Reason::eAbortSignalTimeout
, aRv
);
230 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
231 already_AddRefed
<AbortSignal
> AbortSignal::Timeout(GlobalObject
& aGlobal
,
232 uint64_t aMilliseconds
,
234 // Step 2. Let global be signal’s relevant global object.
235 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
237 // Step 1. Let signal be a new AbortSignal object.
238 RefPtr
<AbortSignal
> signal
=
239 new AbortSignal(global
, false, JS::UndefinedHandleValue
);
241 // Step 3. Run steps after a timeout given global, "AbortSignal-timeout",
242 // milliseconds, and the following step: ...
243 RefPtr
<TimeoutHandler
> handler
=
244 new AbortSignalTimeoutHandler(aGlobal
.Context(), signal
);
246 // Note: We only supports int32_t range intervals
248 aMilliseconds
> uint64_t(std::numeric_limits
<int32_t>::max())
249 ? std::numeric_limits
<int32_t>::max()
250 : static_cast<int32_t>(aMilliseconds
);
252 SetTimeoutForGlobal(aGlobal
, *handler
, timeout
, aRv
);
257 // Step 4. Return signal.
258 return signal
.forget();
261 // https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
262 already_AddRefed
<AbortSignal
> AbortSignal::Any(
263 GlobalObject
& aGlobal
,
264 const Sequence
<OwningNonNull
<AbortSignal
>>& aSignals
) {
265 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
267 // Step 1. Let resultSignal be a new object implementing AbortSignal using
269 RefPtr
<AbortSignal
> resultSignal
=
270 new AbortSignal(global
, false, JS::UndefinedHandleValue
);
272 // Step 2. For each signal of signals: if signal is aborted, then set
273 // resultSignal's abort reason to signal's abort reason and return
275 for (const auto& signal
: aSignals
) {
276 if (signal
->Aborted()) {
277 JS::Rooted
<JS::Value
> reason(RootingCx(), signal
->RawReason());
278 resultSignal
->SetAborted(reason
);
279 return resultSignal
.forget();
283 // Step 3. Set resultSignal's dependent to true
284 resultSignal
->mDependent
= true;
286 // Step 4. For each signal of signals
287 for (const auto& signal
: aSignals
) {
288 if (!signal
->Dependent()) {
289 // Step 4.1. If signal is not dependent, make resultSignal dependent on it
290 resultSignal
->MakeDependentOn(signal
);
292 // Step 4.2. Otherwise, make resultSignal dependent on its source signals
293 for (const auto& sourceSignal
: signal
->mSourceSignals
) {
294 MOZ_ASSERT(!sourceSignal
->Aborted() && !sourceSignal
->Dependent());
295 resultSignal
->MakeDependentOn(sourceSignal
);
300 // Step 5. Return resultSignal.
301 return resultSignal
.forget();
304 void AbortSignal::MakeDependentOn(AbortSignal
* aSignal
) {
305 MOZ_ASSERT(mDependent
);
307 // append only if not already contained in list
308 // https://infra.spec.whatwg.org/#set-append
309 if (!mSourceSignals
.Contains(aSignal
)) {
310 mSourceSignals
.AppendElement(aSignal
);
312 if (!aSignal
->mDependentSignals
.Contains(this)) {
313 aSignal
->mDependentSignals
.AppendElement(this);
317 // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
318 void AbortSignal::ThrowIfAborted(JSContext
* aCx
, ErrorResult
& aRv
) {
319 aRv
.MightThrowJSException();
322 JS::Rooted
<JS::Value
> reason(aCx
);
323 GetReason(aCx
, &reason
);
324 aRv
.ThrowJSException(aCx
, reason
);
328 // https://dom.spec.whatwg.org/#abortsignal-signal-abort
329 void AbortSignal::SignalAbort(JS::Handle
<JS::Value
> aReason
) {
330 // Step 1, in case "signal abort" algorithm is called directly
336 AbortSignalImpl::SignalAbort(aReason
);
338 // Step 5. Fire an event named abort at this signal
340 init
.mBubbles
= false;
341 init
.mCancelable
= false;
343 RefPtr
<Event
> event
= Event::Constructor(this, u
"abort"_ns
, init
);
344 event
->SetTrusted(true);
346 DispatchEvent(*event
);
348 // Step 6. Abort dependentSignals of this signal
349 for (const auto& dependant
: mDependentSignals
) {
350 MOZ_ASSERT(dependant
->mSourceSignals
.Contains(this));
351 dependant
->SignalAbort(aReason
);
353 // clear dependent signals so that they might be garbage collected
354 mDependentSignals
.Clear();
357 void AbortSignal::RunAbortAlgorithm() {
358 JS::Rooted
<JS::Value
> reason(RootingCx(), Signal()->RawReason());
362 bool AbortSignal::Dependent() const { return mDependent
; }
364 AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); }
367 // ----------------------------------------------------------------------------
369 AbortFollower::~AbortFollower() { Unfollow(); }
371 // https://dom.spec.whatwg.org/#abortsignal-add
372 void AbortFollower::Follow(AbortSignalImpl
* aSignal
) {
374 if (aSignal
->mAborted
) {
378 MOZ_DIAGNOSTIC_ASSERT(aSignal
);
383 mFollowingSignal
= aSignal
;
384 MOZ_ASSERT(!aSignal
->mFollowers
.Contains(this));
385 aSignal
->mFollowers
.AppendElement(this);
388 // https://dom.spec.whatwg.org/#abortsignal-remove
389 void AbortFollower::Unfollow() {
390 if (mFollowingSignal
) {
391 // |Unfollow| is called by cycle-collection unlink code that runs in no
392 // guaranteed order. So we can't, symmetric with |Follow| above, assert
393 // that |this| will be found in |mFollowingSignal->mFollowers|.
394 mFollowingSignal
->mFollowers
.RemoveElement(this);
395 mFollowingSignal
= nullptr;
399 bool AbortFollower::IsFollowing() const { return !!mFollowingSignal
; }
401 } // namespace mozilla::dom