Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / promise / Promise.cpp
blob4eca3fe4d1f968107836bef1d7a1eb5841dcd14e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Promise.h"
8 #include "mozilla/dom/Promise-inl.h"
10 #include "js/Debug.h"
12 #include "mozilla/Atomics.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/CycleCollectedJSContext.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/OwningNonNull.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/ResultExtensions.h"
19 #include "mozilla/Unused.h"
21 #include "mozilla/dom/AutoEntryScript.h"
22 #include "mozilla/dom/BindingUtils.h"
23 #include "mozilla/dom/DOMException.h"
24 #include "mozilla/dom/DOMExceptionBinding.h"
25 #include "mozilla/dom/Exceptions.h"
26 #include "mozilla/dom/MediaStreamError.h"
27 #include "mozilla/dom/PromiseBinding.h"
28 #include "mozilla/dom/ScriptSettings.h"
29 #include "mozilla/dom/UserActivation.h"
30 #include "mozilla/dom/WorkerPrivate.h"
31 #include "mozilla/dom/WorkerRunnable.h"
32 #include "mozilla/dom/WorkerRef.h"
33 #include "mozilla/dom/WorkletImpl.h"
34 #include "mozilla/dom/WorkletGlobalScope.h"
36 #include "jsfriendapi.h"
37 #include "js/Exception.h" // JS::ExceptionStack
38 #include "js/Object.h" // JS::GetCompartment
39 #include "js/StructuredClone.h"
40 #include "nsContentUtils.h"
41 #include "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"
54 namespace mozilla {
55 namespace dom {
57 // Promise
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) {
80 MOZ_ASSERT(mGlobal);
82 mozilla::HoldJSObjects(this);
85 Promise::~Promise() { mozilla::DropJSObjects(this); }
87 // static
88 already_AddRefed<Promise> Promise::Create(
89 nsIGlobalObject* aGlobal, ErrorResult& aRv,
90 PropagateUserInteraction aPropagateUserInteraction) {
91 if (!aGlobal) {
92 aRv.Throw(NS_ERROR_UNEXPECTED);
93 return nullptr;
95 RefPtr<Promise> p = new Promise(aGlobal);
96 p->CreateWrapper(aRv, aPropagateUserInteraction);
97 if (aRv.Failed()) {
98 return nullptr;
100 return p.forget();
103 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);
113 // static
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));
119 if (!p) {
120 aRv.NoteJSContextException(aCx);
121 return nullptr;
124 return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
127 // static
128 already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
129 JSContext* aCx,
130 JS::Handle<JS::Value> aValue,
131 ErrorResult& aRv) {
132 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
133 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
134 if (!p) {
135 aRv.NoteJSContextException(aCx);
136 return nullptr;
139 // This promise will never be resolved, so we pass
140 // eDontPropagateUserInteraction for aPropagateUserInteraction
141 // unconditionally.
142 return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
145 // static
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));
150 if (!globalObj) {
151 aRv.Throw(NS_ERROR_UNEXPECTED);
152 return nullptr;
155 nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
156 if (!global) {
157 aRv.Throw(NS_ERROR_UNEXPECTED);
158 return nullptr;
161 JS::RootedVector<JSObject*> promises(aCx);
162 if (!promises.reserve(aPromiseList.Length())) {
163 aRv.NoteJSContextException(aCx);
164 return nullptr;
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);
172 return nullptr;
174 promises.infallibleAppend(promiseObj);
177 JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
178 if (!result) {
179 aRv.NoteJSContextException(aCx);
180 return nullptr;
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
198 // should be OK.
199 JS::Rooted<JSObject*> promise(aCx, PromiseObj());
200 if (!JS_WrapObject(aCx, &promise)) {
201 aRv.NoteJSContextException(aCx);
202 return;
205 JS::Rooted<JSObject*> resolveCallback(aCx);
206 if (aResolveCallback) {
207 resolveCallback = aResolveCallback->CallbackOrNull();
208 if (!JS_WrapObject(aCx, &resolveCallback)) {
209 aRv.NoteJSContextException(aCx);
210 return;
214 JS::Rooted<JSObject*> rejectCallback(aCx);
215 if (aRejectCallback) {
216 rejectCallback = aRejectCallback->CallbackOrNull();
217 if (!JS_WrapObject(aCx, &rejectCallback)) {
218 aRv.NoteJSContextException(aCx);
219 return;
223 JS::Rooted<JSObject*> retval(aCx);
224 retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
225 rejectCallback);
226 if (!retval) {
227 aRv.NoteJSContextException(aCx);
228 return;
231 aRetval.setObject(*retval);
234 void PromiseNativeThenHandlerBase::ResolvedCallback(
235 JSContext* aCx, JS::Handle<JS::Value> aValue) {
236 RefPtr<Promise> promise = CallResolveCallback(aCx, aValue);
237 if (promise) {
238 mPromise->MaybeResolve(promise);
239 } else {
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)
253 tmp->Traverse(cb);
254 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
256 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
257 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
258 tmp->Unlink();
259 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
261 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
262 NS_INTERFACE_MAP_ENTRY(nsISupports)
263 NS_INTERFACE_MAP_END
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) {
276 AutoJSAPI jsapi;
277 if (!jsapi.Init(mGlobal)) {
278 aRv.Throw(NS_ERROR_UNEXPECTED);
279 return;
281 JSContext* cx = jsapi.cx();
282 mPromiseObj = JS::NewPromiseObject(cx, nullptr);
283 if (!mPromiseObj) {
284 JS_ClearPendingException(cx);
285 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
286 return;
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 };
318 MOZ_CAN_RUN_SCRIPT
319 static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
320 JS::Value* aVp) {
321 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
323 JS::Value v =
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));
339 } else {
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));
345 return true;
348 static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
349 JS::Handle<JSObject*> aHolder,
350 NativeHandlerTask aTask) {
351 JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
352 /* nargs = */ 1,
353 /* flags = */ 0, nullptr);
354 if (!func) {
355 return 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)));
366 return obj;
369 namespace {
371 class PromiseNativeHandlerShim final : public PromiseNativeHandler {
372 RefPtr<PromiseNativeHandler> mInner;
374 ~PromiseNativeHandlerShim() = default;
376 public:
377 explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
378 : mInner(aInner) {
379 MOZ_ASSERT(mInner);
382 MOZ_CAN_RUN_SCRIPT
383 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
384 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
385 inner->ResolvedCallback(aCx, aValue);
386 MOZ_ASSERT(!mInner);
389 MOZ_CAN_RUN_SCRIPT
390 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
391 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
392 inner->RejectedCallback(aCx, aValue);
393 MOZ_ASSERT(!mInner);
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)
412 NS_INTERFACE_MAP_END
414 } // anonymous namespace
416 void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
417 NS_ASSERT_OWNINGTHREAD(Promise);
419 AutoJSAPI jsapi;
420 if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
421 // Our API doesn't allow us to return a useful error. Not like this should
422 // happen anyway.
423 return;
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();
440 return;
443 JS::Rooted<JSObject*> resolveFunc(cx);
444 resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
445 NativeHandlerTask::Resolve);
446 if (NS_WARN_IF(!resolveFunc)) {
447 jsapi.ClearException();
448 return;
451 JS::Rooted<JSObject*> rejectFunc(cx);
452 rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
453 NativeHandlerTask::Reject);
454 if (NS_WARN_IF(!rejectFunc)) {
455 jsapi.ClearException();
456 return;
459 JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
460 if (NS_WARN_IF(
461 !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
462 jsapi.ClearException();
463 return;
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);
476 // static
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()) {
486 return nullptr;
488 return p.forget();
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
526 // window.
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);
567 } else {
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);
572 return;
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());
594 } else {
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)) {
609 HandleException(cx);
610 return;
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)) {
625 HandleException(cx);
626 return;
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 {
634 public:
635 PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
636 PromiseWorkerProxy::RunCallbackFunc aFunc)
637 : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
638 WorkerThreadUnchangedBusyCount),
639 mPromiseWorkerProxy(aPromiseWorkerProxy),
640 mFunc(aFunc) {
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);
658 return false;
661 (workerPromise->*mFunc)(aCx, value);
663 // Release the Promise because it has been resolved/rejected for sure.
664 mPromiseWorkerProxy->CleanUp();
665 return true;
668 protected:
669 ~PromiseWorkerProxyRunnable() = default;
671 private:
672 RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
674 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
675 PromiseWorkerProxy::RunCallbackFunc mFunc;
678 /* static */
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();
699 return nullptr;
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),
717 mCleanedUp(false),
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.
732 mCleanedUp = true;
733 mWorkerPromise = nullptr;
734 mWorkerRef = nullptr;
736 // Clear the StructuredCloneHolderBase class.
737 Clear();
740 WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
741 #ifdef DEBUG
742 if (NS_IsMainThread()) {
743 mCleanUpLock.AssertCurrentThreadOwns();
745 #endif
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.
767 if (CleanedUp()) {
768 return;
771 // The |aValue| is written into the StructuredCloneHolderBase.
772 if (!Write(aCx, aValue)) {
773 JS_ClearPendingException(aCx);
774 MOZ_ASSERT(false,
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());
799 if (CleanedUp()) {
800 return;
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;
811 CleanProperties();
813 Release();
816 JSObject* PromiseWorkerProxy::CustomReadHandler(
817 JSContext* aCx, JSStructuredCloneReader* aReader,
818 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
819 uint32_t aIndex) {
820 if (NS_WARN_IF(!mCallbacks)) {
821 return nullptr;
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)) {
832 return false;
835 return mCallbacks->Write(aCx, aWriter, this, aObj);
838 // Specializations of MaybeRejectBrokenly we actually support.
839 template <>
840 void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
841 MaybeSomething(aArg, &Promise::MaybeReject);
843 template <>
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);
871 } // namespace dom
872 } // namespace mozilla
874 extern "C" {
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);
881 aPromise->AddRef();
884 void DomPromise_Release(mozilla::dom::Promise* aPromise) {
885 MOZ_ASSERT(aPromise);
886 aPromise->Release();
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"); \
902 return; \
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