Bug 1854367 - Mark storage-access-permission.sub.https.window.html subtest as intermi...
[gecko.git] / dom / abort / AbortSignal.cpp
blob01d0ac947fcafe508c29877e0710b9224e71989d
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 steps 1-4
45 void AbortSignalImpl::SignalAbort(JS::Handle<JS::Value> aReason) {
46 // Step 1.
47 if (mAborted) {
48 return;
51 // Step 2.
52 mAborted = true;
53 mReason = aReason;
55 // Step 3.
56 // When there are multiple followers, the follower removal algorithm
57 // https://dom.spec.whatwg.org/#abortsignal-remove could be invoked in an
58 // earlier algorithm to remove a later algorithm, so |mFollowers| must be a
59 // |nsTObserverArray| to defend against mutation.
60 for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
61 MOZ_ASSERT(follower->mFollowingSignal == this);
62 follower->RunAbortAlgorithm();
65 // Step 4.
66 UnlinkFollowers();
69 void AbortSignalImpl::Traverse(AbortSignalImpl* aSignal,
70 nsCycleCollectionTraversalCallback& cb) {
71 ImplCycleCollectionTraverse(cb, aSignal->mFollowers, "mFollowers", 0);
74 void AbortSignalImpl::Unlink(AbortSignalImpl* aSignal) {
75 aSignal->mReason.setUndefined();
76 aSignal->UnlinkFollowers();
79 void AbortSignalImpl::MaybeAssignAbortError(JSContext* aCx) {
80 MOZ_ASSERT(mAborted);
81 if (!mReason.isUndefined()) {
82 return;
85 JS::Rooted<JS::Value> exception(aCx);
86 RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
88 if (NS_WARN_IF(!ToJSValue(aCx, dom, &exception))) {
89 return;
92 mReason.set(exception);
95 void AbortSignalImpl::UnlinkFollowers() {
96 // Manually unlink all followers before destructing the array, or otherwise
97 // the array will be accessed by Unfollow() while being destructed.
98 for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
99 follower->mFollowingSignal = nullptr;
101 mFollowers.Clear();
104 // AbortSignal
105 // ----------------------------------------------------------------------------
107 NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal)
109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal,
110 DOMEventTargetHelper)
111 AbortSignalImpl::Traverse(static_cast<AbortSignalImpl*>(tmp), cb);
112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
114 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal,
115 DOMEventTargetHelper)
116 AbortSignalImpl::Unlink(static_cast<AbortSignalImpl*>(tmp));
117 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
119 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal)
120 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
122 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AbortSignal,
123 DOMEventTargetHelper)
124 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason)
125 NS_IMPL_CYCLE_COLLECTION_TRACE_END
127 NS_IMPL_ADDREF_INHERITED(AbortSignal, DOMEventTargetHelper)
128 NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper)
130 AbortSignal::AbortSignal(nsIGlobalObject* aGlobalObject, bool aAborted,
131 JS::Handle<JS::Value> aReason)
132 : DOMEventTargetHelper(aGlobalObject), AbortSignalImpl(aAborted, aReason) {
133 mozilla::HoldJSObjects(this);
136 JSObject* AbortSignal::WrapObject(JSContext* aCx,
137 JS::Handle<JSObject*> aGivenProto) {
138 return AbortSignal_Binding::Wrap(aCx, this, aGivenProto);
141 already_AddRefed<AbortSignal> AbortSignal::Abort(
142 GlobalObject& aGlobal, JS::Handle<JS::Value> aReason) {
143 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
145 RefPtr<AbortSignal> abortSignal = new AbortSignal(global, true, aReason);
146 return abortSignal.forget();
149 class AbortSignalTimeoutHandler final : public TimeoutHandler {
150 public:
151 AbortSignalTimeoutHandler(JSContext* aCx, AbortSignal* aSignal)
152 : TimeoutHandler(aCx), mSignal(aSignal) {}
154 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
155 NS_DECL_CYCLE_COLLECTION_CLASS(AbortSignalTimeoutHandler)
157 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
158 // Step 3
159 MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
160 AutoJSAPI jsapi;
161 if (NS_WARN_IF(!jsapi.Init(mSignal->GetParentObject()))) {
162 // (false is only for setInterval, see
163 // nsGlobalWindowInner::RunTimeoutHandler)
164 return true;
167 // Step 1. Queue a global task on the timer task source given global to
168 // signal abort given signal and a new "TimeoutError" DOMException.
169 JS::Rooted<JS::Value> exception(jsapi.cx());
170 RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_TIMEOUT_ERR);
171 if (NS_WARN_IF(!ToJSValue(jsapi.cx(), dom, &exception))) {
172 return true;
175 mSignal->SignalAbort(exception);
176 return true;
179 private:
180 ~AbortSignalTimeoutHandler() override = default;
182 RefPtr<AbortSignal> mSignal;
185 NS_IMPL_CYCLE_COLLECTION(AbortSignalTimeoutHandler, mSignal)
186 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortSignalTimeoutHandler)
187 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalTimeoutHandler)
188 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignalTimeoutHandler)
189 NS_INTERFACE_MAP_ENTRY(nsISupports)
190 NS_INTERFACE_MAP_END
192 static void SetTimeoutForGlobal(GlobalObject& aGlobal, TimeoutHandler& aHandler,
193 int32_t timeout, ErrorResult& aRv) {
194 if (NS_IsMainThread()) {
195 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
196 do_QueryInterface(aGlobal.GetAsSupports());
197 if (!innerWindow) {
198 aRv.ThrowInvalidStateError("Could not find window.");
199 return;
202 int32_t handle;
203 nsresult rv = innerWindow->TimeoutManager().SetTimeout(
204 &aHandler, timeout, /* aIsInterval */ false,
205 Timeout::Reason::eAbortSignalTimeout, &handle);
206 if (NS_FAILED(rv)) {
207 aRv.Throw(rv);
208 return;
210 } else {
211 WorkerPrivate* workerPrivate =
212 GetWorkerPrivateFromContext(aGlobal.Context());
213 workerPrivate->SetTimeout(aGlobal.Context(), &aHandler, timeout,
214 /* aIsInterval */ false,
215 Timeout::Reason::eAbortSignalTimeout, aRv);
216 if (aRv.Failed()) {
217 return;
222 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
223 already_AddRefed<AbortSignal> AbortSignal::Timeout(GlobalObject& aGlobal,
224 uint64_t aMilliseconds,
225 ErrorResult& aRv) {
226 // Step 2. Let global be signal’s relevant global object.
227 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
229 // Step 1. Let signal be a new AbortSignal object.
230 RefPtr<AbortSignal> signal =
231 new AbortSignal(global, false, JS::UndefinedHandleValue);
233 // Step 3. Run steps after a timeout given global, "AbortSignal-timeout",
234 // milliseconds, and the following step: ...
235 RefPtr<TimeoutHandler> handler =
236 new AbortSignalTimeoutHandler(aGlobal.Context(), signal);
238 // Note: We only supports int32_t range intervals
239 int32_t timeout =
240 aMilliseconds > uint64_t(std::numeric_limits<int32_t>::max())
241 ? std::numeric_limits<int32_t>::max()
242 : static_cast<int32_t>(aMilliseconds);
244 SetTimeoutForGlobal(aGlobal, *handler, timeout, aRv);
245 if (aRv.Failed()) {
246 return nullptr;
249 // Step 4. Return signal.
250 return signal.forget();
253 // https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
254 void AbortSignal::ThrowIfAborted(JSContext* aCx, ErrorResult& aRv) {
255 aRv.MightThrowJSException();
257 if (Aborted()) {
258 JS::Rooted<JS::Value> reason(aCx);
259 GetReason(aCx, &reason);
260 aRv.ThrowJSException(aCx, reason);
264 // https://dom.spec.whatwg.org/#abortsignal-signal-abort
265 void AbortSignal::SignalAbort(JS::Handle<JS::Value> aReason) {
266 // Step 1, in case "signal abort" algorithm is called directly
267 if (Aborted()) {
268 return;
271 // Steps 1-4.
272 AbortSignalImpl::SignalAbort(aReason);
274 // Step 5.
275 EventInit init;
276 init.mBubbles = false;
277 init.mCancelable = false;
279 RefPtr<Event> event = Event::Constructor(this, u"abort"_ns, init);
280 event->SetTrusted(true);
282 DispatchEvent(*event);
285 void AbortSignal::RunAbortAlgorithm() {
286 JS::Rooted<JS::Value> reason(RootingCx(), Signal()->RawReason());
287 SignalAbort(reason);
290 AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); }
292 // AbortFollower
293 // ----------------------------------------------------------------------------
295 AbortFollower::~AbortFollower() { Unfollow(); }
297 // https://dom.spec.whatwg.org/#abortsignal-add
298 void AbortFollower::Follow(AbortSignalImpl* aSignal) {
299 // Step 1.
300 if (aSignal->mAborted) {
301 return;
304 MOZ_DIAGNOSTIC_ASSERT(aSignal);
306 Unfollow();
308 // Step 2.
309 mFollowingSignal = aSignal;
310 MOZ_ASSERT(!aSignal->mFollowers.Contains(this));
311 aSignal->mFollowers.AppendElement(this);
314 // https://dom.spec.whatwg.org/#abortsignal-remove
315 void AbortFollower::Unfollow() {
316 if (mFollowingSignal) {
317 // |Unfollow| is called by cycle-collection unlink code that runs in no
318 // guaranteed order. So we can't, symmetric with |Follow| above, assert
319 // that |this| will be found in |mFollowingSignal->mFollowers|.
320 mFollowingSignal->mFollowers.RemoveElement(this);
321 mFollowingSignal = nullptr;
325 bool AbortFollower::IsFollowing() const { return !!mFollowingSignal; }
327 } // namespace mozilla::dom