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"
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"
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
{
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) {
82 mozilla::HoldJSObjects(this);
85 Promise::~Promise() { mozilla::DropJSObjects(this); }
88 already_AddRefed
<Promise
> Promise::Create(
89 nsIGlobalObject
* aGlobal
, ErrorResult
& aRv
,
90 PropagateUserInteraction aPropagateUserInteraction
) {
92 aRv
.Throw(NS_ERROR_UNEXPECTED
);
95 RefPtr
<Promise
> p
= new Promise(aGlobal
);
96 p
->CreateWrapper(aRv
, aPropagateUserInteraction
);
104 already_AddRefed
<Promise
> Promise::CreateInfallible(
105 nsIGlobalObject
* aGlobal
,
106 PropagateUserInteraction aPropagateUserInteraction
) {
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
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());
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
);
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
));
145 aRv
.NoteJSContextException(aCx
);
149 return CreateFromExisting(aGlobal
, p
, aPropagateUserInteraction
);
153 already_AddRefed
<Promise
> Promise::Reject(nsIGlobalObject
* aGlobal
,
155 JS::Handle
<JS::Value
> aValue
,
157 JSAutoRealm
ar(aCx
, aGlobal
->GetGlobalJSObject());
158 JS::Rooted
<JSObject
*> p(aCx
, JS::CallOriginalPromiseReject(aCx
, aValue
));
160 aRv
.NoteJSContextException(aCx
);
164 // This promise will never be resolved, so we pass
165 // eDontPropagateUserInteraction for aPropagateUserInteraction
167 return CreateFromExisting(aGlobal
, p
, eDontPropagateUserInteraction
);
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
));
176 aRv
.Throw(NS_ERROR_UNEXPECTED
);
180 nsCOMPtr
<nsIGlobalObject
> global
= xpc::NativeGlobal(globalObj
);
182 aRv
.Throw(NS_ERROR_UNEXPECTED
);
186 JS::RootedVector
<JSObject
*> promises(aCx
);
187 if (!promises
.reserve(aPromiseList
.Length())) {
188 aRv
.NoteJSContextException(aCx
);
192 for (const auto& promise
: aPromiseList
) {
193 JS::Rooted
<JSObject
*> promiseObj(aCx
, promise
->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
);
204 promises
.infallibleAppend(promiseObj
);
207 JS::Rooted
<JSObject
*> result(aCx
, JS::GetWaitForAllPromise(aCx
, promises
));
209 aRv
.NoteJSContextException(aCx
);
213 return CreateFromExisting(global
, result
, aPropagateUserInteraction
);
216 void Promise::Then(JSContext
* aCx
,
217 // aCalleeGlobal may not be in the compartment of aCx, when
218 // called over Xrays.
219 JS::Handle
<JSObject
*> aCalleeGlobal
,
220 AnyCallback
* aResolveCallback
, AnyCallback
* aRejectCallback
,
221 JS::MutableHandle
<JS::Value
> aRetval
, ErrorResult
& aRv
) {
222 NS_ASSERT_OWNINGTHREAD(Promise
);
224 // Let's hope this does the right thing with Xrays... Ensure everything is
225 // just in the caller compartment; that ought to do the trick. In theory we
226 // should consider aCalleeGlobal, but in practice our only caller is
227 // DOMRequest::Then, which is not working with a Promise subclass, so things
229 JS::Rooted
<JSObject
*> promise(aCx
, PromiseObj());
231 // This promise is no-op, so do nothing.
235 if (!JS_WrapObject(aCx
, &promise
)) {
236 aRv
.NoteJSContextException(aCx
);
240 JS::Rooted
<JSObject
*> resolveCallback(aCx
);
241 if (aResolveCallback
) {
242 resolveCallback
= aResolveCallback
->CallbackOrNull();
243 if (!JS_WrapObject(aCx
, &resolveCallback
)) {
244 aRv
.NoteJSContextException(aCx
);
249 JS::Rooted
<JSObject
*> rejectCallback(aCx
);
250 if (aRejectCallback
) {
251 rejectCallback
= aRejectCallback
->CallbackOrNull();
252 if (!JS_WrapObject(aCx
, &rejectCallback
)) {
253 aRv
.NoteJSContextException(aCx
);
258 JS::Rooted
<JSObject
*> retval(aCx
);
259 retval
= JS::CallOriginalPromiseThen(aCx
, promise
, resolveCallback
,
262 aRv
.NoteJSContextException(aCx
);
266 aRetval
.setObject(*retval
);
269 static void SettlePromise(Promise
* aSettlingPromise
, Promise
* aCallbackPromise
,
271 if (!aSettlingPromise
) {
274 if (aRv
.IsUncatchableException()) {
278 aSettlingPromise
->MaybeReject(std::move(aRv
));
281 if (aCallbackPromise
) {
282 aSettlingPromise
->MaybeResolve(aCallbackPromise
);
284 aSettlingPromise
->MaybeResolveWithUndefined();
288 void PromiseNativeThenHandlerBase::ResolvedCallback(
289 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
) {
290 if (!HasResolvedCallback()) {
291 mPromise
->MaybeResolve(aValue
);
294 RefPtr
<Promise
> promise
= CallResolveCallback(aCx
, aValue
, aRv
);
295 SettlePromise(mPromise
, promise
, aRv
);
298 void PromiseNativeThenHandlerBase::RejectedCallback(
299 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
) {
300 if (!HasRejectedCallback()) {
301 mPromise
->MaybeReject(aValue
);
304 RefPtr
<Promise
> promise
= CallRejectCallback(aCx
, aValue
, aRv
);
305 SettlePromise(mPromise
, promise
, aRv
);
308 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase
)
310 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase
)
311 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise
)
313 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
315 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase
)
316 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise
)
318 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
320 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase
)
321 NS_INTERFACE_MAP_ENTRY(nsISupports
)
324 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PromiseNativeThenHandlerBase
)
325 tmp
->Trace(aCallbacks
, aClosure
);
326 NS_IMPL_CYCLE_COLLECTION_TRACE_END
328 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase
)
329 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase
)
331 Result
<RefPtr
<Promise
>, nsresult
> Promise::ThenWithoutCycleCollection(
332 const std::function
<already_AddRefed
<Promise
>(
333 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
)>&
335 return ThenWithCycleCollectedArgs(aCallback
);
338 void Promise::CreateWrapper(
339 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
341 if (!jsapi
.Init(mGlobal
)) {
342 aRv
.Throw(NS_ERROR_UNEXPECTED
);
345 JSContext
* cx
= jsapi
.cx();
346 mPromiseObj
= JS::NewPromiseObject(cx
, nullptr);
348 JS_ClearPendingException(cx
);
349 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
352 if (aPropagateUserInteraction
== ePropagateUserInteraction
) {
353 Unused
<< MaybePropagateUserInputEventHandling();
357 void Promise::MaybeResolve(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
358 NS_ASSERT_OWNINGTHREAD(Promise
);
360 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
361 if (!p
|| !JS::ResolvePromise(aCx
, p
, aValue
)) {
362 // Now what? There's nothing sane to do here.
363 JS_ClearPendingException(aCx
);
367 void Promise::MaybeReject(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
368 NS_ASSERT_OWNINGTHREAD(Promise
);
370 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
371 if (!p
|| !JS::RejectPromise(aCx
, p
, aValue
)) {
372 // Now what? There's nothing sane to do here.
373 JS_ClearPendingException(aCx
);
377 #define SLOT_NATIVEHANDLER 0
378 #define SLOT_NATIVEHANDLER_TASK 1
380 enum class NativeHandlerTask
: int32_t { Resolve
, Reject
};
383 static bool NativeHandlerCallback(JSContext
* aCx
, unsigned aArgc
,
385 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
388 js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER
);
389 MOZ_ASSERT(v
.isObject());
391 JS::Rooted
<JSObject
*> obj(aCx
, &v
.toObject());
392 PromiseNativeHandler
* handler
= nullptr;
393 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler
, &obj
, handler
))) {
394 return Throw(aCx
, NS_ERROR_UNEXPECTED
);
397 v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER_TASK
);
398 NativeHandlerTask task
= static_cast<NativeHandlerTask
>(v
.toInt32());
401 if (task
== NativeHandlerTask::Resolve
) {
402 // handler is kept alive by "obj" on the stack.
403 MOZ_KnownLive(handler
)->ResolvedCallback(aCx
, args
.get(0), rv
);
405 MOZ_ASSERT(task
== NativeHandlerTask::Reject
);
406 // handler is kept alive by "obj" on the stack.
407 MOZ_KnownLive(handler
)->RejectedCallback(aCx
, args
.get(0), rv
);
410 return !rv
.MaybeSetPendingException(aCx
);
413 static JSObject
* CreateNativeHandlerFunction(JSContext
* aCx
,
414 JS::Handle
<JSObject
*> aHolder
,
415 NativeHandlerTask aTask
) {
416 JSFunction
* func
= js::NewFunctionWithReserved(aCx
, NativeHandlerCallback
,
418 /* flags = */ 0, nullptr);
423 JS::Rooted
<JSObject
*> obj(aCx
, JS_GetFunctionObject(func
));
425 JS::AssertObjectIsNotGray(aHolder
);
426 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER
,
427 JS::ObjectValue(*aHolder
));
428 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER_TASK
,
429 JS::Int32Value(static_cast<int32_t>(aTask
)));
436 class PromiseNativeHandlerShim final
: public PromiseNativeHandler
{
437 RefPtr
<PromiseNativeHandler
> mInner
;
438 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
445 InnerState mState
= NotCleared
;
448 ~PromiseNativeHandlerShim() = default;
451 explicit PromiseNativeHandlerShim(PromiseNativeHandler
* aInner
)
453 MOZ_DIAGNOSTIC_ASSERT(mInner
);
457 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
458 ErrorResult
& aRv
) override
{
459 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
460 MOZ_DIAGNOSTIC_ASSERT(mState
!= ClearedFromResolve
);
461 MOZ_DIAGNOSTIC_ASSERT(mState
!= ClearedFromReject
);
462 MOZ_DIAGNOSTIC_ASSERT(mState
!= ClearedFromCC
);
468 RefPtr
<PromiseNativeHandler
> inner
= std::move(mInner
);
469 inner
->ResolvedCallback(aCx
, aValue
, aRv
);
471 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
472 mState
= ClearedFromResolve
;
477 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
478 ErrorResult
& aRv
) override
{
479 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
480 MOZ_DIAGNOSTIC_ASSERT(mState
!= ClearedFromResolve
);
481 MOZ_DIAGNOSTIC_ASSERT(mState
!= ClearedFromReject
);
482 MOZ_DIAGNOSTIC_ASSERT(mState
!= ClearedFromCC
);
488 RefPtr
<PromiseNativeHandler
> inner
= std::move(mInner
);
489 inner
->RejectedCallback(aCx
, aValue
, aRv
);
491 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
492 mState
= ClearedFromReject
;
496 bool WrapObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
,
497 JS::MutableHandle
<JSObject
*> aWrapper
) {
498 return PromiseNativeHandler_Binding::Wrap(aCx
, this, aGivenProto
, aWrapper
);
501 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
502 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim
)
505 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim
)
506 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeHandlerShim
)
507 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner
)
508 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
509 tmp
->mState
= ClearedFromCC
;
511 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeHandlerShim
)
513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner
)
514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
516 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim
)
517 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim
)
519 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim
)
520 NS_INTERFACE_MAP_ENTRY(nsISupports
)
523 } // anonymous namespace
525 void Promise::AppendNativeHandler(PromiseNativeHandler
* aRunnable
) {
526 NS_ASSERT_OWNINGTHREAD(Promise
);
529 if (NS_WARN_IF(!mPromiseObj
|| !jsapi
.Init(mGlobal
))) {
530 // Our API doesn't allow us to return a useful error. Not like this should
535 // The self-hosted promise js may keep the object we pass to it alive
536 // for quite a while depending on when GC runs. Therefore, pass a shim
537 // object instead. The shim will free its inner PromiseNativeHandler
538 // after the promise has settled just like our previous c++ promises did.
539 RefPtr
<PromiseNativeHandlerShim
> shim
=
540 new PromiseNativeHandlerShim(aRunnable
);
542 JSContext
* cx
= jsapi
.cx();
543 JS::Rooted
<JSObject
*> handlerWrapper(cx
);
544 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
545 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
546 if (NS_WARN_IF(!shim
->WrapObject(cx
, nullptr, &handlerWrapper
))) {
547 // Again, no way to report errors.
548 jsapi
.ClearException();
552 JS::Rooted
<JSObject
*> resolveFunc(cx
);
553 resolveFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
554 NativeHandlerTask::Resolve
);
555 if (NS_WARN_IF(!resolveFunc
)) {
556 jsapi
.ClearException();
560 JS::Rooted
<JSObject
*> rejectFunc(cx
);
561 rejectFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
562 NativeHandlerTask::Reject
);
563 if (NS_WARN_IF(!rejectFunc
)) {
564 jsapi
.ClearException();
568 JS::Rooted
<JSObject
*> promiseObj(cx
, PromiseObj());
570 !JS::AddPromiseReactions(cx
, promiseObj
, resolveFunc
, rejectFunc
))) {
571 jsapi
.ClearException();
576 void Promise::HandleException(JSContext
* aCx
) {
577 JS::Rooted
<JS::Value
> exn(aCx
);
578 if (JS_GetPendingException(aCx
, &exn
)) {
579 JS_ClearPendingException(aCx
);
580 // Always reject even if this was called in *Resolve.
581 MaybeReject(aCx
, exn
);
586 already_AddRefed
<Promise
> Promise::RejectWithExceptionFromContext(
587 nsIGlobalObject
* aGlobal
, JSContext
* aCx
, ErrorResult
& aError
) {
588 JS::Rooted
<JS::Value
> exn(aCx
);
589 if (!JS_GetPendingException(aCx
, &exn
)) {
590 // This is very important: if there is no pending exception here but we're
591 // ending up in this code, that means the callee threw an uncatchable
592 // exception. Just propagate that out as-is.
593 aError
.ThrowUncatchableException();
597 JSAutoRealm
ar(aCx
, aGlobal
->GetGlobalJSObject());
598 if (!JS_WrapValue(aCx
, &exn
)) {
600 aError
.StealExceptionFromJSContext(aCx
);
604 JS_ClearPendingException(aCx
);
606 IgnoredErrorResult error
;
607 RefPtr
<Promise
> promise
= Promise::Reject(aGlobal
, aCx
, exn
, error
);
609 // We just give up, let's store the exception in the ErrorResult.
610 aError
.ThrowJSException(aCx
, exn
);
614 return promise
.forget();
618 already_AddRefed
<Promise
> Promise::CreateFromExisting(
619 nsIGlobalObject
* aGlobal
, JS::Handle
<JSObject
*> aPromiseObj
,
620 PropagateUserInteraction aPropagateUserInteraction
) {
621 MOZ_ASSERT(JS::GetCompartment(aGlobal
->GetGlobalJSObjectPreserveColor()) ==
622 JS::GetCompartment(aPromiseObj
));
623 RefPtr
<Promise
> p
= new Promise(aGlobal
);
624 p
->mPromiseObj
= aPromiseObj
;
625 if (aPropagateUserInteraction
== ePropagateUserInteraction
&&
626 !p
->MaybePropagateUserInputEventHandling()) {
632 void Promise::MaybeResolveWithUndefined() {
633 NS_ASSERT_OWNINGTHREAD(Promise
);
635 MaybeResolve(JS::UndefinedHandleValue
);
638 void Promise::MaybeReject(const RefPtr
<MediaStreamError
>& aArg
) {
639 NS_ASSERT_OWNINGTHREAD(Promise
);
641 MaybeSomething(aArg
, &Promise::MaybeReject
);
644 void Promise::MaybeRejectWithUndefined() {
645 NS_ASSERT_OWNINGTHREAD(Promise
);
647 MaybeSomething(JS::UndefinedHandleValue
, &Promise::MaybeReject
);
650 void Promise::ReportRejectedPromise(JSContext
* aCx
,
651 JS::Handle
<JSObject
*> aPromise
) {
652 MOZ_ASSERT(!js::IsWrapper(aPromise
));
654 MOZ_ASSERT(JS::GetPromiseState(aPromise
) == JS::PromiseState::Rejected
);
656 bool isChrome
= false;
657 uint64_t innerWindowID
= 0;
658 nsGlobalWindowInner
* winForDispatch
= nullptr;
659 if (MOZ_LIKELY(NS_IsMainThread())) {
660 isChrome
= nsContentUtils::ObjectPrincipal(aPromise
)->IsSystemPrincipal();
662 if (nsGlobalWindowInner
* win
= xpc::WindowGlobalOrNull(aPromise
)) {
663 winForDispatch
= win
;
664 innerWindowID
= win
->WindowID();
665 } else if (nsGlobalWindowInner
* win
= xpc::SandboxWindowOrNull(
666 JS::GetNonCCWObjectGlobal(aPromise
), aCx
)) {
667 // Don't dispatch rejections from the sandbox to the associated DOM
669 innerWindowID
= win
->WindowID();
671 } else if (const WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate()) {
672 isChrome
= wp
->UsesSystemPrincipal();
673 innerWindowID
= wp
->WindowID();
674 } else if (nsCOMPtr
<nsIGlobalObject
> global
= xpc::NativeGlobal(aPromise
)) {
675 if (nsCOMPtr
<WorkletGlobalScope
> workletGlobal
=
676 do_QueryInterface(global
)) {
677 WorkletImpl
* impl
= workletGlobal
->Impl();
678 isChrome
= impl
->PrincipalInfo().type() ==
679 mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo
;
680 innerWindowID
= impl
->LoadInfo().InnerWindowID();
684 JS::Rooted
<JS::Value
> result(aCx
, JS::GetPromiseResult(aPromise
));
685 // resolutionSite can be null if async stacks are disabled.
686 JS::Rooted
<JSObject
*> resolutionSite(aCx
,
687 JS::GetPromiseResolutionSite(aPromise
));
689 // We're inspecting the rejection value only to report it to the console, and
690 // we do so without side-effects, so we can safely unwrap it without regard to
691 // the privileges of the Promise object that holds it. If we don't unwrap
692 // before trying to create the error report, we wind up reporting any
693 // cross-origin objects as "uncaught exception: Object".
694 RefPtr
<xpc::ErrorReport
> xpcReport
= new xpc::ErrorReport();
696 Maybe
<JSAutoRealm
> ar
;
697 JS::Rooted
<JS::Value
> unwrapped(aCx
, result
);
698 if (unwrapped
.isObject()) {
699 unwrapped
.setObject(*js::UncheckedUnwrap(&unwrapped
.toObject()));
700 ar
.emplace(aCx
, &unwrapped
.toObject());
703 JS::ErrorReportBuilder
report(aCx
);
704 RefPtr
<Exception
> exn
;
705 if (unwrapped
.isObject() &&
706 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException
, &unwrapped
, exn
)) ||
707 NS_SUCCEEDED(UNWRAP_OBJECT(Exception
, &unwrapped
, exn
)))) {
708 xpcReport
->Init(aCx
, exn
, isChrome
, innerWindowID
);
710 // Use the resolution site as the exception stack
711 JS::ExceptionStack
exnStack(aCx
, unwrapped
, resolutionSite
);
712 if (!report
.init(aCx
, exnStack
, JS::ErrorReportBuilder::NoSideEffects
)) {
713 JS_ClearPendingException(aCx
);
717 xpcReport
->Init(report
.report(), report
.toStringResult().c_str(),
718 isChrome
, innerWindowID
);
722 // Used to initialize the similarly named nsISciptError attribute.
723 xpcReport
->mIsPromiseRejection
= true;
725 // Now post an event to do the real reporting async
726 RefPtr
<AsyncErrorReporter
> event
= new AsyncErrorReporter(xpcReport
);
727 if (winForDispatch
) {
728 if (!winForDispatch
->IsDying()) {
729 // Exceptions from a dying window will cause the window to leak.
730 event
->SetException(aCx
, result
);
731 if (resolutionSite
) {
732 event
->SerializeStack(aCx
, resolutionSite
);
735 winForDispatch
->Dispatch(mozilla::TaskCategory::Other
, event
.forget());
737 NS_DispatchToMainThread(event
);
741 void Promise::MaybeResolveWithClone(JSContext
* aCx
,
742 JS::Handle
<JS::Value
> aValue
) {
743 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
744 AutoEntryScript
aes(GetParentObject(), "Promise resolution");
745 JSContext
* cx
= aes
.cx();
746 JS::Rooted
<JS::Value
> value(cx
, aValue
);
748 xpc::StackScopedCloneOptions options
;
749 options
.wrapReflectors
= true;
750 if (!StackScopedClone(cx
, options
, sourceScope
, &value
)) {
754 MaybeResolve(aCx
, value
);
757 void Promise::MaybeRejectWithClone(JSContext
* aCx
,
758 JS::Handle
<JS::Value
> aValue
) {
759 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
760 AutoEntryScript
aes(GetParentObject(), "Promise rejection");
761 JSContext
* cx
= aes
.cx();
762 JS::Rooted
<JS::Value
> value(cx
, aValue
);
764 xpc::StackScopedCloneOptions options
;
765 options
.wrapReflectors
= true;
766 if (!StackScopedClone(cx
, options
, sourceScope
, &value
)) {
770 MaybeReject(aCx
, value
);
773 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
774 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
775 class PromiseWorkerProxyRunnable
: public WorkerRunnable
{
777 PromiseWorkerProxyRunnable(PromiseWorkerProxy
* aPromiseWorkerProxy
,
778 PromiseWorkerProxy::RunCallbackFunc aFunc
)
779 : WorkerRunnable(aPromiseWorkerProxy
->GetWorkerPrivate(),
780 WorkerThreadUnchangedBusyCount
),
781 mPromiseWorkerProxy(aPromiseWorkerProxy
),
783 MOZ_ASSERT(NS_IsMainThread());
784 MOZ_ASSERT(mPromiseWorkerProxy
);
787 virtual bool WorkerRun(JSContext
* aCx
,
788 WorkerPrivate
* aWorkerPrivate
) override
{
789 MOZ_ASSERT(aWorkerPrivate
);
790 aWorkerPrivate
->AssertIsOnWorkerThread();
791 MOZ_ASSERT(aWorkerPrivate
== mWorkerPrivate
);
793 MOZ_ASSERT(mPromiseWorkerProxy
);
794 RefPtr
<Promise
> workerPromise
= mPromiseWorkerProxy
->WorkerPromise();
796 // Here we convert the buffer to a JS::Value.
797 JS::Rooted
<JS::Value
> value(aCx
);
798 if (!mPromiseWorkerProxy
->Read(aCx
, &value
)) {
799 JS_ClearPendingException(aCx
);
803 (workerPromise
->*mFunc
)(aCx
, value
);
805 // Release the Promise because it has been resolved/rejected for sure.
806 mPromiseWorkerProxy
->CleanUp();
811 ~PromiseWorkerProxyRunnable() = default;
814 RefPtr
<PromiseWorkerProxy
> mPromiseWorkerProxy
;
816 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
817 PromiseWorkerProxy::RunCallbackFunc mFunc
;
821 already_AddRefed
<PromiseWorkerProxy
> PromiseWorkerProxy::Create(
822 WorkerPrivate
* aWorkerPrivate
, Promise
* aWorkerPromise
,
823 const PromiseWorkerProxyStructuredCloneCallbacks
* aCb
) {
824 MOZ_ASSERT(aWorkerPrivate
);
825 aWorkerPrivate
->AssertIsOnWorkerThread();
826 MOZ_ASSERT(aWorkerPromise
);
827 MOZ_ASSERT_IF(aCb
, !!aCb
->Write
&& !!aCb
->Read
);
829 RefPtr
<PromiseWorkerProxy
> proxy
=
830 new PromiseWorkerProxy(aWorkerPromise
, aCb
);
832 // Maintain a reference so that we have a valid object to clean up when
833 // removing the feature.
834 proxy
.get()->AddRef();
836 // We do this to make sure the worker thread won't shut down before the
837 // promise is resolved/rejected on the worker thread.
838 RefPtr
<StrongWorkerRef
> workerRef
= StrongWorkerRef::Create(
839 aWorkerPrivate
, "PromiseWorkerProxy", [proxy
]() { proxy
->CleanUp(); });
841 if (NS_WARN_IF(!workerRef
)) {
842 // Probably the worker is terminating. We cannot complete the operation
843 // and we have to release all the resources. CleanUp releases the extra
849 proxy
->mWorkerRef
= new ThreadSafeWorkerRef(workerRef
);
851 return proxy
.forget();
854 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy
)
856 PromiseWorkerProxy::PromiseWorkerProxy(
857 Promise
* aWorkerPromise
,
858 const PromiseWorkerProxyStructuredCloneCallbacks
* aCallbacks
)
859 : mWorkerPromise(aWorkerPromise
),
861 mCallbacks(aCallbacks
),
862 mCleanUpLock("cleanUpLock") {}
864 PromiseWorkerProxy::~PromiseWorkerProxy() {
865 MOZ_ASSERT(mCleanedUp
);
866 MOZ_ASSERT(!mWorkerPromise
);
867 MOZ_ASSERT(!mWorkerRef
);
870 WorkerPrivate
* PromiseWorkerProxy::GetWorkerPrivate() const {
872 if (NS_IsMainThread()) {
873 mCleanUpLock
.AssertCurrentThreadOwns();
876 // Safe to check this without a lock since we assert lock ownership on the
877 // main thread above.
878 MOZ_ASSERT(!mCleanedUp
);
879 MOZ_ASSERT(mWorkerRef
);
881 return mWorkerRef
->Private();
884 bool PromiseWorkerProxy::OnWritingThread() const {
885 return IsCurrentThreadRunningWorker();
888 Promise
* PromiseWorkerProxy::WorkerPromise() const {
889 MOZ_ASSERT(IsCurrentThreadRunningWorker());
890 MOZ_ASSERT(mWorkerPromise
);
891 return mWorkerPromise
;
894 void PromiseWorkerProxy::RunCallback(JSContext
* aCx
,
895 JS::Handle
<JS::Value
> aValue
,
896 RunCallbackFunc aFunc
) {
897 MOZ_ASSERT(NS_IsMainThread());
899 MutexAutoLock
lock(Lock());
900 // If the worker thread's been cancelled we don't need to resolve the Promise.
905 // The |aValue| is written into the StructuredCloneHolderBase.
906 if (!Write(aCx
, aValue
)) {
907 JS_ClearPendingException(aCx
);
909 "cannot serialize the value with the StructuredCloneAlgorithm!");
912 RefPtr
<PromiseWorkerProxyRunnable
> runnable
=
913 new PromiseWorkerProxyRunnable(this, aFunc
);
915 runnable
->Dispatch();
918 void PromiseWorkerProxy::ResolvedCallback(JSContext
* aCx
,
919 JS::Handle
<JS::Value
> aValue
,
921 RunCallback(aCx
, aValue
, &Promise::MaybeResolve
);
924 void PromiseWorkerProxy::RejectedCallback(JSContext
* aCx
,
925 JS::Handle
<JS::Value
> aValue
,
927 RunCallback(aCx
, aValue
, &Promise::MaybeReject
);
930 void PromiseWorkerProxy::CleanUp() {
931 // Can't release Mutex while it is still locked, so scope the lock.
933 MutexAutoLock
lock(Lock());
939 // We can be called if we failed to get a WorkerRef
941 mWorkerRef
->Private()->AssertIsOnWorkerThread();
944 // Release the Promise and remove the PromiseWorkerProxy from the holders of
945 // the worker thread since the Promise has been resolved/rejected or the
946 // worker thread has been cancelled.
948 mWorkerPromise
= nullptr;
949 mWorkerRef
= nullptr;
951 // Clear the StructuredCloneHolderBase class.
957 JSObject
* PromiseWorkerProxy::CustomReadHandler(
958 JSContext
* aCx
, JSStructuredCloneReader
* aReader
,
959 const JS::CloneDataPolicy
& aCloneDataPolicy
, uint32_t aTag
,
961 if (NS_WARN_IF(!mCallbacks
)) {
965 return mCallbacks
->Read(aCx
, aReader
, this, aTag
, aIndex
);
968 bool PromiseWorkerProxy::CustomWriteHandler(JSContext
* aCx
,
969 JSStructuredCloneWriter
* aWriter
,
970 JS::Handle
<JSObject
*> aObj
,
971 bool* aSameProcessScopeRequired
) {
972 if (NS_WARN_IF(!mCallbacks
)) {
976 return mCallbacks
->Write(aCx
, aWriter
, this, aObj
);
979 // Specializations of MaybeRejectBrokenly we actually support.
981 void Promise::MaybeRejectBrokenly(const RefPtr
<DOMException
>& aArg
) {
982 MaybeSomething(aArg
, &Promise::MaybeReject
);
985 void Promise::MaybeRejectBrokenly(const nsAString
& aArg
) {
986 MaybeSomething(aArg
, &Promise::MaybeReject
);
989 Promise::PromiseState
Promise::State() const {
990 JS::Rooted
<JSObject
*> p(RootingCx(), PromiseObj());
991 const JS::PromiseState state
= JS::GetPromiseState(p
);
993 if (state
== JS::PromiseState::Fulfilled
) {
994 return PromiseState::Resolved
;
997 if (state
== JS::PromiseState::Rejected
) {
998 return PromiseState::Rejected
;
1001 return PromiseState::Pending
;
1004 bool Promise::SetSettledPromiseIsHandled() {
1006 // Do nothing as it's a no-op promise
1009 AutoAllowLegacyScriptExecution exemption
;
1010 AutoEntryScript
aes(mGlobal
, "Set settled promise handled");
1011 JSContext
* cx
= aes
.cx();
1012 JS::Rooted
<JSObject
*> promiseObj(cx
, mPromiseObj
);
1013 return JS::SetSettledPromiseIsHandled(cx
, promiseObj
);
1016 bool Promise::SetAnyPromiseIsHandled() {
1018 // Do nothing as it's a no-op promise
1021 AutoAllowLegacyScriptExecution exemption
;
1022 AutoEntryScript
aes(mGlobal
, "Set any promise handled");
1023 JSContext
* cx
= aes
.cx();
1024 JS::Rooted
<JSObject
*> promiseObj(cx
, mPromiseObj
);
1025 return JS::SetAnyPromiseIsHandled(cx
, promiseObj
);
1029 already_AddRefed
<Promise
> Promise::CreateResolvedWithUndefined(
1030 nsIGlobalObject
* global
, ErrorResult
& aRv
) {
1031 RefPtr
<Promise
> returnPromise
= Promise::Create(global
, aRv
);
1035 returnPromise
->MaybeResolveWithUndefined();
1036 return returnPromise
.forget();
1039 already_AddRefed
<Promise
> Promise::CreateRejected(
1040 nsIGlobalObject
* aGlobal
, JS::Handle
<JS::Value
> aRejectionError
,
1042 RefPtr
<Promise
> promise
= Promise::Create(aGlobal
, aRv
);
1046 promise
->MaybeReject(aRejectionError
);
1047 return promise
.forget();
1050 already_AddRefed
<Promise
> Promise::CreateRejectedWithTypeError(
1051 nsIGlobalObject
* aGlobal
, const nsACString
& aMessage
, ErrorResult
& aRv
) {
1052 RefPtr
<Promise
> returnPromise
= Promise::Create(aGlobal
, aRv
);
1056 returnPromise
->MaybeRejectWithTypeError(aMessage
);
1057 return returnPromise
.forget();
1060 already_AddRefed
<Promise
> Promise::CreateRejectedWithErrorResult(
1061 nsIGlobalObject
* aGlobal
, ErrorResult
& aRejectionError
) {
1062 RefPtr
<Promise
> returnPromise
= Promise::Create(aGlobal
, IgnoreErrors());
1063 if (!returnPromise
) {
1066 returnPromise
->MaybeReject(std::move(aRejectionError
));
1067 return returnPromise
.forget();
1070 nsresult
Promise::TryExtractNSResultFromRejectionValue(
1071 JS::Handle
<JS::Value
> aValue
) {
1072 if (aValue
.isInt32()) {
1073 return nsresult(aValue
.toInt32());
1076 if (aValue
.isObject()) {
1077 RefPtr
<DOMException
> domException
;
1078 UNWRAP_OBJECT(DOMException
, aValue
, domException
);
1080 return domException
->GetResult();
1084 return NS_ERROR_DOM_NOT_NUMBER_ERR
;
1087 } // namespace mozilla::dom
1091 // These functions are used in the implementation of ffi bindings for
1092 // dom::Promise from Rust.
1094 void DomPromise_AddRef(mozilla::dom::Promise
* aPromise
) {
1095 MOZ_ASSERT(aPromise
);
1099 void DomPromise_Release(mozilla::dom::Promise
* aPromise
) {
1100 MOZ_ASSERT(aPromise
);
1101 aPromise
->Release();
1104 #define DOM_PROMISE_FUNC_WITH_VARIANT(name, func) \
1105 void name(mozilla::dom::Promise* aPromise, nsIVariant* aVariant) { \
1106 MOZ_ASSERT(aPromise); \
1107 MOZ_ASSERT(aVariant); \
1108 mozilla::dom::AutoEntryScript aes(aPromise->GetGlobalObject(), \
1109 "Promise resolution or rejection"); \
1110 JSContext* cx = aes.cx(); \
1112 JS::Rooted<JS::Value> val(cx); \
1113 nsresult rv = NS_OK; \
1114 if (!XPCVariant::VariantDataToJS(cx, aVariant, &rv, &val)) { \
1115 aPromise->MaybeRejectWithTypeError( \
1116 "Failed to convert nsIVariant to JS"); \
1119 aPromise->func(val); \
1122 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_RejectWithVariant
, MaybeReject
)
1123 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_ResolveWithVariant
, MaybeResolve
)
1125 #undef DOM_PROMISE_FUNC_WITH_VARIANT