summaryrefslogtreecommitdiffstats
path: root/dom/promise/Promise.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/promise/Promise.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/promise/Promise.cpp')
-rw-r--r--dom/promise/Promise.cpp1129
1 files changed, 1129 insertions, 0 deletions
diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp
new file mode 100644
index 0000000000..fb4989c43d
--- /dev/null
+++ b/dom/promise/Promise.cpp
@@ -0,0 +1,1129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+
+#include "js/Debug.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/MediaStreamError.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkletImpl.h"
+#include "mozilla/dom/WorkletGlobalScope.h"
+
+#include "jsfriendapi.h"
+#include "js/Exception.h" // JS::ExceptionStack
+#include "js/Object.h" // JS::GetCompartment
+#include "js/StructuredClone.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsJSEnvironment.h"
+#include "nsJSPrincipals.h"
+#include "nsJSUtils.h"
+#include "nsPIDOMWindow.h"
+#include "PromiseDebugging.h"
+#include "PromiseNativeHandler.h"
+#include "PromiseWorkerProxy.h"
+#include "WrapperFactory.h"
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+
+namespace mozilla::dom {
+
+// Promise
+
+NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(Promise)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+ tmp->mPromiseObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
+ // If you add new JS member variables, you may need to stop using
+ // NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+Promise::Promise(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal), mPromiseObj(nullptr) {
+ MOZ_ASSERT(mGlobal);
+
+ mozilla::HoldJSObjects(this);
+}
+
+Promise::~Promise() { mozilla::DropJSObjects(this); }
+
+// static
+already_AddRefed<Promise> Promise::Create(
+ nsIGlobalObject* aGlobal, ErrorResult& aRv,
+ PropagateUserInteraction aPropagateUserInteraction) {
+ if (!aGlobal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ RefPtr<Promise> p = new Promise(aGlobal);
+ p->CreateWrapper(aRv, aPropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return p.forget();
+}
+
+// static
+already_AddRefed<Promise> Promise::CreateInfallible(
+ nsIGlobalObject* aGlobal,
+ PropagateUserInteraction aPropagateUserInteraction) {
+ MOZ_ASSERT(aGlobal);
+ RefPtr<Promise> p = new Promise(aGlobal);
+ IgnoredErrorResult rv;
+ p->CreateWrapper(rv, aPropagateUserInteraction);
+ if (rv.Failed() && rv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY)) {
+ MOZ_CRASH("Out of memory");
+ }
+
+ // We may have failed to init the wrapper here, because nsIGlobalObject had
+ // null GlobalJSObject. In that case we consider the JS realm is dead, which
+ // means:
+ // 1. This promise can't be settled.
+ // 2. Nothing can subscribe this promise anymore from that realm.
+ // Such condition makes this promise a no-op object.
+ (void)NS_WARN_IF(!p->PromiseObj());
+
+ return p.forget();
+}
+
+bool Promise::MaybePropagateUserInputEventHandling() {
+ MOZ_ASSERT(mPromiseObj,
+ "Should be called only if the wrapper is successfully created");
+ JS::PromiseUserInputEventHandlingState state =
+ UserActivation::IsHandlingUserInput()
+ ? JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
+ : JS::PromiseUserInputEventHandlingState::
+ DidntHaveUserInteractionAtCreation;
+ JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj);
+ return JS::SetPromiseUserInputEventHandlingState(p, state);
+}
+
+// static
+already_AddRefed<Promise> Promise::Resolve(
+ nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
+ JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
+ JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseResolve(aCx, aValue));
+ if (!p) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
+}
+
+// static
+already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
+ JS::Rooted<JSObject*> p(aCx, JS::CallOriginalPromiseReject(aCx, aValue));
+ if (!p) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ // This promise will never be resolved, so we pass
+ // eDontPropagateUserInteraction for aPropagateUserInteraction
+ // unconditionally.
+ return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
+}
+
+// static
+already_AddRefed<Promise> Promise::All(
+ JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
+ ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
+ JS::Rooted<JSObject*> globalObj(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (!globalObj) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ JS::RootedVector<JSObject*> promises(aCx);
+ if (!promises.reserve(aPromiseList.Length())) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ for (const auto& promise : aPromiseList) {
+ JS::Rooted<JSObject*> promiseObj(aCx, promise->PromiseObj());
+ if (!promiseObj) {
+ // No-op object will never settle, so we return a no-op Promise here,
+ // which is equivalent of returning the existing no-op one.
+ return do_AddRef(promise);
+ }
+ // Just in case, make sure these are all in the context compartment.
+ if (!JS_WrapObject(aCx, &promiseObj)) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+ promises.infallibleAppend(promiseObj);
+ }
+
+ JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
+ if (!result) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ return CreateFromExisting(global, result, aPropagateUserInteraction);
+}
+
+void Promise::Then(JSContext* aCx,
+ // aCalleeGlobal may not be in the compartment of aCx, when
+ // called over Xrays.
+ JS::Handle<JSObject*> aCalleeGlobal,
+ AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ // Let's hope this does the right thing with Xrays... Ensure everything is
+ // just in the caller compartment; that ought to do the trick. In theory we
+ // should consider aCalleeGlobal, but in practice our only caller is
+ // DOMRequest::Then, which is not working with a Promise subclass, so things
+ // should be OK.
+ JS::Rooted<JSObject*> promise(aCx, PromiseObj());
+ if (!promise) {
+ // This promise is no-op, so do nothing.
+ return;
+ }
+
+ if (!JS_WrapObject(aCx, &promise)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ JS::Rooted<JSObject*> resolveCallback(aCx);
+ if (aResolveCallback) {
+ resolveCallback = aResolveCallback->CallbackOrNull();
+ if (!JS_WrapObject(aCx, &resolveCallback)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+
+ JS::Rooted<JSObject*> rejectCallback(aCx);
+ if (aRejectCallback) {
+ rejectCallback = aRejectCallback->CallbackOrNull();
+ if (!JS_WrapObject(aCx, &rejectCallback)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+
+ JS::Rooted<JSObject*> retval(aCx);
+ retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
+ rejectCallback);
+ if (!retval) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aRetval.setObject(*retval);
+}
+
+static void SettlePromise(Promise* aSettlingPromise, Promise* aCallbackPromise,
+ ErrorResult& aRv) {
+ if (!aSettlingPromise) {
+ return;
+ }
+ if (aRv.IsUncatchableException()) {
+ return;
+ }
+ if (aRv.Failed()) {
+ aSettlingPromise->MaybeReject(std::move(aRv));
+ return;
+ }
+ if (aCallbackPromise) {
+ aSettlingPromise->MaybeResolve(aCallbackPromise);
+ } else {
+ aSettlingPromise->MaybeResolveWithUndefined();
+ }
+}
+
+void PromiseNativeThenHandlerBase::ResolvedCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ if (!HasResolvedCallback()) {
+ mPromise->MaybeResolve(aValue);
+ return;
+ }
+ RefPtr<Promise> promise = CallResolveCallback(aCx, aValue, aRv);
+ SettlePromise(mPromise, promise, aRv);
+}
+
+void PromiseNativeThenHandlerBase::RejectedCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ if (!HasRejectedCallback()) {
+ mPromise->MaybeReject(aValue);
+ return;
+ }
+ RefPtr<Promise> promise = CallRejectCallback(aCx, aValue, aRv);
+ SettlePromise(mPromise, promise, aRv);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+ tmp->Traverse(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ tmp->Unlink();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PromiseNativeThenHandlerBase)
+ tmp->Trace(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase)
+
+Result<RefPtr<Promise>, nsresult> Promise::ThenWithoutCycleCollection(
+ const std::function<already_AddRefed<Promise>(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv)>&
+ aCallback) {
+ return ThenWithCycleCollectedArgs(aCallback);
+}
+
+void Promise::CreateWrapper(
+ ErrorResult& aRv, PropagateUserInteraction aPropagateUserInteraction) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ mPromiseObj = JS::NewPromiseObject(cx, nullptr);
+ if (!mPromiseObj) {
+ JS_ClearPendingException(cx);
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ if (aPropagateUserInteraction == ePropagateUserInteraction) {
+ Unused << MaybePropagateUserInputEventHandling();
+ }
+}
+
+void Promise::MaybeResolve(JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ JS::Rooted<JSObject*> p(aCx, PromiseObj());
+ if (!p || !JS::ResolvePromise(aCx, p, aValue)) {
+ // Now what? There's nothing sane to do here.
+ JS_ClearPendingException(aCx);
+ }
+}
+
+void Promise::MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ JS::Rooted<JSObject*> p(aCx, PromiseObj());
+ if (!p || !JS::RejectPromise(aCx, p, aValue)) {
+ // Now what? There's nothing sane to do here.
+ JS_ClearPendingException(aCx);
+ }
+}
+
+#define SLOT_NATIVEHANDLER 0
+#define SLOT_NATIVEHANDLER_TASK 1
+
+enum class NativeHandlerTask : int32_t { Resolve, Reject };
+
+MOZ_CAN_RUN_SCRIPT
+static bool NativeHandlerCallback(JSContext* aCx, unsigned aArgc,
+ JS::Value* aVp) {
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+ JS::Value v =
+ js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER);
+ MOZ_ASSERT(v.isObject());
+
+ JS::Rooted<JSObject*> obj(aCx, &v.toObject());
+ PromiseNativeHandler* handler = nullptr;
+ if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
+ return Throw(aCx, NS_ERROR_UNEXPECTED);
+ }
+
+ v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
+ NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
+
+ ErrorResult rv;
+ if (task == NativeHandlerTask::Resolve) {
+ // handler is kept alive by "obj" on the stack.
+ MOZ_KnownLive(handler)->ResolvedCallback(aCx, args.get(0), rv);
+ } else {
+ MOZ_ASSERT(task == NativeHandlerTask::Reject);
+ // handler is kept alive by "obj" on the stack.
+ MOZ_KnownLive(handler)->RejectedCallback(aCx, args.get(0), rv);
+ }
+
+ return !rv.MaybeSetPendingException(aCx);
+}
+
+static JSObject* CreateNativeHandlerFunction(JSContext* aCx,
+ JS::Handle<JSObject*> aHolder,
+ NativeHandlerTask aTask) {
+ JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
+ /* nargs = */ 1,
+ /* flags = */ 0, nullptr);
+ if (!func) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
+
+ JS::AssertObjectIsNotGray(aHolder);
+ js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
+ JS::ObjectValue(*aHolder));
+ js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
+ JS::Int32Value(static_cast<int32_t>(aTask)));
+
+ return obj;
+}
+
+namespace {
+
+class PromiseNativeHandlerShim final : public PromiseNativeHandler {
+ RefPtr<PromiseNativeHandler> mInner;
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ enum InnerState{
+ NotCleared,
+ ClearedFromResolve,
+ ClearedFromReject,
+ ClearedFromCC,
+ };
+ InnerState mState = NotCleared;
+#endif
+
+ ~PromiseNativeHandlerShim() = default;
+
+ public:
+ explicit PromiseNativeHandlerShim(PromiseNativeHandler* aInner)
+ : mInner(aInner) {
+ MOZ_DIAGNOSTIC_ASSERT(mInner);
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromResolve);
+ MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromReject);
+ MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromCC);
+#else
+ if (!mInner) {
+ return;
+ }
+#endif
+ RefPtr<PromiseNativeHandler> inner = std::move(mInner);
+ inner->ResolvedCallback(aCx, aValue, aRv);
+ MOZ_ASSERT(!mInner);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mState = ClearedFromResolve;
+#endif
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromResolve);
+ MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromReject);
+ MOZ_DIAGNOSTIC_ASSERT(mState != ClearedFromCC);
+#else
+ if (!mInner) {
+ return;
+ }
+#endif
+ RefPtr<PromiseNativeHandler> inner = std::move(mInner);
+ inner->RejectedCallback(aCx, aValue, aRv);
+ MOZ_ASSERT(!mInner);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mState = ClearedFromReject;
+#endif
+ }
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper) {
+ return PromiseNativeHandler_Binding::Wrap(aCx, this, aGivenProto, aWrapper);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeHandlerShim)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeHandlerShim)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner)
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ tmp->mState = ClearedFromCC;
+#endif
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeHandlerShim)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeHandlerShim)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeHandlerShim)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeHandlerShim)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+} // anonymous namespace
+
+void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!mPromiseObj || !jsapi.Init(mGlobal))) {
+ // Our API doesn't allow us to return a useful error. Not like this should
+ // happen anyway.
+ return;
+ }
+
+ // The self-hosted promise js may keep the object we pass to it alive
+ // for quite a while depending on when GC runs. Therefore, pass a shim
+ // object instead. The shim will free its inner PromiseNativeHandler
+ // after the promise has settled just like our previous c++ promises did.
+ RefPtr<PromiseNativeHandlerShim> shim =
+ new PromiseNativeHandlerShim(aRunnable);
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> handlerWrapper(cx);
+ // Note: PromiseNativeHandler is NOT wrappercached. So we can't use
+ // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
+ if (NS_WARN_IF(!shim->WrapObject(cx, nullptr, &handlerWrapper))) {
+ // Again, no way to report errors.
+ jsapi.ClearException();
+ return;
+ }
+
+ JS::Rooted<JSObject*> resolveFunc(cx);
+ resolveFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
+ NativeHandlerTask::Resolve);
+ if (NS_WARN_IF(!resolveFunc)) {
+ jsapi.ClearException();
+ return;
+ }
+
+ JS::Rooted<JSObject*> rejectFunc(cx);
+ rejectFunc = CreateNativeHandlerFunction(cx, handlerWrapper,
+ NativeHandlerTask::Reject);
+ if (NS_WARN_IF(!rejectFunc)) {
+ jsapi.ClearException();
+ return;
+ }
+
+ JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
+ if (NS_WARN_IF(
+ !JS::AddPromiseReactions(cx, promiseObj, resolveFunc, rejectFunc))) {
+ jsapi.ClearException();
+ return;
+ }
+}
+
+void Promise::HandleException(JSContext* aCx) {
+ JS::Rooted<JS::Value> exn(aCx);
+ if (JS_GetPendingException(aCx, &exn)) {
+ JS_ClearPendingException(aCx);
+ // Always reject even if this was called in *Resolve.
+ MaybeReject(aCx, exn);
+ }
+}
+
+// static
+already_AddRefed<Promise> Promise::RejectWithExceptionFromContext(
+ nsIGlobalObject* aGlobal, JSContext* aCx, ErrorResult& aError) {
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ // This is very important: if there is no pending exception here but we're
+ // ending up in this code, that means the callee threw an uncatchable
+ // exception. Just propagate that out as-is.
+ aError.ThrowUncatchableException();
+ return nullptr;
+ }
+
+ JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
+ if (!JS_WrapValue(aCx, &exn)) {
+ // We just give up.
+ aError.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ JS_ClearPendingException(aCx);
+
+ IgnoredErrorResult error;
+ RefPtr<Promise> promise = Promise::Reject(aGlobal, aCx, exn, error);
+ if (!promise) {
+ // We just give up, let's store the exception in the ErrorResult.
+ aError.ThrowJSException(aCx, exn);
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+// static
+already_AddRefed<Promise> Promise::CreateFromExisting(
+ nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
+ PropagateUserInteraction aPropagateUserInteraction) {
+ MOZ_ASSERT(JS::GetCompartment(aGlobal->GetGlobalJSObjectPreserveColor()) ==
+ JS::GetCompartment(aPromiseObj));
+ RefPtr<Promise> p = new Promise(aGlobal);
+ p->mPromiseObj = aPromiseObj;
+ if (aPropagateUserInteraction == ePropagateUserInteraction &&
+ !p->MaybePropagateUserInputEventHandling()) {
+ return nullptr;
+ }
+ return p.forget();
+}
+
+void Promise::MaybeResolveWithUndefined() {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeResolve(JS::UndefinedHandleValue);
+}
+
+void Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeSomething(aArg, &Promise::MaybeReject);
+}
+
+void Promise::MaybeRejectWithUndefined() {
+ NS_ASSERT_OWNINGTHREAD(Promise);
+
+ MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
+}
+
+void Promise::ReportRejectedPromise(JSContext* aCx,
+ JS::Handle<JSObject*> aPromise) {
+ MOZ_ASSERT(!js::IsWrapper(aPromise));
+
+ MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
+
+ bool isChrome = false;
+ uint64_t innerWindowID = 0;
+ nsGlobalWindowInner* winForDispatch = nullptr;
+ if (MOZ_LIKELY(NS_IsMainThread())) {
+ isChrome = nsContentUtils::ObjectPrincipal(aPromise)->IsSystemPrincipal();
+
+ if (nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aPromise)) {
+ winForDispatch = win;
+ innerWindowID = win->WindowID();
+ } else if (nsGlobalWindowInner* win = xpc::SandboxWindowOrNull(
+ JS::GetNonCCWObjectGlobal(aPromise), aCx)) {
+ // Don't dispatch rejections from the sandbox to the associated DOM
+ // window.
+ innerWindowID = win->WindowID();
+ }
+ } else if (const WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) {
+ isChrome = wp->UsesSystemPrincipal();
+ innerWindowID = wp->WindowID();
+ } else if (nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(aPromise)) {
+ if (nsCOMPtr<WorkletGlobalScope> workletGlobal =
+ do_QueryInterface(global)) {
+ WorkletImpl* impl = workletGlobal->Impl();
+ isChrome = impl->PrincipalInfo().type() ==
+ mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo;
+ innerWindowID = impl->LoadInfo().InnerWindowID();
+ }
+ }
+
+ JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
+ // resolutionSite can be null if async stacks are disabled.
+ JS::Rooted<JSObject*> resolutionSite(aCx,
+ JS::GetPromiseResolutionSite(aPromise));
+
+ // We're inspecting the rejection value only to report it to the console, and
+ // we do so without side-effects, so we can safely unwrap it without regard to
+ // the privileges of the Promise object that holds it. If we don't unwrap
+ // before trying to create the error report, we wind up reporting any
+ // cross-origin objects as "uncaught exception: Object".
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ {
+ Maybe<JSAutoRealm> ar;
+ JS::Rooted<JS::Value> unwrapped(aCx, result);
+ if (unwrapped.isObject()) {
+ unwrapped.setObject(*js::UncheckedUnwrap(&unwrapped.toObject()));
+ ar.emplace(aCx, &unwrapped.toObject());
+ }
+
+ JS::ErrorReportBuilder report(aCx);
+ RefPtr<Exception> exn;
+ if (unwrapped.isObject() &&
+ (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &unwrapped, exn)) ||
+ NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &unwrapped, exn)))) {
+ xpcReport->Init(aCx, exn, isChrome, innerWindowID);
+ } else {
+ // Use the resolution site as the exception stack
+ JS::ExceptionStack exnStack(aCx, unwrapped, resolutionSite);
+ if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ xpcReport->Init(report.report(), report.toStringResult().c_str(),
+ isChrome, innerWindowID);
+ }
+ }
+
+ // Used to initialize the similarly named nsISciptError attribute.
+ xpcReport->mIsPromiseRejection = true;
+
+ // Now post an event to do the real reporting async
+ RefPtr<AsyncErrorReporter> event = new AsyncErrorReporter(xpcReport);
+ if (winForDispatch) {
+ if (!winForDispatch->IsDying()) {
+ // Exceptions from a dying window will cause the window to leak.
+ event->SetException(aCx, result);
+ if (resolutionSite) {
+ event->SerializeStack(aCx, resolutionSite);
+ }
+ }
+ winForDispatch->Dispatch(event.forget());
+ } else {
+ NS_DispatchToMainThread(event);
+ }
+}
+
+void Promise::MaybeResolveWithClone(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
+ AutoEntryScript aes(GetParentObject(), "Promise resolution");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JS::Value> value(cx, aValue);
+
+ xpc::StackScopedCloneOptions options;
+ options.wrapReflectors = true;
+ if (!StackScopedClone(cx, options, sourceScope, &value)) {
+ HandleException(cx);
+ return;
+ }
+ MaybeResolve(aCx, value);
+}
+
+void Promise::MaybeRejectWithClone(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ JS::Rooted<JSObject*> sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
+ AutoEntryScript aes(GetParentObject(), "Promise rejection");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JS::Value> value(cx, aValue);
+
+ xpc::StackScopedCloneOptions options;
+ options.wrapReflectors = true;
+ if (!StackScopedClone(cx, options, sourceScope, &value)) {
+ HandleException(cx);
+ return;
+ }
+ MaybeReject(aCx, value);
+}
+
+// A WorkerRunnable to resolve/reject the Promise on the worker thread.
+// Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
+class PromiseWorkerProxyRunnable final : public WorkerRunnable {
+ public:
+ PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
+ PromiseWorkerProxy::RunCallbackFunc aFunc)
+ : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
+ "PromiseWorkerProxyRunnable", WorkerThread),
+ mPromiseWorkerProxy(aPromiseWorkerProxy),
+ mFunc(aFunc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPromiseWorkerProxy);
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
+
+ MOZ_ASSERT(mPromiseWorkerProxy);
+ RefPtr<Promise> workerPromise = mPromiseWorkerProxy->GetWorkerPromise();
+ // Once Worker had already started shutdown, workerPromise would be nullptr
+ if (!workerPromise) {
+ return true;
+ }
+
+ // Here we convert the buffer to a JS::Value.
+ JS::Rooted<JS::Value> value(aCx);
+ if (!mPromiseWorkerProxy->Read(aCx, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ (workerPromise->*mFunc)(aCx, value);
+
+ // Release the Promise because it has been resolved/rejected for sure.
+ mPromiseWorkerProxy->CleanUp();
+ return true;
+ }
+
+ protected:
+ ~PromiseWorkerProxyRunnable() = default;
+
+ private:
+ RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+
+ // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
+ PromiseWorkerProxy::RunCallbackFunc mFunc;
+};
+
+/* static */
+already_AddRefed<PromiseWorkerProxy> PromiseWorkerProxy::Create(
+ WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCb) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aWorkerPromise);
+ MOZ_ASSERT_IF(aCb, !!aCb->Write && !!aCb->Read);
+
+ RefPtr<PromiseWorkerProxy> proxy =
+ new PromiseWorkerProxy(aWorkerPromise, aCb);
+
+ // Maintain a reference so that we have a valid object to clean up when
+ // removing the feature.
+ proxy.get()->AddRef();
+
+ // We do this to make sure the worker thread won't shut down before the
+ // promise is resolved/rejected on the worker thread.
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ aWorkerPrivate, "PromiseWorkerProxy", [proxy]() { proxy->CleanUp(); });
+
+ if (NS_WARN_IF(!workerRef)) {
+ // Probably the worker is terminating. We cannot complete the operation
+ // and we have to release all the resources. CleanUp releases the extra
+ // ref, too
+ proxy->CleanUp();
+ return nullptr;
+ }
+
+ proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+
+ return proxy.forget();
+}
+
+NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
+
+PromiseWorkerProxy::PromiseWorkerProxy(
+ Promise* aWorkerPromise,
+ const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks)
+ : mWorkerPromise(aWorkerPromise),
+ mCleanedUp(false),
+ mCallbacks(aCallbacks),
+ mCleanUpLock("cleanUpLock") {}
+
+PromiseWorkerProxy::~PromiseWorkerProxy() {
+ MOZ_ASSERT(mCleanedUp);
+ MOZ_ASSERT(!mWorkerPromise);
+ MOZ_ASSERT(!mWorkerRef);
+}
+
+WorkerPrivate* PromiseWorkerProxy::GetWorkerPrivate() const {
+#ifdef DEBUG
+ if (NS_IsMainThread()) {
+ mCleanUpLock.AssertCurrentThreadOwns();
+ }
+#endif
+ // Safe to check this without a lock since we assert lock ownership on the
+ // main thread above.
+ MOZ_ASSERT(!mCleanedUp);
+ MOZ_ASSERT(mWorkerRef);
+
+ return mWorkerRef->Private();
+}
+
+bool PromiseWorkerProxy::OnWritingThread() const {
+ return IsCurrentThreadRunningWorker();
+}
+
+Promise* PromiseWorkerProxy::GetWorkerPromise() const {
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+ return mWorkerPromise;
+}
+
+void PromiseWorkerProxy::RunCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ RunCallbackFunc aFunc) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(Lock());
+ // If the worker thread's been cancelled we don't need to resolve the Promise.
+ if (CleanedUp()) {
+ return;
+ }
+
+ // The |aValue| is written into the StructuredCloneHolderBase.
+ if (!Write(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ MOZ_ASSERT(false,
+ "cannot serialize the value with the StructuredCloneAlgorithm!");
+ }
+
+ RefPtr<PromiseWorkerProxyRunnable> runnable =
+ new PromiseWorkerProxyRunnable(this, aFunc);
+
+ runnable->Dispatch();
+}
+
+void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ RunCallback(aCx, aValue, &Promise::MaybeResolve);
+}
+
+void PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ RunCallback(aCx, aValue, &Promise::MaybeReject);
+}
+
+void PromiseWorkerProxy::CleanUp() {
+ // Can't release Mutex while it is still locked, so scope the lock.
+ {
+ MutexAutoLock lock(Lock());
+
+ if (CleanedUp()) {
+ return;
+ }
+
+ // We can be called if we failed to get a WorkerRef
+ if (mWorkerRef) {
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+ }
+
+ // Release the Promise and remove the PromiseWorkerProxy from the holders of
+ // the worker thread since the Promise has been resolved/rejected or the
+ // worker thread has been cancelled.
+ mCleanedUp = true;
+ mWorkerPromise = nullptr;
+ mWorkerRef = nullptr;
+
+ // Clear the StructuredCloneHolderBase class.
+ Clear();
+ }
+ Release();
+}
+
+JSObject* PromiseWorkerProxy::CustomReadHandler(
+ JSContext* aCx, JSStructuredCloneReader* aReader,
+ const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag,
+ uint32_t aIndex) {
+ if (NS_WARN_IF(!mCallbacks)) {
+ return nullptr;
+ }
+
+ return mCallbacks->Read(aCx, aReader, this, aTag, aIndex);
+}
+
+bool PromiseWorkerProxy::CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ bool* aSameProcessScopeRequired) {
+ if (NS_WARN_IF(!mCallbacks)) {
+ return false;
+ }
+
+ return mCallbacks->Write(aCx, aWriter, this, aObj);
+}
+
+// Specializations of MaybeRejectBrokenly we actually support.
+template <>
+void Promise::MaybeRejectBrokenly(const RefPtr<DOMException>& aArg) {
+ MaybeSomething(aArg, &Promise::MaybeReject);
+}
+template <>
+void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
+ MaybeSomething(aArg, &Promise::MaybeReject);
+}
+
+Promise::PromiseState Promise::State() const {
+ JS::Rooted<JSObject*> p(RootingCx(), PromiseObj());
+ const JS::PromiseState state = JS::GetPromiseState(p);
+
+ if (state == JS::PromiseState::Fulfilled) {
+ return PromiseState::Resolved;
+ }
+
+ if (state == JS::PromiseState::Rejected) {
+ return PromiseState::Rejected;
+ }
+
+ return PromiseState::Pending;
+}
+
+bool Promise::SetSettledPromiseIsHandled() {
+ if (!mPromiseObj) {
+ // Do nothing as it's a no-op promise
+ return false;
+ }
+ AutoAllowLegacyScriptExecution exemption;
+ AutoEntryScript aes(mGlobal, "Set settled promise handled");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> promiseObj(cx, mPromiseObj);
+ return JS::SetSettledPromiseIsHandled(cx, promiseObj);
+}
+
+bool Promise::SetAnyPromiseIsHandled() {
+ if (!mPromiseObj) {
+ // Do nothing as it's a no-op promise
+ return false;
+ }
+ AutoAllowLegacyScriptExecution exemption;
+ AutoEntryScript aes(mGlobal, "Set any promise handled");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> promiseObj(cx, mPromiseObj);
+ return JS::SetAnyPromiseIsHandled(cx, promiseObj);
+}
+
+/* static */
+already_AddRefed<Promise> Promise::CreateResolvedWithUndefined(
+ nsIGlobalObject* global, ErrorResult& aRv) {
+ RefPtr<Promise> returnPromise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ returnPromise->MaybeResolveWithUndefined();
+ return returnPromise.forget();
+}
+
+already_AddRefed<Promise> Promise::CreateRejected(
+ nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aRejectionError,
+ ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ promise->MaybeReject(aRejectionError);
+ return promise.forget();
+}
+
+already_AddRefed<Promise> Promise::CreateRejectedWithTypeError(
+ nsIGlobalObject* aGlobal, const nsACString& aMessage, ErrorResult& aRv) {
+ RefPtr<Promise> returnPromise = Promise::Create(aGlobal, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ returnPromise->MaybeRejectWithTypeError(aMessage);
+ return returnPromise.forget();
+}
+
+already_AddRefed<Promise> Promise::CreateRejectedWithErrorResult(
+ nsIGlobalObject* aGlobal, ErrorResult& aRejectionError) {
+ RefPtr<Promise> returnPromise = Promise::Create(aGlobal, IgnoreErrors());
+ if (!returnPromise) {
+ return nullptr;
+ }
+ returnPromise->MaybeReject(std::move(aRejectionError));
+ return returnPromise.forget();
+}
+
+nsresult Promise::TryExtractNSResultFromRejectionValue(
+ JS::Handle<JS::Value> aValue) {
+ if (aValue.isInt32()) {
+ return nsresult(aValue.toInt32());
+ }
+
+ if (aValue.isObject()) {
+ RefPtr<DOMException> domException;
+ UNWRAP_OBJECT(DOMException, aValue, domException);
+ if (domException) {
+ return domException->GetResult();
+ }
+ }
+
+ return NS_ERROR_DOM_NOT_NUMBER_ERR;
+}
+
+} // namespace mozilla::dom
+
+extern "C" {
+
+// These functions are used in the implementation of ffi bindings for
+// dom::Promise from Rust.
+
+void DomPromise_AddRef(mozilla::dom::Promise* aPromise) {
+ MOZ_ASSERT(aPromise);
+ aPromise->AddRef();
+}
+
+void DomPromise_Release(mozilla::dom::Promise* aPromise) {
+ MOZ_ASSERT(aPromise);
+ aPromise->Release();
+}
+
+#define DOM_PROMISE_FUNC_WITH_VARIANT(name, func) \
+ void name(mozilla::dom::Promise* aPromise, nsIVariant* aVariant) { \
+ MOZ_ASSERT(aPromise); \
+ MOZ_ASSERT(aVariant); \
+ mozilla::dom::AutoEntryScript aes(aPromise->GetGlobalObject(), \
+ "Promise resolution or rejection"); \
+ JSContext* cx = aes.cx(); \
+ \
+ JS::Rooted<JS::Value> val(cx); \
+ nsresult rv = NS_OK; \
+ if (!XPCVariant::VariantDataToJS(cx, aVariant, &rv, &val)) { \
+ aPromise->MaybeRejectWithTypeError( \
+ "Failed to convert nsIVariant to JS"); \
+ return; \
+ } \
+ aPromise->func(val); \
+ }
+
+DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_RejectWithVariant, MaybeReject)
+DOM_PROMISE_FUNC_WITH_VARIANT(DomPromise_ResolveWithVariant, MaybeResolve)
+
+#undef DOM_PROMISE_FUNC_WITH_VARIANT
+}