Bug 1853814 [wpt PR 42017] - LoAF: Expose script URL for promise resolvers, a=testonly
[gecko.git] / dom / promise / Promise.cpp
blobc44e053172d1beeec6052d4b4a03092ff2793512
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 "nsCycleCollectionParticipant.h"
42 #include "nsDebug.h"
43 #include "nsGlobalWindowInner.h"
44 #include "nsIScriptObjectPrincipal.h"
45 #include "nsJSEnvironment.h"
46 #include "nsJSPrincipals.h"
47 #include "nsJSUtils.h"
48 #include "nsPIDOMWindow.h"
49 #include "PromiseDebugging.h"
50 #include "PromiseNativeHandler.h"
51 #include "PromiseWorkerProxy.h"
52 #include "WrapperFactory.h"
53 #include "xpcpublic.h"
54 #include "xpcprivate.h"
56 namespace mozilla::dom {
58 // Promise
60 NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(Promise)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
63 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
65 tmp->mPromiseObj = nullptr;
66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
72 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
73 // If you add new JS member variables, you may need to stop using
74 // NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
75 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
76 NS_IMPL_CYCLE_COLLECTION_TRACE_END
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 // static
104 already_AddRefed<Promise> Promise::CreateInfallible(
105 nsIGlobalObject* aGlobal,
106 PropagateUserInteraction aPropagateUserInteraction) {
107 MOZ_ASSERT(aGlobal);
108 RefPtr<Promise> p = new Promise(aGlobal);
109 IgnoredErrorResult rv;
110 p->CreateWrapper(rv, aPropagateUserInteraction);
111 if (rv.Failed() && rv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)) {
112 MOZ_CRASH("Out of memory");
115 // We may have failed to init the wrapper here, because nsIGlobalObject had
116 // null GlobalJSObject. In that case we consider the JS realm is dead, which
117 // means:
118 // 1. This promise can't be settled.
119 // 2. Nothing can subscribe this promise anymore from that realm.
120 // Such condition makes this promise a no-op object.
121 (void)NS_WARN_IF(!p->PromiseObj());
123 return p.forget();
126 bool Promise::MaybePropagateUserInputEventHandling() {
127 MOZ_ASSERT(mPromiseObj,
128 "Should be called only if the wrapper is successfully created");
129 JS::PromiseUserInputEventHandlingState state =
130 UserActivation::IsHandlingUserInput()
131 ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
132 : JS::PromiseUserInputEventHandlingState::
133 DidntHaveUserInteractionAtCreation;
134 JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj);
135 return JS::SetPromiseUserInputEventHandlingState(p, state);
138 // static
139 already_AddRefed<Promise> Promise::Resolve(
140 nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue,
141 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
142 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
143 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseResolve(aCx, aValue));
144 if (!p) {
145 aRv.NoteJSContextException(aCx);
146 return nullptr;
149 return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
152 // static
153 already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
154 JSContext* aCx,
155 JS::Handle<JS::Value> aValue,
156 ErrorResult& aRv) {
157 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
158 JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
159 if (!p) {
160 aRv.NoteJSContextException(aCx);
161 return nullptr;
164 // This promise will never be resolved, so we pass
165 // eDontPropagateUserInteraction for aPropagateUserInteraction
166 // unconditionally.
167 return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
170 // static
171 already_AddRefed<Promise> Promise::All(
172 JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
173 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
174 JS::Rooted<JSObject*> globalObj(aCx, JS::CurrentGlobalOrNull(aCx));
175 if (!globalObj) {
176 aRv.Throw(NS_ERROR_UNEXPECTED);
177 return nullptr;
180 nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
181 if (!global) {
182 aRv.Throw(NS_ERROR_UNEXPECTED);
183 return nullptr;
186 JS::RootedVector<JSObject*> promises(aCx);
187 if (!promises.reserve(aPromiseList.Length())) {
188 aRv.NoteJSContextException(aCx);
189 return nullptr;
192 for (const auto& promise : aPromiseList) {
193 JS::Rooted<JSObject*> promiseObj(aCx, promise->PromiseObj());
194 if (!promiseObj) {
195 // No-op object will never settle, so we return a no-op Promise here,
196 // which is equivalent of returning the existing no-op one.
197 return do_AddRef(promise);
199 // Just in case, make sure these are all in the context compartment.
200 if (!JS_WrapObject(aCx, &promiseObj)) {
201 aRv.NoteJSContextException(aCx);
202 return nullptr;
204 promises.infallibleAppend(promiseObj);
207 JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
208 if (!result) {
209 aRv.NoteJSContextException(aCx);
210 return nullptr;
213 return CreateFromExisting(global, result, aPropagateUserInteraction);
216 void Promise::Then(JSContext* aCx,
217 // aCalleeGlobal may not be in the compartment of aCx, when
218 // called over Xrays.
219 JS::Handle<JSObject*> aCalleeGlobal,
220 AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
221 JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
222 NS_ASSERT_OWNINGTHREAD(Promise);
224 // Let's hope this does the right thing with Xrays... Ensure everything is
225 // just in the caller compartment; that ought to do the trick. In theory we
226 // should consider aCalleeGlobal, but in practice our only caller is
227 // DOMRequest::Then, which is not working with a Promise subclass, so things
228 // should be OK.
229 JS::Rooted<JSObject*> promise(aCx, PromiseObj());
230 if (!promise) {
231 // This promise is no-op, so do nothing.
232 return;
235 if (!JS_WrapObject(aCx, &promise)) {
236 aRv.NoteJSContextException(aCx);
237 return;
240 JS::Rooted<JSObject*> resolveCallback(aCx);
241 if (aResolveCallback) {
242 resolveCallback = aResolveCallback->CallbackOrNull();
243 if (!JS_WrapObject(aCx, &resolveCallback)) {
244 aRv.NoteJSContextException(aCx);
245 return;
249 JS::Rooted<JSObject*> rejectCallback(aCx);
250 if (aRejectCallback) {
251 rejectCallback = aRejectCallback->CallbackOrNull();
252 if (!JS_WrapObject(aCx, &rejectCallback)) {
253 aRv.NoteJSContextException(aCx);
254 return;
258 JS::Rooted<JSObject*> retval(aCx);
259 retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
260 rejectCallback);
261 if (!retval) {
262 aRv.NoteJSContextException(aCx);
263 return;
266 aRetval.setObject(*retval);
269 static void SettlePromise(Promise* aSettlingPromise, Promise* aCallbackPromise,
270 ErrorResult& aRv) {
271 if (!aSettlingPromise) {
272 return;
274 if (aRv.IsUncatchableException()) {
275 return;
277 if (aRv.Failed()) {
278 aSettlingPromise->MaybeReject(std::move(aRv));
279 return;
281 if (aCallbackPromise) {
282 aSettlingPromise->MaybeResolve(aCallbackPromise);
283 } else {
284 aSettlingPromise->MaybeResolveWithUndefined();
288 void PromiseNativeThenHandlerBase::ResolvedCallback(
289 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
290 if (!HasResolvedCallback()) {
291 mPromise->MaybeResolve(aValue);
292 return;
294 RefPtr<Promise> promise = CallResolveCallback(aCx, aValue, aRv);
295 SettlePromise(mPromise, promise, aRv);
298 void PromiseNativeThenHandlerBase::RejectedCallback(
299 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
300 if (!HasRejectedCallback()) {
301 mPromise->MaybeReject(aValue);
302 return;
304 RefPtr<Promise> promise = CallRejectCallback(aCx, aValue, aRv);
305 SettlePromise(mPromise, promise, aRv);
308 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
310 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase)
311 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
312 tmp->Traverse(cb);
313 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
315 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
316 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
317 tmp->Unlink();
318 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
320 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
321 NS_INTERFACE_MAP_ENTRY(nsISupports)
322 NS_INTERFACE_MAP_END
324 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PromiseNativeThenHandlerBase)
325 tmp->Trace(aCallbacks, aClosure);
326 NS_IMPL_CYCLE_COLLECTION_TRACE_END
328 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase)
329 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase)
331 Result<RefPtr<Promise>, nsresult> Promise::ThenWithoutCycleCollection(
332 const std::function<already_AddRefed<Promise>(
333 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv)>&
334 aCallback) {
335 return ThenWithCycleCollectedArgs(aCallback);
338 void Promise::CreateWrapper(
339 ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
340 AutoJSAPI jsapi;
341 if (!jsapi.Init(mGlobal)) {
342 aRv.Throw(NS_ERROR_UNEXPECTED);
343 return;
345 JSContext* cx = jsapi.cx();
346 mPromiseObj = JS::NewPromiseObject(cx, nullptr);
347 if (!mPromiseObj) {
348 JS_ClearPendingException(cx);
349 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
350 return;
352 if (aPropagateUserInteraction == ePropagateUserInteraction) {
353 Unused << MaybePropagateUserInputEventHandling();
357 void Promise::MaybeResolve(JSContext* aCx, JS::Handle<JS::Value> aValue) {
358 NS_ASSERT_OWNINGTHREAD(Promise);
360 JS::Rooted<JSObject*> p(aCx, PromiseObj());
361 if (!p || !JS::ResolvePromise(aCx, p, aValue)) {
362 // Now what? There's nothing sane to do here.
363 JS_ClearPendingException(aCx);
367 void Promise::MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue) {
368 NS_ASSERT_OWNINGTHREAD(Promise);
370 JS::Rooted<JSObject*> p(aCx, PromiseObj());
371 if (!p || !JS::RejectPromise(aCx, p, aValue)) {
372 // Now what? There's nothing sane to do here.
373 JS_ClearPendingException(aCx);
377 #define SLOT_NATIVEHANDLER 0
378 #define SLOT_NATIVEHANDLER_TASK 1
380 enum class NativeHandlerTask : int32_t { Resolve, Reject };
382 MOZ_CAN_RUN_SCRIPT
383 static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
384 JS::Value* aVp) {
385 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
387 JS::Value v =
388 js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER);
389 MOZ_ASSERT(v.isObject());
391 JS::Rooted<JSObject*> obj(aCx, &v.toObject());
392 PromiseNativeHandler* handler = nullptr;
393 if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
394 return Throw(aCx, NS_ERROR_UNEXPECTED);
397 v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
398 NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
400 ErrorResult rv;
401 if (task == NativeHandlerTask::Resolve) {
402 // handler is kept alive by "obj" on the stack.
403 MOZ_KnownLive(handler)->ResolvedCallback(aCx, args.get(0), rv);
404 } else {
405 MOZ_ASSERT(task == NativeHandlerTask::Reject);
406 // handler is kept alive by "obj" on the stack.
407 MOZ_KnownLive(handler)->RejectedCallback(aCx, args.get(0), rv);
410 return !rv.MaybeSetPendingException(aCx);
413 static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
414 JS::Handle<JSObject*> aHolder,
415 NativeHandlerTask aTask) {
416 JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
417 /* nargs = */ 1,
418 /* flags = */ 0, nullptr);
419 if (!func) {
420 return nullptr;
423 JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
425 JS::AssertObjectIsNotGray(aHolder);
426 js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
427 JS::ObjectValue(*aHolder));
428 js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
429 JS::Int32Value(static_cast<int32_t>(aTask)));
431 return obj;
434 namespace {
436 class PromiseNativeHandlerShim final : public PromiseNativeHandler {
437 RefPtr<PromiseNativeHandler> mInner;
438 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
439 enum InnerState{
440 NotCleared,
441 ClearedFromResolve,
442 ClearedFromReject,
443 ClearedFromCC,
445 InnerState mState = NotCleared;
446 #endif
448 ~PromiseNativeHandlerShim() = default;
450 public:
451 explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
452 : mInner(aInner) {
453 MOZ_DIAGNOSTIC_ASSERT(mInner);
456 MOZ_CAN_RUN_SCRIPT
457 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
458 ErrorResult& aRv) override {
459 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
460 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromResolve);
461 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromReject);
462 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromCC);
463 #else
464 if (!mInner) {
465 return;
467 #endif
468 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
469 inner->ResolvedCallback(aCx, aValue, aRv);
470 MOZ_ASSERT(!mInner);
471 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
472 mState = ClearedFromResolve;
473 #endif
476 MOZ_CAN_RUN_SCRIPT
477 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
478 ErrorResult& aRv) override {
479 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
480 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromResolve);
481 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromReject);
482 MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromCC);
483 #else
484 if (!mInner) {
485 return;
487 #endif
488 RefPtr<PromiseNativeHandler> inner = std::move(mInner);
489 inner->RejectedCallback(aCx, aValue, aRv);
490 MOZ_ASSERT(!mInner);
491 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
492 mState = ClearedFromReject;
493 #endif
496 bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
497 JS::MutableHandle<JSObject*> aWrapper) {
498 return PromiseNativeHandler_Binding::Wrap(aCx, this, aGivenProto, aWrapper);
501 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
502 NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
505 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
506 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeHandlerShim)
507 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner)
508 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
509 tmp->mState = ClearedFromCC;
510 #endif
511 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeHandlerShim)
513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
516 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim)
517 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim)
519 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim)
520 NS_INTERFACE_MAP_ENTRY(nsISupports)
521 NS_INTERFACE_MAP_END
523 } // anonymous namespace
525 void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
526 NS_ASSERT_OWNINGTHREAD(Promise);
528 AutoJSAPI jsapi;
529 if (NS_WARN_IF(!mPromiseObj || !jsapi.Init(mGlobal))) {
530 // Our API doesn't allow us to return a useful error. Not like this should
531 // happen anyway.
532 return;
535 // The self-hosted promise js may keep the object we pass to it alive
536 // for quite a while depending on when GC runs. Therefore, pass a shim
537 // object instead. The shim will free its inner PromiseNativeHandler
538 // after the promise has settled just like our previous c++ promises did.
539 RefPtr<PromiseNativeHandlerShim> shim =
540 new PromiseNativeHandlerShim(aRunnable);
542 JSContext* cx = jsapi.cx();
543 JS::Rooted<JSObject*> handlerWrapper(cx);
544 // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
545 // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
546 if (NS_WARN_IF(!shim->WrapObject(cx, nullptr, &handlerWrapper))) {
547 // Again, no way to report errors.
548 jsapi.ClearException();
549 return;
552 JS::Rooted<JSObject*> resolveFunc(cx);
553 resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
554 NativeHandlerTask::Resolve);
555 if (NS_WARN_IF(!resolveFunc)) {
556 jsapi.ClearException();
557 return;
560 JS::Rooted<JSObject*> rejectFunc(cx);
561 rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
562 NativeHandlerTask::Reject);
563 if (NS_WARN_IF(!rejectFunc)) {
564 jsapi.ClearException();
565 return;
568 JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
569 if (NS_WARN_IF(
570 !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
571 jsapi.ClearException();
572 return;
576 void Promise::HandleException(JSContext* aCx) {
577 JS::Rooted<JS::Value> exn(aCx);
578 if (JS_GetPendingException(aCx, &exn)) {
579 JS_ClearPendingException(aCx);
580 // Always reject even if this was called in *Resolve.
581 MaybeReject(aCx, exn);
585 // static
586 already_AddRefed<Promise> Promise::RejectWithExceptionFromContext(
587 nsIGlobalObject* aGlobal, JSContext* aCx, ErrorResult& aError) {
588 JS::Rooted<JS::Value> exn(aCx);
589 if (!JS_GetPendingException(aCx, &exn)) {
590 // This is very important: if there is no pending exception here but we're
591 // ending up in this code, that means the callee threw an uncatchable
592 // exception. Just propagate that out as-is.
593 aError.ThrowUncatchableException();
594 return nullptr;
597 JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
598 if (!JS_WrapValue(aCx, &exn)) {
599 // We just give up.
600 aError.StealExceptionFromJSContext(aCx);
601 return nullptr;
604 JS_ClearPendingException(aCx);
606 IgnoredErrorResult error;
607 RefPtr<Promise> promise = Promise::Reject(aGlobal, aCx, exn, error);
608 if (!promise) {
609 // We just give up, let's store the exception in the ErrorResult.
610 aError.ThrowJSException(aCx, exn);
611 return nullptr;
614 return promise.forget();
617 // static
618 already_AddRefed<Promise> Promise::CreateFromExisting(
619 nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
620 PropagateUserInteraction aPropagateUserInteraction) {
621 MOZ_ASSERT(JS::GetCompartment(aGlobal->GetGlobalJSObjectPreserveColor()) ==
622 JS::GetCompartment(aPromiseObj));
623 RefPtr<Promise> p = new Promise(aGlobal);
624 p->mPromiseObj = aPromiseObj;
625 if (aPropagateUserInteraction == ePropagateUserInteraction &&
626 !p->MaybePropagateUserInputEventHandling()) {
627 return nullptr;
629 return p.forget();
632 void Promise::MaybeResolveWithUndefined() {
633 NS_ASSERT_OWNINGTHREAD(Promise);
635 MaybeResolve(JS::UndefinedHandleValue);
638 void Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
639 NS_ASSERT_OWNINGTHREAD(Promise);
641 MaybeSomething(aArg, &Promise::MaybeReject);
644 void Promise::MaybeRejectWithUndefined() {
645 NS_ASSERT_OWNINGTHREAD(Promise);
647 MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
650 void Promise::ReportRejectedPromise(JSContext* aCx,
651 JS::Handle<JSObject*> aPromise) {
652 MOZ_ASSERT(!js::IsWrapper(aPromise));
654 MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
656 bool isChrome = false;
657 uint64_t innerWindowID = 0;
658 nsGlobalWindowInner* winForDispatch = nullptr;
659 if (MOZ_LIKELY(NS_IsMainThread())) {
660 isChrome = nsContentUtils::ObjectPrincipal(aPromise)->IsSystemPrincipal();
662 if (nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aPromise)) {
663 winForDispatch = win;
664 innerWindowID = win->WindowID();
665 } else if (nsGlobalWindowInner* win = xpc::SandboxWindowOrNull(
666 JS::GetNonCCWObjectGlobal(aPromise), aCx)) {
667 // Don't dispatch rejections from the sandbox to the associated DOM
668 // window.
669 innerWindowID = win->WindowID();
671 } else if (const WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) {
672 isChrome = wp->UsesSystemPrincipal();
673 innerWindowID = wp->WindowID();
674 } else if (nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(aPromise)) {
675 if (nsCOMPtr<WorkletGlobalScope> workletGlobal =
676 do_QueryInterface(global)) {
677 WorkletImpl* impl = workletGlobal->Impl();
678 isChrome = impl->PrincipalInfo().type() ==
679 mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo;
680 innerWindowID = impl->LoadInfo().InnerWindowID();
684 JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
685 // resolutionSite can be null if async stacks are disabled.
686 JS::Rooted<JSObject*> resolutionSite(aCx,
687 JS::GetPromiseResolutionSite(aPromise));
689 // We're inspecting the rejection value only to report it to the console, and
690 // we do so without side-effects, so we can safely unwrap it without regard to
691 // the privileges of the Promise object that holds it. If we don't unwrap
692 // before trying to create the error report, we wind up reporting any
693 // cross-origin objects as "uncaught exception: Object".
694 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
696 Maybe<JSAutoRealm> ar;
697 JS::Rooted<JS::Value> unwrapped(aCx, result);
698 if (unwrapped.isObject()) {
699 unwrapped.setObject(*js::UncheckedUnwrap(&unwrapped.toObject()));
700 ar.emplace(aCx, &unwrapped.toObject());
703 JS::ErrorReportBuilder report(aCx);
704 RefPtr<Exception> exn;
705 if (unwrapped.isObject() &&
706 (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &unwrapped, exn)) ||
707 NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &unwrapped, exn)))) {
708 xpcReport->Init(aCx, exn, isChrome, innerWindowID);
709 } else {
710 // Use the resolution site as the exception stack
711 JS::ExceptionStack exnStack(aCx, unwrapped, resolutionSite);
712 if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
713 JS_ClearPendingException(aCx);
714 return;
717 xpcReport->Init(report.report(), report.toStringResult().c_str(),
718 isChrome, innerWindowID);
722 // Used to initialize the similarly named nsISciptError attribute.
723 xpcReport->mIsPromiseRejection = true;
725 // Now post an event to do the real reporting async
726 RefPtr<AsyncErrorReporter> event = new AsyncErrorReporter(xpcReport);
727 if (winForDispatch) {
728 if (!winForDispatch->IsDying()) {
729 // Exceptions from a dying window will cause the window to leak.
730 event->SetException(aCx, result);
731 if (resolutionSite) {
732 event->SerializeStack(aCx, resolutionSite);
735 winForDispatch->Dispatch(mozilla::TaskCategory::Other, event.forget());
736 } else {
737 NS_DispatchToMainThread(event);
741 void Promise::MaybeResolveWithClone(JSContext* aCx,
742 JS::Handle<JS::Value> aValue) {
743 JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
744 AutoEntryScript aes(GetParentObject(), "Promise resolution");
745 JSContext* cx = aes.cx();
746 JS::Rooted<JS::Value> value(cx, aValue);
748 xpc::StackScopedCloneOptions options;
749 options.wrapReflectors = true;
750 if (!StackScopedClone(cx, options, sourceScope, &value)) {
751 HandleException(cx);
752 return;
754 MaybeResolve(aCx, value);
757 void Promise::MaybeRejectWithClone(JSContext* aCx,
758 JS::Handle<JS::Value> aValue) {
759 JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
760 AutoEntryScript aes(GetParentObject(), "Promise rejection");
761 JSContext* cx = aes.cx();
762 JS::Rooted<JS::Value> value(cx, aValue);
764 xpc::StackScopedCloneOptions options;
765 options.wrapReflectors = true;
766 if (!StackScopedClone(cx, options, sourceScope, &value)) {
767 HandleException(cx);
768 return;
770 MaybeReject(aCx, value);
773 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
774 // Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
775 class PromiseWorkerProxyRunnable : public WorkerRunnable {
776 public:
777 PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
778 PromiseWorkerProxy::RunCallbackFunc aFunc)
779 : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
780 WorkerThreadUnchangedBusyCount),
781 mPromiseWorkerProxy(aPromiseWorkerProxy),
782 mFunc(aFunc) {
783 MOZ_ASSERT(NS_IsMainThread());
784 MOZ_ASSERT(mPromiseWorkerProxy);
787 virtual bool WorkerRun(JSContext* aCx,
788 WorkerPrivate* aWorkerPrivate) override {
789 MOZ_ASSERT(aWorkerPrivate);
790 aWorkerPrivate->AssertIsOnWorkerThread();
791 MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
793 MOZ_ASSERT(mPromiseWorkerProxy);
794 RefPtr<Promise> workerPromise = mPromiseWorkerProxy->WorkerPromise();
796 // Here we convert the buffer to a JS::Value.
797 JS::Rooted<JS::Value> value(aCx);
798 if (!mPromiseWorkerProxy->Read(aCx, &value)) {
799 JS_ClearPendingException(aCx);
800 return false;
803 (workerPromise->*mFunc)(aCx, value);
805 // Release the Promise because it has been resolved/rejected for sure.
806 mPromiseWorkerProxy->CleanUp();
807 return true;
810 protected:
811 ~PromiseWorkerProxyRunnable() = default;
813 private:
814 RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
816 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
817 PromiseWorkerProxy::RunCallbackFunc mFunc;
820 /* static */
821 already_AddRefed<PromiseWorkerProxy> PromiseWorkerProxy::Create(
822 WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise,
823 const PromiseWorkerProxyStructuredCloneCallbacks* aCb) {
824 MOZ_ASSERT(aWorkerPrivate);
825 aWorkerPrivate->AssertIsOnWorkerThread();
826 MOZ_ASSERT(aWorkerPromise);
827 MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
829 RefPtr<PromiseWorkerProxy> proxy =
830 new PromiseWorkerProxy(aWorkerPromise, aCb);
832 // Maintain a reference so that we have a valid object to clean up when
833 // removing the feature.
834 proxy.get()->AddRef();
836 // We do this to make sure the worker thread won't shut down before the
837 // promise is resolved/rejected on the worker thread.
838 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
839 aWorkerPrivate, "PromiseWorkerProxy", [proxy]() { proxy->CleanUp(); });
841 if (NS_WARN_IF(!workerRef)) {
842 // Probably the worker is terminating. We cannot complete the operation
843 // and we have to release all the resources. CleanUp releases the extra
844 // ref, too
845 proxy->CleanUp();
846 return nullptr;
849 proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
851 return proxy.forget();
854 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
856 PromiseWorkerProxy::PromiseWorkerProxy(
857 Promise* aWorkerPromise,
858 const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
859 : mWorkerPromise(aWorkerPromise),
860 mCleanedUp(false),
861 mCallbacks(aCallbacks),
862 mCleanUpLock("cleanUpLock") {}
864 PromiseWorkerProxy::~PromiseWorkerProxy() {
865 MOZ_ASSERT(mCleanedUp);
866 MOZ_ASSERT(!mWorkerPromise);
867 MOZ_ASSERT(!mWorkerRef);
870 WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
871 #ifdef DEBUG
872 if (NS_IsMainThread()) {
873 mCleanUpLock.AssertCurrentThreadOwns();
875 #endif
876 // Safe to check this without a lock since we assert lock ownership on the
877 // main thread above.
878 MOZ_ASSERT(!mCleanedUp);
879 MOZ_ASSERT(mWorkerRef);
881 return mWorkerRef->Private();
884 bool PromiseWorkerProxy::OnWritingThread() const {
885 return IsCurrentThreadRunningWorker();
888 Promise* PromiseWorkerProxy::WorkerPromise() const {
889 MOZ_ASSERT(IsCurrentThreadRunningWorker());
890 MOZ_ASSERT(mWorkerPromise);
891 return mWorkerPromise;
894 void PromiseWorkerProxy::RunCallback(JSContext* aCx,
895 JS::Handle<JS::Value> aValue,
896 RunCallbackFunc aFunc) {
897 MOZ_ASSERT(NS_IsMainThread());
899 MutexAutoLock lock(Lock());
900 // If the worker thread's been cancelled we don't need to resolve the Promise.
901 if (CleanedUp()) {
902 return;
905 // The |aValue| is written into the StructuredCloneHolderBase.
906 if (!Write(aCx, aValue)) {
907 JS_ClearPendingException(aCx);
908 MOZ_ASSERT(false,
909 "cannot serialize the value with the StructuredCloneAlgorithm!");
912 RefPtr<PromiseWorkerProxyRunnable> runnable =
913 new PromiseWorkerProxyRunnable(this, aFunc);
915 runnable->Dispatch();
918 void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
919 JS::Handle<JS::Value> aValue,
920 ErrorResult& aRv) {
921 RunCallback(aCx, aValue, &Promise::MaybeResolve);
924 void PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
925 JS::Handle<JS::Value> aValue,
926 ErrorResult& aRv) {
927 RunCallback(aCx, aValue, &Promise::MaybeReject);
930 void PromiseWorkerProxy::CleanUp() {
931 // Can't release Mutex while it is still locked, so scope the lock.
933 MutexAutoLock lock(Lock());
935 if (CleanedUp()) {
936 return;
939 // We can be called if we failed to get a WorkerRef
940 if (mWorkerRef) {
941 mWorkerRef->Private()->AssertIsOnWorkerThread();
944 // Release the Promise and remove the PromiseWorkerProxy from the holders of
945 // the worker thread since the Promise has been resolved/rejected or the
946 // worker thread has been cancelled.
947 mCleanedUp = true;
948 mWorkerPromise = nullptr;
949 mWorkerRef = nullptr;
951 // Clear the StructuredCloneHolderBase class.
952 Clear();
954 Release();
957 JSObject* PromiseWorkerProxy::CustomReadHandler(
958 JSContext* aCx, JSStructuredCloneReader* aReader,
959 const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
960 uint32_t aIndex) {
961 if (NS_WARN_IF(!mCallbacks)) {
962 return nullptr;
965 return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
968 bool PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
969 JSStructuredCloneWriter* aWriter,
970 JS::Handle<JSObject*> aObj,
971 bool* aSameProcessScopeRequired) {
972 if (NS_WARN_IF(!mCallbacks)) {
973 return false;
976 return mCallbacks->Write(aCx, aWriter, this, aObj);
979 // Specializations of MaybeRejectBrokenly we actually support.
980 template <>
981 void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
982 MaybeSomething(aArg, &Promise::MaybeReject);
984 template <>
985 void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
986 MaybeSomething(aArg, &Promise::MaybeReject);
989 Promise::PromiseState Promise::State() const {
990 JS::Rooted<JSObject*> p(RootingCx(), PromiseObj());
991 const JS::PromiseState state = JS::GetPromiseState(p);
993 if (state == JS::PromiseState::Fulfilled) {
994 return PromiseState::Resolved;
997 if (state == JS::PromiseState::Rejected) {
998 return PromiseState::Rejected;
1001 return PromiseState::Pending;
1004 bool Promise::SetSettledPromiseIsHandled() {
1005 if (!mPromiseObj) {
1006 // Do nothing as it's a no-op promise
1007 return false;
1009 AutoAllowLegacyScriptExecution exemption;
1010 AutoEntryScript aes(mGlobal, "Set settled promise handled");
1011 JSContext* cx = aes.cx();
1012 JS::Rooted<JSObject*> promiseObj(cx, mPromiseObj);
1013 return JS::SetSettledPromiseIsHandled(cx, promiseObj);
1016 bool Promise::SetAnyPromiseIsHandled() {
1017 if (!mPromiseObj) {
1018 // Do nothing as it's a no-op promise
1019 return false;
1021 AutoAllowLegacyScriptExecution exemption;
1022 AutoEntryScript aes(mGlobal, "Set any promise handled");
1023 JSContext* cx = aes.cx();
1024 JS::Rooted<JSObject*> promiseObj(cx, mPromiseObj);
1025 return JS::SetAnyPromiseIsHandled(cx, promiseObj);
1028 /* static */
1029 already_AddRefed<Promise> Promise::CreateResolvedWithUndefined(
1030 nsIGlobalObject* global, ErrorResult& aRv) {
1031 RefPtr<Promise> returnPromise = Promise::Create(global, aRv);
1032 if (aRv.Failed()) {
1033 return nullptr;
1035 returnPromise->MaybeResolveWithUndefined();
1036 return returnPromise.forget();
1039 already_AddRefed<Promise> Promise::CreateRejected(
1040 nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aRejectionError,
1041 ErrorResult& aRv) {
1042 RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
1043 if (aRv.Failed()) {
1044 return nullptr;
1046 promise->MaybeReject(aRejectionError);
1047 return promise.forget();
1050 already_AddRefed<Promise> Promise::CreateRejectedWithTypeError(
1051 nsIGlobalObject* aGlobal, const nsACString& aMessage, ErrorResult& aRv) {
1052 RefPtr<Promise> returnPromise = Promise::Create(aGlobal, aRv);
1053 if (aRv.Failed()) {
1054 return nullptr;
1056 returnPromise->MaybeRejectWithTypeError(aMessage);
1057 return returnPromise.forget();
1060 already_AddRefed<Promise> Promise::CreateRejectedWithErrorResult(
1061 nsIGlobalObject* aGlobal, ErrorResult& aRejectionError) {
1062 RefPtr<Promise> returnPromise = Promise::Create(aGlobal, IgnoreErrors());
1063 if (!returnPromise) {
1064 return nullptr;
1066 returnPromise->MaybeReject(std::move(aRejectionError));
1067 return returnPromise.forget();
1070 nsresult Promise::TryExtractNSResultFromRejectionValue(
1071 JS::Handle<JS::Value> aValue) {
1072 if (aValue.isInt32()) {
1073 return nsresult(aValue.toInt32());
1076 if (aValue.isObject()) {
1077 RefPtr<DOMException> domException;
1078 UNWRAP_OBJECT(DOMException, aValue, domException);
1079 if (domException) {
1080 return domException->GetResult();
1084 return NS_ERROR_DOM_NOT_NUMBER_ERR;
1087 } // namespace mozilla::dom
1089 extern "C" {
1091 // These functions are used in the implementation of ffi bindings for
1092 // dom::Promise from Rust.
1094 void DomPromise_AddRef(mozilla::dom::Promise* aPromise) {
1095 MOZ_ASSERT(aPromise);
1096 aPromise->AddRef();
1099 void DomPromise_Release(mozilla::dom::Promise* aPromise) {
1100 MOZ_ASSERT(aPromise);
1101 aPromise->Release();
1104 #define DOM_PROMISE_FUNC_WITH_VARIANT(name, func) \
1105 void name(mozilla::dom::Promise* aPromise, nsIVariant* aVariant) { \
1106 MOZ_ASSERT(aPromise); \
1107 MOZ_ASSERT(aVariant); \
1108 mozilla::dom::AutoEntryScript aes(aPromise->GetGlobalObject(), \
1109 "Promise resolution or rejection"); \
1110 JSContext* cx = aes.cx(); \
1112 JS::Rooted<JS::Value> val(cx); \
1113 nsresult rv = NS_OK; \
1114 if (!XPCVariant::VariantDataToJS(cx, aVariant, &rv, &val)) { \
1115 aPromise->MaybeRejectWithTypeError( \
1116 "Failed to convert nsIVariant to JS"); \
1117 return; \
1119 aPromise->func(val); \
1122 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_RejectWithVariant, MaybeReject)
1123 DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_ResolveWithVariant, MaybeResolve)
1125 #undef DOM_PROMISE_FUNC_WITH_VARIANT