Bug 1880558: Allow PnP windows to enter macOS native fullscreen. r=pip-reviewers...
[gecko.git] / dom / abort / AbortSignal.cpp
blobde5f24c080ecf915603133f1bb015b3af7d91a77
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 SetAborted(aReason);
54 // Step 3.
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();
64 // Step 4.
65 UnlinkFollowers();
68 void AbortSignalImpl::SetAborted(JS::Handle<JS::Value> aReason) {
69 mAborted = true;
70 mReason = 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) {
84 MOZ_ASSERT(mAborted);
85 if (!mReason.isUndefined()) {
86 return;
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))) {
93 return;
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;
105 mFollowers.Clear();
108 // AbortSignal
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),
140 mDependent(false) {
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 {
158 public:
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
166 // Step 3
167 MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
168 AutoJSAPI jsapi;
169 if (NS_WARN_IF(!jsapi.Init(mSignal->GetParentObject()))) {
170 // (false is only for setInterval, see
171 // nsGlobalWindowInner::RunTimeoutHandler)
172 return true;
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))) {
180 return true;
183 mSignal->SignalAbort(exception);
184 return true;
187 private:
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)
198 NS_INTERFACE_MAP_END
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());
205 if (!innerWindow) {
206 aRv.ThrowInvalidStateError("Could not find window.");
207 return;
210 int32_t handle;
211 nsresult rv = innerWindow->TimeoutManager().SetTimeout(
212 &aHandler, timeout, /* aIsInterval */ false,
213 Timeout::Reason::eAbortSignalTimeout, &handle);
214 if (NS_FAILED(rv)) {
215 aRv.Throw(rv);
216 return;
218 } else {
219 WorkerPrivate* workerPrivate =
220 GetWorkerPrivateFromContext(aGlobal.Context());
221 workerPrivate->SetTimeout(aGlobal.Context(), &aHandler, timeout,
222 /* aIsInterval */ false,
223 Timeout::Reason::eAbortSignalTimeout, aRv);
224 if (aRv.Failed()) {
225 return;
230 // https://dom.spec.whatwg.org/#dom-abortsignal-timeout
231 already_AddRefed<AbortSignal> AbortSignal::Timeout(GlobalObject& aGlobal,
232 uint64_t aMilliseconds,
233 ErrorResult& aRv) {
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
247 int32_t timeout =
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);
253 if (aRv.Failed()) {
254 return nullptr;
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
268 // realm
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
274 // resultSignal.
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);
291 } else {
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);
306 MOZ_ASSERT(aSignal);
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();
321 if (Aborted()) {
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
331 if (Aborted()) {
332 return;
335 // Steps 1-4.
336 AbortSignalImpl::SignalAbort(aReason);
338 // Step 5. Fire an event named abort at this signal
339 EventInit init;
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());
359 SignalAbort(reason);
362 bool AbortSignal::Dependent() const { return mDependent; }
364 AbortSignal::~AbortSignal() { mozilla::DropJSObjects(this); }
366 // AbortFollower
367 // ----------------------------------------------------------------------------
369 AbortFollower::~AbortFollower() { Unfollow(); }
371 // https://dom.spec.whatwg.org/#abortsignal-add
372 void AbortFollower::Follow(AbortSignalImpl* aSignal) {
373 // Step 1.
374 if (aSignal->mAborted) {
375 return;
378 MOZ_DIAGNOSTIC_ASSERT(aSignal);
380 Unfollow();
382 // Step 2.
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