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 "nsGlobalWindow.h"
42 #include "nsIScriptObjectPrincipal.h"
43 #include "nsJSEnvironment.h"
44 #include "nsJSPrincipals.h"
45 #include "nsJSUtils.h"
46 #include "nsPIDOMWindow.h"
47 #include "PromiseDebugging.h"
48 #include "PromiseNativeHandler.h"
49 #include "PromiseWorkerProxy.h"
50 #include "WrapperFactory.h"
51 #include "xpcpublic.h"
52 #include "xpcprivate.h"
59 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise
)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise
)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
)
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
64 tmp
->mPromiseObj
= nullptr;
65 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise
)
68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal
)
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
71 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise
)
72 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj
);
73 NS_IMPL_CYCLE_COLLECTION_TRACE_END
75 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Promise
, AddRef
)
76 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Promise
, Release
)
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
);
103 bool Promise::MaybePropagateUserInputEventHandling() {
104 JS::PromiseUserInputEventHandlingState state
=
105 UserActivation::IsHandlingUserInput()
106 ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
107 : JS::PromiseUserInputEventHandlingState::
108 DidntHaveUserInteractionAtCreation
;
109 JS::Rooted
<JSObject
*> p(RootingCx(), mPromiseObj
);
110 return JS::SetPromiseUserInputEventHandlingState(p
, state
);
114 already_AddRefed
<Promise
> Promise::Resolve(
115 nsIGlobalObject
* aGlobal
, JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
116 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
117 JSAutoRealm
ar(aCx
, aGlobal
->GetGlobalJSObject());
118 JS::Rooted
<JSObject
*> p(aCx
, JS::CallOriginalPromiseResolve(aCx
, aValue
));
120 aRv
.NoteJSContextException(aCx
);
124 return CreateFromExisting(aGlobal
, p
, aPropagateUserInteraction
);
128 already_AddRefed
<Promise
> Promise::Reject(nsIGlobalObject
* aGlobal
,
130 JS::Handle
<JS::Value
> aValue
,
132 JSAutoRealm
ar(aCx
, aGlobal
->GetGlobalJSObject());
133 JS::Rooted
<JSObject
*> p(aCx
, JS::CallOriginalPromiseReject(aCx
, aValue
));
135 aRv
.NoteJSContextException(aCx
);
139 // This promise will never be resolved, so we pass
140 // eDontPropagateUserInteraction for aPropagateUserInteraction
142 return CreateFromExisting(aGlobal
, p
, eDontPropagateUserInteraction
);
146 already_AddRefed
<Promise
> Promise::All(
147 JSContext
* aCx
, const nsTArray
<RefPtr
<Promise
>>& aPromiseList
,
148 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
149 JS::Rooted
<JSObject
*> globalObj(aCx
, JS::CurrentGlobalOrNull(aCx
));
151 aRv
.Throw(NS_ERROR_UNEXPECTED
);
155 nsCOMPtr
<nsIGlobalObject
> global
= xpc::NativeGlobal(globalObj
);
157 aRv
.Throw(NS_ERROR_UNEXPECTED
);
161 JS::RootedVector
<JSObject
*> promises(aCx
);
162 if (!promises
.reserve(aPromiseList
.Length())) {
163 aRv
.NoteJSContextException(aCx
);
167 for (auto& promise
: aPromiseList
) {
168 JS::Rooted
<JSObject
*> promiseObj(aCx
, promise
->PromiseObj());
169 // Just in case, make sure these are all in the context compartment.
170 if (!JS_WrapObject(aCx
, &promiseObj
)) {
171 aRv
.NoteJSContextException(aCx
);
174 promises
.infallibleAppend(promiseObj
);
177 JS::Rooted
<JSObject
*> result(aCx
, JS::GetWaitForAllPromise(aCx
, promises
));
179 aRv
.NoteJSContextException(aCx
);
183 return CreateFromExisting(global
, result
, aPropagateUserInteraction
);
186 void Promise::Then(JSContext
* aCx
,
187 // aCalleeGlobal may not be in the compartment of aCx, when
188 // called over Xrays.
189 JS::Handle
<JSObject
*> aCalleeGlobal
,
190 AnyCallback
* aResolveCallback
, AnyCallback
* aRejectCallback
,
191 JS::MutableHandle
<JS::Value
> aRetval
, ErrorResult
& aRv
) {
192 NS_ASSERT_OWNINGTHREAD(Promise
);
194 // Let's hope this does the right thing with Xrays... Ensure everything is
195 // just in the caller compartment; that ought to do the trick. In theory we
196 // should consider aCalleeGlobal, but in practice our only caller is
197 // DOMRequest::Then, which is not working with a Promise subclass, so things
199 JS::Rooted
<JSObject
*> promise(aCx
, PromiseObj());
200 if (!JS_WrapObject(aCx
, &promise
)) {
201 aRv
.NoteJSContextException(aCx
);
205 JS::Rooted
<JSObject
*> resolveCallback(aCx
);
206 if (aResolveCallback
) {
207 resolveCallback
= aResolveCallback
->CallbackOrNull();
208 if (!JS_WrapObject(aCx
, &resolveCallback
)) {
209 aRv
.NoteJSContextException(aCx
);
214 JS::Rooted
<JSObject
*> rejectCallback(aCx
);
215 if (aRejectCallback
) {
216 rejectCallback
= aRejectCallback
->CallbackOrNull();
217 if (!JS_WrapObject(aCx
, &rejectCallback
)) {
218 aRv
.NoteJSContextException(aCx
);
223 JS::Rooted
<JSObject
*> retval(aCx
);
224 retval
= JS::CallOriginalPromiseThen(aCx
, promise
, resolveCallback
,
227 aRv
.NoteJSContextException(aCx
);
231 aRetval
.setObject(*retval
);
234 void PromiseNativeThenHandlerBase::ResolvedCallback(
235 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
236 RefPtr
<Promise
> promise
= CallResolveCallback(aCx
, aValue
);
238 mPromise
->MaybeResolve(promise
);
240 mPromise
->MaybeResolveWithUndefined();
244 void PromiseNativeThenHandlerBase::RejectedCallback(
245 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
246 mPromise
->MaybeReject(aValue
);
249 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase
)
251 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase
)
252 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise
)
254 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
256 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase
)
257 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise
)
259 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
261 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase
)
262 NS_INTERFACE_MAP_ENTRY(nsISupports
)
265 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase
)
266 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase
)
268 Result
<RefPtr
<Promise
>, nsresult
> Promise::ThenWithoutCycleCollection(
269 const std::function
<already_AddRefed
<Promise
>(
270 JSContext
* aCx
, JS::HandleValue aValue
)>& aCallback
) {
271 return ThenWithCycleCollectedArgs(aCallback
);
274 void Promise::CreateWrapper(
275 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
277 if (!jsapi
.Init(mGlobal
)) {
278 aRv
.Throw(NS_ERROR_UNEXPECTED
);
281 JSContext
* cx
= jsapi
.cx();
282 mPromiseObj
= JS::NewPromiseObject(cx
, nullptr);
284 JS_ClearPendingException(cx
);
285 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
288 if (aPropagateUserInteraction
== ePropagateUserInteraction
) {
289 Unused
<< MaybePropagateUserInputEventHandling();
293 void Promise::MaybeResolve(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
294 NS_ASSERT_OWNINGTHREAD(Promise
);
296 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
297 if (!JS::ResolvePromise(aCx
, p
, aValue
)) {
298 // Now what? There's nothing sane to do here.
299 JS_ClearPendingException(aCx
);
303 void Promise::MaybeReject(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
304 NS_ASSERT_OWNINGTHREAD(Promise
);
306 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
307 if (!JS::RejectPromise(aCx
, p
, aValue
)) {
308 // Now what? There's nothing sane to do here.
309 JS_ClearPendingException(aCx
);
313 #define SLOT_NATIVEHANDLER 0
314 #define SLOT_NATIVEHANDLER_TASK 1
316 enum class NativeHandlerTask
: int32_t { Resolve
, Reject
};
319 static bool NativeHandlerCallback(JSContext
* aCx
, unsigned aArgc
,
321 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
324 js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER
);
325 MOZ_ASSERT(v
.isObject());
327 JS::Rooted
<JSObject
*> obj(aCx
, &v
.toObject());
328 PromiseNativeHandler
* handler
= nullptr;
329 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler
, &obj
, handler
))) {
330 return Throw(aCx
, NS_ERROR_UNEXPECTED
);
333 v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER_TASK
);
334 NativeHandlerTask task
= static_cast<NativeHandlerTask
>(v
.toInt32());
336 if (task
== NativeHandlerTask::Resolve
) {
337 // handler is kept alive by "obj" on the stack.
338 MOZ_KnownLive(handler
)->ResolvedCallback(aCx
, args
.get(0));
340 MOZ_ASSERT(task
== NativeHandlerTask::Reject
);
341 // handler is kept alive by "obj" on the stack.
342 MOZ_KnownLive(handler
)->RejectedCallback(aCx
, args
.get(0));
348 static JSObject
* CreateNativeHandlerFunction(JSContext
* aCx
,
349 JS::Handle
<JSObject
*> aHolder
,
350 NativeHandlerTask aTask
) {
351 JSFunction
* func
= js::NewFunctionWithReserved(aCx
, NativeHandlerCallback
,
353 /* flags = */ 0, nullptr);
358 JS::Rooted
<JSObject
*> obj(aCx
, JS_GetFunctionObject(func
));
360 JS::AssertObjectIsNotGray(aHolder
);
361 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER
,
362 JS::ObjectValue(*aHolder
));
363 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER_TASK
,
364 JS::Int32Value(static_cast<int32_t>(aTask
)));
371 class PromiseNativeHandlerShim final
: public PromiseNativeHandler
{
372 RefPtr
<PromiseNativeHandler
> mInner
;
374 ~PromiseNativeHandlerShim() = default;
377 explicit PromiseNativeHandlerShim(PromiseNativeHandler
* aInner
)
383 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
{
384 RefPtr
<PromiseNativeHandler
> inner
= std::move(mInner
);
385 inner
->ResolvedCallback(aCx
, aValue
);
390 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
{
391 RefPtr
<PromiseNativeHandler
> inner
= std::move(mInner
);
392 inner
->RejectedCallback(aCx
, aValue
);
396 bool WrapObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
,
397 JS::MutableHandle
<JSObject
*> aWrapper
) {
398 return PromiseNativeHandler_Binding::Wrap(aCx
, this, aGivenProto
, aWrapper
);
401 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
402 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim
)
405 NS_IMPL_CYCLE_COLLECTION(PromiseNativeHandlerShim
, mInner
)
407 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim
)
408 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim
)
410 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim
)
411 NS_INTERFACE_MAP_ENTRY(nsISupports
)
414 } // anonymous namespace
416 void Promise::AppendNativeHandler(PromiseNativeHandler
* aRunnable
) {
417 NS_ASSERT_OWNINGTHREAD(Promise
);
420 if (NS_WARN_IF(!jsapi
.Init(mGlobal
))) {
421 // Our API doesn't allow us to return a useful error. Not like this should
426 // The self-hosted promise js may keep the object we pass to it alive
427 // for quite a while depending on when GC runs. Therefore, pass a shim
428 // object instead. The shim will free its inner PromiseNativeHandler
429 // after the promise has settled just like our previous c++ promises did.
430 RefPtr
<PromiseNativeHandlerShim
> shim
=
431 new PromiseNativeHandlerShim(aRunnable
);
433 JSContext
* cx
= jsapi
.cx();
434 JS::Rooted
<JSObject
*> handlerWrapper(cx
);
435 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
436 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
437 if (NS_WARN_IF(!shim
->WrapObject(cx
, nullptr, &handlerWrapper
))) {
438 // Again, no way to report errors.
439 jsapi
.ClearException();
443 JS::Rooted
<JSObject
*> resolveFunc(cx
);
444 resolveFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
445 NativeHandlerTask::Resolve
);
446 if (NS_WARN_IF(!resolveFunc
)) {
447 jsapi
.ClearException();
451 JS::Rooted
<JSObject
*> rejectFunc(cx
);
452 rejectFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
453 NativeHandlerTask::Reject
);
454 if (NS_WARN_IF(!rejectFunc
)) {
455 jsapi
.ClearException();
459 JS::Rooted
<JSObject
*> promiseObj(cx
, PromiseObj());
461 !JS::AddPromiseReactions(cx
, promiseObj
, resolveFunc
, rejectFunc
))) {
462 jsapi
.ClearException();
467 void Promise::HandleException(JSContext
* aCx
) {
468 JS::Rooted
<JS::Value
> exn(aCx
);
469 if (JS_GetPendingException(aCx
, &exn
)) {
470 JS_ClearPendingException(aCx
);
471 // Always reject even if this was called in *Resolve.
472 MaybeReject(aCx
, exn
);
477 already_AddRefed
<Promise
> Promise::CreateFromExisting(
478 nsIGlobalObject
* aGlobal
, JS::Handle
<JSObject
*> aPromiseObj
,
479 PropagateUserInteraction aPropagateUserInteraction
) {
480 MOZ_ASSERT(JS::GetCompartment(aGlobal
->GetGlobalJSObjectPreserveColor()) ==
481 JS::GetCompartment(aPromiseObj
));
482 RefPtr
<Promise
> p
= new Promise(aGlobal
);
483 p
->mPromiseObj
= aPromiseObj
;
484 if (aPropagateUserInteraction
== ePropagateUserInteraction
&&
485 !p
->MaybePropagateUserInputEventHandling()) {
491 void Promise::MaybeResolveWithUndefined() {
492 NS_ASSERT_OWNINGTHREAD(Promise
);
494 MaybeResolve(JS::UndefinedHandleValue
);
497 void Promise::MaybeReject(const RefPtr
<MediaStreamError
>& aArg
) {
498 NS_ASSERT_OWNINGTHREAD(Promise
);
500 MaybeSomething(aArg
, &Promise::MaybeReject
);
503 void Promise::MaybeRejectWithUndefined() {
504 NS_ASSERT_OWNINGTHREAD(Promise
);
506 MaybeSomething(JS::UndefinedHandleValue
, &Promise::MaybeReject
);
509 void Promise::ReportRejectedPromise(JSContext
* aCx
, JS::HandleObject aPromise
) {
510 MOZ_ASSERT(!js::IsWrapper(aPromise
));
512 MOZ_ASSERT(JS::GetPromiseState(aPromise
) == JS::PromiseState::Rejected
);
514 bool isChrome
= false;
515 uint64_t innerWindowID
= 0;
516 nsGlobalWindowInner
* winForDispatch
= nullptr;
517 if (MOZ_LIKELY(NS_IsMainThread())) {
518 isChrome
= nsContentUtils::ObjectPrincipal(aPromise
)->IsSystemPrincipal();
520 if (nsGlobalWindowInner
* win
= xpc::WindowGlobalOrNull(aPromise
)) {
521 winForDispatch
= win
;
522 innerWindowID
= win
->WindowID();
523 } else if (nsGlobalWindowInner
* win
= xpc::SandboxWindowOrNull(
524 JS::GetNonCCWObjectGlobal(aPromise
), aCx
)) {
525 // Don't dispatch rejections from the sandbox to the associated DOM
527 innerWindowID
= win
->WindowID();
529 } else if (const WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate()) {
530 isChrome
= wp
->UsesSystemPrincipal();
531 innerWindowID
= wp
->WindowID();
532 } else if (nsCOMPtr
<nsIGlobalObject
> global
= xpc::NativeGlobal(aPromise
)) {
533 if (nsCOMPtr
<WorkletGlobalScope
> workletGlobal
=
534 do_QueryInterface(global
)) {
535 WorkletImpl
* impl
= workletGlobal
->Impl();
536 isChrome
= impl
->PrincipalInfo().type() ==
537 mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo
;
538 innerWindowID
= impl
->LoadInfo().InnerWindowID();
542 JS::Rooted
<JS::Value
> result(aCx
, JS::GetPromiseResult(aPromise
));
543 // resolutionSite can be null if async stacks are disabled.
544 JS::Rooted
<JSObject
*> resolutionSite(aCx
,
545 JS::GetPromiseResolutionSite(aPromise
));
547 // We're inspecting the rejection value only to report it to the console, and
548 // we do so without side-effects, so we can safely unwrap it without regard to
549 // the privileges of the Promise object that holds it. If we don't unwrap
550 // before trying to create the error report, we wind up reporting any
551 // cross-origin objects as "uncaught exception: Object".
552 RefPtr
<xpc::ErrorReport
> xpcReport
= new xpc::ErrorReport();
554 Maybe
<JSAutoRealm
> ar
;
555 JS::Rooted
<JS::Value
> unwrapped(aCx
, result
);
556 if (unwrapped
.isObject()) {
557 unwrapped
.setObject(*js::UncheckedUnwrap(&unwrapped
.toObject()));
558 ar
.emplace(aCx
, &unwrapped
.toObject());
561 JS::ErrorReportBuilder
report(aCx
);
562 RefPtr
<Exception
> exn
;
563 if (unwrapped
.isObject() &&
564 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException
, &unwrapped
, exn
)) ||
565 NS_SUCCEEDED(UNWRAP_OBJECT(Exception
, &unwrapped
, exn
)))) {
566 xpcReport
->Init(aCx
, exn
, isChrome
, innerWindowID
);
568 // Use the resolution site as the exception stack
569 JS::ExceptionStack
exnStack(aCx
, unwrapped
, resolutionSite
);
570 if (!report
.init(aCx
, exnStack
, JS::ErrorReportBuilder::NoSideEffects
)) {
571 JS_ClearPendingException(aCx
);
575 xpcReport
->Init(report
.report(), report
.toStringResult().c_str(),
576 isChrome
, innerWindowID
);
580 // Used to initialize the similarly named nsISciptError attribute.
581 xpcReport
->mIsPromiseRejection
= true;
583 // Now post an event to do the real reporting async
584 RefPtr
<AsyncErrorReporter
> event
= new AsyncErrorReporter(xpcReport
);
585 if (winForDispatch
) {
586 if (!winForDispatch
->IsDying()) {
587 // Exceptions from a dying window will cause the window to leak.
588 event
->SetException(aCx
, result
);
589 if (resolutionSite
) {
590 event
->SerializeStack(aCx
, resolutionSite
);
593 winForDispatch
->Dispatch(mozilla::TaskCategory::Other
, event
.forget());
595 NS_DispatchToMainThread(event
);
599 void Promise::MaybeResolveWithClone(JSContext
* aCx
,
600 JS::Handle
<JS::Value
> aValue
) {
601 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
602 AutoEntryScript
aes(GetParentObject(), "Promise resolution");
603 JSContext
* cx
= aes
.cx();
604 JS::Rooted
<JS::Value
> value(cx
, aValue
);
606 xpc::StackScopedCloneOptions options
;
607 options
.wrapReflectors
= true;
608 if (!StackScopedClone(cx
, options
, sourceScope
, &value
)) {
612 MaybeResolve(aCx
, value
);
615 void Promise::MaybeRejectWithClone(JSContext
* aCx
,
616 JS::Handle
<JS::Value
> aValue
) {
617 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
618 AutoEntryScript
aes(GetParentObject(), "Promise rejection");
619 JSContext
* cx
= aes
.cx();
620 JS::Rooted
<JS::Value
> value(cx
, aValue
);
622 xpc::StackScopedCloneOptions options
;
623 options
.wrapReflectors
= true;
624 if (!StackScopedClone(cx
, options
, sourceScope
, &value
)) {
628 MaybeReject(aCx
, value
);
631 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
632 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
633 class PromiseWorkerProxyRunnable
: public WorkerRunnable
{
635 PromiseWorkerProxyRunnable(PromiseWorkerProxy
* aPromiseWorkerProxy
,
636 PromiseWorkerProxy::RunCallbackFunc aFunc
)
637 : WorkerRunnable(aPromiseWorkerProxy
->GetWorkerPrivate(),
638 WorkerThreadUnchangedBusyCount
),
639 mPromiseWorkerProxy(aPromiseWorkerProxy
),
641 MOZ_ASSERT(NS_IsMainThread());
642 MOZ_ASSERT(mPromiseWorkerProxy
);
645 virtual bool WorkerRun(JSContext
* aCx
,
646 WorkerPrivate
* aWorkerPrivate
) override
{
647 MOZ_ASSERT(aWorkerPrivate
);
648 aWorkerPrivate
->AssertIsOnWorkerThread();
649 MOZ_ASSERT(aWorkerPrivate
== mWorkerPrivate
);
651 MOZ_ASSERT(mPromiseWorkerProxy
);
652 RefPtr
<Promise
> workerPromise
= mPromiseWorkerProxy
->WorkerPromise();
654 // Here we convert the buffer to a JS::Value.
655 JS::Rooted
<JS::Value
> value(aCx
);
656 if (!mPromiseWorkerProxy
->Read(aCx
, &value
)) {
657 JS_ClearPendingException(aCx
);
661 (workerPromise
->*mFunc
)(aCx
, value
);
663 // Release the Promise because it has been resolved/rejected for sure.
664 mPromiseWorkerProxy
->CleanUp();
669 ~PromiseWorkerProxyRunnable() = default;
672 RefPtr
<PromiseWorkerProxy
> mPromiseWorkerProxy
;
674 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
675 PromiseWorkerProxy::RunCallbackFunc mFunc
;
679 already_AddRefed
<PromiseWorkerProxy
> PromiseWorkerProxy::Create(
680 WorkerPrivate
* aWorkerPrivate
, Promise
* aWorkerPromise
,
681 const PromiseWorkerProxyStructuredCloneCallbacks
* aCb
) {
682 MOZ_ASSERT(aWorkerPrivate
);
683 aWorkerPrivate
->AssertIsOnWorkerThread();
684 MOZ_ASSERT(aWorkerPromise
);
685 MOZ_ASSERT_IF(aCb
, !!aCb
->Write
&& !!aCb
->Read
);
687 RefPtr
<PromiseWorkerProxy
> proxy
=
688 new PromiseWorkerProxy(aWorkerPromise
, aCb
);
690 // We do this to make sure the worker thread won't shut down before the
691 // promise is resolved/rejected on the worker thread.
692 RefPtr
<StrongWorkerRef
> workerRef
= StrongWorkerRef::Create(
693 aWorkerPrivate
, "PromiseWorkerProxy", [proxy
]() { proxy
->CleanUp(); });
695 if (NS_WARN_IF(!workerRef
)) {
696 // Probably the worker is terminating. We cannot complete the operation
697 // and we have to release all the resources.
698 proxy
->CleanProperties();
702 proxy
->mWorkerRef
= new ThreadSafeWorkerRef(workerRef
);
704 // Maintain a reference so that we have a valid object to clean up when
705 // removing the feature.
706 proxy
.get()->AddRef();
708 return proxy
.forget();
711 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy
)
713 PromiseWorkerProxy::PromiseWorkerProxy(
714 Promise
* aWorkerPromise
,
715 const PromiseWorkerProxyStructuredCloneCallbacks
* aCallbacks
)
716 : mWorkerPromise(aWorkerPromise
),
718 mCallbacks(aCallbacks
),
719 mCleanUpLock("cleanUpLock") {}
721 PromiseWorkerProxy::~PromiseWorkerProxy() {
722 MOZ_ASSERT(mCleanedUp
);
723 MOZ_ASSERT(!mWorkerPromise
);
724 MOZ_ASSERT(!mWorkerRef
);
727 void PromiseWorkerProxy::CleanProperties() {
728 MOZ_ASSERT(IsCurrentThreadRunningWorker());
730 // Ok to do this unprotected from Create().
731 // CleanUp() holds the lock before calling this.
733 mWorkerPromise
= nullptr;
734 mWorkerRef
= nullptr;
736 // Clear the StructuredCloneHolderBase class.
740 WorkerPrivate
* PromiseWorkerProxy::GetWorkerPrivate() const {
742 if (NS_IsMainThread()) {
743 mCleanUpLock
.AssertCurrentThreadOwns();
746 // Safe to check this without a lock since we assert lock ownership on the
747 // main thread above.
748 MOZ_ASSERT(!mCleanedUp
);
749 MOZ_ASSERT(mWorkerRef
);
751 return mWorkerRef
->Private();
754 Promise
* PromiseWorkerProxy::WorkerPromise() const {
755 MOZ_ASSERT(IsCurrentThreadRunningWorker());
756 MOZ_ASSERT(mWorkerPromise
);
757 return mWorkerPromise
;
760 void PromiseWorkerProxy::RunCallback(JSContext
* aCx
,
761 JS::Handle
<JS::Value
> aValue
,
762 RunCallbackFunc aFunc
) {
763 MOZ_ASSERT(NS_IsMainThread());
765 MutexAutoLock
lock(Lock());
766 // If the worker thread's been cancelled we don't need to resolve the Promise.
771 // The |aValue| is written into the StructuredCloneHolderBase.
772 if (!Write(aCx
, aValue
)) {
773 JS_ClearPendingException(aCx
);
775 "cannot serialize the value with the StructuredCloneAlgorithm!");
778 RefPtr
<PromiseWorkerProxyRunnable
> runnable
=
779 new PromiseWorkerProxyRunnable(this, aFunc
);
781 runnable
->Dispatch();
784 void PromiseWorkerProxy::ResolvedCallback(JSContext
* aCx
,
785 JS::Handle
<JS::Value
> aValue
) {
786 RunCallback(aCx
, aValue
, &Promise::MaybeResolve
);
789 void PromiseWorkerProxy::RejectedCallback(JSContext
* aCx
,
790 JS::Handle
<JS::Value
> aValue
) {
791 RunCallback(aCx
, aValue
, &Promise::MaybeReject
);
794 void PromiseWorkerProxy::CleanUp() {
795 // Can't release Mutex while it is still locked, so scope the lock.
797 MutexAutoLock
lock(Lock());
803 MOZ_ASSERT(mWorkerRef
);
804 mWorkerRef
->Private()->AssertIsOnWorkerThread();
806 // Release the Promise and remove the PromiseWorkerProxy from the holders of
807 // the worker thread since the Promise has been resolved/rejected or the
808 // worker thread has been cancelled.
809 mWorkerRef
= nullptr;
816 JSObject
* PromiseWorkerProxy::CustomReadHandler(
817 JSContext
* aCx
, JSStructuredCloneReader
* aReader
,
818 const JS::CloneDataPolicy
& aCloneDataPolicy
, uint32_t aTag
,
820 if (NS_WARN_IF(!mCallbacks
)) {
824 return mCallbacks
->Read(aCx
, aReader
, this, aTag
, aIndex
);
827 bool PromiseWorkerProxy::CustomWriteHandler(JSContext
* aCx
,
828 JSStructuredCloneWriter
* aWriter
,
829 JS::Handle
<JSObject
*> aObj
,
830 bool* aSameProcessScopeRequired
) {
831 if (NS_WARN_IF(!mCallbacks
)) {
835 return mCallbacks
->Write(aCx
, aWriter
, this, aObj
);
838 // Specializations of MaybeRejectBrokenly we actually support.
840 void Promise::MaybeRejectBrokenly(const RefPtr
<DOMException
>& aArg
) {
841 MaybeSomething(aArg
, &Promise::MaybeReject
);
844 void Promise::MaybeRejectBrokenly(const nsAString
& aArg
) {
845 MaybeSomething(aArg
, &Promise::MaybeReject
);
848 Promise::PromiseState
Promise::State() const {
849 JS::Rooted
<JSObject
*> p(RootingCx(), PromiseObj());
850 const JS::PromiseState state
= JS::GetPromiseState(p
);
852 if (state
== JS::PromiseState::Fulfilled
) {
853 return PromiseState::Resolved
;
856 if (state
== JS::PromiseState::Rejected
) {
857 return PromiseState::Rejected
;
860 return PromiseState::Pending
;
863 void Promise::SetSettledPromiseIsHandled() {
864 AutoAllowLegacyScriptExecution exemption
;
865 AutoEntryScript
aes(mGlobal
, "Set settled promise handled");
866 JSContext
* cx
= aes
.cx();
867 JS::RootedObject
promiseObj(cx
, mPromiseObj
);
868 JS::SetSettledPromiseIsHandled(cx
, promiseObj
);
872 } // namespace mozilla
876 // These functions are used in the implementation of ffi bindings for
877 // dom::Promise from Rust.
879 void DomPromise_AddRef(mozilla::dom::Promise
* aPromise
) {
880 MOZ_ASSERT(aPromise
);
884 void DomPromise_Release(mozilla::dom::Promise
* aPromise
) {
885 MOZ_ASSERT(aPromise
);
889 #define DOM_PROMISE_FUNC_WITH_VARIANT(name, func) \
890 void name(mozilla::dom::Promise* aPromise, nsIVariant* aVariant) { \
891 MOZ_ASSERT(aPromise); \
892 MOZ_ASSERT(aVariant); \
893 mozilla::dom::AutoEntryScript aes(aPromise->GetGlobalObject(), \
894 "Promise resolution or rejection"); \
895 JSContext* cx = aes.cx(); \
897 JS::Rooted<JS::Value> val(cx); \
898 nsresult rv = NS_OK; \
899 if (!XPCVariant::VariantDataToJS(cx, aVariant, &rv, &val)) { \
900 aPromise->MaybeRejectWithTypeError( \
901 "Failed to convert nsIVariant to JS"); \
904 aPromise->func(val); \
907 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_RejectWithVariant
, MaybeReject
)
908 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_ResolveWithVariant
, MaybeResolve
)
910 #undef DOM_PROMISE_FUNC_WITH_VARIANT