Bug 1885489 - Part 5: Add SnapshotIterator::readInt32(). r=iain
[gecko.git] / dom / promise / Promise.cpp
blob3abb517ac722e0eabdee3d6dd1baaf52ebb1f3a0
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Promise.h"
8 #include "mozilla/dom/Promise-inl.h"
10 #include "js/Debug.h"
12 #include "mozilla/Atomics.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/CycleCollectedJSContext.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/OwningNonNull.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/ResultExtensions.h"
19 #include "mozilla/Unused.h"
21 #include "mozilla/dom/AutoEntryScript.h"
22 #include "mozilla/dom/BindingUtils.h"
23 #include "mozilla/dom/DOMException.h"
24 #include "mozilla/dom/DOMExceptionBinding.h"
25 #include "mozilla/dom/Exceptions.h"
26 #include "mozilla/dom/MediaStreamError.h"
27 #include "mozilla/dom/PromiseBinding.h"
28 #include "mozilla/dom/ScriptSettings.h"
29 #include "mozilla/dom/UserActivation.h"
30 #include "mozilla/dom/WorkerPrivate.h"
31 #include "mozilla/dom/WorkerRunnable.h"
32 #include "mozilla/dom/WorkerRef.h"
33 #include "mozilla/dom/WorkletImpl.h"
34 #include "mozilla/dom/WorkletGlobalScope.h"
36 #include "jsfriendapi.h"
37 #include "js/Exception.h" // JS::ExceptionStack
38 #include "js/Object.h" // JS::GetCompartment
39 #include "js/StructuredClone.h"
40 #include "nsContentUtils.h"
41 #include "nsCycleCollectionParticipant.h"
42 #include "nsDebug.h"
43 #include "nsGlobalWindowInner.h"
44 #include "nsIScriptObjectPrincipal.h"
45 #include "nsJSEnvironment.h"
46 #include "nsJSPrincipals.h"
47 #include "nsJSUtils.h"
48 #include "nsPIDOMWindow.h"
49 #include "PromiseDebugging.h"
50 #include "PromiseNativeHandler.h"
51 #include "PromiseWorkerProxy.h"
52 #include "WrapperFactory.h"
53 #include "xpcpublic.h"
54 #include "xpcprivate.h"
56 namespace mozilla::dom {
58 // Promise
60 NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(Promise)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
63 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
65 tmp->mPromiseObj = nullptr;
66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
72 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
73 // If you add new JS member variables, you may need to stop using
74 // NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
75 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
76 NS_IMPL_CYCLE_COLLECTION_TRACE_END
78 Promise::Promise(nsIGlobalObject* aGlobal)
79 : mGlobal(aGlobal), mPromiseObj(nullptr) {
80 MOZ_ASSERT(mGlobal);
82 mozilla::HoldJSObjects(this);
85 Promise::~Promise() { mozilla::DropJSObjects(this); }
87 // static
88 already_AddRefed<Promise> Promise::Create(
89 nsIGlobalObject* aGlobal, ErrorResult& aRv,
90 PropagateUserInteraction aPropagateUserInteraction) {
91 if (!aGlobal) {
92 aRv.Throw(NS_ERROR_UNEXPECTED);
93 return nullptr;
95 RefPtr<Promise> p = new Promise(aGlobal);
96 p->CreateWrapper(aRv, aPropagateUserInteraction);
97 if (aRv.Failed()) {
98 return nullptr;
100 return p.forget();
103 // static
104 already_AddRefed<Promise> Promise::CreateInfallible(
105 nsIGlobalObject* aGlobal,
106 PropagateUserInteraction aPropagateUserInteraction) {
107 MOZ_ASSERT(aGlobal);
108 RefPtr<Promise> p = new Promise(aGlobal);
109 IgnoredErrorResult rv;
110 p->CreateWrapper(rv, aPropagateUserInteraction);
111 if (rv.Failed() && rv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)) {
112 MOZ_CRASH("Out of memory");
115 // We may have failed to init the wrapper here, because nsIGlobalObject had
116 // null GlobalJSObject. In that case we consider the JS realm is dead, which
117 // means:
118 // 1. This promise can't be settled.
119 // 2. Nothing can subscribe this promise anymore from that realm.
120 // Such condition makes this promise a no-op object.
121 (void)NS_WARN_IF(!p->PromiseObj());
123 return p.forget();
126 bool Promise::MaybePropagateUserInputEventHandling() {
127 MOZ_ASSERT(mPromiseObj,
128 "Should be called only if the wrapper is successfully created");
129 JS::PromiseUserInputEventHandlingState state =
130 UserActivation::IsHandlingUserInput()
131 ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
132 : JS::PromiseUserInputEventHandlingState::
133 DidntHaveUserInteractionAtCreation;
134 JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj);
135 return JS::SetPromiseUserInputEventHandlingState(p, state);
138 // static
139 already_AddRefed<Promise> Promise::Resolve(
140 nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue,
141 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
142 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
143 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseResolve(aCx, aValue));
144 if (!p) {
145 aRv.NoteJSContextException(aCx);
146 return nullptr;
149 return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
152 // static
153 already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
154 JSContext* aCx,
155 JS::Handle<JS::Value> aValue,
156 ErrorResult& aRv) {
157 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
158 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
159 if (!p) {
160 aRv.NoteJSContextException(aCx);
161 return nullptr;
164 // This promise will never be resolved, so we pass
165 // eDontPropagateUserInteraction for aPropagateUserInteraction
166 // unconditionally.
167 return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
170 // static
171 already_AddRefed<Promise> Promise::All(
172 JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
173 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
174 JS::Rooted<JSObject*> globalObj(aCx, JS::CurrentGlobalOrNull(aCx));
175 if (!globalObj) {
176 aRv.Throw(NS_ERROR_UNEXPECTED);
177 return nullptr;
180 nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
181 if (!global) {
182 aRv.Throw(NS_ERROR_UNEXPECTED);
183 return nullptr;
186 JS::RootedVector<JSObject*> promises(aCx);
187 if (!promises.reserve(aPromiseList.Length())) {
188 aRv.NoteJSContextException(aCx);
189 return nullptr;
192 for (const auto& promise : aPromiseList) {
193 JS::Rooted<JSObject*> promiseObj(aCx, promise->PromiseObj());
194 if (!promiseObj) {
195 // No-op object will never settle, so we return a no-op Promise here,
196 // which is equivalent of returning the existing no-op one.
197 return do_AddRef(promise);
199 // Just in case, make sure these are all in the context compartment.
200 if (!JS_WrapObject(aCx, &promiseObj)) {
201 aRv.NoteJSContextException(aCx);
202 return nullptr;
204 promises.infallibleAppend(promiseObj);
207 JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
208 if (!result) {
209 aRv.NoteJSContextException(aCx);
210 return nullptr;
213 return CreateFromExisting(global, result, aPropagateUserInteraction);
216 static void SettlePromise(Promise* aSettlingPromise, Promise* aCallbackPromise,
217 ErrorResult& aRv) {
218 if (!aSettlingPromise) {
219 return;
221 if (aRv.IsUncatchableException()) {
222 return;
224 if (aRv.Failed()) {
225 aSettlingPromise->MaybeReject(std::move(aRv));
226 return;
228 if (aCallbackPromise) {
229 aSettlingPromise->MaybeResolve(aCallbackPromise);
230 } else {
231 aSettlingPromise->MaybeResolveWithUndefined();
235 void PromiseNativeThenHandlerBase::ResolvedCallback(
236 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
237 if (!HasResolvedCallback()) {
238 mPromise->MaybeResolve(aValue);
239 return;
241 RefPtr<Promise> promise = CallResolveCallback(aCx, aValue, aRv);
242 SettlePromise(mPromise, promise, aRv);
245 void PromiseNativeThenHandlerBase::RejectedCallback(
246 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
247 if (!HasRejectedCallback()) {
248 mPromise->MaybeReject(aValue);
249 return;
251 RefPtr<Promise> promise = CallRejectCallback(aCx, aValue, aRv);
252 SettlePromise(mPromise, promise, aRv);
255 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
257 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase)
258 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
259 tmp->Traverse(cb);
260 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
262 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
263 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
264 tmp->Unlink();
265 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
267 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
268 NS_INTERFACE_MAP_ENTRY(nsISupports)
269 NS_INTERFACE_MAP_END
271 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PromiseNativeThenHandlerBase)
272 tmp->Trace(aCallbacks, aClosure);
273 NS_IMPL_CYCLE_COLLECTION_TRACE_END
275 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase)
276 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase)
278 Result<RefPtr<Promise>, nsresult> Promise::ThenWithoutCycleCollection(
279 const std::function<already_AddRefed<Promise>(
280 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv)>&
281 aCallback) {
282 return ThenWithCycleCollectedArgs(aCallback);
285 void Promise::CreateWrapper(
286 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
287 AutoJSAPI jsapi;
288 if (!jsapi.Init(mGlobal)) {
289 aRv.Throw(NS_ERROR_UNEXPECTED);
290 return;
292 JSContext* cx = jsapi.cx();
293 mPromiseObj = JS::NewPromiseObject(cx, nullptr);
294 if (!mPromiseObj) {
295 JS_ClearPendingException(cx);
296 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
297 return;
299 if (aPropagateUserInteraction == ePropagateUserInteraction) {
300 Unused << MaybePropagateUserInputEventHandling();
304 void Promise::MaybeResolve(JSContext* aCx, JS::Handle<JS::Value> aValue) {
305 NS_ASSERT_OWNINGTHREAD(Promise);
307 JS::Rooted<JSObject*> p(aCx, PromiseObj());
308 if (!p || !JS::ResolvePromise(aCx, p, aValue)) {
309 // Now what? There's nothing sane to do here.
310 JS_ClearPendingException(aCx);
314 void Promise::MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue) {
315 NS_ASSERT_OWNINGTHREAD(Promise);
317 JS::Rooted<JSObject*> p(aCx, PromiseObj());
318 if (!p || !JS::RejectPromise(aCx, p, aValue)) {
319 // Now what? There's nothing sane to do here.
320 JS_ClearPendingException(aCx);
324 #define SLOT_NATIVEHANDLER 0
325 #define SLOT_NATIVEHANDLER_TASK 1
327 enum class NativeHandlerTask : int32_t { Resolve, Reject };
329 MOZ_CAN_RUN_SCRIPT
330 static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
331 JS::Value* aVp) {
332 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
334 JS::Value v =
335 js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER);
336 MOZ_ASSERT(v.isObject());
338 JS::Rooted<JSObject*> obj(aCx, &v.toObject());
339 PromiseNativeHandler* handler = nullptr;
340 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
341 return Throw(aCx, NS_ERROR_UNEXPECTED);
344 v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
345 NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
347 ErrorResult rv;
348 if (task == NativeHandlerTask::Resolve) {
349 // handler is kept alive by "obj" on the stack.
350 MOZ_KnownLive(handler)->ResolvedCallback(aCx, args.get(0), rv);
351 } else {
352 MOZ_ASSERT(task == NativeHandlerTask::Reject);
353 // handler is kept alive by "obj" on the stack.
354 MOZ_KnownLive(handler)->RejectedCallback(aCx, args.get(0), rv);
357 return !rv.MaybeSetPendingException(aCx);
360 static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
361 JS::Handle<JSObject*> aHolder,
362 NativeHandlerTask aTask) {
363 JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
364 /* nargs = */ 1,
365 /* flags = */ 0, nullptr);
366 if (!func) {
367 return nullptr;
370 JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
372 JS::AssertObjectIsNotGray(aHolder);
373 js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
374 JS::ObjectValue(*aHolder));
375 js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
376 JS::Int32Value(static_cast<int32_t>(aTask)));
378 return obj;
381 namespace {
383 class PromiseNativeHandlerShim final : public PromiseNativeHandler {
384 RefPtr<PromiseNativeHandler> mInner;
385 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
386 enum InnerState{
387 NotCleared,
388 ClearedFromResolve,
389 ClearedFromReject,
390 ClearedFromCC,
392 InnerState mState = NotCleared;
393 #endif
395 ~PromiseNativeHandlerShim() = default;
397 public:
398 explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
399 : mInner(aInner) {
400 MOZ_DIAGNOSTIC_ASSERT(mInner);
403 MOZ_CAN_RUN_SCRIPT
404 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
405 ErrorResult& aRv) override {
406 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
407 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromResolve);
408 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromReject);
409 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromCC);
410 #else
411 if (!mInner) {
412 return;
414 #endif
415 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
416 inner->ResolvedCallback(aCx, aValue, aRv);
417 MOZ_ASSERT(!mInner);
418 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
419 mState = ClearedFromResolve;
420 #endif
423 MOZ_CAN_RUN_SCRIPT
424 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
425 ErrorResult& aRv) override {
426 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
427 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromResolve);
428 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromReject);
429 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromCC);
430 #else
431 if (!mInner) {
432 return;
434 #endif
435 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
436 inner->RejectedCallback(aCx, aValue, aRv);
437 MOZ_ASSERT(!mInner);
438 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
439 mState = ClearedFromReject;
440 #endif
443 bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
444 JS::MutableHandle<JSObject*> aWrapper) {
445 return PromiseNativeHandler_Binding::Wrap(aCx, this, aGivenProto, aWrapper);
448 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
449 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
452 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
453 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeHandlerShim)
454 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner)
455 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
456 tmp->mState = ClearedFromCC;
457 #endif
458 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeHandlerShim)
460 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
461 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
463 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim)
464 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim)
466 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim)
467 NS_INTERFACE_MAP_ENTRY(nsISupports)
468 NS_INTERFACE_MAP_END
470 } // anonymous namespace
472 void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
473 NS_ASSERT_OWNINGTHREAD(Promise);
475 AutoJSAPI jsapi;
476 if (NS_WARN_IF(!mPromiseObj || !jsapi.Init(mGlobal))) {
477 // Our API doesn't allow us to return a useful error. Not like this should
478 // happen anyway.
479 return;
482 // The self-hosted promise js may keep the object we pass to it alive
483 // for quite a while depending on when GC runs. Therefore, pass a shim
484 // object instead. The shim will free its inner PromiseNativeHandler
485 // after the promise has settled just like our previous c++ promises did.
486 RefPtr<PromiseNativeHandlerShim> shim =
487 new PromiseNativeHandlerShim(aRunnable);
489 JSContext* cx = jsapi.cx();
490 JS::Rooted<JSObject*> handlerWrapper(cx);
491 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
492 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
493 if (NS_WARN_IF(!shim->WrapObject(cx, nullptr, &handlerWrapper))) {
494 // Again, no way to report errors.
495 jsapi.ClearException();
496 return;
499 JS::Rooted<JSObject*> resolveFunc(cx);
500 resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
501 NativeHandlerTask::Resolve);
502 if (NS_WARN_IF(!resolveFunc)) {
503 jsapi.ClearException();
504 return;
507 JS::Rooted<JSObject*> rejectFunc(cx);
508 rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
509 NativeHandlerTask::Reject);
510 if (NS_WARN_IF(!rejectFunc)) {
511 jsapi.ClearException();
512 return;
515 JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
516 if (NS_WARN_IF(
517 !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
518 jsapi.ClearException();
519 return;
523 void Promise::HandleException(JSContext* aCx) {
524 JS::Rooted<JS::Value> exn(aCx);
525 if (JS_GetPendingException(aCx, &exn)) {
526 JS_ClearPendingException(aCx);
527 // Always reject even if this was called in *Resolve.
528 MaybeReject(aCx, exn);
532 // static
533 already_AddRefed<Promise> Promise::RejectWithExceptionFromContext(
534 nsIGlobalObject* aGlobal, JSContext* aCx, ErrorResult& aError) {
535 JS::Rooted<JS::Value> exn(aCx);
536 if (!JS_GetPendingException(aCx, &exn)) {
537 // This is very important: if there is no pending exception here but we're
538 // ending up in this code, that means the callee threw an uncatchable
539 // exception. Just propagate that out as-is.
540 aError.ThrowUncatchableException();
541 return nullptr;
544 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
545 if (!JS_WrapValue(aCx, &exn)) {
546 // We just give up.
547 aError.StealExceptionFromJSContext(aCx);
548 return nullptr;
551 JS_ClearPendingException(aCx);
553 IgnoredErrorResult error;
554 RefPtr<Promise> promise = Promise::Reject(aGlobal, aCx, exn, error);
555 if (!promise) {
556 // We just give up, let's store the exception in the ErrorResult.
557 aError.ThrowJSException(aCx, exn);
558 return nullptr;
561 return promise.forget();
564 // static
565 already_AddRefed<Promise> Promise::CreateFromExisting(
566 nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
567 PropagateUserInteraction aPropagateUserInteraction) {
568 MOZ_ASSERT(JS::GetCompartment(aGlobal->GetGlobalJSObjectPreserveColor()) ==
569 JS::GetCompartment(aPromiseObj));
570 RefPtr<Promise> p = new Promise(aGlobal);
571 p->mPromiseObj = aPromiseObj;
572 if (aPropagateUserInteraction == ePropagateUserInteraction &&
573 !p->MaybePropagateUserInputEventHandling()) {
574 return nullptr;
576 return p.forget();
579 void Promise::MaybeResolveWithUndefined() {
580 NS_ASSERT_OWNINGTHREAD(Promise);
582 MaybeResolve(JS::UndefinedHandleValue);
585 void Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
586 NS_ASSERT_OWNINGTHREAD(Promise);
588 MaybeSomething(aArg, &Promise::MaybeReject);
591 void Promise::MaybeRejectWithUndefined() {
592 NS_ASSERT_OWNINGTHREAD(Promise);
594 MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
597 void Promise::ReportRejectedPromise(JSContext* aCx,
598 JS::Handle<JSObject*> aPromise) {
599 MOZ_ASSERT(!js::IsWrapper(aPromise));
601 MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
603 bool isChrome = false;
604 uint64_t innerWindowID = 0;
605 nsGlobalWindowInner* winForDispatch = nullptr;
606 if (MOZ_LIKELY(NS_IsMainThread())) {
607 isChrome = nsContentUtils::ObjectPrincipal(aPromise)->IsSystemPrincipal();
609 if (nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aPromise)) {
610 winForDispatch = win;
611 innerWindowID = win->WindowID();
612 } else if (nsGlobalWindowInner* win = xpc::SandboxWindowOrNull(
613 JS::GetNonCCWObjectGlobal(aPromise), aCx)) {
614 // Don't dispatch rejections from the sandbox to the associated DOM
615 // window.
616 innerWindowID = win->WindowID();
618 } else if (const WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) {
619 isChrome = wp->UsesSystemPrincipal();
620 innerWindowID = wp->WindowID();
621 } else if (nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(aPromise)) {
622 if (nsCOMPtr<WorkletGlobalScope> workletGlobal =
623 do_QueryInterface(global)) {
624 WorkletImpl* impl = workletGlobal->Impl();
625 isChrome = impl->PrincipalInfo().type() ==
626 mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo;
627 innerWindowID = impl->LoadInfo().InnerWindowID();
631 JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
632 // resolutionSite can be null if async stacks are disabled.
633 JS::Rooted<JSObject*> resolutionSite(aCx,
634 JS::GetPromiseResolutionSite(aPromise));
636 // We're inspecting the rejection value only to report it to the console, and
637 // we do so without side-effects, so we can safely unwrap it without regard to
638 // the privileges of the Promise object that holds it. If we don't unwrap
639 // before trying to create the error report, we wind up reporting any
640 // cross-origin objects as "uncaught exception: Object".
641 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
643 Maybe<JSAutoRealm> ar;
644 JS::Rooted<JS::Value> unwrapped(aCx, result);
645 if (unwrapped.isObject()) {
646 unwrapped.setObject(*js::UncheckedUnwrap(&unwrapped.toObject()));
647 ar.emplace(aCx, &unwrapped.toObject());
650 JS::ErrorReportBuilder report(aCx);
651 RefPtr<Exception> exn;
652 if (unwrapped.isObject() &&
653 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &unwrapped, exn)) ||
654 NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &unwrapped, exn)))) {
655 xpcReport->Init(aCx, exn, isChrome, innerWindowID);
656 } else {
657 // Use the resolution site as the exception stack
658 JS::ExceptionStack exnStack(aCx, unwrapped, resolutionSite);
659 if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
660 JS_ClearPendingException(aCx);
661 return;
664 xpcReport->Init(report.report(), report.toStringResult().c_str(),
665 isChrome, innerWindowID);
669 // Used to initialize the similarly named nsISciptError attribute.
670 xpcReport->mIsPromiseRejection = true;
672 // Now post an event to do the real reporting async
673 RefPtr<AsyncErrorReporter> event = new AsyncErrorReporter(xpcReport);
674 if (winForDispatch) {
675 if (!winForDispatch->IsDying()) {
676 // Exceptions from a dying window will cause the window to leak.
677 event->SetException(aCx, result);
678 if (resolutionSite) {
679 event->SerializeStack(aCx, resolutionSite);
682 winForDispatch->Dispatch(event.forget());
683 } else {
684 NS_DispatchToMainThread(event);
688 void Promise::MaybeResolveWithClone(JSContext* aCx,
689 JS::Handle<JS::Value> aValue) {
690 JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
691 AutoEntryScript aes(GetParentObject(), "Promise resolution");
692 JSContext* cx = aes.cx();
693 JS::Rooted<JS::Value> value(cx, aValue);
695 xpc::StackScopedCloneOptions options;
696 options.wrapReflectors = true;
697 if (!StackScopedClone(cx, options, sourceScope, &value)) {
698 HandleException(cx);
699 return;
701 MaybeResolve(aCx, value);
704 void Promise::MaybeRejectWithClone(JSContext* aCx,
705 JS::Handle<JS::Value> aValue) {
706 JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
707 AutoEntryScript aes(GetParentObject(), "Promise rejection");
708 JSContext* cx = aes.cx();
709 JS::Rooted<JS::Value> value(cx, aValue);
711 xpc::StackScopedCloneOptions options;
712 options.wrapReflectors = true;
713 if (!StackScopedClone(cx, options, sourceScope, &value)) {
714 HandleException(cx);
715 return;
717 MaybeReject(aCx, value);
720 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
721 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
722 class PromiseWorkerProxyRunnable final : public WorkerRunnable {
723 public:
724 PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
725 PromiseWorkerProxy::RunCallbackFunc aFunc)
726 : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
727 "PromiseWorkerProxyRunnable", WorkerThread),
728 mPromiseWorkerProxy(aPromiseWorkerProxy),
729 mFunc(aFunc) {
730 MOZ_ASSERT(NS_IsMainThread());
731 MOZ_ASSERT(mPromiseWorkerProxy);
734 virtual bool WorkerRun(JSContext* aCx,
735 WorkerPrivate* aWorkerPrivate) override {
736 MOZ_ASSERT(aWorkerPrivate);
737 aWorkerPrivate->AssertIsOnWorkerThread();
738 MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
740 MOZ_ASSERT(mPromiseWorkerProxy);
741 RefPtr<Promise> workerPromise = mPromiseWorkerProxy->GetWorkerPromise();
742 // Once Worker had already started shutdown, workerPromise would be nullptr
743 if (!workerPromise) {
744 return true;
747 // Here we convert the buffer to a JS::Value.
748 JS::Rooted<JS::Value> value(aCx);
749 if (!mPromiseWorkerProxy->Read(aCx, &value)) {
750 JS_ClearPendingException(aCx);
751 return false;
754 (workerPromise->*mFunc)(aCx, value);
756 // Release the Promise because it has been resolved/rejected for sure.
757 mPromiseWorkerProxy->CleanUp();
758 return true;
761 protected:
762 ~PromiseWorkerProxyRunnable() = default;
764 private:
765 RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
767 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
768 PromiseWorkerProxy::RunCallbackFunc mFunc;
771 /* static */
772 already_AddRefed<PromiseWorkerProxy> PromiseWorkerProxy::Create(
773 WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise,
774 const PromiseWorkerProxyStructuredCloneCallbacks* aCb) {
775 MOZ_ASSERT(aWorkerPrivate);
776 aWorkerPrivate->AssertIsOnWorkerThread();
777 MOZ_ASSERT(aWorkerPromise);
778 MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
780 RefPtr<PromiseWorkerProxy> proxy =
781 new PromiseWorkerProxy(aWorkerPromise, aCb);
783 // Maintain a reference so that we have a valid object to clean up when
784 // removing the feature.
785 proxy.get()->AddRef();
787 // We do this to make sure the worker thread won't shut down before the
788 // promise is resolved/rejected on the worker thread.
789 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
790 aWorkerPrivate, "PromiseWorkerProxy", [proxy]() { proxy->CleanUp(); });
792 if (NS_WARN_IF(!workerRef)) {
793 // Probably the worker is terminating. We cannot complete the operation
794 // and we have to release all the resources. CleanUp releases the extra
795 // ref, too
796 proxy->CleanUp();
797 return nullptr;
800 proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
802 return proxy.forget();
805 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
807 PromiseWorkerProxy::PromiseWorkerProxy(
808 Promise* aWorkerPromise,
809 const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
810 : mWorkerPromise(aWorkerPromise),
811 mCleanedUp(false),
812 mCallbacks(aCallbacks),
813 mCleanUpLock("cleanUpLock") {}
815 PromiseWorkerProxy::~PromiseWorkerProxy() {
816 MOZ_ASSERT(mCleanedUp);
817 MOZ_ASSERT(!mWorkerPromise);
818 MOZ_ASSERT(!mWorkerRef);
821 WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
822 #ifdef DEBUG
823 if (NS_IsMainThread()) {
824 mCleanUpLock.AssertCurrentThreadOwns();
826 #endif
827 // Safe to check this without a lock since we assert lock ownership on the
828 // main thread above.
829 MOZ_ASSERT(!mCleanedUp);
830 MOZ_ASSERT(mWorkerRef);
832 return mWorkerRef->Private();
835 bool PromiseWorkerProxy::OnWritingThread() const {
836 return IsCurrentThreadRunningWorker();
839 Promise* PromiseWorkerProxy::GetWorkerPromise() const {
840 MOZ_ASSERT(IsCurrentThreadRunningWorker());
841 return mWorkerPromise;
844 void PromiseWorkerProxy::RunCallback(JSContext* aCx,
845 JS::Handle<JS::Value> aValue,
846 RunCallbackFunc aFunc) {
847 MOZ_ASSERT(NS_IsMainThread());
849 MutexAutoLock lock(Lock());
850 // If the worker thread's been cancelled we don't need to resolve the Promise.
851 if (CleanedUp()) {
852 return;
855 // The |aValue| is written into the StructuredCloneHolderBase.
856 if (!Write(aCx, aValue)) {
857 JS_ClearPendingException(aCx);
858 MOZ_ASSERT(false,
859 "cannot serialize the value with the StructuredCloneAlgorithm!");
862 RefPtr<PromiseWorkerProxyRunnable> runnable =
863 new PromiseWorkerProxyRunnable(this, aFunc);
865 runnable->Dispatch();
868 void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
869 JS::Handle<JS::Value> aValue,
870 ErrorResult& aRv) {
871 RunCallback(aCx, aValue, &Promise::MaybeResolve);
874 void PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
875 JS::Handle<JS::Value> aValue,
876 ErrorResult& aRv) {
877 RunCallback(aCx, aValue, &Promise::MaybeReject);
880 void PromiseWorkerProxy::CleanUp() {
881 // Can't release Mutex while it is still locked, so scope the lock.
883 MutexAutoLock lock(Lock());
885 if (CleanedUp()) {
886 return;
889 // We can be called if we failed to get a WorkerRef
890 if (mWorkerRef) {
891 mWorkerRef->Private()->AssertIsOnWorkerThread();
894 // Release the Promise and remove the PromiseWorkerProxy from the holders of
895 // the worker thread since the Promise has been resolved/rejected or the
896 // worker thread has been cancelled.
897 mCleanedUp = true;
898 mWorkerPromise = nullptr;
899 mWorkerRef = nullptr;
901 // Clear the StructuredCloneHolderBase class.
902 Clear();
904 Release();
907 JSObject* PromiseWorkerProxy::CustomReadHandler(
908 JSContext* aCx, JSStructuredCloneReader* aReader,
909 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
910 uint32_t aIndex) {
911 if (NS_WARN_IF(!mCallbacks)) {
912 return nullptr;
915 return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
918 bool PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
919 JSStructuredCloneWriter* aWriter,
920 JS::Handle<JSObject*> aObj,
921 bool* aSameProcessScopeRequired) {
922 if (NS_WARN_IF(!mCallbacks)) {
923 return false;
926 return mCallbacks->Write(aCx, aWriter, this, aObj);
929 // Specializations of MaybeRejectBrokenly we actually support.
930 template <>
931 void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
932 MaybeSomething(aArg, &Promise::MaybeReject);
934 template <>
935 void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
936 MaybeSomething(aArg, &Promise::MaybeReject);
939 Promise::PromiseState Promise::State() const {
940 JS::Rooted<JSObject*> p(RootingCx(), PromiseObj());
941 const JS::PromiseState state = JS::GetPromiseState(p);
943 if (state == JS::PromiseState::Fulfilled) {
944 return PromiseState::Resolved;
947 if (state == JS::PromiseState::Rejected) {
948 return PromiseState::Rejected;
951 return PromiseState::Pending;
954 bool Promise::SetSettledPromiseIsHandled() {
955 if (!mPromiseObj) {
956 // Do nothing as it's a no-op promise
957 return false;
959 AutoAllowLegacyScriptExecution exemption;
960 AutoEntryScript aes(mGlobal, "Set settled promise handled");
961 JSContext* cx = aes.cx();
962 JS::Rooted<JSObject*> promiseObj(cx, mPromiseObj);
963 return JS::SetSettledPromiseIsHandled(cx, promiseObj);
966 bool Promise::SetAnyPromiseIsHandled() {
967 if (!mPromiseObj) {
968 // Do nothing as it's a no-op promise
969 return false;
971 AutoAllowLegacyScriptExecution exemption;
972 AutoEntryScript aes(mGlobal, "Set any promise handled");
973 JSContext* cx = aes.cx();
974 JS::Rooted<JSObject*> promiseObj(cx, mPromiseObj);
975 return JS::SetAnyPromiseIsHandled(cx, promiseObj);
978 /* static */
979 already_AddRefed<Promise> Promise::CreateResolvedWithUndefined(
980 nsIGlobalObject* global, ErrorResult& aRv) {
981 RefPtr<Promise> returnPromise = Promise::Create(global, aRv);
982 if (aRv.Failed()) {
983 return nullptr;
985 returnPromise->MaybeResolveWithUndefined();
986 return returnPromise.forget();
989 already_AddRefed<Promise> Promise::CreateRejected(
990 nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aRejectionError,
991 ErrorResult& aRv) {
992 RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
993 if (aRv.Failed()) {
994 return nullptr;
996 promise->MaybeReject(aRejectionError);
997 return promise.forget();
1000 already_AddRefed<Promise> Promise::CreateRejectedWithTypeError(
1001 nsIGlobalObject* aGlobal, const nsACString& aMessage, ErrorResult& aRv) {
1002 RefPtr<Promise> returnPromise = Promise::Create(aGlobal, aRv);
1003 if (aRv.Failed()) {
1004 return nullptr;
1006 returnPromise->MaybeRejectWithTypeError(aMessage);
1007 return returnPromise.forget();
1010 already_AddRefed<Promise> Promise::CreateRejectedWithErrorResult(
1011 nsIGlobalObject* aGlobal, ErrorResult& aRejectionError) {
1012 RefPtr<Promise> returnPromise = Promise::Create(aGlobal, IgnoreErrors());
1013 if (!returnPromise) {
1014 return nullptr;
1016 returnPromise->MaybeReject(std::move(aRejectionError));
1017 return returnPromise.forget();
1020 nsresult Promise::TryExtractNSResultFromRejectionValue(
1021 JS::Handle<JS::Value> aValue) {
1022 if (aValue.isInt32()) {
1023 return nsresult(aValue.toInt32());
1026 if (aValue.isObject()) {
1027 RefPtr<DOMException> domException;
1028 UNWRAP_OBJECT(DOMException, aValue, domException);
1029 if (domException) {
1030 return domException->GetResult();
1034 return NS_ERROR_DOM_NOT_NUMBER_ERR;
1037 } // namespace mozilla::dom
1039 extern "C" {
1041 // These functions are used in the implementation of ffi bindings for
1042 // dom::Promise from Rust.
1044 void DomPromise_AddRef(mozilla::dom::Promise* aPromise) {
1045 MOZ_ASSERT(aPromise);
1046 aPromise->AddRef();
1049 void DomPromise_Release(mozilla::dom::Promise* aPromise) {
1050 MOZ_ASSERT(aPromise);
1051 aPromise->Release();
1054 #define DOM_PROMISE_FUNC_WITH_VARIANT(name, func) \
1055 void name(mozilla::dom::Promise* aPromise, nsIVariant* aVariant) { \
1056 MOZ_ASSERT(aPromise); \
1057 MOZ_ASSERT(aVariant); \
1058 mozilla::dom::AutoEntryScript aes(aPromise->GetGlobalObject(), \
1059 "Promise resolution or rejection"); \
1060 JSContext* cx = aes.cx(); \
1062 JS::Rooted<JS::Value> val(cx); \
1063 nsresult rv = NS_OK; \
1064 if (!XPCVariant::VariantDataToJS(cx, aVariant, &rv, &val)) { \
1065 aPromise->MaybeRejectWithTypeError( \
1066 "Failed to convert nsIVariant to JS"); \
1067 return; \
1069 aPromise->func(val); \
1072 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_RejectWithVariant, MaybeReject)
1073 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_ResolveWithVariant, MaybeResolve)
1075 #undef DOM_PROMISE_FUNC_WITH_VARIANT