Bug 1639230: Remove vendor prefix for printing with webdriver r=jgraham,webdriver...
[gecko.git] / dom / promise / Promise.cpp
blob69418152d9969b1fa787ae4325cc5b51189a3346
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/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"
50 namespace mozilla {
51 namespace dom {
53 // Promise
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)
77 NS_INTERFACE_MAP_END
79 Promise::Promise(nsIGlobalObject* aGlobal)
80 : mGlobal(aGlobal), mPromiseObj(nullptr) {
81 MOZ_ASSERT(mGlobal);
83 mozilla::HoldJSObjects(this);
86 Promise::~Promise() { mozilla::DropJSObjects(this); }
88 // static
89 already_AddRefed<Promise> Promise::Create(
90 nsIGlobalObject* aGlobal, ErrorResult& aRv,
91 PropagateUserInteraction aPropagateUserInteraction) {
92 if (!aGlobal) {
93 aRv.Throw(NS_ERROR_UNEXPECTED);
94 return nullptr;
96 RefPtr<Promise> p = new Promise(aGlobal);
97 p->CreateWrapper(aRv, aPropagateUserInteraction);
98 if (aRv.Failed()) {
99 return nullptr;
101 return p.forget();
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);
114 // static
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));
120 if (!p) {
121 aRv.NoteJSContextException(aCx);
122 return nullptr;
125 return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
128 // static
129 already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
130 JSContext* aCx,
131 JS::Handle<JS::Value> aValue,
132 ErrorResult& aRv) {
133 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
134 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
135 if (!p) {
136 aRv.NoteJSContextException(aCx);
137 return nullptr;
140 // This promise will never be resolved, so we pass
141 // eDontPropagateUserInteraction for aPropagateUserInteraction
142 // unconditionally.
143 return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
146 // static
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));
151 if (!globalObj) {
152 aRv.Throw(NS_ERROR_UNEXPECTED);
153 return nullptr;
156 nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
157 if (!global) {
158 aRv.Throw(NS_ERROR_UNEXPECTED);
159 return nullptr;
162 JS::RootedVector<JSObject*> promises(aCx);
163 if (!promises.reserve(aPromiseList.Length())) {
164 aRv.NoteJSContextException(aCx);
165 return nullptr;
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);
173 return nullptr;
175 promises.infallibleAppend(promiseObj);
178 JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
179 if (!result) {
180 aRv.NoteJSContextException(aCx);
181 return nullptr;
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
199 // should be OK.
200 JS::Rooted<JSObject*> promise(aCx, PromiseObj());
201 if (!JS_WrapObject(aCx, &promise)) {
202 aRv.NoteJSContextException(aCx);
203 return;
206 JS::Rooted<JSObject*> resolveCallback(aCx);
207 if (aResolveCallback) {
208 resolveCallback = aResolveCallback->CallbackOrNull();
209 if (!JS_WrapObject(aCx, &resolveCallback)) {
210 aRv.NoteJSContextException(aCx);
211 return;
215 JS::Rooted<JSObject*> rejectCallback(aCx);
216 if (aRejectCallback) {
217 rejectCallback = aRejectCallback->CallbackOrNull();
218 if (!JS_WrapObject(aCx, &rejectCallback)) {
219 aRv.NoteJSContextException(aCx);
220 return;
224 JS::Rooted<JSObject*> retval(aCx);
225 retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
226 rejectCallback);
227 if (!retval) {
228 aRv.NoteJSContextException(aCx);
229 return;
232 aRetval.setObject(*retval);
235 void PromiseNativeThenHandlerBase::ResolvedCallback(
236 JSContext* aCx, JS::Handle<JS::Value> aValue) {
237 RefPtr<Promise> promise = CallResolveCallback(aCx, aValue);
238 if (promise) {
239 mPromise->MaybeResolve(promise);
240 } else {
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)
254 tmp->Traverse(cb);
255 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
257 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
258 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
259 tmp->Unlink();
260 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
262 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
263 NS_INTERFACE_MAP_ENTRY(nsISupports)
264 NS_INTERFACE_MAP_END
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) {
277 AutoJSAPI jsapi;
278 if (!jsapi.Init(mGlobal)) {
279 aRv.Throw(NS_ERROR_UNEXPECTED);
280 return;
282 JSContext* cx = jsapi.cx();
283 mPromiseObj = JS::NewPromiseObject(cx, nullptr);
284 if (!mPromiseObj) {
285 JS_ClearPendingException(cx);
286 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
287 return;
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 };
319 MOZ_CAN_RUN_SCRIPT
320 static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
321 JS::Value* aVp) {
322 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
324 JS::Value v =
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));
340 } else {
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));
346 return true;
349 static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
350 JS::Handle<JSObject*> aHolder,
351 NativeHandlerTask aTask) {
352 JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
353 /* nargs = */ 1,
354 /* flags = */ 0, nullptr);
355 if (!func) {
356 return 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)));
367 return obj;
370 namespace {
372 class PromiseNativeHandlerShim final : public PromiseNativeHandler {
373 RefPtr<PromiseNativeHandler> mInner;
375 ~PromiseNativeHandlerShim() = default;
377 public:
378 explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
379 : mInner(aInner) {
380 MOZ_ASSERT(mInner);
383 MOZ_CAN_RUN_SCRIPT
384 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
385 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
386 inner->ResolvedCallback(aCx, aValue);
387 MOZ_ASSERT(!mInner);
390 MOZ_CAN_RUN_SCRIPT
391 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
392 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
393 inner->RejectedCallback(aCx, aValue);
394 MOZ_ASSERT(!mInner);
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)
413 NS_INTERFACE_MAP_END
415 } // anonymous namespace
417 void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
418 NS_ASSERT_OWNINGTHREAD(Promise);
420 AutoJSAPI jsapi;
421 if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
422 // Our API doesn't allow us to return a useful error. Not like this should
423 // happen anyway.
424 return;
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();
441 return;
444 JS::Rooted<JSObject*> resolveFunc(cx);
445 resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
446 NativeHandlerTask::Resolve);
447 if (NS_WARN_IF(!resolveFunc)) {
448 jsapi.ClearException();
449 return;
452 JS::Rooted<JSObject*> rejectFunc(cx);
453 rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
454 NativeHandlerTask::Reject);
455 if (NS_WARN_IF(!rejectFunc)) {
456 jsapi.ClearException();
457 return;
460 JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
461 if (NS_WARN_IF(
462 !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
463 jsapi.ClearException();
464 return;
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);
477 // static
478 already_AddRefed<Promise> Promise::CreateFromExisting(
479 nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
480 PropagateUserInteraction aPropagateUserInteraction) {
481 MOZ_ASSERT(
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()) {
488 return nullptr;
490 return p.forget();
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);
561 } else {
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);
566 return;
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);
579 if (win) {
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());
588 } else {
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)) {
603 HandleException(cx);
604 return;
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)) {
619 HandleException(cx);
620 return;
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 {
628 public:
629 PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
630 PromiseWorkerProxy::RunCallbackFunc aFunc)
631 : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
632 WorkerThreadUnchangedBusyCount),
633 mPromiseWorkerProxy(aPromiseWorkerProxy),
634 mFunc(aFunc) {
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);
652 return false;
655 (workerPromise->*mFunc)(aCx, value);
657 // Release the Promise because it has been resolved/rejected for sure.
658 mPromiseWorkerProxy->CleanUp();
659 return true;
662 protected:
663 ~PromiseWorkerProxyRunnable() = default;
665 private:
666 RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
668 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
669 PromiseWorkerProxy::RunCallbackFunc mFunc;
672 /* static */
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();
693 return nullptr;
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),
711 mCleanedUp(false),
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.
726 mCleanedUp = true;
727 mWorkerPromise = nullptr;
728 mWorkerRef = nullptr;
730 // Clear the StructuredCloneHolderBase class.
731 Clear();
734 WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
735 #ifdef DEBUG
736 if (NS_IsMainThread()) {
737 mCleanUpLock.AssertCurrentThreadOwns();
739 #endif
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.
761 if (CleanedUp()) {
762 return;
765 // The |aValue| is written into the StructuredCloneHolderBase.
766 if (!Write(aCx, aValue)) {
767 JS_ClearPendingException(aCx);
768 MOZ_ASSERT(false,
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());
793 if (CleanedUp()) {
794 return;
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;
805 CleanProperties();
807 Release();
810 JSObject* PromiseWorkerProxy::CustomReadHandler(
811 JSContext* aCx, JSStructuredCloneReader* aReader,
812 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
813 uint32_t aIndex) {
814 if (NS_WARN_IF(!mCallbacks)) {
815 return nullptr;
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)) {
826 return false;
829 return mCallbacks->Write(aCx, aWriter, this, aObj);
832 // Specializations of MaybeRejectBrokenly we actually support.
833 template <>
834 void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
835 MaybeSomething(aArg, &Promise::MaybeReject);
837 template <>
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;
857 } // namespace dom
858 } // namespace mozilla