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/CycleCollectedJSContext.h"
14 #include "mozilla/EventStateManager.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/WorkerPrivate.h"
27 #include "mozilla/dom/WorkerRunnable.h"
28 #include "mozilla/dom/WorkerRef.h"
30 #include "jsfriendapi.h"
31 #include "js/StructuredClone.h"
32 #include "nsContentUtils.h"
33 #include "nsGlobalWindow.h"
34 #include "nsIScriptObjectPrincipal.h"
35 #include "nsJSEnvironment.h"
36 #include "nsJSPrincipals.h"
37 #include "nsJSUtils.h"
38 #include "nsPIDOMWindow.h"
39 #include "PromiseDebugging.h"
40 #include "PromiseNativeHandler.h"
41 #include "PromiseWorkerProxy.h"
42 #include "WrapperFactory.h"
43 #include "xpcpublic.h"
44 #include "xpcprivate.h"
50 // Generator used by Promise::GetID.
51 Atomic
<uintptr_t> gIDGenerator(0);
56 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise
)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
)
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(nullptr, aRv
, aPropagateUserInteraction
);
104 bool Promise::MaybePropagateUserInputEventHandling() {
105 JS::PromiseUserInputEventHandlingState state
=
106 EventStateManager::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
->MaybeResolve(JS::UndefinedHandleValue
);
245 void PromiseNativeThenHandlerBase::RejectedCallback(
246 JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
247 mPromise
->MaybeReject(aCx
, 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 JS::Handle
<JSObject
*> aDesiredProto
, ErrorResult
& aRv
,
277 PropagateUserInteraction aPropagateUserInteraction
) {
279 if (!jsapi
.Init(mGlobal
)) {
280 aRv
.Throw(NS_ERROR_UNEXPECTED
);
283 JSContext
* cx
= jsapi
.cx();
284 mPromiseObj
= JS::NewPromiseObject(cx
, nullptr, aDesiredProto
);
286 JS_ClearPendingException(cx
);
287 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
290 if (aPropagateUserInteraction
== ePropagateUserInteraction
) {
291 Unused
<< MaybePropagateUserInputEventHandling();
295 void Promise::MaybeResolve(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
296 NS_ASSERT_OWNINGTHREAD(Promise
);
298 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
299 if (!JS::ResolvePromise(aCx
, p
, aValue
)) {
300 // Now what? There's nothing sane to do here.
301 JS_ClearPendingException(aCx
);
305 void Promise::MaybeReject(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
306 NS_ASSERT_OWNINGTHREAD(Promise
);
308 JS::Rooted
<JSObject
*> p(aCx
, PromiseObj());
309 if (!JS::RejectPromise(aCx
, p
, aValue
)) {
310 // Now what? There's nothing sane to do here.
311 JS_ClearPendingException(aCx
);
315 #define SLOT_NATIVEHANDLER 0
316 #define SLOT_NATIVEHANDLER_TASK 1
318 enum class NativeHandlerTask
: int32_t { Resolve
, Reject
};
321 static bool NativeHandlerCallback(JSContext
* aCx
, unsigned aArgc
,
323 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
326 js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER
);
327 MOZ_ASSERT(v
.isObject());
329 JS::Rooted
<JSObject
*> obj(aCx
, &v
.toObject());
330 PromiseNativeHandler
* handler
= nullptr;
331 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler
, &obj
, handler
))) {
332 return Throw(aCx
, NS_ERROR_UNEXPECTED
);
335 v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_NATIVEHANDLER_TASK
);
336 NativeHandlerTask task
= static_cast<NativeHandlerTask
>(v
.toInt32());
338 if (task
== NativeHandlerTask::Resolve
) {
339 // handler is kept alive by "obj" on the stack.
340 MOZ_KnownLive(handler
)->ResolvedCallback(aCx
, args
.get(0));
342 MOZ_ASSERT(task
== NativeHandlerTask::Reject
);
343 // handler is kept alive by "obj" on the stack.
344 MOZ_KnownLive(handler
)->RejectedCallback(aCx
, args
.get(0));
350 static JSObject
* CreateNativeHandlerFunction(JSContext
* aCx
,
351 JS::Handle
<JSObject
*> aHolder
,
352 NativeHandlerTask aTask
) {
353 JSFunction
* func
= js::NewFunctionWithReserved(aCx
, NativeHandlerCallback
,
355 /* flags = */ 0, nullptr);
360 JS::Rooted
<JSObject
*> obj(aCx
, JS_GetFunctionObject(func
));
362 JS::AssertObjectIsNotGray(aHolder
);
363 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER
,
364 JS::ObjectValue(*aHolder
));
365 js::SetFunctionNativeReserved(obj
, SLOT_NATIVEHANDLER_TASK
,
366 JS::Int32Value(static_cast<int32_t>(aTask
)));
373 class PromiseNativeHandlerShim final
: public PromiseNativeHandler
{
374 RefPtr
<PromiseNativeHandler
> mInner
;
376 ~PromiseNativeHandlerShim() {}
379 explicit PromiseNativeHandlerShim(PromiseNativeHandler
* aInner
)
385 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
{
386 RefPtr
<PromiseNativeHandler
> inner
= mInner
.forget();
387 inner
->ResolvedCallback(aCx
, aValue
);
392 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
{
393 RefPtr
<PromiseNativeHandler
> inner
= mInner
.forget();
394 inner
->RejectedCallback(aCx
, aValue
);
398 bool WrapObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
,
399 JS::MutableHandle
<JSObject
*> aWrapper
) {
400 return PromiseNativeHandler_Binding::Wrap(aCx
, this, aGivenProto
, aWrapper
);
403 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
404 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim
)
407 NS_IMPL_CYCLE_COLLECTION(PromiseNativeHandlerShim
, mInner
)
409 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim
)
410 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim
)
412 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim
)
413 NS_INTERFACE_MAP_ENTRY(nsISupports
)
416 } // anonymous namespace
418 void Promise::AppendNativeHandler(PromiseNativeHandler
* aRunnable
) {
419 NS_ASSERT_OWNINGTHREAD(Promise
);
422 if (NS_WARN_IF(!jsapi
.Init(mGlobal
))) {
423 // Our API doesn't allow us to return a useful error. Not like this should
428 // The self-hosted promise js may keep the object we pass to it alive
429 // for quite a while depending on when GC runs. Therefore, pass a shim
430 // object instead. The shim will free its inner PromiseNativeHandler
431 // after the promise has settled just like our previous c++ promises did.
432 RefPtr
<PromiseNativeHandlerShim
> shim
=
433 new PromiseNativeHandlerShim(aRunnable
);
435 JSContext
* cx
= jsapi
.cx();
436 JS::Rooted
<JSObject
*> handlerWrapper(cx
);
437 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
438 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
439 if (NS_WARN_IF(!shim
->WrapObject(cx
, nullptr, &handlerWrapper
))) {
440 // Again, no way to report errors.
441 jsapi
.ClearException();
445 JS::Rooted
<JSObject
*> resolveFunc(cx
);
446 resolveFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
447 NativeHandlerTask::Resolve
);
448 if (NS_WARN_IF(!resolveFunc
)) {
449 jsapi
.ClearException();
453 JS::Rooted
<JSObject
*> rejectFunc(cx
);
454 rejectFunc
= CreateNativeHandlerFunction(cx
, handlerWrapper
,
455 NativeHandlerTask::Reject
);
456 if (NS_WARN_IF(!rejectFunc
)) {
457 jsapi
.ClearException();
461 JS::Rooted
<JSObject
*> promiseObj(cx
, PromiseObj());
463 !JS::AddPromiseReactions(cx
, promiseObj
, resolveFunc
, rejectFunc
))) {
464 jsapi
.ClearException();
469 void Promise::HandleException(JSContext
* aCx
) {
470 JS::Rooted
<JS::Value
> exn(aCx
);
471 if (JS_GetPendingException(aCx
, &exn
)) {
472 JS_ClearPendingException(aCx
);
473 // This is only called from MaybeSomething, so it's OK to MaybeReject here.
474 MaybeReject(aCx
, exn
);
479 already_AddRefed
<Promise
> Promise::CreateFromExisting(
480 nsIGlobalObject
* aGlobal
, JS::Handle
<JSObject
*> aPromiseObj
,
481 PropagateUserInteraction aPropagateUserInteraction
) {
483 js::GetObjectCompartment(aGlobal
->GetGlobalJSObjectPreserveColor()) ==
484 js::GetObjectCompartment(aPromiseObj
));
485 RefPtr
<Promise
> p
= new Promise(aGlobal
);
486 p
->mPromiseObj
= aPromiseObj
;
487 if (aPropagateUserInteraction
== ePropagateUserInteraction
&&
488 !p
->MaybePropagateUserInputEventHandling()) {
494 void Promise::MaybeResolveWithUndefined() {
495 NS_ASSERT_OWNINGTHREAD(Promise
);
497 MaybeResolve(JS::UndefinedHandleValue
);
500 void Promise::MaybeReject(const RefPtr
<MediaStreamError
>& aArg
) {
501 NS_ASSERT_OWNINGTHREAD(Promise
);
503 MaybeSomething(aArg
, &Promise::MaybeReject
);
506 void Promise::MaybeRejectWithUndefined() {
507 NS_ASSERT_OWNINGTHREAD(Promise
);
509 MaybeSomething(JS::UndefinedHandleValue
, &Promise::MaybeReject
);
512 void Promise::ReportRejectedPromise(JSContext
* aCx
, JS::HandleObject aPromise
) {
513 MOZ_ASSERT(!js::IsWrapper(aPromise
));
515 MOZ_ASSERT(JS::GetPromiseState(aPromise
) == JS::PromiseState::Rejected
);
517 JS::Rooted
<JS::Value
> result(aCx
, JS::GetPromiseResult(aPromise
));
519 RefPtr
<xpc::ErrorReport
> xpcReport
= new xpc::ErrorReport();
520 bool isMainThread
= MOZ_LIKELY(NS_IsMainThread());
521 bool isChrome
= isMainThread
? nsContentUtils::IsSystemPrincipal(
522 nsContentUtils::ObjectPrincipal(aPromise
))
523 : IsCurrentThreadRunningChromeWorker();
524 nsGlobalWindowInner
* win
=
525 isMainThread
? xpc::WindowGlobalOrNull(aPromise
) : nullptr;
527 js::ErrorReport
report(aCx
);
528 if (report
.init(aCx
, result
, js::ErrorReport::NoSideEffects
)) {
529 xpcReport
->Init(report
.report(), report
.toStringResult().c_str(), isChrome
,
530 win
? win
->WindowID() : 0);
532 JS_ClearPendingException(aCx
);
534 RefPtr
<Exception
> exn
;
535 if (result
.isObject() &&
536 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException
, &result
, exn
)) ||
537 NS_SUCCEEDED(UNWRAP_OBJECT(Exception
, &result
, exn
)))) {
538 xpcReport
->Init(aCx
, exn
, isChrome
, win
? win
->WindowID() : 0);
544 // Now post an event to do the real reporting async
545 RefPtr
<nsIRunnable
> event
= new AsyncErrorReporter(xpcReport
);
547 win
->Dispatch(mozilla::TaskCategory::Other
, event
.forget());
549 NS_DispatchToMainThread(event
);
553 void Promise::MaybeResolveWithClone(JSContext
* aCx
,
554 JS::Handle
<JS::Value
> aValue
) {
555 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
556 AutoEntryScript
aes(GetParentObject(), "Promise resolution");
557 JSContext
* cx
= aes
.cx();
558 JS::Rooted
<JS::Value
> value(cx
, aValue
);
560 xpc::StackScopedCloneOptions options
;
561 options
.wrapReflectors
= true;
562 StackScopedClone(cx
, options
, sourceScope
, &value
);
563 MaybeResolve(aCx
, value
);
566 void Promise::MaybeRejectWithClone(JSContext
* aCx
,
567 JS::Handle
<JS::Value
> aValue
) {
568 JS::Rooted
<JSObject
*> sourceScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
569 AutoEntryScript
aes(GetParentObject(), "Promise rejection");
570 JSContext
* cx
= aes
.cx();
571 JS::Rooted
<JS::Value
> value(cx
, aValue
);
573 xpc::StackScopedCloneOptions options
;
574 options
.wrapReflectors
= true;
575 StackScopedClone(cx
, options
, sourceScope
, &value
);
576 MaybeReject(aCx
, value
);
579 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
580 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
581 class PromiseWorkerProxyRunnable
: public WorkerRunnable
{
583 PromiseWorkerProxyRunnable(PromiseWorkerProxy
* aPromiseWorkerProxy
,
584 PromiseWorkerProxy::RunCallbackFunc aFunc
)
585 : WorkerRunnable(aPromiseWorkerProxy
->GetWorkerPrivate(),
586 WorkerThreadUnchangedBusyCount
),
587 mPromiseWorkerProxy(aPromiseWorkerProxy
),
589 MOZ_ASSERT(NS_IsMainThread());
590 MOZ_ASSERT(mPromiseWorkerProxy
);
593 virtual bool WorkerRun(JSContext
* aCx
,
594 WorkerPrivate
* aWorkerPrivate
) override
{
595 MOZ_ASSERT(aWorkerPrivate
);
596 aWorkerPrivate
->AssertIsOnWorkerThread();
597 MOZ_ASSERT(aWorkerPrivate
== mWorkerPrivate
);
599 MOZ_ASSERT(mPromiseWorkerProxy
);
600 RefPtr
<Promise
> workerPromise
= mPromiseWorkerProxy
->WorkerPromise();
602 // Here we convert the buffer to a JS::Value.
603 JS::Rooted
<JS::Value
> value(aCx
);
604 if (!mPromiseWorkerProxy
->Read(aCx
, &value
)) {
605 JS_ClearPendingException(aCx
);
609 (workerPromise
->*mFunc
)(aCx
, value
);
611 // Release the Promise because it has been resolved/rejected for sure.
612 mPromiseWorkerProxy
->CleanUp();
617 ~PromiseWorkerProxyRunnable() {}
620 RefPtr
<PromiseWorkerProxy
> mPromiseWorkerProxy
;
622 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
623 PromiseWorkerProxy::RunCallbackFunc mFunc
;
627 already_AddRefed
<PromiseWorkerProxy
> PromiseWorkerProxy::Create(
628 WorkerPrivate
* aWorkerPrivate
, Promise
* aWorkerPromise
,
629 const PromiseWorkerProxyStructuredCloneCallbacks
* aCb
) {
630 MOZ_ASSERT(aWorkerPrivate
);
631 aWorkerPrivate
->AssertIsOnWorkerThread();
632 MOZ_ASSERT(aWorkerPromise
);
633 MOZ_ASSERT_IF(aCb
, !!aCb
->Write
&& !!aCb
->Read
);
635 RefPtr
<PromiseWorkerProxy
> proxy
=
636 new PromiseWorkerProxy(aWorkerPromise
, aCb
);
638 // We do this to make sure the worker thread won't shut down before the
639 // promise is resolved/rejected on the worker thread.
640 RefPtr
<StrongWorkerRef
> workerRef
= StrongWorkerRef::Create(
641 aWorkerPrivate
, "PromiseWorkerProxy", [proxy
]() { proxy
->CleanUp(); });
643 if (NS_WARN_IF(!workerRef
)) {
644 // Probably the worker is terminating. We cannot complete the operation
645 // and we have to release all the resources.
646 proxy
->CleanProperties();
650 proxy
->mWorkerRef
= new ThreadSafeWorkerRef(workerRef
);
652 // Maintain a reference so that we have a valid object to clean up when
653 // removing the feature.
654 proxy
.get()->AddRef();
656 return proxy
.forget();
659 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy
)
661 PromiseWorkerProxy::PromiseWorkerProxy(
662 Promise
* aWorkerPromise
,
663 const PromiseWorkerProxyStructuredCloneCallbacks
* aCallbacks
)
664 : mWorkerPromise(aWorkerPromise
),
666 mCallbacks(aCallbacks
),
667 mCleanUpLock("cleanUpLock") {}
669 PromiseWorkerProxy::~PromiseWorkerProxy() {
670 MOZ_ASSERT(mCleanedUp
);
671 MOZ_ASSERT(!mWorkerPromise
);
672 MOZ_ASSERT(!mWorkerRef
);
675 void PromiseWorkerProxy::CleanProperties() {
676 MOZ_ASSERT(IsCurrentThreadRunningWorker());
678 // Ok to do this unprotected from Create().
679 // CleanUp() holds the lock before calling this.
681 mWorkerPromise
= nullptr;
682 mWorkerRef
= nullptr;
684 // Clear the StructuredCloneHolderBase class.
688 WorkerPrivate
* PromiseWorkerProxy::GetWorkerPrivate() const {
690 if (NS_IsMainThread()) {
691 mCleanUpLock
.AssertCurrentThreadOwns();
694 // Safe to check this without a lock since we assert lock ownership on the
695 // main thread above.
696 MOZ_ASSERT(!mCleanedUp
);
697 MOZ_ASSERT(mWorkerRef
);
699 return mWorkerRef
->Private();
702 Promise
* PromiseWorkerProxy::WorkerPromise() const {
703 MOZ_ASSERT(IsCurrentThreadRunningWorker());
704 MOZ_ASSERT(mWorkerPromise
);
705 return mWorkerPromise
;
708 void PromiseWorkerProxy::RunCallback(JSContext
* aCx
,
709 JS::Handle
<JS::Value
> aValue
,
710 RunCallbackFunc aFunc
) {
711 MOZ_ASSERT(NS_IsMainThread());
713 MutexAutoLock
lock(Lock());
714 // If the worker thread's been cancelled we don't need to resolve the Promise.
719 // The |aValue| is written into the StructuredCloneHolderBase.
720 if (!Write(aCx
, aValue
)) {
721 JS_ClearPendingException(aCx
);
723 "cannot serialize the value with the StructuredCloneAlgorithm!");
726 RefPtr
<PromiseWorkerProxyRunnable
> runnable
=
727 new PromiseWorkerProxyRunnable(this, aFunc
);
729 runnable
->Dispatch();
732 void PromiseWorkerProxy::ResolvedCallback(JSContext
* aCx
,
733 JS::Handle
<JS::Value
> aValue
) {
734 RunCallback(aCx
, aValue
, &Promise::MaybeResolve
);
737 void PromiseWorkerProxy::RejectedCallback(JSContext
* aCx
,
738 JS::Handle
<JS::Value
> aValue
) {
739 RunCallback(aCx
, aValue
, &Promise::MaybeReject
);
742 void PromiseWorkerProxy::CleanUp() {
743 // Can't release Mutex while it is still locked, so scope the lock.
745 MutexAutoLock
lock(Lock());
751 MOZ_ASSERT(mWorkerRef
);
752 mWorkerRef
->Private()->AssertIsOnWorkerThread();
754 // Release the Promise and remove the PromiseWorkerProxy from the holders of
755 // the worker thread since the Promise has been resolved/rejected or the
756 // worker thread has been cancelled.
757 mWorkerRef
= nullptr;
764 JSObject
* PromiseWorkerProxy::CustomReadHandler(
765 JSContext
* aCx
, JSStructuredCloneReader
* aReader
, uint32_t aTag
,
767 if (NS_WARN_IF(!mCallbacks
)) {
771 return mCallbacks
->Read(aCx
, aReader
, this, aTag
, aIndex
);
774 bool PromiseWorkerProxy::CustomWriteHandler(JSContext
* aCx
,
775 JSStructuredCloneWriter
* aWriter
,
776 JS::Handle
<JSObject
*> aObj
) {
777 if (NS_WARN_IF(!mCallbacks
)) {
781 return mCallbacks
->Write(aCx
, aWriter
, this, aObj
);
784 // Specializations of MaybeRejectBrokenly we actually support.
786 void Promise::MaybeRejectBrokenly(const RefPtr
<DOMException
>& aArg
) {
787 MaybeSomething(aArg
, &Promise::MaybeReject
);
790 void Promise::MaybeRejectBrokenly(const nsAString
& aArg
) {
791 MaybeSomething(aArg
, &Promise::MaybeReject
);
794 Promise::PromiseState
Promise::State() const {
795 JS::Rooted
<JSObject
*> p(RootingCx(), PromiseObj());
796 const JS::PromiseState state
= JS::GetPromiseState(p
);
798 if (state
== JS::PromiseState::Fulfilled
) {
799 return PromiseState::Resolved
;
802 if (state
== JS::PromiseState::Rejected
) {
803 return PromiseState::Rejected
;
806 return PromiseState::Pending
;
810 } // namespace mozilla