Bug 1941046 - Part 4: Send a callback request for impression and clicks of MARS Top...
[gecko.git] / dom / abort / AbortSignal.cpp
blobb0e62b8392d932175d3d4c73eb5910dff2024769
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 {
23 // AbortSignalImpl
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) {
35 if (!mAborted) {
36 return;
38 MaybeAssignAbortError(aCx);
39 aReason.set(mReason);
42 JS::Value AbortSignalImpl::RawReason() const { return mReason.get(); }
44 // https://dom.spec.whatwg.org/#abortsignal-signal-abort
45 void AbortSignalImpl::SignalAbort(JS::Handle<JS::Value> aReason) {
46 // Step 1: If signal is aborted, then return.
47 if (mAborted) {
48 return;
51 // Step 2: Set signal’s abort reason to reason if it is given; otherwise to a
52 // new "AbortError" DOMException.
54 // (But given AbortSignalImpl is supposed to run without JS context, the
55 // DOMException creation is deferred to the getter.)
56 SetAborted(aReason);
58 // Step 3 - 6
59 SignalAbortWithDependents();
62 void AbortSignalImpl::SignalAbortWithDependents() {
63 // AbortSignalImpl cannot have dependents, so just run abort steps for itself.
64 RunAbortSteps();
67 // https://dom.spec.whatwg.org/#run-the-abort-steps
68 // This skips event firing as AbortSignalImpl is not supposed to be exposed to
69 // JS. It's done instead in AbortSignal::RunAbortSteps.
70 void AbortSignalImpl::RunAbortSteps() {
71 // Step 1: For each algorithm of signal’s abort algorithms: run algorithm.
73 // When there are multiple followers, the follower removal algorithm
74 // https://dom.spec.whatwg.org/#abortsignal-remove could be invoked in an
75 // earlier algorithm to remove a later algorithm, so |mFollowers| must be a
76 // |nsTObserverArray| to defend against mutation.
77 for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
78 MOZ_ASSERT(follower->mFollowingSignal == this);
79 follower->RunAbortAlgorithm();
82 // Step 2: Empty signal’s abort algorithms.
83 UnlinkFollowers();
86 void AbortSignalImpl::SetAborted(JS::Handle<JS::Value> aReason) {
87 mAborted = true;
88 mReason = aReason;
91 void AbortSignalImpl::Traverse(AbortSignalImpl* aSignal,
92 nsCycleCollectionTraversalCallback& cb) {
93 ImplCycleCollectionTraverse(cb, aSignal->mFollowers, "mFollowers", 0);
96 void AbortSignalImpl::Unlink(AbortSignalImpl* aSignal) {
97 aSignal->mReason.setUndefined();
98 aSignal->UnlinkFollowers();
101 void AbortSignalImpl::MaybeAssignAbortError(JSContext* aCx) {
102 MOZ_ASSERT(mAborted);
103 if (!mReason.isUndefined()) {
104 return;
107 JS::Rooted<JS::Value> exception(aCx);
108 RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
110 if (NS_WARN_IF(!ToJSValue(aCx, dom, &exception))) {
111 return;
114 mReason.set(exception);
117 void AbortSignalImpl::UnlinkFollowers() {
118 // Manually unlink all followers before destructing the array, or otherwise
119 // the array will be accessed by Unfollow() while being destructed.
120 for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
121 follower->mFollowingSignal = nullptr;
123 mFollowers.Clear();
126 // AbortSignal
127 // ----------------------------------------------------------------------------
129 NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal)
131 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal,
132 DOMEventTargetHelper)
133 AbortSignalImpl::Traverse(static_cast<AbortSignalImpl*>(tmp), cb);
134 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDependentSignals)
135 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
137 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal,
138 DOMEventTargetHelper)
139 AbortSignalImpl::Unlink(static_cast<AbortSignalImpl*>(tmp));
140 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDependentSignals)
141 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
143 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal)
144 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
146 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AbortSignal,
147 DOMEventTargetHelper)
148 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason)
149 NS_IMPL_CYCLE_COLLECTION_TRACE_END
151 NS_IMPL_ADDREF_INHERITED(AbortSignal, DOMEventTargetHelper)
152 NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper)
154 AbortSignal::AbortSignal(nsIGlobalObject* aGlobalObject, bool aAborted,
155 JS::Handle<JS::Value> aReason)
156 : DOMEventTargetHelper(aGlobalObject),
157 AbortSignalImpl(aAborted, aReason),
158 mDependent(false) {
159 mozilla::HoldJSObjects(this);
162 JSObject* AbortSignal::WrapObject(JSContext* aCx,
163 JS::Handle<JSObject*> aGivenProto) {
164 return AbortSignal_Binding::Wrap(aCx, this, aGivenProto);
167 already_AddRefed<AbortSignal> AbortSignal::Abort(
168 GlobalObject& aGlobal, JS::Handle<JS::Value> aReason) {
169 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
171 RefPtr<AbortSignal> abortSignal = new AbortSignal(global, true, aReason);
172 return abortSignal.forget();
175 class AbortSignalTimeoutHandler final : public TimeoutHandler {
176 public:
177 AbortSignalTimeoutHandler(JSContext* aCx, AbortSignal* aSignal)
178 : TimeoutHandler(aCx), mSignal(aSignal) {}
180 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
181 NS_DECL_CYCLE_COLLECTION_CLASS(AbortSignalTimeoutHandler)
183 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
184 // Step 3
185 MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
186 AutoJSAPI jsapi;
187 if (NS_WARN_IF(!jsapi.Init(mSignal->GetParentObject()))) {
188 // (false is only for setInterval, see
189 // nsGlobalWindowInner::RunTimeoutHandler)
190 return true;
193 // Step 1. Queue a global task on the timer task source given global to
194 // signal abort given signal and a new "TimeoutError" DOMException.
195 JS::Rooted<JS::Value> exception(jsapi.cx());
196 RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_TIMEOUT_ERR);
197 if (NS_WARN_IF(!ToJSValue(jsapi.cx(), dom, &exception))) {
198 return true;
201 mSignal->SignalAbort(exception);
202 return true;
205 private:
206 ~AbortSignalTimeoutHandler() override = default;
208 RefPtr<AbortSignal> mSignal;
211 NS_IMPL_CYCLE_COLLECTION(AbortSignalTimeoutHandler, mSignal)
212 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortSignalTimeoutHandler)
213 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalTimeoutHandler)
214 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignalTimeoutHandler)
215 NS_INTERFACE_MAP_ENTRY(nsISupports)
216 NS_INTERFACE_MAP_END
218 static void SetTimeoutForGlobal(GlobalObject& aGlobal, TimeoutHandler& aHandler,
219 int32_t timeout, ErrorResult& aRv) {
220 if (NS_IsMainThread()) {
221 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
222 do_QueryInterface(aGlobal.GetAsSupports());
223 if (!innerWindow) {
224 aRv.ThrowInvalidStateError("Could not find window.");
225 return;
228 int32_t handle;
229 nsresult rv =
230 nsGlobalWindowInner::Cast(innerWindow)
231 ->GetTimeoutManager()
232 ->SetTimeout(&aHandler, timeout, /* aIsInterval */ false,
233 Timeout::Reason::eAbortSignalTimeout, &handle);
234 if (NS_FAILED(rv)) {
235 aRv.Throw(rv);
236 return;
238 } else {
239 WorkerPrivate* workerPrivate =
240 GetWorkerPrivateFromContext(aGlobal.Context());
241 workerPrivate->SetTimeout(aGlobal.Context(), &aHandler, timeout,
242 /* aIsInterval */ false,
243 Timeout::Reason::eAbortSignalTimeout, aRv);
244 if (aRv.Failed()) {
245 return;
250 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
251 already_AddRefed<AbortSignal> AbortSignal::Timeout(GlobalObject& aGlobal,
252 uint64_t aMilliseconds,
253 ErrorResult& aRv) {
254 // Step 2. Let global be signal’s relevant global object.
255 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
257 // Step 1. Let signal be a new AbortSignal object.
258 RefPtr<AbortSignal> signal =
259 new AbortSignal(global, false, JS::UndefinedHandleValue);
261 // Step 3. Run steps after a timeout given global, "AbortSignal-timeout",
262 // milliseconds, and the following step: ...
263 RefPtr<TimeoutHandler> handler =
264 new AbortSignalTimeoutHandler(aGlobal.Context(), signal);
266 // Note: We only supports int32_t range intervals
267 int32_t timeout =
268 aMilliseconds > uint64_t(std::numeric_limits<int32_t>::max())
269 ? std::numeric_limits<int32_t>::max()
270 : static_cast<int32_t>(aMilliseconds);
272 SetTimeoutForGlobal(aGlobal, *handler, timeout, aRv);
273 if (aRv.Failed()) {
274 return nullptr;
277 // Step 4. Return signal.
278 return signal.forget();
281 // https://dom.spec.whatwg.org/#create-a-dependent-abort-signal
282 already_AddRefed<AbortSignal> AbortSignal::Any(
283 GlobalObject& aGlobal,
284 const Sequence<OwningNonNull<AbortSignal>>& aSignals) {
285 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
286 mozilla::Span span{aSignals.Elements(), aSignals.Length()};
287 return Any(global, span);
290 already_AddRefed<AbortSignal> AbortSignal::Any(
291 nsIGlobalObject* aGlobal,
292 const Span<const OwningNonNull<AbortSignal>>& aSignals) {
293 // Step 1. Let resultSignal be a new object implementing AbortSignal using
294 // realm
295 RefPtr<AbortSignal> resultSignal =
296 new AbortSignal(aGlobal, false, JS::UndefinedHandleValue);
298 if (!aSignals.IsEmpty()) {
299 // (Prepare for step 2 which uses the reason of this. Cannot use
300 // RawReason because that can cause constructing new DOMException for each
301 // dependent signal instead of sharing the single one.)
302 AutoJSAPI jsapi;
303 if (!jsapi.Init(aGlobal)) {
304 return nullptr;
306 JSContext* cx = jsapi.cx();
308 // Step 2. For each signal of signals: if signal is aborted, then set
309 // resultSignal's abort reason to signal's abort reason and return
310 // resultSignal.
311 for (const auto& signal : aSignals) {
312 if (signal->Aborted()) {
313 JS::Rooted<JS::Value> reason(cx);
314 signal->GetReason(cx, &reason);
315 resultSignal->SetAborted(reason);
316 return resultSignal.forget();
321 // Step 3. Set resultSignal's dependent to true
322 resultSignal->mDependent = true;
324 // Step 4. For each signal of signals
325 for (const auto& signal : aSignals) {
326 if (!signal->Dependent()) {
327 // Step 4.1. If signal is not dependent, make resultSignal dependent on it
328 resultSignal->MakeDependentOn(signal);
329 } else {
330 // Step 4.2. Otherwise, make resultSignal dependent on its source signals
331 for (const auto& sourceSignal : signal->mSourceSignals) {
332 if (!sourceSignal) {
333 // Bug 1908466, sourceSignal might have been garbage collected.
334 // As signal is not aborted, sourceSignal also wasn't.
335 // Thus do not depend on it, as it cannot be aborted anymore.
336 continue;
338 MOZ_ASSERT(!sourceSignal->Aborted() && !sourceSignal->Dependent());
339 resultSignal->MakeDependentOn(sourceSignal);
344 // Step 5. Return resultSignal.
345 return resultSignal.forget();
348 void AbortSignal::MakeDependentOn(AbortSignal* aSignal) {
349 MOZ_ASSERT(mDependent);
350 MOZ_ASSERT(aSignal);
351 // append only if not already contained in list
352 // https://infra.spec.whatwg.org/#set-append
353 if (!mSourceSignals.Contains(aSignal)) {
354 mSourceSignals.AppendElement(aSignal);
356 if (!aSignal->mDependentSignals.Contains(this)) {
357 aSignal->mDependentSignals.AppendElement(this);
361 // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
362 void AbortSignal::ThrowIfAborted(JSContext* aCx, ErrorResult& aRv) {
363 aRv.MightThrowJSException();
365 if (Aborted()) {
366 JS::Rooted<JS::Value> reason(aCx);
367 GetReason(aCx, &reason);
368 aRv.ThrowJSException(aCx, reason);
372 // Step 3 - 6 of https://dom.spec.whatwg.org/#abortsignal-signal-abort
373 void AbortSignal::SignalAbortWithDependents() {
374 // Step 3: Let dependentSignalsToAbort be a new list.
375 nsTArray<RefPtr<AbortSignal>> dependentSignalsToAbort;
377 // mDependentSignals can go away after this function.
378 nsTArray<RefPtr<AbortSignal>> dependentSignals = std::move(mDependentSignals);
380 if (!dependentSignals.IsEmpty()) {
381 // (Prepare for step 4.1.1 which uses the reason of this. Cannot use
382 // RawReason because that can cause constructing new DOMException for each
383 // dependent signal instead of sharing the single one.)
384 AutoJSAPI jsapi;
385 if (!jsapi.Init(GetParentObject())) {
386 return;
388 JSContext* cx = jsapi.cx();
389 JS::Rooted<JS::Value> reason(cx);
390 GetReason(cx, &reason);
392 // Step 4. For each dependentSignal of signal’s dependent signals:
393 for (const auto& dependentSignal : dependentSignals) {
394 MOZ_ASSERT(dependentSignal->mSourceSignals.Contains(this));
395 // Step 4.1: If dependentSignal is not aborted, then:
396 if (!dependentSignal->Aborted()) {
397 // Step 4.1.1: Set dependentSignal’s abort reason to signal’s abort
398 // reason.
399 dependentSignal->SetAborted(reason);
400 // Step 4.1.2: Append dependentSignal to dependentSignalsToAbort.
401 dependentSignalsToAbort.AppendElement(dependentSignal);
406 // Step 5: Run the abort steps for signal.
407 RunAbortSteps();
409 // Step 6: For each dependentSignal of dependentSignalsToAbort, run the abort
410 // steps for dependentSignal.
411 for (const auto& dependentSignal : dependentSignalsToAbort) {
412 dependentSignal->RunAbortSteps();
416 // https://dom.spec.whatwg.org/#run-the-abort-steps
417 void AbortSignal::RunAbortSteps() {
418 // Step 1 - 2:
419 AbortSignalImpl::RunAbortSteps();
421 // Step 3. Fire an event named abort at this signal.
422 EventInit init;
423 init.mBubbles = false;
424 init.mCancelable = false;
426 RefPtr<Event> event = Event::Constructor(this, u"abort"_ns, init);
427 event->SetTrusted(true);
429 DispatchEvent(*event);
432 bool AbortSignal::Dependent() const { return mDependent; }
434 AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); }
436 // AbortFollower
437 // ----------------------------------------------------------------------------
439 AbortFollower::~AbortFollower() { Unfollow(); }
441 // https://dom.spec.whatwg.org/#abortsignal-add
442 void AbortFollower::Follow(AbortSignalImpl* aSignal) {
443 // Step 1.
444 if (aSignal->mAborted) {
445 return;
448 MOZ_DIAGNOSTIC_ASSERT(aSignal);
450 Unfollow();
452 // Step 2.
453 mFollowingSignal = aSignal;
454 MOZ_ASSERT(!aSignal->mFollowers.Contains(this));
455 aSignal->mFollowers.AppendElement(this);
458 // https://dom.spec.whatwg.org/#abortsignal-remove
459 void AbortFollower::Unfollow() {
460 if (mFollowingSignal) {
461 // |Unfollow| is called by cycle-collection unlink code that runs in no
462 // guaranteed order. So we can't, symmetric with |Follow| above, assert
463 // that |this| will be found in |mFollowingSignal->mFollowers|.
464 mFollowingSignal->mFollowers.RemoveElement(this);
465 mFollowingSignal = nullptr;
469 bool AbortFollower::IsFollowing() const { return !!mFollowingSignal; }
471 } // namespace mozilla::dom