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/OwningNonNull.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/ResultExtensions.h"
18 #include "mozilla/Unused.h"
20 #include "mozilla/dom/BindingUtils.h"
21 #include "mozilla/dom/DOMException.h"
22 #include "mozilla/dom/DOMExceptionBinding.h"
23 #include "mozilla/dom/MediaStreamError.h"
24 #include "mozilla/dom/PromiseBinding.h"
25 #include "mozilla/dom/ScriptSettings.h"
26 #include "mozilla/dom/UserActivation.h"
27 #include "mozilla/dom/WorkerPrivate.h"
28 #include "mozilla/dom/WorkerRunnable.h"
29 #include "mozilla/dom/WorkerRef.h"
30 #include "mozilla/dom/WorkletImpl.h"
31 #include "mozilla/dom/WorkletGlobalScope.h"
33 #include "jsfriendapi.h"
34 #include "js/Exception.h" // JS::ExceptionStack
35 #include "js/StructuredClone.h"
36 #include "nsContentUtils.h"
37 #include "nsGlobalWindow.h"
38 #include "nsIScriptObjectPrincipal.h"
39 #include "nsJSEnvironment.h"
40 #include "nsJSPrincipals.h"
41 #include "nsJSUtils.h"
42 #include "nsPIDOMWindow.h"
43 #include "PromiseDebugging.h"
44 #include "PromiseNativeHandler.h"
45 #include "PromiseWorkerProxy.h"
46 #include "WrapperFactory.h"
47 #include "xpcpublic.h"
48 #include "xpcprivate.h"
55 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise
)
57 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise
)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
60 tmp
->mPromiseObj
= nullptr;
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise
)
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal
)
65 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
67 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise
)
68 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj
);
69 NS_IMPL_CYCLE_COLLECTION_TRACE_END
71 NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise
)
72 NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise
)
74 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise
)
75 NS_INTERFACE_MAP_ENTRY(nsISupports
)
76 NS_INTERFACE_MAP_ENTRY(Promise
)
79 Promise::Promise(nsIGlobalObject
* aGlobal
)
80 : mGlobal(aGlobal
), mPromiseObj(nullptr) {
83 mozilla::HoldJSObjects(this);
86 Promise::~Promise() { mozilla::DropJSObjects(this); }
89 already_AddRefed
<Promise
> Promise::Create(
90 nsIGlobalObject
* aGlobal
, ErrorResult
& aRv
,
91 PropagateUserInteraction aPropagateUserInteraction
) {
93 aRv
.Throw(NS_ERROR_UNEXPECTED
);
96 RefPtr
<Promise
> p
= new Promise(aGlobal
);
97 p
->CreateWrapper(aRv
, aPropagateUserInteraction
);
104 bool Promise::MaybePropagateUserInputEventHandling() {
105 JS::PromiseUserInputEventHandlingState state
=
106 UserActivation::IsHandlingUserInput()
107 ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
108 : JS::PromiseUserInputEventHandlingState::
109 DidntHaveUserInteractionAtCreation
;
110 JS::Rooted
<JSObject
*> p(RootingCx(), mPromiseObj
);
111 return JS::SetPromiseUserInputEventHandlingState(p
, state
);
115 already_AddRefed
<Promise
> Promise::Resolve(
116 nsIGlobalObject
* aGlobal
, JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
117 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
118 JSAutoRealm
ar(aCx
, aGlobal
->GetGlobalJSObject());
119 JS::Rooted
<JSObject
*> p(aCx
, JS::CallOriginalPromiseResolve(aCx
, aValue
));
121 aRv
.NoteJSContextException(aCx
);
125 return CreateFromExisting(aGlobal
, p
, aPropagateUserInteraction
);
129 already_AddRefed
<Promise
> Promise::Reject(nsIGlobalObject
* aGlobal
,
131 JS::Handle
<JS::Value
> aValue
,
133 JSAutoRealm
ar(aCx
, aGlobal
->GetGlobalJSObject());
134 JS::Rooted
<JSObject
*> p(aCx
, JS::CallOriginalPromiseReject(aCx
, aValue
));
136 aRv
.NoteJSContextException(aCx
);
140 // This promise will never be resolved, so we pass
141 // eDontPropagateUserInteraction for aPropagateUserInteraction
143 return CreateFromExisting(aGlobal
, p
, eDontPropagateUserInteraction
);
147 already_AddRefed
<Promise
> Promise::All(
148 JSContext
* aCx
, const nsTArray
<RefPtr
<Promise
>>& aPromiseList
,
149 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
150 JS::Rooted
<JSObject
*> globalObj(aCx
, JS::CurrentGlobalOrNull(aCx
));
152 aRv
.Throw(NS_ERROR_UNEXPECTED
);
156 nsCOMPtr
<nsIGlobalObject
> global
= xpc::NativeGlobal(globalObj
);
158 aRv
.Throw(NS_ERROR_UNEXPECTED
);
162 JS::RootedVector
<JSObject
*> promises(aCx
);
163 if (!promises
.reserve(aPromiseList
.Length())) {
164 aRv
.NoteJSContextException(aCx
);
168 for (auto& promise
: aPromiseList
) {
169 JS::Rooted
<JSObject
*> promiseObj(aCx
, promise
->PromiseObj());
170 // Just in case, make sure these are all in the context compartment.
171 if (!JS_WrapObject(aCx
, &promiseObj
)) {
172 aRv
.NoteJSContextException(aCx
);
175 promises
.infallibleAppend(promiseObj
);
178 JS::Rooted
<JSObject
*> result(aCx
, JS::GetWaitForAllPromise(aCx
, promises
));
180 aRv
.NoteJSContextException(aCx
);
184 return CreateFromExisting(global
, result
, aPropagateUserInteraction
);
187 void Promise::Then(JSContext
* aCx
,
188 // aCalleeGlobal may not be in the compartment of aCx, when
189 // called over Xrays.
190 JS::Handle
<JSObject
*> aCalleeGlobal
,
191 AnyCallback
* aResolveCallback
, AnyCallback
* aRejectCallback
,
192 JS::MutableHandle
<JS::Value
> aRetval
, ErrorResult
& aRv
) {
193 NS_ASSERT_OWNINGTHREAD(Promise
);
195 // Let's hope this does the right thing with Xrays... Ensure everything is
196 // just in the caller compartment; that ought to do the trick. In theory we
197 // should consider aCalleeGlobal, but in practice our only caller is
198 // DOMRequest::Then, which is not working with a Promise subclass, so things
200 JS::Rooted
<JSObject
*> promise(aCx
, PromiseObj());
201 if (!JS_WrapObject(aCx
, &promise
)) {
202 aRv
.NoteJSContextException(aCx
);
206 JS::Rooted
<JSObject
*> resolveCallback(aCx
);
207 if (aResolveCallback
) {
208 resolveCallback
= aResolveCallback
->CallbackOrNull();
209 if (!JS_WrapObject(aCx
, &resolveCallback
)) {
210 aRv
.NoteJSContextException(aCx
);
215 JS::Rooted
<JSObject
*> rejectCallback(aCx
);
216 if (aRejectCallback
) {
217 rejectCallback
= aRejectCallback
->CallbackOrNull();
218 if (!JS_WrapObject(aCx
, &rejectCallback
)) {
219 aRv
.NoteJSContextException(aCx
);
224 JS::Rooted
<JSObject
*> retval(aCx
);
225 retval
= JS::CallOriginalPromiseThen(aCx
, promise
, resolveCallback
,
228 aRv
.NoteJSContextException(aCx
);
232 aRetval
.setObject(*retval
);
235 void PromiseNativeThenHandlerBase::ResolvedCallback(
236 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
237 RefPtr
<Promise
> promise
= CallResolveCallback(aCx
, aValue
);
239 mPromise
->MaybeResolve(promise
);
241 mPromise
->MaybeResolveWithUndefined();
245 void PromiseNativeThenHandlerBase::RejectedCallback(
246 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
247 mPromise
->MaybeReject(aValue
);
250 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase
)
252 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase
)
253 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise
)
255 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
257 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase
)
258 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise
)
260 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
262 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase
)
263 NS_INTERFACE_MAP_ENTRY(nsISupports
)
266 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase
)
267 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase
)
269 Result
<RefPtr
<Promise
>, nsresult
> Promise::ThenWithoutCycleCollection(
270 const std::function
<already_AddRefed
<Promise
>(
271 JSContext
* aCx
, JS::HandleValue aValue
)>& aCallback
) {
272 return ThenWithCycleCollectedArgs(aCallback
);
275 void Promise::CreateWrapper(
276 ErrorResult
& aRv
, PropagateUserInteraction aPropagateUserInteraction
) {
278 if (!jsapi
.Init(mGlobal
)) {
279 aRv
.Throw(NS_ERROR_UNEXPECTED
);
282 JSContext
* cx
= jsapi
.cx();
283 mPromiseObj
= JS::NewPromiseObject(cx
, nullptr);
285 JS_ClearPendingException(cx
);
286 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
289 if (aPropagateUserInteraction
== ePropagateUserInteraction
) {
290 Unused
<< MaybePropagateUserInputEventHandling();
294 void Promise::MaybeResolve(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
295 NS_ASSERT_OWNINGTHREAD(Promise
);
297 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
298 if (!JS::ResolvePromise(aCx
, p
, aValue
)) {
299 // Now what? There's nothing sane to do here.
300 JS_ClearPendingException(aCx
);
304 void Promise::MaybeReject(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
305 NS_ASSERT_OWNINGTHREAD(Promise
);
307 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
308 if (!JS::RejectPromise(aCx
, p
, aValue
)) {
309 // Now what? There's nothing sane to do here.
310 JS_ClearPendingException(aCx
);
314 #define SLOT_NATIVEHANDLER 0
315 #define SLOT_NATIVEHANDLER_TASK 1
317 enum class NativeHandlerTask
: int32_t { Resolve
, Reject
};
320 static bool NativeHandlerCallback(JSContext
* aCx
, unsigned aArgc
,
322 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
325 js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER
);
326 MOZ_ASSERT(v
.isObject());
328 JS::Rooted
<JSObject
*> obj(aCx
, &v
.toObject());
329 PromiseNativeHandler
* handler
= nullptr;
330 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler
, &obj
, handler
))) {
331 return Throw(aCx
, NS_ERROR_UNEXPECTED
);
334 v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER_TASK
);
335 NativeHandlerTask task
= static_cast<NativeHandlerTask
>(v
.toInt32());
337 if (task
== NativeHandlerTask::Resolve
) {
338 // handler is kept alive by "obj" on the stack.
339 MOZ_KnownLive(handler
)->ResolvedCallback(aCx
, args
.get(0));
341 MOZ_ASSERT(task
== NativeHandlerTask::Reject
);
342 // handler is kept alive by "obj" on the stack.
343 MOZ_KnownLive(handler
)->RejectedCallback(aCx
, args
.get(0));
349 static JSObject
* CreateNativeHandlerFunction(JSContext
* aCx
,
350 JS::Handle
<JSObject
*> aHolder
,
351 NativeHandlerTask aTask
) {
352 JSFunction
* func
= js::NewFunctionWithReserved(aCx
, NativeHandlerCallback
,
354 /* flags = */ 0, nullptr);
359 JS::Rooted
<JSObject
*> obj(aCx
, JS_GetFunctionObject(func
));
361 JS::AssertObjectIsNotGray(aHolder
);
362 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER
,
363 JS::ObjectValue(*aHolder
));
364 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER_TASK
,
365 JS::Int32Value(static_cast<int32_t>(aTask
)));
372 class PromiseNativeHandlerShim final
: public PromiseNativeHandler
{
373 RefPtr
<PromiseNativeHandler
> mInner
;
375 ~PromiseNativeHandlerShim() = default;
378 explicit PromiseNativeHandlerShim(PromiseNativeHandler
* aInner
)
384 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
{
385 RefPtr
<PromiseNativeHandler
> inner
= std::move(mInner
);
386 inner
->ResolvedCallback(aCx
, aValue
);
391 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
{
392 RefPtr
<PromiseNativeHandler
> inner
= std::move(mInner
);
393 inner
->RejectedCallback(aCx
, aValue
);
397 bool WrapObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
,
398 JS::MutableHandle
<JSObject
*> aWrapper
) {
399 return PromiseNativeHandler_Binding::Wrap(aCx
, this, aGivenProto
, aWrapper
);
402 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
403 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim
)
406 NS_IMPL_CYCLE_COLLECTION(PromiseNativeHandlerShim
, mInner
)
408 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim
)
409 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim
)
411 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim
)
412 NS_INTERFACE_MAP_ENTRY(nsISupports
)
415 } // anonymous namespace
417 void Promise::AppendNativeHandler(PromiseNativeHandler
* aRunnable
) {
418 NS_ASSERT_OWNINGTHREAD(Promise
);
421 if (NS_WARN_IF(!jsapi
.Init(mGlobal
))) {
422 // Our API doesn't allow us to return a useful error. Not like this should
427 // The self-hosted promise js may keep the object we pass to it alive
428 // for quite a while depending on when GC runs. Therefore, pass a shim
429 // object instead. The shim will free its inner PromiseNativeHandler
430 // after the promise has settled just like our previous c++ promises did.
431 RefPtr
<PromiseNativeHandlerShim
> shim
=
432 new PromiseNativeHandlerShim(aRunnable
);
434 JSContext
* cx
= jsapi
.cx();
435 JS::Rooted
<JSObject
*> handlerWrapper(cx
);
436 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
437 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
438 if (NS_WARN_IF(!shim
->WrapObject(cx
, nullptr, &handlerWrapper
))) {
439 // Again, no way to report errors.
440 jsapi
.ClearException();
444 JS::Rooted
<JSObject
*> resolveFunc(cx
);
445 resolveFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
446 NativeHandlerTask::Resolve
);
447 if (NS_WARN_IF(!resolveFunc
)) {
448 jsapi
.ClearException();
452 JS::Rooted
<JSObject
*> rejectFunc(cx
);
453 rejectFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
454 NativeHandlerTask::Reject
);
455 if (NS_WARN_IF(!rejectFunc
)) {
456 jsapi
.ClearException();
460 JS::Rooted
<JSObject
*> promiseObj(cx
, PromiseObj());
462 !JS::AddPromiseReactions(cx
, promiseObj
, resolveFunc
, rejectFunc
))) {
463 jsapi
.ClearException();
468 void Promise::HandleException(JSContext
* aCx
) {
469 JS::Rooted
<JS::Value
> exn(aCx
);
470 if (JS_GetPendingException(aCx
, &exn
)) {
471 JS_ClearPendingException(aCx
);
472 // Always reject even if this was called in *Resolve.
473 MaybeReject(aCx
, exn
);
478 already_AddRefed
<Promise
> Promise::CreateFromExisting(
479 nsIGlobalObject
* aGlobal
, JS::Handle
<JSObject
*> aPromiseObj
,
480 PropagateUserInteraction aPropagateUserInteraction
) {
482 js::GetObjectCompartment(aGlobal
->GetGlobalJSObjectPreserveColor()) ==
483 js::GetObjectCompartment(aPromiseObj
));
484 RefPtr
<Promise
> p
= new Promise(aGlobal
);
485 p
->mPromiseObj
= aPromiseObj
;
486 if (aPropagateUserInteraction
== ePropagateUserInteraction
&&
487 !p
->MaybePropagateUserInputEventHandling()) {
493 void Promise::MaybeResolveWithUndefined() {
494 NS_ASSERT_OWNINGTHREAD(Promise
);
496 MaybeResolve(JS::UndefinedHandleValue
);
499 void Promise::MaybeReject(const RefPtr
<MediaStreamError
>& aArg
) {
500 NS_ASSERT_OWNINGTHREAD(Promise
);
502 MaybeSomething(aArg
, &Promise::MaybeReject
);
505 void Promise::MaybeRejectWithUndefined() {
506 NS_ASSERT_OWNINGTHREAD(Promise
);
508 MaybeSomething(JS::UndefinedHandleValue
, &Promise::MaybeReject
);
511 void Promise::ReportRejectedPromise(JSContext
* aCx
, JS::HandleObject aPromise
) {
512 MOZ_ASSERT(!js::IsWrapper(aPromise
));
514 MOZ_ASSERT(JS::GetPromiseState(aPromise
) == JS::PromiseState::Rejected
);
516 bool isChrome
= false;
517 nsGlobalWindowInner
* win
= nullptr;
518 uint64_t innerWindowID
= 0;
519 if (MOZ_LIKELY(NS_IsMainThread())) {
520 isChrome
= nsContentUtils::ObjectPrincipal(aPromise
)->IsSystemPrincipal();
521 win
= xpc::WindowGlobalOrNull(aPromise
);
522 innerWindowID
= win
? win
->WindowID() : 0;
523 } else if (const WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate()) {
524 isChrome
= wp
->UsesSystemPrincipal();
525 innerWindowID
= wp
->WindowID();
526 } else if (nsCOMPtr
<nsIGlobalObject
> global
= xpc::NativeGlobal(aPromise
)) {
527 if (nsCOMPtr
<WorkletGlobalScope
> workletGlobal
=
528 do_QueryInterface(global
)) {
529 WorkletImpl
* impl
= workletGlobal
->Impl();
530 isChrome
= impl
->PrincipalInfo().type() ==
531 mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo
;
532 innerWindowID
= impl
->LoadInfo().InnerWindowID();
536 JS::Rooted
<JS::Value
> result(aCx
, JS::GetPromiseResult(aPromise
));
537 // resolutionSite can be null if async stacks are disabled.
538 JS::Rooted
<JSObject
*> resolutionSite(aCx
,
539 JS::GetPromiseResolutionSite(aPromise
));
541 // We're inspecting the rejection value only to report it to the console, and
542 // we do so without side-effects, so we can safely unwrap it without regard to
543 // the privileges of the Promise object that holds it. If we don't unwrap
544 // before trying to create the error report, we wind up reporting any
545 // cross-origin objects as "uncaught exception: Object".
546 RefPtr
<xpc::ErrorReport
> xpcReport
= new xpc::ErrorReport();
548 Maybe
<JSAutoRealm
> ar
;
549 JS::Rooted
<JS::Value
> unwrapped(aCx
, result
);
550 if (unwrapped
.isObject()) {
551 unwrapped
.setObject(*js::UncheckedUnwrap(&unwrapped
.toObject()));
552 ar
.emplace(aCx
, &unwrapped
.toObject());
555 JS::ErrorReportBuilder
report(aCx
);
556 RefPtr
<Exception
> exn
;
557 if (unwrapped
.isObject() &&
558 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException
, &unwrapped
, exn
)) ||
559 NS_SUCCEEDED(UNWRAP_OBJECT(Exception
, &unwrapped
, exn
)))) {
560 xpcReport
->Init(aCx
, exn
, isChrome
, innerWindowID
);
562 // Use the resolution site as the exception stack
563 JS::ExceptionStack
exnStack(aCx
, unwrapped
, resolutionSite
);
564 if (!report
.init(aCx
, exnStack
, JS::ErrorReportBuilder::NoSideEffects
)) {
565 JS_ClearPendingException(aCx
);
569 xpcReport
->Init(report
.report(), report
.toStringResult().c_str(),
570 isChrome
, innerWindowID
);
574 // Used to initialize the similarly named nsISciptError attribute.
575 xpcReport
->mIsPromiseRejection
= true;
577 // Now post an event to do the real reporting async
578 RefPtr
<AsyncErrorReporter
> event
= new AsyncErrorReporter(xpcReport
);
580 if (!win
->IsDying()) {
581 // Exceptions from a dying window will cause the window to leak.
582 event
->SetException(aCx
, result
);
583 if (resolutionSite
) {
584 event
->SerializeStack(aCx
, resolutionSite
);
587 win
->Dispatch(mozilla::TaskCategory::Other
, event
.forget());
589 NS_DispatchToMainThread(event
);
593 void Promise::MaybeResolveWithClone(JSContext
* aCx
,
594 JS::Handle
<JS::Value
> aValue
) {
595 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
596 AutoEntryScript
aes(GetParentObject(), "Promise resolution");
597 JSContext
* cx
= aes
.cx();
598 JS::Rooted
<JS::Value
> value(cx
, aValue
);
600 xpc::StackScopedCloneOptions options
;
601 options
.wrapReflectors
= true;
602 if (!StackScopedClone(cx
, options
, sourceScope
, &value
)) {
606 MaybeResolve(aCx
, value
);
609 void Promise::MaybeRejectWithClone(JSContext
* aCx
,
610 JS::Handle
<JS::Value
> aValue
) {
611 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
612 AutoEntryScript
aes(GetParentObject(), "Promise rejection");
613 JSContext
* cx
= aes
.cx();
614 JS::Rooted
<JS::Value
> value(cx
, aValue
);
616 xpc::StackScopedCloneOptions options
;
617 options
.wrapReflectors
= true;
618 if (!StackScopedClone(cx
, options
, sourceScope
, &value
)) {
622 MaybeReject(aCx
, value
);
625 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
626 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
627 class PromiseWorkerProxyRunnable
: public WorkerRunnable
{
629 PromiseWorkerProxyRunnable(PromiseWorkerProxy
* aPromiseWorkerProxy
,
630 PromiseWorkerProxy::RunCallbackFunc aFunc
)
631 : WorkerRunnable(aPromiseWorkerProxy
->GetWorkerPrivate(),
632 WorkerThreadUnchangedBusyCount
),
633 mPromiseWorkerProxy(aPromiseWorkerProxy
),
635 MOZ_ASSERT(NS_IsMainThread());
636 MOZ_ASSERT(mPromiseWorkerProxy
);
639 virtual bool WorkerRun(JSContext
* aCx
,
640 WorkerPrivate
* aWorkerPrivate
) override
{
641 MOZ_ASSERT(aWorkerPrivate
);
642 aWorkerPrivate
->AssertIsOnWorkerThread();
643 MOZ_ASSERT(aWorkerPrivate
== mWorkerPrivate
);
645 MOZ_ASSERT(mPromiseWorkerProxy
);
646 RefPtr
<Promise
> workerPromise
= mPromiseWorkerProxy
->WorkerPromise();
648 // Here we convert the buffer to a JS::Value.
649 JS::Rooted
<JS::Value
> value(aCx
);
650 if (!mPromiseWorkerProxy
->Read(aCx
, &value
)) {
651 JS_ClearPendingException(aCx
);
655 (workerPromise
->*mFunc
)(aCx
, value
);
657 // Release the Promise because it has been resolved/rejected for sure.
658 mPromiseWorkerProxy
->CleanUp();
663 ~PromiseWorkerProxyRunnable() = default;
666 RefPtr
<PromiseWorkerProxy
> mPromiseWorkerProxy
;
668 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
669 PromiseWorkerProxy::RunCallbackFunc mFunc
;
673 already_AddRefed
<PromiseWorkerProxy
> PromiseWorkerProxy::Create(
674 WorkerPrivate
* aWorkerPrivate
, Promise
* aWorkerPromise
,
675 const PromiseWorkerProxyStructuredCloneCallbacks
* aCb
) {
676 MOZ_ASSERT(aWorkerPrivate
);
677 aWorkerPrivate
->AssertIsOnWorkerThread();
678 MOZ_ASSERT(aWorkerPromise
);
679 MOZ_ASSERT_IF(aCb
, !!aCb
->Write
&& !!aCb
->Read
);
681 RefPtr
<PromiseWorkerProxy
> proxy
=
682 new PromiseWorkerProxy(aWorkerPromise
, aCb
);
684 // We do this to make sure the worker thread won't shut down before the
685 // promise is resolved/rejected on the worker thread.
686 RefPtr
<StrongWorkerRef
> workerRef
= StrongWorkerRef::Create(
687 aWorkerPrivate
, "PromiseWorkerProxy", [proxy
]() { proxy
->CleanUp(); });
689 if (NS_WARN_IF(!workerRef
)) {
690 // Probably the worker is terminating. We cannot complete the operation
691 // and we have to release all the resources.
692 proxy
->CleanProperties();
696 proxy
->mWorkerRef
= new ThreadSafeWorkerRef(workerRef
);
698 // Maintain a reference so that we have a valid object to clean up when
699 // removing the feature.
700 proxy
.get()->AddRef();
702 return proxy
.forget();
705 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy
)
707 PromiseWorkerProxy::PromiseWorkerProxy(
708 Promise
* aWorkerPromise
,
709 const PromiseWorkerProxyStructuredCloneCallbacks
* aCallbacks
)
710 : mWorkerPromise(aWorkerPromise
),
712 mCallbacks(aCallbacks
),
713 mCleanUpLock("cleanUpLock") {}
715 PromiseWorkerProxy::~PromiseWorkerProxy() {
716 MOZ_ASSERT(mCleanedUp
);
717 MOZ_ASSERT(!mWorkerPromise
);
718 MOZ_ASSERT(!mWorkerRef
);
721 void PromiseWorkerProxy::CleanProperties() {
722 MOZ_ASSERT(IsCurrentThreadRunningWorker());
724 // Ok to do this unprotected from Create().
725 // CleanUp() holds the lock before calling this.
727 mWorkerPromise
= nullptr;
728 mWorkerRef
= nullptr;
730 // Clear the StructuredCloneHolderBase class.
734 WorkerPrivate
* PromiseWorkerProxy::GetWorkerPrivate() const {
736 if (NS_IsMainThread()) {
737 mCleanUpLock
.AssertCurrentThreadOwns();
740 // Safe to check this without a lock since we assert lock ownership on the
741 // main thread above.
742 MOZ_ASSERT(!mCleanedUp
);
743 MOZ_ASSERT(mWorkerRef
);
745 return mWorkerRef
->Private();
748 Promise
* PromiseWorkerProxy::WorkerPromise() const {
749 MOZ_ASSERT(IsCurrentThreadRunningWorker());
750 MOZ_ASSERT(mWorkerPromise
);
751 return mWorkerPromise
;
754 void PromiseWorkerProxy::RunCallback(JSContext
* aCx
,
755 JS::Handle
<JS::Value
> aValue
,
756 RunCallbackFunc aFunc
) {
757 MOZ_ASSERT(NS_IsMainThread());
759 MutexAutoLock
lock(Lock());
760 // If the worker thread's been cancelled we don't need to resolve the Promise.
765 // The |aValue| is written into the StructuredCloneHolderBase.
766 if (!Write(aCx
, aValue
)) {
767 JS_ClearPendingException(aCx
);
769 "cannot serialize the value with the StructuredCloneAlgorithm!");
772 RefPtr
<PromiseWorkerProxyRunnable
> runnable
=
773 new PromiseWorkerProxyRunnable(this, aFunc
);
775 runnable
->Dispatch();
778 void PromiseWorkerProxy::ResolvedCallback(JSContext
* aCx
,
779 JS::Handle
<JS::Value
> aValue
) {
780 RunCallback(aCx
, aValue
, &Promise::MaybeResolve
);
783 void PromiseWorkerProxy::RejectedCallback(JSContext
* aCx
,
784 JS::Handle
<JS::Value
> aValue
) {
785 RunCallback(aCx
, aValue
, &Promise::MaybeReject
);
788 void PromiseWorkerProxy::CleanUp() {
789 // Can't release Mutex while it is still locked, so scope the lock.
791 MutexAutoLock
lock(Lock());
797 MOZ_ASSERT(mWorkerRef
);
798 mWorkerRef
->Private()->AssertIsOnWorkerThread();
800 // Release the Promise and remove the PromiseWorkerProxy from the holders of
801 // the worker thread since the Promise has been resolved/rejected or the
802 // worker thread has been cancelled.
803 mWorkerRef
= nullptr;
810 JSObject
* PromiseWorkerProxy::CustomReadHandler(
811 JSContext
* aCx
, JSStructuredCloneReader
* aReader
,
812 const JS::CloneDataPolicy
& aCloneDataPolicy
, uint32_t aTag
,
814 if (NS_WARN_IF(!mCallbacks
)) {
818 return mCallbacks
->Read(aCx
, aReader
, this, aTag
, aIndex
);
821 bool PromiseWorkerProxy::CustomWriteHandler(JSContext
* aCx
,
822 JSStructuredCloneWriter
* aWriter
,
823 JS::Handle
<JSObject
*> aObj
,
824 bool* aSameProcessScopeRequired
) {
825 if (NS_WARN_IF(!mCallbacks
)) {
829 return mCallbacks
->Write(aCx
, aWriter
, this, aObj
);
832 // Specializations of MaybeRejectBrokenly we actually support.
834 void Promise::MaybeRejectBrokenly(const RefPtr
<DOMException
>& aArg
) {
835 MaybeSomething(aArg
, &Promise::MaybeReject
);
838 void Promise::MaybeRejectBrokenly(const nsAString
& aArg
) {
839 MaybeSomething(aArg
, &Promise::MaybeReject
);
842 Promise::PromiseState
Promise::State() const {
843 JS::Rooted
<JSObject
*> p(RootingCx(), PromiseObj());
844 const JS::PromiseState state
= JS::GetPromiseState(p
);
846 if (state
== JS::PromiseState::Fulfilled
) {
847 return PromiseState::Resolved
;
850 if (state
== JS::PromiseState::Rejected
) {
851 return PromiseState::Rejected
;
854 return PromiseState::Pending
;
858 } // namespace mozilla