summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/Promise.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/builtin/Promise.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/Promise.cpp')
-rw-r--r--js/src/builtin/Promise.cpp6632
1 files changed, 6632 insertions, 0 deletions
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
new file mode 100644
index 0000000000..fa09eb292d
--- /dev/null
+++ b/js/src/builtin/Promise.cpp
@@ -0,0 +1,6632 @@
+/* -*- 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 "builtin/Promise.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+
+#include "jsapi.h"
+#include "jsexn.h"
+#include "jsfriendapi.h"
+
+#include "js/CallAndConstruct.h" // JS::Construct, JS::IsCallable
+#include "js/experimental/JitInfo.h" // JSJitGetterOp, JSJitInfo
+#include "js/ForOfIterator.h" // JS::ForOfIterator
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertySpec.h"
+#include "js/Stack.h"
+#include "vm/ArrayObject.h"
+#include "vm/AsyncFunction.h"
+#include "vm/AsyncIteration.h"
+#include "vm/CompletionKind.h"
+#include "vm/ErrorObject.h"
+#include "vm/ErrorReporting.h"
+#include "vm/Iteration.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/PromiseLookup.h" // js::PromiseLookup
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseSlot_*
+#include "vm/SelfHosting.h"
+#include "vm/Warnings.h" // js::WarnNumberASCII
+
+#include "debugger/DebugAPI-inl.h"
+#include "vm/Compartment-inl.h"
+#include "vm/ErrorObject-inl.h"
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+static double MillisecondsSinceStartup() {
+ auto now = mozilla::TimeStamp::Now();
+ return (now - mozilla::TimeStamp::FirstTimeStamp()).ToMilliseconds();
+}
+
+enum ResolutionMode { ResolveMode, RejectMode };
+
+/**
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * Promise Resolve Functions
+ * https://tc39.es/ecma262/#sec-promise-resolve-functions
+ */
+enum ResolveFunctionSlots {
+ // NOTE: All slot represent [[AlreadyResolved]].[[Value]].
+ //
+ // The spec creates single record for [[AlreadyResolved]] and shares it
+ // between Promise Resolve Function and Promise Reject Function.
+ //
+ // Step 1. Let alreadyResolved be the Record { [[Value]]: false }.
+ // ...
+ // Step 6. Set resolve.[[AlreadyResolved]] to alreadyResolved.
+ // ...
+ // Step 11. Set reject.[[AlreadyResolved]] to alreadyResolved.
+ //
+ // We implement it by clearing all slots, both in
+ // Promise Resolve Function and Promise Reject Function at the same time.
+ //
+ // If none of slots are undefined, [[AlreadyResolved]].[[Value]] is false.
+ // If all slot are undefined, [[AlreadyResolved]].[[Value]] is true.
+
+ // [[Promise]] slot.
+ // A possibly-wrapped promise.
+ ResolveFunctionSlot_Promise = 0,
+
+ // The corresponding Promise Reject Function.
+ ResolveFunctionSlot_RejectFunction,
+};
+
+/**
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * Promise Reject Functions
+ * https://tc39.es/ecma262/#sec-promise-reject-functions
+ */
+enum RejectFunctionSlots {
+ // [[Promise]] slot.
+ // A possibly-wrapped promise.
+ RejectFunctionSlot_Promise = 0,
+
+ // The corresponding Promise Resolve Function.
+ RejectFunctionSlot_ResolveFunction,
+};
+
+enum PromiseCombinatorElementFunctionSlots {
+ PromiseCombinatorElementFunctionSlot_Data = 0,
+ PromiseCombinatorElementFunctionSlot_ElementIndex,
+};
+
+enum ReactionJobSlots {
+ ReactionJobSlot_ReactionRecord = 0,
+};
+
+enum ThenableJobSlots {
+ // The handler to use as the Promise reaction. It is a callable object
+ // that's guaranteed to be from the same compartment as the
+ // PromiseReactionJob.
+ ThenableJobSlot_Handler = 0,
+
+ // JobData - a, potentially CCW-wrapped, dense list containing data
+ // required for proper execution of the reaction.
+ ThenableJobSlot_JobData,
+};
+
+enum ThenableJobDataIndices {
+ // The Promise to resolve using the given thenable.
+ ThenableJobDataIndex_Promise = 0,
+
+ // The thenable to use as the receiver when calling the `then` function.
+ ThenableJobDataIndex_Thenable,
+
+ ThenableJobDataLength,
+};
+
+enum BuiltinThenableJobSlots {
+ // The Promise to resolve using the given thenable.
+ BuiltinThenableJobSlot_Promise = 0,
+
+ // The thenable to use as the receiver when calling the built-in `then`
+ // function.
+ BuiltinThenableJobSlot_Thenable,
+};
+
+struct PromiseCapability {
+ JSObject* promise = nullptr;
+ JSObject* resolve = nullptr;
+ JSObject* reject = nullptr;
+
+ PromiseCapability() = default;
+
+ void trace(JSTracer* trc);
+};
+
+void PromiseCapability::trace(JSTracer* trc) {
+ if (promise) {
+ TraceRoot(trc, &promise, "PromiseCapability::promise");
+ }
+ if (resolve) {
+ TraceRoot(trc, &resolve, "PromiseCapability::resolve");
+ }
+ if (reject) {
+ TraceRoot(trc, &reject, "PromiseCapability::reject");
+ }
+}
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<PromiseCapability, Wrapper> {
+ const PromiseCapability& capability() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ HandleObject promise() const {
+ return HandleObject::fromMarkedLocation(&capability().promise);
+ }
+ HandleObject resolve() const {
+ return HandleObject::fromMarkedLocation(&capability().resolve);
+ }
+ HandleObject reject() const {
+ return HandleObject::fromMarkedLocation(&capability().reject);
+ }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<PromiseCapability, Wrapper>
+ : public WrappedPtrOperations<PromiseCapability, Wrapper> {
+ PromiseCapability& capability() { return static_cast<Wrapper*>(this)->get(); }
+
+ public:
+ MutableHandleObject promise() {
+ return MutableHandleObject::fromMarkedLocation(&capability().promise);
+ }
+ MutableHandleObject resolve() {
+ return MutableHandleObject::fromMarkedLocation(&capability().resolve);
+ }
+ MutableHandleObject reject() {
+ return MutableHandleObject::fromMarkedLocation(&capability().reject);
+ }
+};
+
+} // namespace js
+
+struct PromiseCombinatorElements;
+
+class PromiseCombinatorDataHolder : public NativeObject {
+ enum {
+ Slot_Promise = 0,
+ Slot_RemainingElements,
+ Slot_ValuesArray,
+ Slot_ResolveOrRejectFunction,
+ SlotsCount,
+ };
+
+ public:
+ static const JSClass class_;
+ JSObject* promiseObj() { return &getFixedSlot(Slot_Promise).toObject(); }
+ JSObject* resolveOrRejectObj() {
+ return &getFixedSlot(Slot_ResolveOrRejectFunction).toObject();
+ }
+ Value valuesArray() { return getFixedSlot(Slot_ValuesArray); }
+ int32_t remainingCount() {
+ return getFixedSlot(Slot_RemainingElements).toInt32();
+ }
+ int32_t increaseRemainingCount() {
+ int32_t remainingCount = getFixedSlot(Slot_RemainingElements).toInt32();
+ remainingCount++;
+ setFixedSlot(Slot_RemainingElements, Int32Value(remainingCount));
+ return remainingCount;
+ }
+ int32_t decreaseRemainingCount() {
+ int32_t remainingCount = getFixedSlot(Slot_RemainingElements).toInt32();
+ remainingCount--;
+ MOZ_ASSERT(remainingCount >= 0, "unpaired calls to decreaseRemainingCount");
+ setFixedSlot(Slot_RemainingElements, Int32Value(remainingCount));
+ return remainingCount;
+ }
+
+ static PromiseCombinatorDataHolder* New(
+ JSContext* cx, HandleObject resultPromise,
+ Handle<PromiseCombinatorElements> elements, HandleObject resolveOrReject);
+};
+
+const JSClass PromiseCombinatorDataHolder::class_ = {
+ "PromiseCombinatorDataHolder", JSCLASS_HAS_RESERVED_SLOTS(SlotsCount)};
+
+// Smart pointer to the "F.[[Values]]" part of the state of a Promise.all or
+// Promise.allSettled invocation, or the "F.[[Errors]]" part of the state of a
+// Promise.any invocation. Copes with compartment issues when setting an
+// element.
+struct MOZ_STACK_CLASS PromiseCombinatorElements final {
+ // Object value holding the elements array. The object can be a wrapper.
+ Value value;
+
+ // Unwrapped elements array. May not belong to the current compartment!
+ ArrayObject* unwrappedArray = nullptr;
+
+ // Set to true if the |setElement| method needs to wrap its input value.
+ bool setElementNeedsWrapping = false;
+
+ PromiseCombinatorElements() = default;
+
+ void trace(JSTracer* trc);
+};
+
+void PromiseCombinatorElements::trace(JSTracer* trc) {
+ TraceRoot(trc, &value, "PromiseCombinatorElements::value");
+ if (unwrappedArray) {
+ TraceRoot(trc, &unwrappedArray,
+ "PromiseCombinatorElements::unwrappedArray");
+ }
+}
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<PromiseCombinatorElements, Wrapper> {
+ const PromiseCombinatorElements& elements() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ HandleValue value() const {
+ return HandleValue::fromMarkedLocation(&elements().value);
+ }
+
+ Handle<ArrayObject*> unwrappedArray() const {
+ return Handle<ArrayObject*>::fromMarkedLocation(&elements().unwrappedArray);
+ }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<PromiseCombinatorElements, Wrapper>
+ : public WrappedPtrOperations<PromiseCombinatorElements, Wrapper> {
+ PromiseCombinatorElements& elements() {
+ return static_cast<Wrapper*>(this)->get();
+ }
+
+ public:
+ MutableHandleValue value() {
+ return MutableHandleValue::fromMarkedLocation(&elements().value);
+ }
+
+ MutableHandle<ArrayObject*> unwrappedArray() {
+ return MutableHandle<ArrayObject*>::fromMarkedLocation(
+ &elements().unwrappedArray);
+ }
+
+ void initialize(ArrayObject* arrayObj) {
+ unwrappedArray().set(arrayObj);
+ value().setObject(*arrayObj);
+
+ // |needsWrapping| isn't tracked here, because all modifications on the
+ // initial elements don't require any wrapping.
+ }
+
+ void initialize(PromiseCombinatorDataHolder* data, ArrayObject* arrayObj,
+ bool needsWrapping) {
+ unwrappedArray().set(arrayObj);
+ value().set(data->valuesArray());
+ elements().setElementNeedsWrapping = needsWrapping;
+ }
+
+ [[nodiscard]] bool pushUndefined(JSContext* cx) {
+ // Helper for the AutoRealm we need to work with |array|. We mostly do this
+ // for performance; we could go ahead and do the define via a cross-
+ // compartment proxy instead...
+ AutoRealm ar(cx, unwrappedArray());
+
+ Handle<ArrayObject*> arrayObj = unwrappedArray();
+ return js::NewbornArrayPush(cx, arrayObj, UndefinedValue());
+ }
+
+ // `Promise.all` Resolve Element Functions
+ // Step 9. Set values[index] to x.
+ //
+ // `Promise.allSettled` Resolve Element Functions
+ // `Promise.allSettled` Reject Element Functions
+ // Step 12. Set values[index] to obj.
+ //
+ // `Promise.any` Reject Element Functions
+ // Step 9. Set errors[index] to x.
+ //
+ // These handler functions are always created in the compartment of the
+ // Promise.all/allSettled/any function, which isn't necessarily the same
+ // compartment as unwrappedArray as explained in NewPromiseCombinatorElements.
+ // So before storing |val| we may need to enter unwrappedArray's compartment.
+ [[nodiscard]] bool setElement(JSContext* cx, uint32_t index,
+ HandleValue val) {
+ // The index is guaranteed to be initialized to `undefined`.
+ MOZ_ASSERT(unwrappedArray()->getDenseElement(index).isUndefined());
+
+ if (elements().setElementNeedsWrapping) {
+ AutoRealm ar(cx, unwrappedArray());
+
+ RootedValue rootedVal(cx, val);
+ if (!cx->compartment()->wrap(cx, &rootedVal)) {
+ return false;
+ }
+ unwrappedArray()->setDenseElement(index, rootedVal);
+ } else {
+ unwrappedArray()->setDenseElement(index, val);
+ }
+ return true;
+ }
+};
+
+} // namespace js
+
+PromiseCombinatorDataHolder* PromiseCombinatorDataHolder::New(
+ JSContext* cx, HandleObject resultPromise,
+ Handle<PromiseCombinatorElements> elements, HandleObject resolveOrReject) {
+ auto* dataHolder = NewBuiltinClassInstance<PromiseCombinatorDataHolder>(cx);
+ if (!dataHolder) {
+ return nullptr;
+ }
+
+ cx->check(resultPromise);
+ cx->check(elements.value());
+ cx->check(resolveOrReject);
+
+ dataHolder->setFixedSlot(Slot_Promise, ObjectValue(*resultPromise));
+ dataHolder->setFixedSlot(Slot_RemainingElements, Int32Value(1));
+ dataHolder->setFixedSlot(Slot_ValuesArray, elements.value());
+ dataHolder->setFixedSlot(Slot_ResolveOrRejectFunction,
+ ObjectValue(*resolveOrReject));
+ return dataHolder;
+}
+
+namespace {
+// Generator used by PromiseObject::getID.
+mozilla::Atomic<uint64_t> gIDGenerator(0);
+} // namespace
+
+class PromiseDebugInfo : public NativeObject {
+ private:
+ enum Slots {
+ Slot_AllocationSite,
+ Slot_ResolutionSite,
+ Slot_AllocationTime,
+ Slot_ResolutionTime,
+ Slot_Id,
+ SlotCount
+ };
+
+ public:
+ static const JSClass class_;
+ static PromiseDebugInfo* create(JSContext* cx,
+ Handle<PromiseObject*> promise) {
+ Rooted<PromiseDebugInfo*> debugInfo(
+ cx, NewBuiltinClassInstance<PromiseDebugInfo>(cx));
+ if (!debugInfo) {
+ return nullptr;
+ }
+
+ RootedObject stack(cx);
+ if (!JS::CaptureCurrentStack(cx, &stack,
+ JS::StackCapture(JS::AllFrames()))) {
+ return nullptr;
+ }
+ debugInfo->setFixedSlot(Slot_AllocationSite, ObjectOrNullValue(stack));
+ debugInfo->setFixedSlot(Slot_ResolutionSite, NullValue());
+ debugInfo->setFixedSlot(Slot_AllocationTime,
+ DoubleValue(MillisecondsSinceStartup()));
+ debugInfo->setFixedSlot(Slot_ResolutionTime, NumberValue(0));
+ promise->setFixedSlot(PromiseSlot_DebugInfo, ObjectValue(*debugInfo));
+
+ return debugInfo;
+ }
+
+ static PromiseDebugInfo* FromPromise(PromiseObject* promise) {
+ Value val = promise->getFixedSlot(PromiseSlot_DebugInfo);
+ if (val.isObject()) {
+ return &val.toObject().as<PromiseDebugInfo>();
+ }
+ return nullptr;
+ }
+
+ /**
+ * Returns the given PromiseObject's process-unique ID.
+ * The ID is lazily assigned when first queried, and then either stored
+ * in the DebugInfo slot if no debug info was recorded for this Promise,
+ * or in the Id slot of the DebugInfo object.
+ */
+ static uint64_t id(PromiseObject* promise) {
+ Value idVal(promise->getFixedSlot(PromiseSlot_DebugInfo));
+ if (idVal.isUndefined()) {
+ idVal.setDouble(++gIDGenerator);
+ promise->setFixedSlot(PromiseSlot_DebugInfo, idVal);
+ } else if (idVal.isObject()) {
+ PromiseDebugInfo* debugInfo = FromPromise(promise);
+ idVal = debugInfo->getFixedSlot(Slot_Id);
+ if (idVal.isUndefined()) {
+ idVal.setDouble(++gIDGenerator);
+ debugInfo->setFixedSlot(Slot_Id, idVal);
+ }
+ }
+ return uint64_t(idVal.toNumber());
+ }
+
+ double allocationTime() {
+ return getFixedSlot(Slot_AllocationTime).toNumber();
+ }
+ double resolutionTime() {
+ return getFixedSlot(Slot_ResolutionTime).toNumber();
+ }
+ JSObject* allocationSite() {
+ return getFixedSlot(Slot_AllocationSite).toObjectOrNull();
+ }
+ JSObject* resolutionSite() {
+ return getFixedSlot(Slot_ResolutionSite).toObjectOrNull();
+ }
+
+ // The |unwrappedRejectionStack| parameter should only be set on promise
+ // rejections and should be the stack of the exception that caused the promise
+ // to be rejected. If the |unwrappedRejectionStack| is null, the current stack
+ // will be used instead. This is also the default behavior for fulfilled
+ // promises.
+ static void setResolutionInfo(JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<SavedFrame*> unwrappedRejectionStack) {
+ MOZ_ASSERT_IF(unwrappedRejectionStack,
+ promise->state() == JS::PromiseState::Rejected);
+
+ if (!JS::IsAsyncStackCaptureEnabledForRealm(cx)) {
+ return;
+ }
+
+ // If async stacks weren't enabled and the Promise's global wasn't a
+ // debuggee when the Promise was created, we won't have a debugInfo
+ // object. We still want to capture the resolution stack, so we
+ // create the object now and change it's slots' values around a bit.
+ Rooted<PromiseDebugInfo*> debugInfo(cx, FromPromise(promise));
+ if (!debugInfo) {
+ RootedValue idVal(cx, promise->getFixedSlot(PromiseSlot_DebugInfo));
+ debugInfo = create(cx, promise);
+ if (!debugInfo) {
+ cx->clearPendingException();
+ return;
+ }
+
+ // The current stack was stored in the AllocationSite slot, move
+ // it to ResolutionSite as that's what it really is.
+ debugInfo->setFixedSlot(Slot_ResolutionSite,
+ debugInfo->getFixedSlot(Slot_AllocationSite));
+ debugInfo->setFixedSlot(Slot_AllocationSite, NullValue());
+
+ // There's no good default for a missing AllocationTime, so
+ // instead of resetting that, ensure that it's the same as
+ // ResolutionTime, so that the diff shows as 0, which isn't great,
+ // but bearable.
+ debugInfo->setFixedSlot(Slot_ResolutionTime,
+ debugInfo->getFixedSlot(Slot_AllocationTime));
+
+ // The Promise's ID might've been queried earlier, in which case
+ // it's stored in the DebugInfo slot. We saved that earlier, so
+ // now we can store it in the right place (or leave it as
+ // undefined if it wasn't ever initialized.)
+ debugInfo->setFixedSlot(Slot_Id, idVal);
+ return;
+ }
+
+ RootedObject stack(cx, unwrappedRejectionStack);
+ if (stack) {
+ // The exception stack is always unwrapped so it might be in
+ // a different compartment.
+ if (!cx->compartment()->wrap(cx, &stack)) {
+ cx->clearPendingException();
+ return;
+ }
+ } else {
+ if (!JS::CaptureCurrentStack(cx, &stack,
+ JS::StackCapture(JS::AllFrames()))) {
+ cx->clearPendingException();
+ return;
+ }
+ }
+
+ debugInfo->setFixedSlot(Slot_ResolutionSite, ObjectOrNullValue(stack));
+ debugInfo->setFixedSlot(Slot_ResolutionTime,
+ DoubleValue(MillisecondsSinceStartup()));
+ }
+};
+
+const JSClass PromiseDebugInfo::class_ = {
+ "PromiseDebugInfo", JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
+
+double PromiseObject::allocationTime() {
+ auto debugInfo = PromiseDebugInfo::FromPromise(this);
+ if (debugInfo) {
+ return debugInfo->allocationTime();
+ }
+ return 0;
+}
+
+double PromiseObject::resolutionTime() {
+ auto debugInfo = PromiseDebugInfo::FromPromise(this);
+ if (debugInfo) {
+ return debugInfo->resolutionTime();
+ }
+ return 0;
+}
+
+JSObject* PromiseObject::allocationSite() {
+ auto debugInfo = PromiseDebugInfo::FromPromise(this);
+ if (debugInfo) {
+ return debugInfo->allocationSite();
+ }
+ return nullptr;
+}
+
+JSObject* PromiseObject::resolutionSite() {
+ auto debugInfo = PromiseDebugInfo::FromPromise(this);
+ if (debugInfo) {
+ JSObject* site = debugInfo->resolutionSite();
+ if (site && !JS_IsDeadWrapper(site)) {
+ MOZ_ASSERT(UncheckedUnwrap(site)->is<SavedFrame>());
+ return site;
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Wrapper for GetAndClearExceptionAndStack that handles cases where
+ * no exception is pending, but an error occurred.
+ * This can be the case if an OOM was encountered while throwing the error.
+ */
+static bool MaybeGetAndClearExceptionAndStack(
+ JSContext* cx, MutableHandleValue rval, MutableHandle<SavedFrame*> stack) {
+ if (!cx->isExceptionPending()) {
+ return false;
+ }
+
+ return GetAndClearExceptionAndStack(cx, rval, stack);
+}
+
+[[nodiscard]] static bool CallPromiseRejectFunction(
+ JSContext* cx, HandleObject rejectFun, HandleValue reason,
+ HandleObject promiseObj, Handle<SavedFrame*> unwrappedRejectionStack,
+ UnhandledRejectionBehavior behavior);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * IfAbruptRejectPromise ( value, capability )
+ * https://tc39.es/ecma262/#sec-ifabruptrejectpromise
+ *
+ * Steps 1.a-b.
+ *
+ * Extracting all of this internal spec algorithm into a helper function would
+ * be tedious, so the check in step 1 and the entirety of step 2 aren't
+ * included.
+ */
+static bool AbruptRejectPromise(JSContext* cx, CallArgs& args,
+ HandleObject promiseObj, HandleObject reject) {
+ // Step 1.a. Perform
+ // ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
+ RootedValue reason(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!MaybeGetAndClearExceptionAndStack(cx, &reason, &stack)) {
+ return false;
+ }
+
+ if (!CallPromiseRejectFunction(cx, reject, reason, promiseObj, stack,
+ UnhandledRejectionBehavior::Report)) {
+ return false;
+ }
+
+ // Step 1.b. Return capability.[[Promise]].
+ args.rval().setObject(*promiseObj);
+ return true;
+}
+
+static bool AbruptRejectPromise(JSContext* cx, CallArgs& args,
+ Handle<PromiseCapability> capability) {
+ return AbruptRejectPromise(cx, args, capability.promise(),
+ capability.reject());
+}
+
+enum ReactionRecordSlots {
+ // This is the promise-like object that gets resolved with the result of this
+ // reaction, if any. If this reaction record was created with .then or .catch,
+ // this is the promise that .then or .catch returned.
+ //
+ // The spec says that a PromiseReaction record has a [[Capability]] field
+ // whose value is either undefined or a PromiseCapability record, but we just
+ // store the PromiseCapability's fields directly in this object. This is the
+ // capability's [[Promise]] field; its [[Resolve]] and [[Reject]] fields are
+ // stored in ReactionRecordSlot_Resolve and ReactionRecordSlot_Reject.
+ //
+ // This can be 'null' in reaction records created for a few situations:
+ //
+ // - When you resolve one promise to another. When you pass a promise P1 to
+ // the 'fulfill' function of a promise P2, so that resolving P1 resolves P2
+ // in the same way, P1 gets a reaction record with the
+ // REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag set and whose
+ // ReactionRecordSlot_GeneratorOrPromiseToResolve slot holds P2.
+ //
+ // - When you await a promise. When an async function or generator awaits a
+ // value V, then the await expression generates an internal promise P,
+ // resolves it to V, and then gives P a reaction record with the
+ // REACTION_FLAG_ASYNC_FUNCTION or REACTION_FLAG_ASYNC_GENERATOR flag set
+ // and whose ReactionRecordSlot_GeneratorOrPromiseToResolve slot holds the
+ // generator object. (Typically V is a promise, so resolving P to V gives V
+ // a REACTION_FLAGS_DEFAULT_RESOLVING_HANDLER reaction record as described
+ // above.)
+ //
+ // - When JS::AddPromiseReactions{,IgnoringUnhandledRejection} cause the
+ // reaction to be created. (These functions act as if they had created a
+ // promise to invoke the appropriate provided reaction function, without
+ // actually allocating a promise for them.)
+ ReactionRecordSlot_Promise = 0,
+
+ // The [[Handler]] field(s) of a PromiseReaction record. We create a
+ // single reaction record for fulfillment and rejection, therefore our
+ // PromiseReaction implementation needs two [[Handler]] fields.
+ //
+ // The slot value is either a callable object, an integer constant from
+ // the |PromiseHandler| enum, or null. If the value is null, either the
+ // REACTION_FLAG_DEBUGGER_DUMMY or the
+ // REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag must be set.
+ //
+ // After setting the target state for a PromiseReaction, the slot of the
+ // no longer used handler gets reused to store the argument of the active
+ // handler.
+ ReactionRecordSlot_OnFulfilled,
+ ReactionRecordSlot_OnRejectedArg = ReactionRecordSlot_OnFulfilled,
+ ReactionRecordSlot_OnRejected,
+ ReactionRecordSlot_OnFulfilledArg = ReactionRecordSlot_OnRejected,
+
+ // The functions to resolve or reject the promise. Matches the
+ // [[Capability]].[[Resolve]] and [[Capability]].[[Reject]] fields from
+ // the spec.
+ //
+ // The slot values are either callable objects or null, but the latter
+ // case is only allowed if the promise is either a built-in Promise object
+ // or null.
+ ReactionRecordSlot_Resolve,
+ ReactionRecordSlot_Reject,
+
+ // The incumbent global for this reaction record. Can be null.
+ ReactionRecordSlot_IncumbentGlobalObject,
+
+ // Bitmask of the REACTION_FLAG values.
+ ReactionRecordSlot_Flags,
+
+ // Additional slot to store extra data for specific reaction record types.
+ //
+ // - When the REACTION_FLAG_ASYNC_FUNCTION flag is set, this slot stores
+ // the (internal) generator object for this promise reaction.
+ // - When the REACTION_FLAG_ASYNC_GENERATOR flag is set, this slot stores
+ // the async generator object for this promise reaction.
+ // - When the REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag is set, this
+ // slot stores the promise to resolve when conceptually "calling" the
+ // OnFulfilled or OnRejected handlers.
+ ReactionRecordSlot_GeneratorOrPromiseToResolve,
+
+ ReactionRecordSlots,
+};
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PromiseReaction Records
+ * https://tc39.es/ecma262/#sec-promisereaction-records
+ */
+class PromiseReactionRecord : public NativeObject {
+ // If this flag is set, this reaction record is already enqueued to the
+ // job queue, and the spec's [[Type]] field is represented by
+ // REACTION_FLAG_FULFILLED flag.
+ //
+ // If this flag isn't yet set, [[Type]] field is undefined.
+ static constexpr uint32_t REACTION_FLAG_RESOLVED = 0x1;
+
+ // This bit is valid only when REACTION_FLAG_RESOLVED flag is set.
+ //
+ // If this flag is set, [[Type]] field is Fulfill.
+ // If this flag isn't set, [[Type]] field is Reject.
+ static constexpr uint32_t REACTION_FLAG_FULFILLED = 0x2;
+
+ // If this flag is set, this reaction record is created for resolving
+ // one promise P1 to another promise P2, and
+ // ReactionRecordSlot_GeneratorOrPromiseToResolve slot holds P2.
+ static constexpr uint32_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4;
+
+ // If this flag is set, this reaction record is created for async function
+ // and ReactionRecordSlot_GeneratorOrPromiseToResolve slot holds
+ // internal generator object of the async function.
+ static constexpr uint32_t REACTION_FLAG_ASYNC_FUNCTION = 0x8;
+
+ // If this flag is set, this reaction record is created for async generator
+ // and ReactionRecordSlot_GeneratorOrPromiseToResolve slot holds
+ // the async generator object of the async generator.
+ static constexpr uint32_t REACTION_FLAG_ASYNC_GENERATOR = 0x10;
+
+ // If this flag is set, this reaction record is created only for providing
+ // information to debugger.
+ static constexpr uint32_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20;
+
+ // This bit is valid only when the promise object is optimized out
+ // for the reaction.
+ //
+ // If this flag is set, unhandled rejection should be ignored.
+ // Otherwise, promise object should be created on-demand for unhandled
+ // rejection.
+ static constexpr uint32_t REACTION_FLAG_IGNORE_UNHANDLED_REJECTION = 0x40;
+
+ void setFlagOnInitialState(uint32_t flag) {
+ int32_t flags = this->flags();
+ MOZ_ASSERT(flags == 0, "Can't modify with non-default flags");
+ flags |= flag;
+ setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
+ }
+
+ uint32_t handlerSlot() {
+ MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+ return targetState() == JS::PromiseState::Fulfilled
+ ? ReactionRecordSlot_OnFulfilled
+ : ReactionRecordSlot_OnRejected;
+ }
+
+ uint32_t handlerArgSlot() {
+ MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+ return targetState() == JS::PromiseState::Fulfilled
+ ? ReactionRecordSlot_OnFulfilledArg
+ : ReactionRecordSlot_OnRejectedArg;
+ }
+
+ public:
+ static const JSClass class_;
+
+ JSObject* promise() {
+ return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull();
+ }
+
+ int32_t flags() const {
+ return getFixedSlot(ReactionRecordSlot_Flags).toInt32();
+ }
+
+ JS::PromiseState targetState() {
+ int32_t flags = this->flags();
+ if (!(flags & REACTION_FLAG_RESOLVED)) {
+ return JS::PromiseState::Pending;
+ }
+ return flags & REACTION_FLAG_FULFILLED ? JS::PromiseState::Fulfilled
+ : JS::PromiseState::Rejected;
+ }
+ void setTargetStateAndHandlerArg(JS::PromiseState state, const Value& arg) {
+ MOZ_ASSERT(targetState() == JS::PromiseState::Pending);
+ MOZ_ASSERT(state != JS::PromiseState::Pending,
+ "Can't revert a reaction to pending.");
+
+ int32_t flags = this->flags();
+ flags |= REACTION_FLAG_RESOLVED;
+ if (state == JS::PromiseState::Fulfilled) {
+ flags |= REACTION_FLAG_FULFILLED;
+ }
+
+ setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
+ setFixedSlot(handlerArgSlot(), arg);
+ }
+
+ void setShouldIgnoreUnhandledRejection() {
+ setFlagOnInitialState(REACTION_FLAG_IGNORE_UNHANDLED_REJECTION);
+ }
+ UnhandledRejectionBehavior unhandledRejectionBehavior() const {
+ int32_t flags = this->flags();
+ return (flags & REACTION_FLAG_IGNORE_UNHANDLED_REJECTION)
+ ? UnhandledRejectionBehavior::Ignore
+ : UnhandledRejectionBehavior::Report;
+ }
+
+ void setIsDefaultResolvingHandler(PromiseObject* promiseToResolve) {
+ setFlagOnInitialState(REACTION_FLAG_DEFAULT_RESOLVING_HANDLER);
+ setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve,
+ ObjectValue(*promiseToResolve));
+ }
+ bool isDefaultResolvingHandler() {
+ int32_t flags = this->flags();
+ return flags & REACTION_FLAG_DEFAULT_RESOLVING_HANDLER;
+ }
+ PromiseObject* defaultResolvingPromise() {
+ MOZ_ASSERT(isDefaultResolvingHandler());
+ const Value& promiseToResolve =
+ getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve);
+ return &promiseToResolve.toObject().as<PromiseObject>();
+ }
+
+ void setIsAsyncFunction(AsyncFunctionGeneratorObject* genObj) {
+ setFlagOnInitialState(REACTION_FLAG_ASYNC_FUNCTION);
+ setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve,
+ ObjectValue(*genObj));
+ }
+ bool isAsyncFunction() {
+ int32_t flags = this->flags();
+ return flags & REACTION_FLAG_ASYNC_FUNCTION;
+ }
+ AsyncFunctionGeneratorObject* asyncFunctionGenerator() {
+ MOZ_ASSERT(isAsyncFunction());
+ const Value& generator =
+ getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve);
+ return &generator.toObject().as<AsyncFunctionGeneratorObject>();
+ }
+
+ void setIsAsyncGenerator(AsyncGeneratorObject* generator) {
+ setFlagOnInitialState(REACTION_FLAG_ASYNC_GENERATOR);
+ setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve,
+ ObjectValue(*generator));
+ }
+ bool isAsyncGenerator() {
+ int32_t flags = this->flags();
+ return flags & REACTION_FLAG_ASYNC_GENERATOR;
+ }
+ AsyncGeneratorObject* asyncGenerator() {
+ MOZ_ASSERT(isAsyncGenerator());
+ const Value& generator =
+ getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve);
+ return &generator.toObject().as<AsyncGeneratorObject>();
+ }
+
+ void setIsDebuggerDummy() {
+ setFlagOnInitialState(REACTION_FLAG_DEBUGGER_DUMMY);
+ }
+ bool isDebuggerDummy() {
+ int32_t flags = this->flags();
+ return flags & REACTION_FLAG_DEBUGGER_DUMMY;
+ }
+
+ Value handler() {
+ MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+ return getFixedSlot(handlerSlot());
+ }
+ Value handlerArg() {
+ MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+ return getFixedSlot(handlerArgSlot());
+ }
+
+ JSObject* getAndClearIncumbentGlobalObject() {
+ JSObject* obj =
+ getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull();
+ setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, UndefinedValue());
+ return obj;
+ }
+};
+
+const JSClass PromiseReactionRecord::class_ = {
+ "PromiseReactionRecord", JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots)};
+
+static void AddPromiseFlags(PromiseObject& promise, int32_t flag) {
+ int32_t flags = promise.flags();
+ promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag));
+}
+
+static void RemovePromiseFlags(PromiseObject& promise, int32_t flag) {
+ int32_t flags = promise.flags();
+ promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags & ~flag));
+}
+
+static bool PromiseHasAnyFlag(PromiseObject& promise, int32_t flag) {
+ return promise.flags() & flag;
+}
+
+static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp);
+static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp);
+
+static JSFunction* GetResolveFunctionFromReject(JSFunction* reject);
+static JSFunction* GetRejectFunctionFromResolve(JSFunction* resolve);
+
+#ifdef DEBUG
+
+/**
+ * Returns Promise Resolve Function's [[AlreadyResolved]].[[Value]].
+ */
+static bool IsAlreadyResolvedMaybeWrappedResolveFunction(
+ JSObject* resolveFunObj) {
+ if (IsWrapper(resolveFunObj)) {
+ resolveFunObj = UncheckedUnwrap(resolveFunObj);
+ }
+
+ JSFunction* resolveFun = &resolveFunObj->as<JSFunction>();
+ MOZ_ASSERT(resolveFun->maybeNative() == ResolvePromiseFunction);
+
+ bool alreadyResolved =
+ resolveFun->getExtendedSlot(ResolveFunctionSlot_Promise).isUndefined();
+
+ // Other slots should agree.
+ if (alreadyResolved) {
+ MOZ_ASSERT(resolveFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
+ .isUndefined());
+ } else {
+ JSFunction* rejectFun = GetRejectFunctionFromResolve(resolveFun);
+ MOZ_ASSERT(
+ !rejectFun->getExtendedSlot(RejectFunctionSlot_Promise).isUndefined());
+ MOZ_ASSERT(!rejectFun->getExtendedSlot(RejectFunctionSlot_ResolveFunction)
+ .isUndefined());
+ }
+
+ return alreadyResolved;
+}
+
+/**
+ * Returns Promise Reject Function's [[AlreadyResolved]].[[Value]].
+ */
+static bool IsAlreadyResolvedMaybeWrappedRejectFunction(
+ JSObject* rejectFunObj) {
+ if (IsWrapper(rejectFunObj)) {
+ rejectFunObj = UncheckedUnwrap(rejectFunObj);
+ }
+
+ JSFunction* rejectFun = &rejectFunObj->as<JSFunction>();
+ MOZ_ASSERT(rejectFun->maybeNative() == RejectPromiseFunction);
+
+ bool alreadyResolved =
+ rejectFun->getExtendedSlot(RejectFunctionSlot_Promise).isUndefined();
+
+ // Other slots should agree.
+ if (alreadyResolved) {
+ MOZ_ASSERT(rejectFun->getExtendedSlot(RejectFunctionSlot_ResolveFunction)
+ .isUndefined());
+ } else {
+ JSFunction* resolveFun = GetResolveFunctionFromReject(rejectFun);
+ MOZ_ASSERT(!resolveFun->getExtendedSlot(ResolveFunctionSlot_Promise)
+ .isUndefined());
+ MOZ_ASSERT(!resolveFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
+ .isUndefined());
+ }
+
+ return alreadyResolved;
+}
+
+#endif // DEBUG
+
+/**
+ * Set Promise Resolve Function's and Promise Reject Function's
+ * [[AlreadyResolved]].[[Value]] to true.
+ *
+ * `resolutionFun` can be either of them.
+ */
+static void SetAlreadyResolvedResolutionFunction(JSFunction* resolutionFun) {
+ JSFunction* resolve;
+ JSFunction* reject;
+ if (resolutionFun->maybeNative() == ResolvePromiseFunction) {
+ resolve = resolutionFun;
+ reject = GetRejectFunctionFromResolve(resolutionFun);
+ } else {
+ resolve = GetResolveFunctionFromReject(resolutionFun);
+ reject = resolutionFun;
+ }
+
+ resolve->setExtendedSlot(ResolveFunctionSlot_Promise, UndefinedValue());
+ resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction,
+ UndefinedValue());
+
+ reject->setExtendedSlot(RejectFunctionSlot_Promise, UndefinedValue());
+ reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, UndefinedValue());
+
+ MOZ_ASSERT(IsAlreadyResolvedMaybeWrappedResolveFunction(resolve));
+ MOZ_ASSERT(IsAlreadyResolvedMaybeWrappedRejectFunction(reject));
+}
+
+/**
+ * Returns true if given promise is created by
+ * CreatePromiseObjectWithoutResolutionFunctions.
+ */
+bool js::IsPromiseWithDefaultResolvingFunction(PromiseObject* promise) {
+ return PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS);
+}
+
+/**
+ * Returns Promise Resolve Function's [[AlreadyResolved]].[[Value]] for
+ * a promise created by CreatePromiseObjectWithoutResolutionFunctions.
+ */
+static bool IsAlreadyResolvedPromiseWithDefaultResolvingFunction(
+ PromiseObject* promise) {
+ MOZ_ASSERT(IsPromiseWithDefaultResolvingFunction(promise));
+
+ if (promise->as<PromiseObject>().state() != JS::PromiseState::Pending) {
+ MOZ_ASSERT(PromiseHasAnyFlag(
+ *promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS_ALREADY_RESOLVED));
+ return true;
+ }
+
+ return PromiseHasAnyFlag(
+ *promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS_ALREADY_RESOLVED);
+}
+
+/**
+ * Set Promise Resolve Function's [[AlreadyResolved]].[[Value]] to true for
+ * a promise created by CreatePromiseObjectWithoutResolutionFunctions.
+ */
+void js::SetAlreadyResolvedPromiseWithDefaultResolvingFunction(
+ PromiseObject* promise) {
+ MOZ_ASSERT(IsPromiseWithDefaultResolvingFunction(promise));
+
+ promise->setFixedSlot(
+ PromiseSlot_Flags,
+ JS::Int32Value(
+ promise->flags() |
+ PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS_ALREADY_RESOLVED));
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * CreateResolvingFunctions ( promise )
+ * https://tc39.es/ecma262/#sec-createresolvingfunctions
+ */
+[[nodiscard]] static MOZ_ALWAYS_INLINE bool CreateResolvingFunctions(
+ JSContext* cx, HandleObject promise, MutableHandleObject resolveFn,
+ MutableHandleObject rejectFn) {
+ // Step 1. Let alreadyResolved be the Record { [[Value]]: false }.
+ // (implicit, see steps 5-6, 10-11 below)
+
+ // Step 2. Let stepsResolve be the algorithm steps defined in Promise Resolve
+ // Functions.
+ // Step 3. Let lengthResolve be the number of non-optional parameters of the
+ // function definition in Promise Resolve Functions.
+ // Step 4. Let resolve be
+ // ! CreateBuiltinFunction(stepsResolve, lengthResolve, "",
+ // « [[Promise]], [[AlreadyResolved]] »).
+ Handle<PropertyName*> funName = cx->names().empty;
+ resolveFn.set(NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!resolveFn) {
+ return false;
+ }
+
+ // Step 7. Let stepsReject be the algorithm steps defined in Promise Reject
+ // Functions.
+ // Step 8. Let lengthReject be the number of non-optional parameters of the
+ // function definition in Promise Reject Functions.
+ // Step 9. Let reject be
+ // ! CreateBuiltinFunction(stepsReject, lengthReject, "",
+ // « [[Promise]], [[AlreadyResolved]] »).
+ rejectFn.set(NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!rejectFn) {
+ return false;
+ }
+
+ JSFunction* resolveFun = &resolveFn->as<JSFunction>();
+ JSFunction* rejectFun = &rejectFn->as<JSFunction>();
+
+ // Step 5. Set resolve.[[Promise]] to promise.
+ // Step 6. Set resolve.[[AlreadyResolved]] to alreadyResolved.
+ //
+ // NOTE: We use these references as [[AlreadyResolved]].[[Value]].
+ // See the comment in ResolveFunctionSlots for more details.
+ resolveFun->initExtendedSlot(ResolveFunctionSlot_Promise,
+ ObjectValue(*promise));
+ resolveFun->initExtendedSlot(ResolveFunctionSlot_RejectFunction,
+ ObjectValue(*rejectFun));
+
+ // Step 10. Set reject.[[Promise]] to promise.
+ // Step 11. Set reject.[[AlreadyResolved]] to alreadyResolved.
+ //
+ // NOTE: We use these references as [[AlreadyResolved]].[[Value]].
+ // See the comment in ResolveFunctionSlots for more details.
+ rejectFun->initExtendedSlot(RejectFunctionSlot_Promise,
+ ObjectValue(*promise));
+ rejectFun->initExtendedSlot(RejectFunctionSlot_ResolveFunction,
+ ObjectValue(*resolveFun));
+
+ MOZ_ASSERT(!IsAlreadyResolvedMaybeWrappedResolveFunction(resolveFun));
+ MOZ_ASSERT(!IsAlreadyResolvedMaybeWrappedRejectFunction(rejectFun));
+
+ // Step 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
+ return true;
+}
+
+static bool IsSettledMaybeWrappedPromise(JSObject* promise) {
+ if (IsProxy(promise)) {
+ promise = UncheckedUnwrap(promise);
+
+ // Caller needs to handle dead wrappers.
+ if (JS_IsDeadWrapper(promise)) {
+ return false;
+ }
+ }
+
+ return promise->as<PromiseObject>().state() != JS::PromiseState::Pending;
+}
+
+[[nodiscard]] static bool RejectMaybeWrappedPromise(
+ JSContext* cx, HandleObject promiseObj, HandleValue reason,
+ Handle<SavedFrame*> unwrappedRejectionStack);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise Reject Functions
+ * https://tc39.es/ecma262/#sec-promise-reject-functions
+ */
+static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JSFunction* reject = &args.callee().as<JSFunction>();
+ HandleValue reasonVal = args.get(0);
+
+ // Step 1. Let F be the active function object.
+ // Step 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
+ // (implicit)
+
+ // Step 3. Let promise be F.[[Promise]].
+ const Value& promiseVal = reject->getExtendedSlot(RejectFunctionSlot_Promise);
+
+ // Step 4. Let alreadyResolved be F.[[AlreadyResolved]].
+ // Step 5. If alreadyResolved.[[Value]] is true, return undefined.
+ //
+ // If the Promise isn't available anymore, it has been resolved and the
+ // reference to it removed to make it eligible for collection.
+ bool alreadyResolved = promiseVal.isUndefined();
+ MOZ_ASSERT(IsAlreadyResolvedMaybeWrappedRejectFunction(reject) ==
+ alreadyResolved);
+ if (alreadyResolved) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedObject promise(cx, &promiseVal.toObject());
+
+ // Step 6. Set alreadyResolved.[[Value]] to true.
+ SetAlreadyResolvedResolutionFunction(reject);
+
+ // In some cases the Promise reference on the resolution function won't
+ // have been removed during resolution, so we need to check that here,
+ // too.
+ if (IsSettledMaybeWrappedPromise(promise)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 7. Return RejectPromise(promise, reason).
+ if (!RejectMaybeWrappedPromise(cx, promise, reasonVal, nullptr)) {
+ return false;
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+[[nodiscard]] static bool FulfillMaybeWrappedPromise(JSContext* cx,
+ HandleObject promiseObj,
+ HandleValue value_);
+
+[[nodiscard]] static bool EnqueuePromiseResolveThenableJob(
+ JSContext* cx, HandleValue promiseToResolve, HandleValue thenable,
+ HandleValue thenVal);
+
+[[nodiscard]] static bool EnqueuePromiseResolveThenableBuiltinJob(
+ JSContext* cx, HandleObject promiseToResolve, HandleObject thenable);
+
+static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal,
+ HandleValue onFulfilled, HandleValue onRejected,
+ MutableHandleValue rval, bool rvalExplicitlyUsed);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise Resolve Functions
+ * https://tc39.es/ecma262/#sec-promise-resolve-functions
+ *
+ * Steps 7-15.
+ */
+[[nodiscard]] bool js::ResolvePromiseInternal(
+ JSContext* cx, JS::Handle<JSObject*> promise,
+ JS::Handle<JS::Value> resolutionVal) {
+ cx->check(promise, resolutionVal);
+ MOZ_ASSERT(!IsSettledMaybeWrappedPromise(promise));
+
+ // (reordered)
+ // Step 8. If Type(resolution) is not Object, then
+ if (!resolutionVal.isObject()) {
+ // Step 8.a. Return FulfillPromise(promise, resolution).
+ return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+ }
+
+ RootedObject resolution(cx, &resolutionVal.toObject());
+
+ // Step 7. If SameValue(resolution, promise) is true, then
+ if (resolution == promise) {
+ // Step 7.a. Let selfResolutionError be a newly created TypeError object.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
+ RootedValue selfResolutionError(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!MaybeGetAndClearExceptionAndStack(cx, &selfResolutionError, &stack)) {
+ return false;
+ }
+
+ // Step 7.b. Return RejectPromise(promise, selfResolutionError).
+ return RejectMaybeWrappedPromise(cx, promise, selfResolutionError, stack);
+ }
+
+ // Step 9. Let then be Get(resolution, "then").
+ RootedValue thenVal(cx);
+ bool status =
+ GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
+
+ RootedValue error(cx);
+ Rooted<SavedFrame*> errorStack(cx);
+
+ // Step 10. If then is an abrupt completion, then
+ if (!status) {
+ // Get the `then.[[Value]]` value used in the step 10.a.
+ if (!MaybeGetAndClearExceptionAndStack(cx, &error, &errorStack)) {
+ return false;
+ }
+ }
+
+ // Testing functions allow to directly settle a promise without going
+ // through the resolving functions. In that case the normal bookkeeping to
+ // ensure only pending promises can be resolved doesn't apply and we need
+ // to manually check for already settled promises. The exception is simply
+ // dropped when this case happens.
+ if (IsSettledMaybeWrappedPromise(promise)) {
+ return true;
+ }
+
+ // Step 10. If then is an abrupt completion, then
+ if (!status) {
+ // Step 10.a. Return RejectPromise(promise, then.[[Value]]).
+ return RejectMaybeWrappedPromise(cx, promise, error, errorStack);
+ }
+
+ // Step 11. Let thenAction be then.[[Value]].
+ // (implicit)
+
+ // Step 12. If IsCallable(thenAction) is false, then
+ if (!IsCallable(thenVal)) {
+ // Step 12.a. Return FulfillPromise(promise, resolution).
+ return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+ }
+
+ // Step 13. Let thenJobCallback be HostMakeJobCallback(thenAction).
+ // (implicit)
+
+ // Step 14. Let job be
+ // NewPromiseResolveThenableJob(promise, resolution,
+ // thenJobCallback).
+ // Step 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
+
+ // If the resolution object is a built-in Promise object and the
+ // `then` property is the original Promise.prototype.then function
+ // from the current realm, we skip storing/calling it.
+ // Additionally we require that |promise| itself is also a built-in
+ // Promise object, so the fast path doesn't need to cope with wrappers.
+ bool isBuiltinThen = false;
+ if (resolution->is<PromiseObject>() && promise->is<PromiseObject>() &&
+ IsNativeFunction(thenVal, Promise_then) &&
+ thenVal.toObject().as<JSFunction>().realm() == cx->realm()) {
+ isBuiltinThen = true;
+ }
+
+ if (!isBuiltinThen) {
+ RootedValue promiseVal(cx, ObjectValue(*promise));
+ if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal,
+ thenVal)) {
+ return false;
+ }
+ } else {
+ if (!EnqueuePromiseResolveThenableBuiltinJob(cx, promise, resolution)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise Resolve Functions
+ * https://tc39.es/ecma262/#sec-promise-resolve-functions
+ */
+static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. Let F be the active function object.
+ // Step 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
+ // (implicit)
+
+ JSFunction* resolve = &args.callee().as<JSFunction>();
+ HandleValue resolutionVal = args.get(0);
+
+ // Step 3. Let promise be F.[[Promise]].
+ const Value& promiseVal =
+ resolve->getExtendedSlot(ResolveFunctionSlot_Promise);
+
+ // Step 4. Let alreadyResolved be F.[[AlreadyResolved]].
+ // Step 5. If alreadyResolved.[[Value]] is true, return undefined.
+ //
+ // NOTE: We use the reference to the reject function as [[AlreadyResolved]].
+ bool alreadyResolved = promiseVal.isUndefined();
+ MOZ_ASSERT(IsAlreadyResolvedMaybeWrappedResolveFunction(resolve) ==
+ alreadyResolved);
+ if (alreadyResolved) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedObject promise(cx, &promiseVal.toObject());
+
+ // Step 6. Set alreadyResolved.[[Value]] to true.
+ SetAlreadyResolvedResolutionFunction(resolve);
+
+ // In some cases the Promise reference on the resolution function won't
+ // have been removed during resolution, so we need to check that here,
+ // too.
+ if (IsSettledMaybeWrappedPromise(promise)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Steps 7-15.
+ if (!ResolvePromiseInternal(cx, promise, resolutionVal)) {
+ return false;
+ }
+
+ // Step 16. Return undefined.
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseReactionJob ( reaction, argument )
+ * https://tc39.es/ecma262/#sec-newpromisereactionjob
+ * HostEnqueuePromiseJob ( job, realm )
+ * https://tc39.es/ecma262/#sec-hostenqueuepromisejob
+ *
+ * Tells the embedding to enqueue a Promise reaction job, based on
+ * three parameters:
+ * reactionObj - The reaction record.
+ * handlerArg_ - The first and only argument to pass to the handler invoked by
+ * the job. This will be stored on the reaction record.
+ * targetState - The PromiseState this reaction job targets. This decides
+ * whether the onFulfilled or onRejected handler is called.
+ */
+[[nodiscard]] static bool EnqueuePromiseReactionJob(
+ JSContext* cx, HandleObject reactionObj, HandleValue handlerArg_,
+ JS::PromiseState targetState) {
+ MOZ_ASSERT(targetState == JS::PromiseState::Fulfilled ||
+ targetState == JS::PromiseState::Rejected);
+
+ // The reaction might have been stored on a Promise from another
+ // compartment, which means it would've been wrapped in a CCW.
+ // To properly handle that case here, unwrap it and enter its
+ // compartment, where the job creation should take place anyway.
+ Rooted<PromiseReactionRecord*> reaction(cx);
+ RootedValue handlerArg(cx, handlerArg_);
+ mozilla::Maybe<AutoRealm> ar;
+ if (!IsProxy(reactionObj)) {
+ MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
+ reaction = &reactionObj->as<PromiseReactionRecord>();
+ if (cx->realm() != reaction->realm()) {
+ // If the compartment has multiple realms, create the job in the
+ // reaction's realm. This is consistent with the code in the else-branch
+ // and avoids problems with running jobs against a dying global (Gecko
+ // drops such jobs).
+ ar.emplace(cx, reaction);
+ }
+ } else {
+ JSObject* unwrappedReactionObj = UncheckedUnwrap(reactionObj);
+ if (JS_IsDeadWrapper(unwrappedReactionObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ reaction = &unwrappedReactionObj->as<PromiseReactionRecord>();
+ MOZ_RELEASE_ASSERT(reaction->is<PromiseReactionRecord>());
+ ar.emplace(cx, reaction);
+ if (!cx->compartment()->wrap(cx, &handlerArg)) {
+ return false;
+ }
+ }
+
+ // Must not enqueue a reaction job more than once.
+ MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
+
+ // NOTE: Instead of capturing reaction and arguments separately in the
+ // Job Abstract Closure below, store arguments (= handlerArg) in
+ // reaction object and capture it.
+ // Also, set reaction.[[Type]] is represented by targetState here.
+ cx->check(handlerArg);
+ reaction->setTargetStateAndHandlerArg(targetState, handlerArg);
+
+ RootedValue reactionVal(cx, ObjectValue(*reaction));
+ RootedValue handler(cx, reaction->handler());
+
+ // NewPromiseReactionJob
+ // Step 2. Let handlerRealm be null.
+ // NOTE: Instead of passing job and realm separately, we use the job's
+ // JSFunction object's realm as the job's realm.
+ // So we should enter the handlerRealm before creating the job function.
+ //
+ // GetFunctionRealm performed inside AutoFunctionOrCurrentRealm uses checked
+ // unwrap and it can hit permission error if there's a security wrapper, and
+ // in that case the reaction job is created in the current realm, instead of
+ // the target function's realm.
+ //
+ // If this reaction crosses chrome/content boundary, and the security
+ // wrapper would allow "call" operation, it still works inside the
+ // reaction job.
+ //
+ // This behavior is observable only when the job belonging to the content
+ // realm stops working (*1, *2), and it won't matter in practice.
+ //
+ // *1: "we can run script" performed inside HostEnqueuePromiseJob
+ // in HTML spec
+ // https://html.spec.whatwg.org/#hostenqueuepromisejob
+ // https://html.spec.whatwg.org/#check-if-we-can-run-script
+ // https://html.spec.whatwg.org/#fully-active
+ // *2: nsIGlobalObject::IsDying performed inside PromiseJobRunnable::Run
+ // in our implementation
+ mozilla::Maybe<AutoFunctionOrCurrentRealm> ar2;
+
+ // NewPromiseReactionJob
+ // Step 3. If reaction.[[Handler]] is not empty, then
+ if (handler.isObject()) {
+ // Step 3.a. Let getHandlerRealmResult be
+ // GetFunctionRealm(reaction.[[Handler]].[[Callback]]).
+ // Step 3.b. If getHandlerRealmResult is a normal completion,
+ // set handlerRealm to getHandlerRealmResult.[[Value]].
+ // Step 3.c. Else, set handlerRealm to the current Realm Record.
+ // Step 3.d. NOTE: handlerRealm is never null unless the handler is
+ // undefined. When the handler is a revoked Proxy and no
+ // ECMAScript code runs, handlerRealm is used to create error
+ // objects.
+ RootedObject handlerObj(cx, &handler.toObject());
+ ar2.emplace(cx, handlerObj);
+
+ // We need to wrap the reaction to store it on the job function.
+ if (!cx->compartment()->wrap(cx, &reactionVal)) {
+ return false;
+ }
+ }
+
+ // NewPromiseReactionJob
+ // Step 1. Let job be a new Job Abstract Closure with no parameters that
+ // captures reaction and argument and performs the following steps
+ // when called:
+ Handle<PropertyName*> funName = cx->names().empty;
+ RootedFunction job(
+ cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!job) {
+ return false;
+ }
+
+ job->setExtendedSlot(ReactionJobSlot_ReactionRecord, reactionVal);
+
+ // When using JS::AddPromiseReactions{,IgnoringUnHandledRejection}, no actual
+ // promise is created, so we might not have one here.
+ // Additionally, we might have an object here that isn't an instance of
+ // Promise. This can happen if content overrides the value of
+ // Promise[@@species] (or invokes Promise#then on a Promise subclass
+ // instance with a non-default @@species value on the constructor) with a
+ // function that returns objects that're not Promise (subclass) instances.
+ // In that case, we just pretend we didn't have an object in the first
+ // place.
+ // If after all this we do have an object, wrap it in case we entered the
+ // handler's compartment above, because we should pass objects from a
+ // single compartment to the enqueuePromiseJob callback.
+ RootedObject promise(cx, reaction->promise());
+ if (promise) {
+ if (promise->is<PromiseObject>()) {
+ if (!cx->compartment()->wrap(cx, &promise)) {
+ return false;
+ }
+ } else if (IsWrapper(promise)) {
+ // `promise` can be already-wrapped promise object at this point.
+ JSObject* unwrappedPromise = UncheckedUnwrap(promise);
+ if (unwrappedPromise->is<PromiseObject>()) {
+ if (!cx->compartment()->wrap(cx, &promise)) {
+ return false;
+ }
+ } else {
+ promise = nullptr;
+ }
+ } else {
+ promise = nullptr;
+ }
+ }
+
+ // Using objectFromIncumbentGlobal, we can derive the incumbent global by
+ // unwrapping and then getting the global. This is very convoluted, but
+ // much better than having to store the original global as a private value
+ // because we couldn't wrap it to store it as a normal JS value.
+ Rooted<GlobalObject*> global(cx);
+ if (JSObject* objectFromIncumbentGlobal =
+ reaction->getAndClearIncumbentGlobalObject()) {
+ objectFromIncumbentGlobal = CheckedUnwrapStatic(objectFromIncumbentGlobal);
+ MOZ_ASSERT(objectFromIncumbentGlobal);
+ global = &objectFromIncumbentGlobal->nonCCWGlobal();
+ }
+
+ // HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
+ //
+ // Note: the global we pass here might be from a different compartment
+ // than job and promise. While it's somewhat unusual to pass objects
+ // from multiple compartments, in this case we specifically need the
+ // global to be unwrapped because wrapping and unwrapping aren't
+ // necessarily symmetric for globals.
+ return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
+}
+
+[[nodiscard]] static bool TriggerPromiseReactions(JSContext* cx,
+ HandleValue reactionsVal,
+ JS::PromiseState state,
+ HandleValue valueOrReason);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * FulfillPromise ( promise, value )
+ * https://tc39.es/ecma262/#sec-fulfillpromise
+ * RejectPromise ( promise, reason )
+ * https://tc39.es/ecma262/#sec-rejectpromise
+ *
+ * This method takes an additional optional |unwrappedRejectionStack| parameter,
+ * which is only used for debugging purposes.
+ * It allows callers to to pass in the stack of some exception which
+ * triggered the rejection of the promise.
+ */
+[[nodiscard]] static bool ResolvePromise(
+ JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueOrReason,
+ JS::PromiseState state,
+ Handle<SavedFrame*> unwrappedRejectionStack = nullptr) {
+ // Step 1. Assert: The value of promise.[[PromiseState]] is pending.
+ MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+ MOZ_ASSERT(state == JS::PromiseState::Fulfilled ||
+ state == JS::PromiseState::Rejected);
+ MOZ_ASSERT_IF(unwrappedRejectionStack, state == JS::PromiseState::Rejected);
+
+ // FulfillPromise
+ // Step 2. Let reactions be promise.[[PromiseFulfillReactions]].
+ // RejectPromise
+ // Step 2. Let reactions be promise.[[PromiseRejectReactions]].
+ //
+ // We only have one list of reactions for both resolution types. So
+ // instead of getting the right list of reactions, we determine the
+ // resolution type to retrieve the right information from the
+ // reaction records.
+ RootedValue reactionsVal(cx, promise->reactions());
+
+ // FulfillPromise
+ // Step 3. Set promise.[[PromiseResult]] to value.
+ // RejectPromise
+ // Step 3. Set promise.[[PromiseResult]] to reason.
+ //
+ // Step 4. Set promise.[[PromiseFulfillReactions]] to undefined.
+ // Step 5. Set promise.[[PromiseRejectReactions]] to undefined.
+ //
+ // The same slot is used for the reactions list and the result, so setting
+ // the result also removes the reactions list.
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason);
+
+ // FulfillPromise
+ // Step 6. Set promise.[[PromiseState]] to fulfilled.
+ // RejectPromise
+ // Step 6. Set promise.[[PromiseState]] to rejected.
+ int32_t flags = promise->flags();
+ flags |= PROMISE_FLAG_RESOLVED;
+ if (state == JS::PromiseState::Fulfilled) {
+ flags |= PROMISE_FLAG_FULFILLED;
+ }
+ promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags));
+
+ // Also null out the resolve/reject functions so they can be GC'd.
+ promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue());
+
+ // Now that everything else is done, do the things the debugger needs.
+
+ // RejectPromise
+ // Step 7. If promise.[[PromiseIsHandled]] is false, perform
+ // HostPromiseRejectionTracker(promise, "reject").
+ PromiseObject::onSettled(cx, promise, unwrappedRejectionStack);
+
+ // FulfillPromise
+ // Step 7. Return TriggerPromiseReactions(reactions, value).
+ // RejectPromise
+ // Step 8. Return TriggerPromiseReactions(reactions, reason).
+ return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * RejectPromise ( promise, reason )
+ * https://tc39.es/ecma262/#sec-rejectpromise
+ */
+[[nodiscard]] bool js::RejectPromiseInternal(
+ JSContext* cx, JS::Handle<PromiseObject*> promise,
+ JS::Handle<JS::Value> reason,
+ JS::Handle<SavedFrame*> unwrappedRejectionStack /* = nullptr */) {
+ return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected,
+ unwrappedRejectionStack);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * FulfillPromise ( promise, value )
+ * https://tc39.es/ecma262/#sec-fulfillpromise
+ */
+[[nodiscard]] static bool FulfillMaybeWrappedPromise(JSContext* cx,
+ HandleObject promiseObj,
+ HandleValue value_) {
+ Rooted<PromiseObject*> promise(cx);
+ RootedValue value(cx, value_);
+
+ mozilla::Maybe<AutoRealm> ar;
+ if (!IsProxy(promiseObj)) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj);
+ if (JS_IsDeadWrapper(unwrappedPromiseObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ promise = &unwrappedPromiseObj->as<PromiseObject>();
+ ar.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &value)) {
+ return false;
+ }
+ }
+
+ return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled);
+}
+
+static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp);
+static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] static PromiseObject* CreatePromiseObjectInternal(
+ JSContext* cx, HandleObject proto = nullptr, bool protoIsWrapped = false,
+ bool informDebugger = true);
+
+enum GetCapabilitiesExecutorSlots {
+ GetCapabilitiesExecutorSlots_Resolve,
+ GetCapabilitiesExecutorSlots_Reject
+};
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise ( executor )
+ * https://tc39.es/ecma262/#sec-promise-executor
+ */
+[[nodiscard]] static PromiseObject*
+CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) {
+ // Steps 3-7.
+ PromiseObject* promise = CreatePromiseObjectInternal(cx);
+ if (!promise) {
+ return nullptr;
+ }
+
+ AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS);
+
+ // Step 11. Return promise.
+ return promise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise ( executor )
+ * https://tc39.es/ecma262/#sec-promise-executor
+ *
+ * As if called with GetCapabilitiesExecutor as the executor argument.
+ */
+[[nodiscard]] static PromiseObject* CreatePromiseWithDefaultResolutionFunctions(
+ JSContext* cx, MutableHandleObject resolve, MutableHandleObject reject) {
+ // Steps 3-7.
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 8. Let resolvingFunctions be CreateResolvingFunctions(promise).
+ if (!CreateResolvingFunctions(cx, promise, resolve, reject)) {
+ return nullptr;
+ }
+
+ promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*reject));
+
+ // Step 11. Return promise.
+ return promise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseCapability ( C )
+ * https://tc39.es/ecma262/#sec-newpromisecapability
+ */
+[[nodiscard]] static bool NewPromiseCapability(
+ JSContext* cx, HandleObject C, MutableHandle<PromiseCapability> capability,
+ bool canOmitResolutionFunctions) {
+ RootedValue cVal(cx, ObjectValue(*C));
+
+ // Step 1. If IsConstructor(C) is false, throw a TypeError exception.
+ // Step 2. NOTE: C is assumed to be a constructor function that supports the
+ // parameter conventions of the Promise constructor (see 27.2.3.1).
+ if (!IsConstructor(C)) {
+ ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_SEARCH_STACK, cVal,
+ nullptr);
+ return false;
+ }
+
+ // If we'd call the original Promise constructor and know that the
+ // resolve/reject functions won't ever escape to content, we can skip
+ // creating and calling the executor function and instead return a Promise
+ // marked as having default resolve/reject functions.
+ //
+ // This can't be used in Promise.all and Promise.race because we have to
+ // pass the reject (and resolve, in the race case) function to thenables
+ // in the list passed to all/race, which (potentially) means exposing them
+ // to content.
+ //
+ // For Promise.all and Promise.race we can only optimize away the creation
+ // of the GetCapabilitiesExecutor function, and directly allocate the
+ // result promise instead of invoking the Promise constructor.
+ if (IsNativeFunction(cVal, PromiseConstructor) &&
+ cVal.toObject().nonCCWRealm() == cx->realm()) {
+ PromiseObject* promise;
+ if (canOmitResolutionFunctions) {
+ promise = CreatePromiseObjectWithoutResolutionFunctions(cx);
+ } else {
+ promise = CreatePromiseWithDefaultResolutionFunctions(
+ cx, capability.resolve(), capability.reject());
+ }
+ if (!promise) {
+ return false;
+ }
+
+ // Step 3. Let promiseCapability be the PromiseCapability Record
+ // { [[Promise]]: undefined, [[Resolve]]: undefined,
+ // [[Reject]]: undefined }.
+ capability.promise().set(promise);
+
+ // Step 10. Return promiseCapability.
+ return true;
+ }
+
+ // Step 4. Let executorClosure be a new Abstract Closure with parameters
+ // (resolve, reject) that captures promiseCapability and performs the
+ // following steps when called:
+ Handle<PropertyName*> funName = cx->names().empty;
+ RootedFunction executor(
+ cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!executor) {
+ return false;
+ }
+
+ // Step 5. Let executor be
+ // ! CreateBuiltinFunction(executorClosure, 2, "", « »).
+ // (omitted)
+
+ // Step 6. Let promise be ? Construct(C, « executor »).
+ // Step 9. Set promiseCapability.[[Promise]] to promise.
+ FixedConstructArgs<1> cargs(cx);
+ cargs[0].setObject(*executor);
+ if (!Construct(cx, cVal, cargs, cVal, capability.promise())) {
+ return false;
+ }
+
+ // Step 7. If IsCallable(promiseCapability.[[Resolve]]) is false,
+ // throw a TypeError exception.
+ const Value& resolveVal =
+ executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve);
+ if (!IsCallable(resolveVal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
+ return false;
+ }
+
+ // Step 8. If IsCallable(promiseCapability.[[Reject]]) is false,
+ // throw a TypeError exception.
+ const Value& rejectVal =
+ executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject);
+ if (!IsCallable(rejectVal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
+ return false;
+ }
+
+ // (reordered)
+ // Step 3. Let promiseCapability be the PromiseCapability Record
+ // { [[Promise]]: undefined, [[Resolve]]: undefined,
+ // [[Reject]]: undefined }.
+ capability.resolve().set(&resolveVal.toObject());
+ capability.reject().set(&rejectVal.toObject());
+
+ // Step 10. Return promiseCapability.
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseCapability ( C )
+ * https://tc39.es/ecma262/#sec-newpromisecapability
+ *
+ * Steps 4.a-e.
+ */
+static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JSFunction* F = &args.callee().as<JSFunction>();
+
+ // Step 4.a. If promiseCapability.[[Resolve]] is not undefined,
+ // throw a TypeError exception.
+ // Step 4.b. If promiseCapability.[[Reject]] is not undefined,
+ // throw a TypeError exception.
+ if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() ||
+ !F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
+ return false;
+ }
+
+ // Step 4.c. Set promiseCapability.[[Resolve]] to resolve.
+ F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0));
+
+ // Step 4.d. Set promiseCapability.[[Reject]] to reject.
+ F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1));
+
+ // Step 4.e. Return undefined.
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * RejectPromise ( promise, reason )
+ * https://tc39.es/ecma262/#sec-rejectpromise
+ */
+[[nodiscard]] static bool RejectMaybeWrappedPromise(
+ JSContext* cx, HandleObject promiseObj, HandleValue reason_,
+ Handle<SavedFrame*> unwrappedRejectionStack) {
+ Rooted<PromiseObject*> promise(cx);
+ RootedValue reason(cx, reason_);
+
+ mozilla::Maybe<AutoRealm> ar;
+ if (!IsProxy(promiseObj)) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj);
+ if (JS_IsDeadWrapper(unwrappedPromiseObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ promise = &unwrappedPromiseObj->as<PromiseObject>();
+ ar.emplace(cx, promise);
+
+ // The rejection reason might've been created in a compartment with higher
+ // privileges than the Promise's. In that case, object-type rejection
+ // values might be wrapped into a wrapper that throws whenever the
+ // Promise's reaction handler wants to do anything useful with it. To
+ // avoid that situation, we synthesize a generic error that doesn't
+ // expose any privileged information but can safely be used in the
+ // rejection handler.
+ if (!cx->compartment()->wrap(cx, &reason)) {
+ return false;
+ }
+ if (reason.isObject() && !CheckedUnwrapStatic(&reason.toObject())) {
+ // Report the existing reason, so we don't just drop it on the
+ // floor.
+ JSObject* realReason = UncheckedUnwrap(&reason.toObject());
+ RootedValue realReasonVal(cx, ObjectValue(*realReason));
+ Rooted<GlobalObject*> realGlobal(cx, &realReason->nonCCWGlobal());
+ ReportErrorToGlobal(cx, realGlobal, realReasonVal);
+
+ // Async stacks are only properly adopted if there's at least one
+ // interpreter frame active right now. If a thenable job with a
+ // throwing `then` function got us here, that'll not be the case,
+ // so we add one by throwing the error from self-hosted code.
+ if (!GetInternalError(cx, JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,
+ &reason)) {
+ return false;
+ }
+ }
+ }
+
+ return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected,
+ unwrappedRejectionStack);
+}
+
+// Apply f to a mutable handle on each member of a collection of reactions, like
+// that stored in PromiseSlot_ReactionsOrResult on a pending promise. When the
+// reaction record is wrapped, we pass the wrapper, without dereferencing it. If
+// f returns false, then we stop the iteration immediately and return false.
+// Otherwise, we return true.
+//
+// There are several different representations for collections:
+//
+// - We represent an empty collection of reactions as an 'undefined' value.
+//
+// - We represent a collection containing a single reaction simply as the given
+// PromiseReactionRecord object, possibly wrapped.
+//
+// - We represent a collection of two or more reactions as a dense array of
+// possibly-wrapped PromiseReactionRecords.
+//
+template <typename F>
+static bool ForEachReaction(JSContext* cx, HandleValue reactionsVal, F f) {
+ if (reactionsVal.isUndefined()) {
+ return true;
+ }
+
+ RootedObject reactions(cx, &reactionsVal.toObject());
+ RootedObject reaction(cx);
+
+ if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions) ||
+ JS_IsDeadWrapper(reactions)) {
+ return f(&reactions);
+ }
+
+ Handle<NativeObject*> reactionsList = reactions.as<NativeObject>();
+ uint32_t reactionsCount = reactionsList->getDenseInitializedLength();
+ MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
+
+ for (uint32_t i = 0; i < reactionsCount; i++) {
+ const Value& reactionVal = reactionsList->getDenseElement(i);
+ MOZ_RELEASE_ASSERT(reactionVal.isObject());
+ reaction = &reactionVal.toObject();
+ if (!f(&reaction)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * TriggerPromiseReactions ( reactions, argument )
+ * https://tc39.es/ecma262/#sec-triggerpromisereactions
+ */
+[[nodiscard]] static bool TriggerPromiseReactions(JSContext* cx,
+ HandleValue reactionsVal,
+ JS::PromiseState state,
+ HandleValue valueOrReason) {
+ MOZ_ASSERT(state == JS::PromiseState::Fulfilled ||
+ state == JS::PromiseState::Rejected);
+
+ // Step 1. For each element reaction of reactions, do
+ // Step 2. Return undefined.
+ return ForEachReaction(cx, reactionsVal, [&](MutableHandleObject reaction) {
+ // Step 1.a. Let job be NewPromiseReactionJob(reaction, argument).
+ // Step 1.b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
+ return EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state);
+ });
+}
+
+[[nodiscard]] static bool CallPromiseResolveFunction(JSContext* cx,
+ HandleObject resolveFun,
+ HandleValue value,
+ HandleObject promiseObj);
+
+/**
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * NewPromiseReactionJob ( reaction, argument )
+ * https://tc39.es/ecma262/#sec-newpromisereactionjob
+ *
+ * Step 1.
+ *
+ * Implements PromiseReactionJob optimized for the case when the reaction
+ * handler is one of the default resolving functions as created by the
+ * CreateResolvingFunctions abstract operation.
+ */
+[[nodiscard]] static bool DefaultResolvingPromiseReactionJob(
+ JSContext* cx, Handle<PromiseReactionRecord*> reaction) {
+ MOZ_ASSERT(reaction->targetState() != JS::PromiseState::Pending);
+
+ Rooted<PromiseObject*> promiseToResolve(cx,
+ reaction->defaultResolvingPromise());
+
+ // Testing functions allow to directly settle a promise without going
+ // through the resolving functions. In that case the normal bookkeeping to
+ // ensure only pending promises can be resolved doesn't apply and we need
+ // to manually check for already settled promises. We still call
+ // Run{Fulfill,Reject}Function for consistency with PromiseReactionJob.
+ ResolutionMode resolutionMode = ResolveMode;
+ RootedValue handlerResult(cx, UndefinedValue());
+ Rooted<SavedFrame*> unwrappedRejectionStack(cx);
+ if (promiseToResolve->state() == JS::PromiseState::Pending) {
+ RootedValue argument(cx, reaction->handlerArg());
+
+ // Step 1.e. Else, let handlerResult be
+ // Completion(HostCallJobCallback(handler, undefined,
+ // « argument »)).
+ bool ok;
+ if (reaction->targetState() == JS::PromiseState::Fulfilled) {
+ ok = ResolvePromiseInternal(cx, promiseToResolve, argument);
+ } else {
+ ok = RejectPromiseInternal(cx, promiseToResolve, argument);
+ }
+
+ if (!ok) {
+ resolutionMode = RejectMode;
+ if (!MaybeGetAndClearExceptionAndStack(cx, &handlerResult,
+ &unwrappedRejectionStack)) {
+ return false;
+ }
+ }
+ }
+
+ // Steps 1.f-i.
+ RootedObject promiseObj(cx, reaction->promise());
+ RootedObject callee(cx);
+ if (resolutionMode == ResolveMode) {
+ callee =
+ reaction->getFixedSlot(ReactionRecordSlot_Resolve).toObjectOrNull();
+
+ return CallPromiseResolveFunction(cx, callee, handlerResult, promiseObj);
+ }
+
+ callee = reaction->getFixedSlot(ReactionRecordSlot_Reject).toObjectOrNull();
+
+ return CallPromiseRejectFunction(cx, callee, handlerResult, promiseObj,
+ unwrappedRejectionStack,
+ reaction->unhandledRejectionBehavior());
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Await in async function
+ * https://tc39.es/ecma262/#await
+ *
+ * Step 3. fulfilledClosure Abstract Closure.
+ * Step 5. rejectedClosure Abstract Closure.
+ */
+[[nodiscard]] static bool AsyncFunctionPromiseReactionJob(
+ JSContext* cx, Handle<PromiseReactionRecord*> reaction) {
+ MOZ_ASSERT(reaction->isAsyncFunction());
+
+ auto handler = static_cast<PromiseHandler>(reaction->handler().toInt32());
+ RootedValue argument(cx, reaction->handlerArg());
+ Rooted<AsyncFunctionGeneratorObject*> generator(
+ cx, reaction->asyncFunctionGenerator());
+
+ // Await's handlers don't return a value, nor throw any exceptions.
+ // They fail only on OOM.
+
+ if (handler == PromiseHandler::AsyncFunctionAwaitedFulfilled) {
+ // Step 3. fulfilledClosure Abstract Closure.
+ return AsyncFunctionAwaitedFulfilled(cx, generator, argument);
+ }
+
+ // Step 5. rejectedClosure Abstract Closure.
+ MOZ_ASSERT(handler == PromiseHandler::AsyncFunctionAwaitedRejected);
+ return AsyncFunctionAwaitedRejected(cx, generator, argument);
+}
+
+/**
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * NewPromiseReactionJob ( reaction, argument )
+ * https://tc39.es/ecma262/#sec-newpromisereactionjob
+ *
+ * Step 1.
+ *
+ * Callback triggering the fulfill/reject reaction for a resolved Promise,
+ * to be invoked by the embedding during its processing of the Promise job
+ * queue.
+ *
+ * A PromiseReactionJob is set as the native function of an extended
+ * JSFunction object, with all information required for the job's
+ * execution stored in in a reaction record in its first extended slot.
+ */
+static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction job(cx, &args.callee().as<JSFunction>());
+
+ // Promise reactions don't return any value.
+ args.rval().setUndefined();
+
+ RootedObject reactionObj(
+ cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject());
+
+ // To ensure that the embedding ends up with the right entry global, we're
+ // guaranteeing that the reaction job function gets created in the same
+ // compartment as the handler function. That's not necessarily the global
+ // that the job was triggered from, though.
+ // We can find the triggering global via the job's reaction record. To go
+ // back, we check if the reaction is a wrapper and if so, unwrap it and
+ // enter its compartment.
+ mozilla::Maybe<AutoRealm> ar;
+ if (!IsProxy(reactionObj)) {
+ MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
+ } else {
+ reactionObj = UncheckedUnwrap(reactionObj);
+ if (JS_IsDeadWrapper(reactionObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
+ ar.emplace(cx, reactionObj);
+ }
+
+ // Optimized/special cases.
+ Handle<PromiseReactionRecord*> reaction =
+ reactionObj.as<PromiseReactionRecord>();
+ if (reaction->isDefaultResolvingHandler()) {
+ return DefaultResolvingPromiseReactionJob(cx, reaction);
+ }
+ if (reaction->isAsyncFunction()) {
+ return AsyncFunctionPromiseReactionJob(cx, reaction);
+ }
+ if (reaction->isAsyncGenerator()) {
+ RootedValue argument(cx, reaction->handlerArg());
+ Rooted<AsyncGeneratorObject*> generator(cx, reaction->asyncGenerator());
+ auto handler = static_cast<PromiseHandler>(reaction->handler().toInt32());
+ return AsyncGeneratorPromiseReactionJob(cx, handler, generator, argument);
+ }
+ if (reaction->isDebuggerDummy()) {
+ return true;
+ }
+
+ // Step 1.a. Let promiseCapability be reaction.[[Capability]].
+ // (implicit)
+
+ // Step 1.c. Let handler be reaction.[[Handler]].
+ RootedValue handlerVal(cx, reaction->handler());
+
+ RootedValue argument(cx, reaction->handlerArg());
+
+ RootedValue handlerResult(cx);
+ ResolutionMode resolutionMode = ResolveMode;
+
+ Rooted<SavedFrame*> unwrappedRejectionStack(cx);
+
+ // Step 1.d. If handler is empty, then
+ if (handlerVal.isInt32()) {
+ // Step 1.b. Let type be reaction.[[Type]].
+ // (reordered)
+ auto handlerNum = static_cast<PromiseHandler>(handlerVal.toInt32());
+
+ // Step 1.d.i. If type is Fulfill, let handlerResult be
+ // NormalCompletion(argument).
+ if (handlerNum == PromiseHandler::Identity) {
+ handlerResult = argument;
+ } else if (handlerNum == PromiseHandler::Thrower) {
+ // Step 1.d.ii. Else,
+ // Step 1.d.ii.1. Assert: type is Reject.
+ // Step 1.d.ii.2. Let handlerResult be ThrowCompletion(argument).
+ resolutionMode = RejectMode;
+ handlerResult = argument;
+ } else {
+ // Special case for Async-from-Sync Iterator.
+
+ MOZ_ASSERT(handlerNum ==
+ PromiseHandler::AsyncFromSyncIteratorValueUnwrapDone ||
+ handlerNum ==
+ PromiseHandler::AsyncFromSyncIteratorValueUnwrapNotDone);
+
+ bool done =
+ handlerNum == PromiseHandler::AsyncFromSyncIteratorValueUnwrapDone;
+ // 25.1.4.2.5 Async-from-Sync Iterator Value Unwrap Functions, steps 1-2.
+ PlainObject* resultObj = CreateIterResultObject(cx, argument, done);
+ if (!resultObj) {
+ return false;
+ }
+
+ handlerResult = ObjectValue(*resultObj);
+ }
+ } else {
+ MOZ_ASSERT(handlerVal.isObject());
+ MOZ_ASSERT(IsCallable(handlerVal));
+
+ // Step 1.e. Else, let handlerResult be
+ // Completion(HostCallJobCallback(handler, undefined,
+ // « argument »)).
+ if (!Call(cx, handlerVal, UndefinedHandleValue, argument, &handlerResult)) {
+ resolutionMode = RejectMode;
+ if (!MaybeGetAndClearExceptionAndStack(cx, &handlerResult,
+ &unwrappedRejectionStack)) {
+ return false;
+ }
+ }
+ }
+
+ // Steps 1.f-i.
+ RootedObject promiseObj(cx, reaction->promise());
+ RootedObject callee(cx);
+ if (resolutionMode == ResolveMode) {
+ callee =
+ reaction->getFixedSlot(ReactionRecordSlot_Resolve).toObjectOrNull();
+
+ return CallPromiseResolveFunction(cx, callee, handlerResult, promiseObj);
+ }
+
+ callee = reaction->getFixedSlot(ReactionRecordSlot_Reject).toObjectOrNull();
+
+ return CallPromiseRejectFunction(cx, callee, handlerResult, promiseObj,
+ unwrappedRejectionStack,
+ reaction->unhandledRejectionBehavior());
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )
+ * https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
+ *
+ * Steps 1.a-d.
+ *
+ * A PromiseResolveThenableJob is set as the native function of an extended
+ * JSFunction object, with all information required for the job's
+ * execution stored in the function's extended slots.
+ *
+ * Usage of the function's extended slots is described in the ThenableJobSlots
+ * enum.
+ */
+static bool PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction job(cx, &args.callee().as<JSFunction>());
+ RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler));
+ MOZ_ASSERT(then.isObject());
+ Rooted<NativeObject*> jobArgs(cx,
+ &job->getExtendedSlot(ThenableJobSlot_JobData)
+ .toObject()
+ .as<NativeObject>());
+
+ RootedObject promise(
+ cx, &jobArgs->getDenseElement(ThenableJobDataIndex_Promise).toObject());
+ RootedValue thenable(cx,
+ jobArgs->getDenseElement(ThenableJobDataIndex_Thenable));
+
+ // Step 1.a. Let resolvingFunctions be
+ // CreateResolvingFunctions(promiseToResolve).
+ RootedObject resolveFn(cx);
+ RootedObject rejectFn(cx);
+ if (!CreateResolvingFunctions(cx, promise, &resolveFn, &rejectFn)) {
+ return false;
+ }
+
+ // Step 1.b. Let thenCallResult be
+ // HostCallJobCallback(then, thenable,
+ // « resolvingFunctions.[[Resolve]],
+ // resolvingFunctions.[[Reject]] »).
+ FixedInvokeArgs<2> args2(cx);
+ args2[0].setObject(*resolveFn);
+ args2[1].setObject(*rejectFn);
+
+ // In difference to the usual pattern, we return immediately on success.
+ RootedValue rval(cx);
+ if (Call(cx, then, thenable, args2, &rval)) {
+ // Step 1.d. Return Completion(thenCallResult).
+ return true;
+ }
+
+ // Step 1.c. If thenCallResult is an abrupt completion, then
+
+ Rooted<SavedFrame*> stack(cx);
+ if (!MaybeGetAndClearExceptionAndStack(cx, &rval, &stack)) {
+ return false;
+ }
+
+ // Step 1.c.i. Let status be
+ // Call(resolvingFunctions.[[Reject]], undefined,
+ // « thenCallResult.[[Value]] »).
+ // Step 1.c.ii. Return Completion(status).
+ RootedValue rejectVal(cx, ObjectValue(*rejectFn));
+ return Call(cx, rejectVal, UndefinedHandleValue, rval, &rval);
+}
+
+[[nodiscard]] static bool OriginalPromiseThenWithoutSettleHandlers(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseObject*> promiseToResolve);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )
+ * https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
+ *
+ * Step 1.a-d.
+ *
+ * Specialization of PromiseResolveThenableJob when the `thenable` is a
+ * built-in Promise object and the `then` property is the built-in
+ * `Promise.prototype.then` function.
+ *
+ * A PromiseResolveBuiltinThenableJob is set as the native function of an
+ * extended JSFunction object, with all information required for the job's
+ * execution stored in the function's extended slots.
+ *
+ * Usage of the function's extended slots is described in the
+ * BuiltinThenableJobSlots enum.
+ */
+static bool PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction job(cx, &args.callee().as<JSFunction>());
+ RootedObject promise(
+ cx, &job->getExtendedSlot(BuiltinThenableJobSlot_Promise).toObject());
+ RootedObject thenable(
+ cx, &job->getExtendedSlot(BuiltinThenableJobSlot_Thenable).toObject());
+
+ cx->check(promise, thenable);
+ MOZ_ASSERT(promise->is<PromiseObject>());
+ MOZ_ASSERT(thenable->is<PromiseObject>());
+
+ // Step 1.a. Let resolvingFunctions be
+ // CreateResolvingFunctions(promiseToResolve).
+ // (skipped)
+
+ // Step 1.b. Let thenCallResult be HostCallJobCallback(
+ // then, thenable,
+ // « resolvingFunctions.[[Resolve]],
+ // resolvingFunctions.[[Reject]] »).
+ //
+ // NOTE: In difference to the usual pattern, we return immediately on success.
+ if (OriginalPromiseThenWithoutSettleHandlers(cx, thenable.as<PromiseObject>(),
+ promise.as<PromiseObject>())) {
+ // Step 1.d. Return Completion(thenCallResult).
+ return true;
+ }
+
+ // Step 1.c. If thenCallResult is an abrupt completion, then
+ RootedValue exception(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!MaybeGetAndClearExceptionAndStack(cx, &exception, &stack)) {
+ return false;
+ }
+
+ // Testing functions allow to directly settle a promise without going
+ // through the resolving functions. In that case the normal bookkeeping to
+ // ensure only pending promises can be resolved doesn't apply and we need
+ // to manually check for already settled promises. The exception is simply
+ // dropped when this case happens.
+ if (promise->as<PromiseObject>().state() != JS::PromiseState::Pending) {
+ return true;
+ }
+
+ // Step 1.c.i. Let status be
+ // Call(resolvingFunctions.[[Reject]], undefined,
+ // « thenCallResult.[[Value]] »).
+ // Step 1.c.ii. Return Completion(status).
+ return RejectPromiseInternal(cx, promise.as<PromiseObject>(), exception,
+ stack);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )
+ * https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
+ * HostEnqueuePromiseJob ( job, realm )
+ * https://tc39.es/ecma262/#sec-hostenqueuepromisejob
+ *
+ * Tells the embedding to enqueue a Promise resolve thenable job, based on
+ * three parameters:
+ * promiseToResolve_ - The promise to resolve, obviously.
+ * thenable_ - The thenable to resolve the Promise with.
+ * thenVal - The `then` function to invoke with the `thenable` as the receiver.
+ */
+[[nodiscard]] static bool EnqueuePromiseResolveThenableJob(
+ JSContext* cx, HandleValue promiseToResolve_, HandleValue thenable_,
+ HandleValue thenVal) {
+ // Need to re-root these to enable wrapping them below.
+ RootedValue promiseToResolve(cx, promiseToResolve_);
+ RootedValue thenable(cx, thenable_);
+
+ // Step 2. Let getThenRealmResult be GetFunctionRealm(then.[[Callback]]).
+ // Step 3. If getThenRealmResult is a normal completion, let thenRealm be
+ // getThenRealmResult.[[Value]].
+ // Step 4. Else, let thenRealm be the current Realm Record.
+ // Step 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked
+ // Proxy and no code runs, thenRealm is used to create error objects.
+ //
+ // NOTE: Instead of passing job and realm separately, we use the job's
+ // JSFunction object's realm as the job's realm.
+ // So we should enter the thenRealm before creating the job function.
+ //
+ // GetFunctionRealm performed inside AutoFunctionOrCurrentRealm uses checked
+ // unwrap and this is fine given the behavior difference (see the comment
+ // around AutoFunctionOrCurrentRealm usage in EnqueuePromiseReactionJob for
+ // more details) is observable only when the `thenable` is from content realm
+ // and `then` is from chrome realm, that shouldn't happen in practice.
+ //
+ // NOTE: If `thenable` is also from chrome realm, accessing `then` silently
+ // fails and it returns `undefined`, and that case doesn't reach here.
+ RootedObject then(cx, &thenVal.toObject());
+ AutoFunctionOrCurrentRealm ar(cx, then);
+ if (then->maybeCCWRealm() != cx->realm()) {
+ if (!cx->compartment()->wrap(cx, &then)) {
+ return false;
+ }
+ }
+
+ // Wrap the `promiseToResolve` and `thenable` arguments.
+ if (!cx->compartment()->wrap(cx, &promiseToResolve)) {
+ return false;
+ }
+
+ MOZ_ASSERT(thenable.isObject());
+ if (!cx->compartment()->wrap(cx, &thenable)) {
+ return false;
+ }
+
+ // Step 1. Let job be a new Job Abstract Closure with no parameters that
+ // captures promiseToResolve, thenable, and then and performs the
+ // following steps when called:
+ Handle<PropertyName*> funName = cx->names().empty;
+ RootedFunction job(
+ cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!job) {
+ return false;
+ }
+
+ // Store the `then` function on the callback.
+ job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
+
+ // Create a dense array to hold the data needed for the reaction job to
+ // work.
+ // The layout is described in the ThenableJobDataIndices enum.
+ Rooted<ArrayObject*> data(
+ cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
+ if (!data) {
+ return false;
+ }
+
+ // Set the `promiseToResolve` and `thenable` arguments.
+ data->setDenseInitializedLength(ThenableJobDataLength);
+ data->initDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
+ data->initDenseElement(ThenableJobDataIndex_Thenable, thenable);
+
+ // Store the data array on the reaction job.
+ job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
+
+ // At this point the promise is guaranteed to be wrapped into the job's
+ // compartment.
+ RootedObject promise(cx, &promiseToResolve.toObject());
+
+ Rooted<GlobalObject*> incumbentGlobal(cx,
+ cx->runtime()->getIncumbentGlobal(cx));
+
+ // Step X. HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
+ return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )
+ * https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
+ * HostEnqueuePromiseJob ( job, realm )
+ * https://tc39.es/ecma262/#sec-hostenqueuepromisejob
+ *
+ * Tells the embedding to enqueue a Promise resolve thenable built-in job,
+ * based on two parameters:
+ * promiseToResolve - The promise to resolve, obviously.
+ * thenable - The thenable to resolve the Promise with.
+ */
+[[nodiscard]] static bool EnqueuePromiseResolveThenableBuiltinJob(
+ JSContext* cx, HandleObject promiseToResolve, HandleObject thenable) {
+ cx->check(promiseToResolve, thenable);
+ MOZ_ASSERT(promiseToResolve->is<PromiseObject>());
+ MOZ_ASSERT(thenable->is<PromiseObject>());
+
+ // Step 1. Let job be a new Job Abstract Closure with no parameters that
+ // captures promiseToResolve, thenable, and then and performs the
+ // following steps when called:
+ Handle<PropertyName*> funName = cx->names().empty;
+ RootedFunction job(
+ cx, NewNativeFunction(cx, PromiseResolveBuiltinThenableJob, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!job) {
+ return false;
+ }
+
+ // Steps 2-5.
+ // (implicit)
+ // `then` is built-in Promise.prototype.then in the current realm.,
+ // thus `thenRealm` is also current realm, and we have nothing to do here.
+
+ // Store the promise and the thenable on the reaction job.
+ job->setExtendedSlot(BuiltinThenableJobSlot_Promise,
+ ObjectValue(*promiseToResolve));
+ job->setExtendedSlot(BuiltinThenableJobSlot_Thenable, ObjectValue(*thenable));
+
+ Rooted<GlobalObject*> incumbentGlobal(cx,
+ cx->runtime()->getIncumbentGlobal(cx));
+
+ // HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
+ return cx->runtime()->enqueuePromiseJob(cx, job, promiseToResolve,
+ incumbentGlobal);
+}
+
+[[nodiscard]] static bool AddDummyPromiseReactionForDebugger(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ HandleObject dependentPromise);
+
+[[nodiscard]] static bool AddPromiseReaction(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseReactionRecord*> reaction);
+
+static JSFunction* GetResolveFunctionFromReject(JSFunction* reject) {
+ MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction);
+ Value resolveFunVal =
+ reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction);
+ MOZ_ASSERT(IsNativeFunction(resolveFunVal, ResolvePromiseFunction));
+ return &resolveFunVal.toObject().as<JSFunction>();
+}
+
+static JSFunction* GetRejectFunctionFromResolve(JSFunction* resolve) {
+ MOZ_ASSERT(resolve->maybeNative() == ResolvePromiseFunction);
+ Value rejectFunVal =
+ resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction);
+ MOZ_ASSERT(IsNativeFunction(rejectFunVal, RejectPromiseFunction));
+ return &rejectFunVal.toObject().as<JSFunction>();
+}
+
+static JSFunction* GetResolveFunctionFromPromise(PromiseObject* promise) {
+ Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction);
+ if (rejectFunVal.isUndefined()) {
+ return nullptr;
+ }
+ JSObject* rejectFunObj = &rejectFunVal.toObject();
+
+ // We can safely unwrap it because all we want is to get the resolve
+ // function.
+ if (IsWrapper(rejectFunObj)) {
+ rejectFunObj = UncheckedUnwrap(rejectFunObj);
+ }
+
+ if (!rejectFunObj->is<JSFunction>()) {
+ return nullptr;
+ }
+
+ JSFunction* rejectFun = &rejectFunObj->as<JSFunction>();
+
+ // Only the original RejectPromiseFunction has a reference to the resolve
+ // function.
+ if (rejectFun->maybeNative() != &RejectPromiseFunction) {
+ return nullptr;
+ }
+
+ // The reject function was already called and cleared its resolve-function
+ // extended slot.
+ if (rejectFun->getExtendedSlot(RejectFunctionSlot_ResolveFunction)
+ .isUndefined()) {
+ return nullptr;
+ }
+
+ return GetResolveFunctionFromReject(rejectFun);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise ( executor )
+ * https://tc39.es/ecma262/#sec-promise-executor
+ *
+ * Steps 3-7.
+ */
+[[nodiscard]] static MOZ_ALWAYS_INLINE PromiseObject*
+CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
+ bool protoIsWrapped /* = false */,
+ bool informDebugger /* = true */) {
+ // Enter the unwrapped proto's compartment, if that's different from
+ // the current one.
+ // All state stored in a Promise's fixed slots must be created in the
+ // same compartment, so we get all of that out of the way here.
+ // (Except for the resolution functions, which are created below.)
+ mozilla::Maybe<AutoRealm> ar;
+ if (protoIsWrapped) {
+ ar.emplace(cx, proto);
+ }
+
+ // Step 3. Let promise be
+ // ? OrdinaryCreateFromConstructor(
+ // NewTarget, "%Promise.prototype%",
+ // « [[PromiseState]], [[PromiseResult]],
+ // [[PromiseFulfillReactions]], [[PromiseRejectReactions]],
+ // [[PromiseIsHandled]] »).
+ PromiseObject* promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 4. Set promise.[[PromiseState]] to pending.
+ promise->initFixedSlot(PromiseSlot_Flags, Int32Value(0));
+
+ // Step 5. Set promise.[[PromiseFulfillReactions]] to a new empty List.
+ // Step 6. Set promise.[[PromiseRejectReactions]] to a new empty List.
+ // (omitted)
+ // We allocate our single list of reaction records lazily.
+
+ // Step 7. Set promise.[[PromiseIsHandled]] to false.
+ // (implicit)
+ // The handled flag is unset by default.
+
+ if (MOZ_LIKELY(!JS::IsAsyncStackCaptureEnabledForRealm(cx))) {
+ return promise;
+ }
+
+ // Store an allocation stack so we can later figure out what the
+ // control flow was for some unexpected results. Frightfully expensive,
+ // but oh well.
+
+ Rooted<PromiseObject*> promiseRoot(cx, promise);
+
+ PromiseDebugInfo* debugInfo = PromiseDebugInfo::create(cx, promiseRoot);
+ if (!debugInfo) {
+ return nullptr;
+ }
+
+ // Let the Debugger know about this Promise.
+ if (informDebugger) {
+ DebugAPI::onNewPromise(cx, promiseRoot);
+ }
+
+ return promiseRoot;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise ( executor )
+ * https://tc39.es/ecma262/#sec-promise-executor
+ */
+static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. If NewTarget is undefined, throw a TypeError exception.
+ if (!ThrowIfNotConstructing(cx, args, "Promise")) {
+ return false;
+ }
+
+ // Step 2. If IsCallable(executor) is false, throw a TypeError exception.
+ HandleValue executorVal = args.get(0);
+ if (!IsCallable(executorVal)) {
+ return ReportIsNotFunction(cx, executorVal);
+ }
+ RootedObject executor(cx, &executorVal.toObject());
+
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+
+ // If the constructor is called via an Xray wrapper, then the newTarget
+ // hasn't been unwrapped. We want that because, while the actual instance
+ // should be created in the target compartment, the constructor's code
+ // should run in the wrapper's compartment.
+ //
+ // This is so that the resolve and reject callbacks get created in the
+ // wrapper's compartment, which is required for code in that compartment
+ // to freely interact with it, and, e.g., pass objects as arguments, which
+ // it wouldn't be able to if the callbacks were themselves wrapped in Xray
+ // wrappers.
+ //
+ // At the same time, just creating the Promise itself in the wrapper's
+ // compartment wouldn't be helpful: if the wrapper forbids interactions
+ // with objects except for specific actions, such as calling them, then
+ // the code we want to expose it to can't actually treat it as a Promise:
+ // calling .then on it would throw, for example.
+ //
+ // Another scenario where it's important to create the Promise in a
+ // different compartment from the resolution functions is when we want to
+ // give non-privileged code a Promise resolved with the result of a
+ // Promise from privileged code; as a return value of a JS-implemented
+ // API, say. If the resolution functions were unprivileged, then resolving
+ // with a privileged Promise would cause `resolve` to attempt accessing
+ // .then on the passed Promise, which would throw an exception, so we'd
+ // just end up with a rejected Promise. Really, we want to chain the two
+ // Promises, with the unprivileged one resolved with the resolution of the
+ // privileged one.
+
+ bool needsWrapping = false;
+ RootedObject proto(cx);
+ if (IsWrapper(newTarget)) {
+ JSObject* unwrappedNewTarget = CheckedUnwrapStatic(newTarget);
+ MOZ_ASSERT(unwrappedNewTarget);
+ MOZ_ASSERT(unwrappedNewTarget != newTarget);
+
+ newTarget = unwrappedNewTarget;
+ {
+ AutoRealm ar(cx, newTarget);
+ Handle<GlobalObject*> global = cx->global();
+ JSObject* promiseCtor =
+ GlobalObject::getOrCreatePromiseConstructor(cx, global);
+ if (!promiseCtor) {
+ return false;
+ }
+
+ // Promise subclasses don't get the special Xray treatment, so
+ // we only need to do the complex wrapping and unwrapping scheme
+ // described above for instances of Promise itself.
+ if (newTarget == promiseCtor) {
+ needsWrapping = true;
+ proto = GlobalObject::getOrCreatePromisePrototype(cx, cx->global());
+ if (!proto) {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (needsWrapping) {
+ if (!cx->compartment()->wrap(cx, &proto)) {
+ return false;
+ }
+ } else {
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Promise,
+ &proto)) {
+ return false;
+ }
+ }
+ PromiseObject* promise =
+ PromiseObject::create(cx, executor, proto, needsWrapping);
+ if (!promise) {
+ return false;
+ }
+
+ // Step 11.
+ args.rval().setObject(*promise);
+ if (needsWrapping) {
+ return cx->compartment()->wrap(cx, args.rval());
+ }
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise ( executor )
+ * https://tc39.es/ecma262/#sec-promise-executor
+ *
+ * Steps 3-11.
+ */
+/* static */
+PromiseObject* PromiseObject::create(JSContext* cx, HandleObject executor,
+ HandleObject proto /* = nullptr */,
+ bool needsWrapping /* = false */) {
+ MOZ_ASSERT(executor->isCallable());
+
+ RootedObject usedProto(cx, proto);
+ // If the proto is wrapped, that means the current function is running
+ // with a different compartment active from the one the Promise instance
+ // is to be created in.
+ // See the comment in PromiseConstructor for details.
+ if (needsWrapping) {
+ MOZ_ASSERT(proto);
+ usedProto = CheckedUnwrapStatic(proto);
+ if (!usedProto) {
+ ReportAccessDenied(cx);
+ return nullptr;
+ }
+ }
+
+ // Steps 3-7.
+ Rooted<PromiseObject*> promise(
+ cx, CreatePromiseObjectInternal(cx, usedProto, needsWrapping, false));
+ if (!promise) {
+ return nullptr;
+ }
+
+ RootedObject promiseObj(cx, promise);
+ if (needsWrapping && !cx->compartment()->wrap(cx, &promiseObj)) {
+ return nullptr;
+ }
+
+ // Step 8. Let resolvingFunctions be CreateResolvingFunctions(promise).
+ //
+ // The resolving functions are created in the compartment active when the
+ // (maybe wrapped) Promise constructor was called. They contain checks and
+ // can unwrap the Promise if required.
+ RootedObject resolveFn(cx);
+ RootedObject rejectFn(cx);
+ if (!CreateResolvingFunctions(cx, promiseObj, &resolveFn, &rejectFn)) {
+ return nullptr;
+ }
+
+ // Need to wrap the resolution functions before storing them on the Promise.
+ MOZ_ASSERT(promise->getFixedSlot(PromiseSlot_RejectFunction).isUndefined(),
+ "Slot must be undefined so initFixedSlot can be used");
+ if (needsWrapping) {
+ AutoRealm ar(cx, promise);
+ RootedObject wrappedRejectFn(cx, rejectFn);
+ if (!cx->compartment()->wrap(cx, &wrappedRejectFn)) {
+ return nullptr;
+ }
+ promise->initFixedSlot(PromiseSlot_RejectFunction,
+ ObjectValue(*wrappedRejectFn));
+ } else {
+ promise->initFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*rejectFn));
+ }
+
+ // Step 9. Let completion be
+ // Call(executor, undefined, « resolvingFunctions.[[Resolve]],
+ // resolvingFunctions.[[Reject]] »).
+ bool success;
+ {
+ FixedInvokeArgs<2> args(cx);
+ args[0].setObject(*resolveFn);
+ args[1].setObject(*rejectFn);
+
+ RootedValue calleeOrRval(cx, ObjectValue(*executor));
+ success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval);
+ }
+
+ // Step 10. If completion is an abrupt completion, then
+ if (!success) {
+ RootedValue exceptionVal(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!MaybeGetAndClearExceptionAndStack(cx, &exceptionVal, &stack)) {
+ return nullptr;
+ }
+
+ // Step 10.a. Perform
+ // ? Call(resolvingFunctions.[[Reject]], undefined,
+ // « completion.[[Value]] »).
+ RootedValue calleeOrRval(cx, ObjectValue(*rejectFn));
+ if (!Call(cx, calleeOrRval, UndefinedHandleValue, exceptionVal,
+ &calleeOrRval)) {
+ return nullptr;
+ }
+ }
+
+ // Let the Debugger know about this Promise.
+ DebugAPI::onNewPromise(cx, promise);
+
+ // Step 11. Return promise.
+ return promise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise ( executor )
+ * https://tc39.es/ecma262/#sec-promise-executor
+ *
+ * skipping creation of resolution functions and executor function invocation.
+ */
+/* static */
+PromiseObject* PromiseObject::createSkippingExecutor(JSContext* cx) {
+ return CreatePromiseObjectWithoutResolutionFunctions(cx);
+}
+
+class MOZ_STACK_CLASS PromiseForOfIterator : public JS::ForOfIterator {
+ public:
+ using JS::ForOfIterator::ForOfIterator;
+
+ bool isOptimizedDenseArrayIteration() {
+ MOZ_ASSERT(valueIsIterable());
+ return index != NOT_ARRAY && IsPackedArray(iterator);
+ }
+};
+
+[[nodiscard]] static bool PerformPromiseAll(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done);
+
+[[nodiscard]] static bool PerformPromiseAllSettled(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done);
+
+[[nodiscard]] static bool PerformPromiseAny(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done);
+
+[[nodiscard]] static bool PerformPromiseRace(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done);
+
+enum class CombinatorKind { All, AllSettled, Any, Race };
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Unified implementation of
+ *
+ * Promise.all ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.all
+ * Promise.allSettled ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.allsettled
+ * Promise.race ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.race
+ * Promise.any ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.any
+ * GetPromiseResolve ( promiseConstructor )
+ * https://tc39.es/ecma262/#sec-getpromiseresolve
+ */
+[[nodiscard]] static bool CommonPromiseCombinator(JSContext* cx, CallArgs& args,
+ CombinatorKind kind) {
+ HandleValue iterable = args.get(0);
+
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ // (moved from NewPromiseCapability, step 1).
+ HandleValue CVal = args.thisv();
+ if (!CVal.isObject()) {
+ const char* message;
+ switch (kind) {
+ case CombinatorKind::All:
+ message = "Receiver of Promise.all call";
+ break;
+ case CombinatorKind::AllSettled:
+ message = "Receiver of Promise.allSettled call";
+ break;
+ case CombinatorKind::Any:
+ message = "Receiver of Promise.any call";
+ break;
+ case CombinatorKind::Race:
+ message = "Receiver of Promise.race call";
+ break;
+ }
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_OBJECT_REQUIRED, message);
+ return false;
+ }
+
+ // Step 1. Let C be the this value.
+ RootedObject C(cx, &CVal.toObject());
+
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseCapability> promiseCapability(cx);
+ if (!NewPromiseCapability(cx, C, &promiseCapability, false)) {
+ return false;
+ }
+
+ RootedValue promiseResolve(cx, UndefinedValue());
+ {
+ JSObject* promiseCtor =
+ GlobalObject::getOrCreatePromiseConstructor(cx, cx->global());
+ if (!promiseCtor) {
+ return false;
+ }
+
+ PromiseLookup& promiseLookup = cx->realm()->promiseLookup;
+ if (C != promiseCtor || !promiseLookup.isDefaultPromiseState(cx)) {
+ // Step 3. Let promiseResolve be GetPromiseResolve(C).
+
+ // GetPromiseResolve
+ // Step 1. Let promiseResolve be ? Get(promiseConstructor, "resolve").
+ if (!GetProperty(cx, C, C, cx->names().resolve, &promiseResolve)) {
+ // Step 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+
+ // GetPromiseResolve
+ // Step 2. If IsCallable(promiseResolve) is false,
+ // throw a TypeError exception.
+ if (!IsCallable(promiseResolve)) {
+ ReportIsNotFunction(cx, promiseResolve);
+
+ // Step 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+ }
+ }
+
+ // Step 5. Let iteratorRecord be GetIterator(iterable).
+ PromiseForOfIterator iter(cx);
+ if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
+ // Step 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+
+ if (!iter.valueIsIterable()) {
+ // Step 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
+ const char* message;
+ switch (kind) {
+ case CombinatorKind::All:
+ message = "Argument of Promise.all";
+ break;
+ case CombinatorKind::AllSettled:
+ message = "Argument of Promise.allSettled";
+ break;
+ case CombinatorKind::Any:
+ message = "Argument of Promise.any";
+ break;
+ case CombinatorKind::Race:
+ message = "Argument of Promise.race";
+ break;
+ }
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
+ message);
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+
+ bool done, result;
+ switch (kind) {
+ case CombinatorKind::All:
+ // Promise.all
+ // Step 7. Let result be
+ // PerformPromiseAll(iteratorRecord, C, promiseCapability,
+ // promiseResolve).
+ result = PerformPromiseAll(cx, iter, C, promiseCapability, promiseResolve,
+ &done);
+ break;
+ case CombinatorKind::AllSettled:
+ // Promise.allSettled
+ // Step 7. Let result be
+ // PerformPromiseAllSettled(iteratorRecord, C, promiseCapability,
+ // promiseResolve).
+ result = PerformPromiseAllSettled(cx, iter, C, promiseCapability,
+ promiseResolve, &done);
+ break;
+ case CombinatorKind::Any:
+ // Promise.any
+ // Step 7. Let result be
+ // PerformPromiseAny(iteratorRecord, C, promiseCapability,
+ // promiseResolve).
+ result = PerformPromiseAny(cx, iter, C, promiseCapability, promiseResolve,
+ &done);
+ break;
+ case CombinatorKind::Race:
+ // Promise.race
+ // Step 7. Let result be
+ // PerformPromiseRace(iteratorRecord, C, promiseCapability,
+ // promiseResolve).
+ result = PerformPromiseRace(cx, iter, C, promiseCapability,
+ promiseResolve, &done);
+ break;
+ }
+
+ // Step 8. If result is an abrupt completion, then
+ if (!result) {
+ // Step 8.a. If iteratorRecord.[[Done]] is false,
+ // set result to IteratorClose(iteratorRecord, result).
+ if (!done) {
+ iter.closeThrow();
+ }
+
+ // Step 8.b. IfAbruptRejectPromise(result, promiseCapability).
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+
+ // Step 9. Return Completion(result).
+ args.rval().setObject(*promiseCapability.promise());
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.all ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.all
+ */
+static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CommonPromiseCombinator(cx, args, CombinatorKind::All);
+}
+
+[[nodiscard]] static bool PerformPromiseThen(
+ JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
+ HandleValue onRejected_, Handle<PromiseCapability> resultCapability);
+
+[[nodiscard]] static bool PerformPromiseThenWithoutSettleHandlers(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseObject*> promiseToResolve,
+ Handle<PromiseCapability> resultCapability);
+
+static JSFunction* NewPromiseCombinatorElementFunction(
+ JSContext* cx, Native native,
+ Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index);
+
+static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
+ Value* vp);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.all ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.all
+ * PerformPromiseAll ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseall
+ *
+ * Unforgeable version.
+ */
+[[nodiscard]] JSObject* js::GetWaitForAllPromise(
+ JSContext* cx, JS::HandleObjectVector promises) {
+#ifdef DEBUG
+ for (size_t i = 0, len = promises.length(); i < len; i++) {
+ JSObject* obj = promises[i];
+ cx->check(obj);
+ MOZ_ASSERT(UncheckedUnwrap(obj)->is<PromiseObject>());
+ }
+#endif
+
+ // Step 1. Let C be the this value.
+ RootedObject C(cx,
+ GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
+ if (!C) {
+ return nullptr;
+ }
+
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseCapability> resultCapability(cx);
+ if (!NewPromiseCapability(cx, C, &resultCapability, false)) {
+ return nullptr;
+ }
+
+ // Steps 3-6 for iterator and iteratorRecord.
+ // (omitted)
+
+ // Step 7. Let result be
+ // PerformPromiseAll(iteratorRecord, C, promiseCapability,
+ // promiseResolve).
+ //
+ // Implemented as an inlined, simplied version of PerformPromiseAll.
+ {
+ uint32_t promiseCount = promises.length();
+ // PerformPromiseAll
+
+ // Step 1. Let values be a new empty List.
+ Rooted<PromiseCombinatorElements> values(cx);
+ {
+ auto* valuesArray = NewDenseFullyAllocatedArray(cx, promiseCount);
+ if (!valuesArray) {
+ return nullptr;
+ }
+ valuesArray->ensureDenseInitializedLength(0, promiseCount);
+
+ values.initialize(valuesArray);
+ }
+
+ // Step 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
+ //
+ // Create our data holder that holds all the things shared across
+ // every step of the iterator. In particular, this holds the
+ // remainingElementsCount (as an integer reserved slot), the array of
+ // values, and the resolve function from our PromiseCapability.
+ Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
+ dataHolder = PromiseCombinatorDataHolder::New(
+ cx, resultCapability.promise(), values, resultCapability.resolve());
+ if (!dataHolder) {
+ return nullptr;
+ }
+
+ // Call PerformPromiseThen with resolve and reject set to nullptr.
+ Rooted<PromiseCapability> resultCapabilityWithoutResolving(cx);
+ resultCapabilityWithoutResolving.promise().set(resultCapability.promise());
+
+ // Step 3. Let index be 0.
+ // Step 4. Repeat,
+ // Step 4.t. Set index to index + 1.
+ for (uint32_t index = 0; index < promiseCount; index++) {
+ // Steps 4.a-c for IteratorStep.
+ // (omitted)
+
+ // Step 4.d. (implemented after the loop).
+
+ // Steps 4.e-g for IteratorValue
+ // (omitted)
+
+ // Step 4.h. Append undefined to values.
+ values.unwrappedArray()->setDenseElement(index, UndefinedHandleValue);
+
+ // Step 4.i. Let nextPromise be
+ // ? Call(promiseResolve, constructor, « nextValue »).
+ RootedObject nextPromiseObj(cx, promises[index]);
+
+ // Steps 4.j-q.
+ JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(
+ cx, PromiseAllResolveElementFunction, dataHolder, index);
+ if (!resolveFunc) {
+ return nullptr;
+ }
+
+ // Step 4.r. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] + 1.
+ dataHolder->increaseRemainingCount();
+
+ // Step 4.s. Perform
+ // ? Invoke(nextPromise, "then",
+ // « onFulfilled, resultCapability.[[Reject]] »).
+ RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
+ RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject()));
+ Rooted<PromiseObject*> nextPromise(cx);
+
+ // GetWaitForAllPromise is used internally only and must not
+ // trigger content-observable effects when registering a reaction.
+ // It's also meant to work on wrapped Promises, potentially from
+ // compartments with principals inaccessible from the current
+ // compartment. To make that work, it unwraps promises with
+ // UncheckedUnwrap,
+ nextPromise = &UncheckedUnwrap(nextPromiseObj)->as<PromiseObject>();
+
+ if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal,
+ resultCapabilityWithoutResolving)) {
+ return nullptr;
+ }
+ }
+
+ // Step 4.d.i. Set iteratorRecord.[[Done]] to true.
+ // (implicit)
+
+ // Step 4.d.ii. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] - 1.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Step 4.d.iii.If remainingElementsCount.[[Value]] is 0, then
+ if (remainingCount == 0) {
+ // Step 4.d.iii.1. Let valuesArray be ! CreateArrayFromList(values).
+ // (already performed)
+
+ // Step 4.d.iii.2. Perform
+ // ? Call(resultCapability.[[Resolve]], undefined,
+ // « valuesArray »).
+ if (!ResolvePromiseInternal(cx, resultCapability.promise(),
+ values.value())) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Step 4.d.iv. Return resultCapability.[[Promise]].
+ return resultCapability.promise();
+}
+
+static bool CallDefaultPromiseResolveFunction(JSContext* cx,
+ Handle<PromiseObject*> promise,
+ HandleValue resolutionValue);
+static bool CallDefaultPromiseRejectFunction(
+ JSContext* cx, Handle<PromiseObject*> promise, HandleValue rejectionValue,
+ JS::Handle<SavedFrame*> unwrappedRejectionStack = nullptr);
+
+/**
+ * Perform Call(promiseCapability.[[Resolve]], undefined ,« value ») given
+ * promiseCapability = { promiseObj, resolveFun }.
+ *
+ * Also,
+ *
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * NewPromiseReactionJob ( reaction, argument )
+ * https://tc39.es/ecma262/#sec-newpromisereactionjob
+ *
+ * Steps 1.f-i. "type is Fulfill" case.
+ */
+[[nodiscard]] static bool CallPromiseResolveFunction(JSContext* cx,
+ HandleObject resolveFun,
+ HandleValue value,
+ HandleObject promiseObj) {
+ cx->check(resolveFun);
+ cx->check(value);
+ cx->check(promiseObj);
+
+ // NewPromiseReactionJob
+ // Step 1.g. Assert: promiseCapability is a PromiseCapability Record.
+ // (implicit)
+
+ if (resolveFun) {
+ // NewPromiseReactionJob
+ // Step 1.h. If handlerResult is an abrupt completion, then
+ // (handled in CallPromiseRejectFunction)
+ // Step 1.i. Else,
+ // Step 1.i.i. Return
+ // ? Call(promiseCapability.[[Resolve]], undefined,
+ // « handlerResult.[[Value]] »).
+ RootedValue calleeOrRval(cx, ObjectValue(*resolveFun));
+ return Call(cx, calleeOrRval, UndefinedHandleValue, value, &calleeOrRval);
+ }
+
+ // `promiseObj` can be optimized away if it's known to be unused.
+ //
+ // NewPromiseReactionJob
+ // Step f. If promiseCapability is undefined, then
+ // (reordered)
+ //
+ // NOTE: "promiseCapability is undefined" case is represented by
+ // `resolveFun == nullptr && promiseObj == nullptr`.
+ if (!promiseObj) {
+ // NewPromiseReactionJob
+ // Step f.i. Assert: handlerResult is not an abrupt completion.
+ // (implicit)
+
+ // Step f.ii. Return empty.
+ return true;
+ }
+
+ // NewPromiseReactionJob
+ // Step 1.h. If handlerResult is an abrupt completion, then
+ // (handled in CallPromiseRejectFunction)
+ // Step 1.i. Else,
+ // Step 1.i.i. Return
+ // ? Call(promiseCapability.[[Resolve]], undefined,
+ // « handlerResult.[[Value]] »).
+ Handle<PromiseObject*> promise = promiseObj.as<PromiseObject>();
+ if (IsPromiseWithDefaultResolvingFunction(promise)) {
+ return CallDefaultPromiseResolveFunction(cx, promise, value);
+ }
+
+ // This case is used by resultCapabilityWithoutResolving in
+ // GetWaitForAllPromise, and nothing should be done.
+
+ return true;
+}
+
+/**
+ * Perform Call(promiseCapability.[[Reject]], undefined ,« reason ») given
+ * promiseCapability = { promiseObj, rejectFun }.
+ *
+ * Also,
+ *
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * NewPromiseReactionJob ( reaction, argument )
+ * https://tc39.es/ecma262/#sec-newpromisereactionjob
+ *
+ * Steps 1.g-i. "type is Reject" case.
+ */
+[[nodiscard]] static bool CallPromiseRejectFunction(
+ JSContext* cx, HandleObject rejectFun, HandleValue reason,
+ HandleObject promiseObj, Handle<SavedFrame*> unwrappedRejectionStack,
+ UnhandledRejectionBehavior behavior) {
+ cx->check(rejectFun);
+ cx->check(reason);
+ cx->check(promiseObj);
+
+ // NewPromiseReactionJob
+ // Step 1.g. Assert: promiseCapability is a PromiseCapability Record.
+ // (implicit)
+
+ if (rejectFun) {
+ // NewPromiseReactionJob
+ // Step 1.h. If handlerResult is an abrupt completion, then
+ // Step 1.h.i. Return
+ // ? Call(promiseCapability.[[Reject]], undefined,
+ // « handlerResult.[[Value]] »).
+ RootedValue calleeOrRval(cx, ObjectValue(*rejectFun));
+ return Call(cx, calleeOrRval, UndefinedHandleValue, reason, &calleeOrRval);
+ }
+
+ // NewPromiseReactionJob
+ // See the comment in CallPromiseResolveFunction for promiseCapability field
+ //
+ // Step f. If promiseCapability is undefined, then
+ // Step f.i. Assert: handlerResult is not an abrupt completion.
+ //
+ // The spec doesn't allow promiseCapability to be undefined for reject case,
+ // but `promiseObj` can be optimized away if it's known to be unused.
+ if (!promiseObj) {
+ if (behavior == UnhandledRejectionBehavior::Ignore) {
+ // Do nothing if unhandled rejections are to be ignored.
+ return true;
+ }
+
+ // Otherwise create and reject a promise on the fly. The promise's
+ // allocation time will be wrong. So it goes.
+ Rooted<PromiseObject*> temporaryPromise(
+ cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+ if (!temporaryPromise) {
+ cx->clearPendingException();
+ return true;
+ }
+
+ // NewPromiseReactionJob
+ // Step 1.h. If handlerResult is an abrupt completion, then
+ // Step 1.h.i. Return
+ // ? Call(promiseCapability.[[Reject]], undefined,
+ // « handlerResult.[[Value]] »).
+ return RejectPromiseInternal(cx, temporaryPromise, reason,
+ unwrappedRejectionStack);
+ }
+
+ // NewPromiseReactionJob
+ // Step 1.h. If handlerResult is an abrupt completion, then
+ // Step 1.h.i. Return
+ // ? Call(promiseCapability.[[Reject]], undefined,
+ // « handlerResult.[[Value]] »).
+ Handle<PromiseObject*> promise = promiseObj.as<PromiseObject>();
+ if (IsPromiseWithDefaultResolvingFunction(promise)) {
+ return CallDefaultPromiseRejectFunction(cx, promise, reason,
+ unwrappedRejectionStack);
+ }
+
+ // This case is used by resultCapabilityWithoutResolving in
+ // GetWaitForAllPromise, and nothing should be done.
+
+ return true;
+}
+
+[[nodiscard]] static JSObject* CommonStaticResolveRejectImpl(
+ JSContext* cx, HandleValue thisVal, HandleValue argVal,
+ ResolutionMode mode);
+
+static bool IsPromiseSpecies(JSContext* cx, JSFunction* species);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Unified implementation of
+ *
+ * PerformPromiseAll ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseall
+ * PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseallsettled
+ * PerformPromiseRace ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiserace
+ * PerformPromiseAny ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseany
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ */
+template <typename T>
+[[nodiscard]] static bool CommonPerformPromiseCombinator(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ HandleObject resultPromise, HandleValue promiseResolve, bool* done,
+ bool resolveReturnsUndefined, T getResolveAndReject) {
+ RootedObject promiseCtor(
+ cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
+ if (!promiseCtor) {
+ return false;
+ }
+
+ // Optimized dense array iteration ensures no side-effects take place
+ // during the iteration.
+ bool iterationMayHaveSideEffects = !iterator.isOptimizedDenseArrayIteration();
+
+ PromiseLookup& promiseLookup = cx->realm()->promiseLookup;
+
+ // Try to optimize when the Promise object is in its default state, guarded
+ // by |C == promiseCtor| because we can only perform this optimization
+ // for the builtin Promise constructor.
+ bool isDefaultPromiseState =
+ C == promiseCtor && promiseLookup.isDefaultPromiseState(cx);
+ bool validatePromiseState = iterationMayHaveSideEffects;
+
+ RootedValue CVal(cx, ObjectValue(*C));
+ RootedValue resolveFunVal(cx);
+ RootedValue rejectFunVal(cx);
+
+ // We're reusing rooted variables in the loop below, so we don't need to
+ // declare a gazillion different rooted variables here. Rooted variables
+ // which are reused include "Or" in their name.
+ RootedValue nextValueOrNextPromise(cx);
+ RootedObject nextPromiseObj(cx);
+ RootedValue thenVal(cx);
+ RootedObject thenSpeciesOrBlockedPromise(cx);
+ Rooted<PromiseCapability> thenCapability(cx);
+
+ // PerformPromiseAll, PerformPromiseAllSettled, PerformPromiseAny
+ // Step 4.
+ // PerformPromiseRace
+ // Step 1.
+ while (true) {
+ // Step a. Let next be IteratorStep(iteratorRecord).
+ // Step b. If next is an abrupt completion, set iteratorRecord.[[Done]] to
+ // true.
+ // Step c. ReturnIfAbrupt(next).
+ // Step e. Let nextValue be IteratorValue(next).
+ // Step f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]]
+ // to true.
+ // Step g. ReturnIfAbrupt(nextValue).
+ RootedValue& nextValue = nextValueOrNextPromise;
+ if (!iterator.next(&nextValue, done)) {
+ *done = true;
+ return false;
+ }
+
+ // Step d. If next is false, then
+ if (*done) {
+ return true;
+ }
+
+ // Set to false when we can skip the [[Get]] for "then" and instead
+ // use the built-in Promise.prototype.then function.
+ bool getThen = true;
+
+ if (isDefaultPromiseState && validatePromiseState) {
+ isDefaultPromiseState = promiseLookup.isDefaultPromiseState(cx);
+ }
+
+ RootedValue& nextPromise = nextValueOrNextPromise;
+ if (isDefaultPromiseState) {
+ PromiseObject* nextValuePromise = nullptr;
+ if (nextValue.isObject() && nextValue.toObject().is<PromiseObject>()) {
+ nextValuePromise = &nextValue.toObject().as<PromiseObject>();
+ }
+
+ if (nextValuePromise &&
+ promiseLookup.isDefaultInstanceWhenPromiseStateIsSane(
+ cx, nextValuePromise)) {
+ // The below steps don't produce any side-effects, so we can
+ // skip the Promise state revalidation in the next iteration
+ // when the iterator itself also doesn't produce any
+ // side-effects.
+ validatePromiseState = iterationMayHaveSideEffects;
+
+ // Step {i, h}. Let nextPromise be
+ // ? Call(promiseResolve, constructor, « nextValue »).
+ // Promise.resolve is a no-op for the default case.
+ MOZ_ASSERT(&nextPromise.toObject() == nextValuePromise);
+
+ // `nextPromise` uses the built-in `then` function.
+ getThen = false;
+ } else {
+ // Need to revalidate the Promise state in the next iteration,
+ // because CommonStaticResolveRejectImpl may have modified it.
+ validatePromiseState = true;
+
+ // Step {i, h}. Let nextPromise be
+ // ? Call(promiseResolve, constructor, « nextValue »).
+ // Inline the call to Promise.resolve.
+ JSObject* res =
+ CommonStaticResolveRejectImpl(cx, CVal, nextValue, ResolveMode);
+ if (!res) {
+ return false;
+ }
+
+ nextPromise.setObject(*res);
+ }
+ } else if (promiseResolve.isUndefined()) {
+ // |promiseResolve| is undefined when the Promise constructor was
+ // initially in its default state, i.e. if it had been retrieved, it would
+ // have been set to |Promise.resolve|.
+
+ // Step {i, h}. Let nextPromise be
+ // ? Call(promiseResolve, constructor, « nextValue »).
+ // Inline the call to Promise.resolve.
+ JSObject* res =
+ CommonStaticResolveRejectImpl(cx, CVal, nextValue, ResolveMode);
+ if (!res) {
+ return false;
+ }
+
+ nextPromise.setObject(*res);
+ } else {
+ // Step {i, h}. Let nextPromise be
+ // ? Call(promiseResolve, constructor, « nextValue »).
+ if (!Call(cx, promiseResolve, CVal, nextValue, &nextPromise)) {
+ return false;
+ }
+ }
+
+ // Get the resolving functions for this iteration.
+ // PerformPromiseAll
+ // Steps j-r.
+ // PerformPromiseAllSettled
+ // Steps j-aa.
+ // PerformPromiseRace
+ // Step i.
+ // PerformPromiseAny
+ // Steps j-q.
+ if (!getResolveAndReject(&resolveFunVal, &rejectFunVal)) {
+ return false;
+ }
+
+ // Call |nextPromise.then| with the provided hooks and add
+ // |resultPromise| to the list of dependent promises.
+ //
+ // If |nextPromise.then| is the original |Promise.prototype.then|
+ // function and the call to |nextPromise.then| would use the original
+ // |Promise| constructor to create the resulting promise, we skip the
+ // call to |nextPromise.then| and thus creating a new promise that
+ // would not be observable by content.
+
+ // PerformPromiseAll
+ // Step s. Perform
+ // ? Invoke(nextPromise, "then",
+ // « onFulfilled, resultCapability.[[Reject]] »).
+ // PerformPromiseAllSettled
+ // Step ab. Perform
+ // ? Invoke(nextPromise, "then", « onFulfilled, onRejected »).
+ // PerformPromiseRace
+ // Step i. Perform
+ // ? Invoke(nextPromise, "then",
+ // « resultCapability.[[Resolve]],
+ // resultCapability.[[Reject]] »).
+ // PerformPromiseAny
+ // Step s. Perform
+ // ? Invoke(nextPromise, "then",
+ // « resultCapability.[[Resolve]], onRejected »).
+ nextPromiseObj = ToObject(cx, nextPromise);
+ if (!nextPromiseObj) {
+ return false;
+ }
+
+ bool isBuiltinThen;
+ if (getThen) {
+ // We don't use the Promise lookup cache here, because this code
+ // is only called when we had a lookup cache miss, so it's likely
+ // we'd get another cache miss when trying to use the cache here.
+ if (!GetProperty(cx, nextPromiseObj, nextPromise, cx->names().then,
+ &thenVal)) {
+ return false;
+ }
+
+ // |nextPromise| is an unwrapped Promise, and |then| is the
+ // original |Promise.prototype.then|, inline it here.
+ isBuiltinThen = nextPromiseObj->is<PromiseObject>() &&
+ IsNativeFunction(thenVal, Promise_then);
+ } else {
+ isBuiltinThen = true;
+ }
+
+ // By default, the blocked promise is added as an extra entry to the
+ // rejected promises list.
+ bool addToDependent = true;
+
+ if (isBuiltinThen) {
+ MOZ_ASSERT(nextPromise.isObject());
+ MOZ_ASSERT(&nextPromise.toObject() == nextPromiseObj);
+
+ // Promise.prototype.then
+ // Step 3. Let C be ? SpeciesConstructor(promise, %Promise%).
+ RootedObject& thenSpecies = thenSpeciesOrBlockedPromise;
+ if (getThen) {
+ thenSpecies = SpeciesConstructor(cx, nextPromiseObj, JSProto_Promise,
+ IsPromiseSpecies);
+ if (!thenSpecies) {
+ return false;
+ }
+ } else {
+ thenSpecies = promiseCtor;
+ }
+
+ // The fast path here and the one in NewPromiseCapability may not
+ // set the resolve and reject handlers, so we need to clear the
+ // fields in case they were set in the previous iteration.
+ thenCapability.resolve().set(nullptr);
+ thenCapability.reject().set(nullptr);
+
+ // Skip the creation of a built-in Promise object if:
+ // 1. `thenSpecies` is the built-in Promise constructor.
+ // 2. `resolveFun` doesn't return an object, which ensures no side effects
+ // occur in ResolvePromiseInternal.
+ // 3. The result promise is a built-in Promise object.
+ // 4. The result promise doesn't use the default resolving functions,
+ // which in turn means Run{Fulfill,Reject}Function when called from
+ // PromiseReactionJob won't try to resolve the promise.
+ if (thenSpecies == promiseCtor && resolveReturnsUndefined &&
+ resultPromise->is<PromiseObject>() &&
+ !IsPromiseWithDefaultResolvingFunction(
+ &resultPromise->as<PromiseObject>())) {
+ thenCapability.promise().set(resultPromise);
+ addToDependent = false;
+ } else {
+ // Promise.prototype.then
+ // Step 4. Let resultCapability be ? NewPromiseCapability(C).
+ if (!NewPromiseCapability(cx, thenSpecies, &thenCapability, true)) {
+ return false;
+ }
+ }
+
+ // Promise.prototype.then
+ // Step 5. Return
+ // PerformPromiseThen(promise, onFulfilled, onRejected,
+ // resultCapability).
+ Handle<PromiseObject*> promise = nextPromiseObj.as<PromiseObject>();
+ if (!PerformPromiseThen(cx, promise, resolveFunVal, rejectFunVal,
+ thenCapability)) {
+ return false;
+ }
+ } else {
+ // Optimization failed, do the normal call.
+ RootedValue& ignored = thenVal;
+ if (!Call(cx, thenVal, nextPromise, resolveFunVal, rejectFunVal,
+ &ignored)) {
+ return false;
+ }
+
+ // In case the value to depend on isn't an object at all, there's
+ // nothing more to do here: we can only add reactions to Promise
+ // objects (potentially after unwrapping them), and non-object
+ // values can't be Promise objects. This can happen if Promise.all
+ // is called on an object with a `resolve` method that returns
+ // primitives.
+ if (!nextPromise.isObject()) {
+ addToDependent = false;
+ }
+ }
+
+ // Adds |resultPromise| to the list of dependent promises.
+ if (addToDependent) {
+ // The object created by the |promise.then| call or the inlined
+ // version of it above is visible to content (either because
+ // |promise.then| was overridden by content and could leak it,
+ // or because a constructor other than the original value of
+ // |Promise| was used to create it). To have both that object and
+ // |resultPromise| show up as dependent promises in the debugger,
+ // add a dummy reaction to the list of reject reactions that
+ // contains |resultPromise|, but otherwise does nothing.
+ RootedObject& blockedPromise = thenSpeciesOrBlockedPromise;
+ blockedPromise = resultPromise;
+
+ mozilla::Maybe<AutoRealm> ar;
+ if (IsProxy(nextPromiseObj)) {
+ nextPromiseObj = CheckedUnwrapStatic(nextPromiseObj);
+ if (!nextPromiseObj) {
+ ReportAccessDenied(cx);
+ return false;
+ }
+ if (JS_IsDeadWrapper(nextPromiseObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ ar.emplace(cx, nextPromiseObj);
+ if (!cx->compartment()->wrap(cx, &blockedPromise)) {
+ return false;
+ }
+ }
+
+ // If either the object to depend on (`nextPromiseObj`) or the
+ // object that gets blocked (`resultPromise`) isn't a,
+ // maybe-wrapped, Promise instance, we ignore it. All this does is
+ // lose some small amount of debug information in scenarios that
+ // are highly unlikely to occur in useful code.
+ if (nextPromiseObj->is<PromiseObject>() &&
+ resultPromise->is<PromiseObject>()) {
+ Handle<PromiseObject*> promise = nextPromiseObj.as<PromiseObject>();
+ if (!AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise)) {
+ return false;
+ }
+ }
+ }
+ }
+}
+
+// Create the elements for the Promise combinators Promise.all and
+// Promise.allSettled.
+[[nodiscard]] static bool NewPromiseCombinatorElements(
+ JSContext* cx, Handle<PromiseCapability> resultCapability,
+ MutableHandle<PromiseCombinatorElements> elements) {
+ // We have to be very careful about which compartments we create things for
+ // the Promise combinators. In particular, we have to maintain the invariant
+ // that anything stored in a reserved slot is same-compartment with the object
+ // whose reserved slot it's in. But we want to create the values array in the
+ // compartment of the result capability's Promise, because that array can get
+ // exposed as the Promise's resolution value to code that has access to the
+ // Promise (in particular code from that compartment), and that should work,
+ // even if the Promise compartment is less-privileged than our caller
+ // compartment.
+ //
+ // So the plan is as follows: Create the values array in the promise
+ // compartment. Create the promise resolving functions and the data holder in
+ // our current compartment, i.e. the compartment of the Promise combinator
+ // function. Store a cross-compartment wrapper to the values array in the
+ // holder. This should be OK because the only things we hand the promise
+ // resolving functions to are the "then" calls we do and in the case when the
+ // Promise's compartment is not the current compartment those are happening
+ // over Xrays anyway, which means they get the canonical "then" function and
+ // content can't see our promise resolving functions.
+
+ if (IsWrapper(resultCapability.promise())) {
+ JSObject* unwrappedPromiseObj =
+ CheckedUnwrapStatic(resultCapability.promise());
+ MOZ_ASSERT(unwrappedPromiseObj);
+
+ {
+ AutoRealm ar(cx, unwrappedPromiseObj);
+ auto* array = NewDenseEmptyArray(cx);
+ if (!array) {
+ return false;
+ }
+ elements.initialize(array);
+ }
+
+ if (!cx->compartment()->wrap(cx, elements.value())) {
+ return false;
+ }
+ } else {
+ auto* array = NewDenseEmptyArray(cx);
+ if (!array) {
+ return false;
+ }
+
+ elements.initialize(array);
+ }
+ return true;
+}
+
+// Retrieve the combinator elements from the data holder.
+[[nodiscard]] static bool GetPromiseCombinatorElements(
+ JSContext* cx, Handle<PromiseCombinatorDataHolder*> data,
+ MutableHandle<PromiseCombinatorElements> elements) {
+ bool needsWrapping = false;
+ JSObject* valuesObj = &data->valuesArray().toObject();
+ if (IsProxy(valuesObj)) {
+ // See comment for NewPromiseCombinatorElements for why we unwrap here.
+ valuesObj = UncheckedUnwrap(valuesObj);
+
+ if (JS_IsDeadWrapper(valuesObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+
+ needsWrapping = true;
+ }
+
+ elements.initialize(data, &valuesObj->as<ArrayObject>(), needsWrapping);
+ return true;
+}
+
+static JSFunction* NewPromiseCombinatorElementFunction(
+ JSContext* cx, Native native,
+ Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index) {
+ JSFunction* fn = NewNativeFunction(
+ cx, native, 1, nullptr, gc::AllocKind::FUNCTION_EXTENDED, GenericObject);
+ if (!fn) {
+ return nullptr;
+ }
+
+ fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data,
+ ObjectValue(*dataHolder));
+ fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex,
+ Int32Value(index));
+ return fn;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Unified implementation of
+ *
+ * Promise.all Resolve Element Functions
+ * https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions
+ *
+ * Steps 1-4.
+ *
+ * Promise.allSettled Resolve Element Functions
+ * https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions
+ *
+ * Steps 1-5.
+ *
+ * Promise.allSettled Reject Element Functions
+ * https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions
+ *
+ * Steps 1-5.
+ *
+ * Common implementation for Promise combinator element functions to check if
+ * they've already been called.
+ */
+static bool PromiseCombinatorElementFunctionAlreadyCalled(
+ const CallArgs& args, MutableHandle<PromiseCombinatorDataHolder*> data,
+ uint32_t* index) {
+ // Step 1. Let F be the active function object.
+ JSFunction* fn = &args.callee().as<JSFunction>();
+
+ // Promise.all functions
+ // Step 2. If F.[[AlreadyCalled]] is true, return undefined.
+ // Promise.allSettled functions
+ // Step 2. Let alreadyCalled be F.[[AlreadyCalled]].
+ // Step 3. If alreadyCalled.[[Value]] is true, return undefined.
+ //
+ // We use the existence of the data holder as a signal for whether the Promise
+ // combinator element function was already called. Upon resolution, it's reset
+ // to `undefined`.
+ const Value& dataVal =
+ fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_Data);
+ if (dataVal.isUndefined()) {
+ return true;
+ }
+
+ data.set(&dataVal.toObject().as<PromiseCombinatorDataHolder>());
+
+ // Promise.all functions
+ // Step 3. Set F.[[AlreadyCalled]] to true.
+ // Promise.allSettled functions
+ // Step 4. Set alreadyCalled.[[Value]] to true.
+ fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data,
+ UndefinedValue());
+
+ // Promise.all functions
+ // Step 4. Let index be F.[[Index]].
+ // Promise.allSettled functions
+ // Step 5. Let index be F.[[Index]].
+ int32_t idx =
+ fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex)
+ .toInt32();
+ MOZ_ASSERT(idx >= 0);
+ *index = uint32_t(idx);
+
+ return false;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseAll ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseall
+ */
+[[nodiscard]] static bool PerformPromiseAll(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done) {
+ *done = false;
+
+ MOZ_ASSERT(C->isConstructor());
+
+ // Step 1. Let values be a new empty List.
+ Rooted<PromiseCombinatorElements> values(cx);
+ if (!NewPromiseCombinatorElements(cx, resultCapability, &values)) {
+ return false;
+ }
+
+ // Step 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
+ //
+ // Create our data holder that holds all the things shared across
+ // every step of the iterator. In particular, this holds the
+ // remainingElementsCount (as an integer reserved slot), the array of
+ // values, and the resolve function from our PromiseCapability.
+ Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
+ dataHolder = PromiseCombinatorDataHolder::New(
+ cx, resultCapability.promise(), values, resultCapability.resolve());
+ if (!dataHolder) {
+ return false;
+ }
+
+ // Step 3. Let index be 0.
+ uint32_t index = 0;
+
+ auto getResolveAndReject = [cx, &resultCapability, &values, &dataHolder,
+ &index](MutableHandleValue resolveFunVal,
+ MutableHandleValue rejectFunVal) {
+ // Step 4.h. Append undefined to values.
+ if (!values.pushUndefined(cx)) {
+ return false;
+ }
+
+ // Steps 4.j-q.
+ JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(
+ cx, PromiseAllResolveElementFunction, dataHolder, index);
+ if (!resolveFunc) {
+ return false;
+ }
+
+ // Step 4.r. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] + 1.
+ dataHolder->increaseRemainingCount();
+
+ // Step 4.t. Set index to index + 1.
+ index++;
+ MOZ_ASSERT(index > 0);
+
+ resolveFunVal.setObject(*resolveFunc);
+ rejectFunVal.setObject(*resultCapability.reject());
+ return true;
+ };
+
+ // Steps 4.
+ if (!CommonPerformPromiseCombinator(
+ cx, iterator, C, resultCapability.promise(), promiseResolve, done,
+ true, getResolveAndReject)) {
+ return false;
+ }
+
+ // Step 4.d.ii. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] - 1.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Step 4.d.iii. If remainingElementsCount.[[Value]] is 0, then
+ if (remainingCount == 0) {
+ // Step 4.d.iii.1. Let valuesArray be ! CreateArrayFromList(values).
+ // (already performed)
+
+ // Step 4.d.iii.2. Perform
+ // ? Call(resultCapability.[[Resolve]], undefined,
+ // « valuesArray »).
+ return CallPromiseResolveFunction(cx, resultCapability.resolve(),
+ values.value(),
+ resultCapability.promise());
+ }
+
+ // Step 4.d.iv. Return resultCapability.[[Promise]].
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.all Resolve Element Functions
+ * https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions
+ */
+static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue xVal = args.get(0);
+
+ // Steps 1-4.
+ Rooted<PromiseCombinatorDataHolder*> data(cx);
+ uint32_t index;
+ if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 5. Let values be F.[[Values]].
+ Rooted<PromiseCombinatorElements> values(cx);
+ if (!GetPromiseCombinatorElements(cx, data, &values)) {
+ return false;
+ }
+
+ // Step 8. Set values[index] to x.
+ if (!values.setElement(cx, index, xVal)) {
+ return false;
+ }
+
+ // (reordered)
+ // Step 7. Let remainingElementsCount be F.[[RemainingElements]].
+ //
+ // Step 9. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] - 1.
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ // Step 10. If remainingElementsCount.[[Value]] is 0, then
+ if (remainingCount == 0) {
+ // Step 10.a. Let valuesArray be ! CreateArrayFromList(values).
+ // (already performed)
+
+ // (reordered)
+ // Step 6. Let promiseCapability be F.[[Capability]].
+ //
+ // Step 10.b. Return
+ // ? Call(promiseCapability.[[Resolve]], undefined,
+ // « valuesArray »).
+ RootedObject resolveAllFun(cx, data->resolveOrRejectObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+ if (!CallPromiseResolveFunction(cx, resolveAllFun, values.value(),
+ promiseObj)) {
+ return false;
+ }
+ }
+
+ // Step 11. Return undefined.
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.race ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.race
+ */
+static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CommonPromiseCombinator(cx, args, CombinatorKind::Race);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseRace ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiserace
+ */
+[[nodiscard]] static bool PerformPromiseRace(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done) {
+ *done = false;
+
+ MOZ_ASSERT(C->isConstructor());
+
+ // BlockOnPromise fast path requires the passed onFulfilled function
+ // doesn't return an object value, because otherwise the skipped promise
+ // creation is detectable due to missing property lookups.
+ bool isDefaultResolveFn =
+ IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction);
+
+ auto getResolveAndReject = [&resultCapability](
+ MutableHandleValue resolveFunVal,
+ MutableHandleValue rejectFunVal) {
+ resolveFunVal.setObject(*resultCapability.resolve());
+ rejectFunVal.setObject(*resultCapability.reject());
+ return true;
+ };
+
+ // Step 1.
+ return CommonPerformPromiseCombinator(
+ cx, iterator, C, resultCapability.promise(), promiseResolve, done,
+ isDefaultResolveFn, getResolveAndReject);
+}
+
+enum class PromiseAllSettledElementFunctionKind { Resolve, Reject };
+
+template <PromiseAllSettledElementFunctionKind Kind>
+static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
+ Value* vp);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.allSettled ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.allsettled
+ */
+static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CommonPromiseCombinator(cx, args, CombinatorKind::AllSettled);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseallsettled
+ */
+[[nodiscard]] static bool PerformPromiseAllSettled(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done) {
+ *done = false;
+
+ MOZ_ASSERT(C->isConstructor());
+
+ // Step 1. Let values be a new empty List.
+ Rooted<PromiseCombinatorElements> values(cx);
+ if (!NewPromiseCombinatorElements(cx, resultCapability, &values)) {
+ return false;
+ }
+
+ // Step 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
+ //
+ // Create our data holder that holds all the things shared across every step
+ // of the iterator. In particular, this holds the remainingElementsCount
+ // (as an integer reserved slot), the array of values, and the resolve
+ // function from our PromiseCapability.
+ Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
+ dataHolder = PromiseCombinatorDataHolder::New(
+ cx, resultCapability.promise(), values, resultCapability.resolve());
+ if (!dataHolder) {
+ return false;
+ }
+
+ // Step 3. Let index be 0.
+ uint32_t index = 0;
+
+ auto getResolveAndReject = [cx, &values, &dataHolder, &index](
+ MutableHandleValue resolveFunVal,
+ MutableHandleValue rejectFunVal) {
+ // Step 4.h. Append undefined to values.
+ if (!values.pushUndefined(cx)) {
+ return false;
+ }
+
+ auto PromiseAllSettledResolveElementFunction =
+ PromiseAllSettledElementFunction<
+ PromiseAllSettledElementFunctionKind::Resolve>;
+ auto PromiseAllSettledRejectElementFunction =
+ PromiseAllSettledElementFunction<
+ PromiseAllSettledElementFunctionKind::Reject>;
+
+ // Steps 4.j-r.
+ JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(
+ cx, PromiseAllSettledResolveElementFunction, dataHolder, index);
+ if (!resolveFunc) {
+ return false;
+ }
+ resolveFunVal.setObject(*resolveFunc);
+
+ // Steps 4.s-z.
+ JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
+ cx, PromiseAllSettledRejectElementFunction, dataHolder, index);
+ if (!rejectFunc) {
+ return false;
+ }
+ rejectFunVal.setObject(*rejectFunc);
+
+ // Step 4.aa. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] + 1.
+ dataHolder->increaseRemainingCount();
+
+ // Step 4.ac. Set index to index + 1.
+ index++;
+ MOZ_ASSERT(index > 0);
+
+ return true;
+ };
+
+ // Steps 4.
+ if (!CommonPerformPromiseCombinator(
+ cx, iterator, C, resultCapability.promise(), promiseResolve, done,
+ true, getResolveAndReject)) {
+ return false;
+ }
+
+ // Step 4.d.ii. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] - 1.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Step 4.d.iii. If remainingElementsCount.[[Value]] is 0, then
+ if (remainingCount == 0) {
+ // Step 4.d.iii.1. Let valuesArray be ! CreateArrayFromList(values).
+ // (already performed)
+
+ // Step 4.d.iii.2. Perform
+ // ? Call(resultCapability.[[Resolve]], undefined,
+ // « valuesArray »).
+ return CallPromiseResolveFunction(cx, resultCapability.resolve(),
+ values.value(),
+ resultCapability.promise());
+ }
+
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Unified implementation of
+ *
+ * Promise.allSettled Resolve Element Functions
+ * https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions
+ * Promise.allSettled Reject Element Functions
+ * https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions
+ */
+template <PromiseAllSettledElementFunctionKind Kind>
+static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue valueOrReason = args.get(0);
+
+ // Steps 1-5.
+ Rooted<PromiseCombinatorDataHolder*> data(cx);
+ uint32_t index;
+ if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 6. Let values be F.[[Values]].
+ Rooted<PromiseCombinatorElements> values(cx);
+ if (!GetPromiseCombinatorElements(cx, data, &values)) {
+ return false;
+ }
+
+ // Step 2. Let alreadyCalled be F.[[AlreadyCalled]].
+ // Step 3. If alreadyCalled.[[Value]] is true, return undefined.
+ //
+ // The already-called check above only handles the case when |this| function
+ // is called repeatedly, so we still need to check if the other pair of this
+ // resolving function was already called:
+ // We use the element value as a signal for whether the Promise was already
+ // fulfilled. Upon resolution, it's set to the result object created below.
+ if (!values.unwrappedArray()->getDenseElement(index).isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 9. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
+ Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+
+ // Promise.allSettled Resolve Element Functions
+ // Step 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
+ // Promise.allSettled Reject Element Functions
+ // Step 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
+ RootedId id(cx, NameToId(cx->names().status));
+ RootedValue statusValue(cx);
+ if (Kind == PromiseAllSettledElementFunctionKind::Resolve) {
+ statusValue.setString(cx->names().fulfilled);
+ } else {
+ statusValue.setString(cx->names().rejected);
+ }
+ if (!NativeDefineDataProperty(cx, obj, id, statusValue, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // Promise.allSettled Resolve Element Functions
+ // Step 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x).
+ // Promise.allSettled Reject Element Functions
+ // Step 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x).
+ if (Kind == PromiseAllSettledElementFunctionKind::Resolve) {
+ id = NameToId(cx->names().value);
+ } else {
+ id = NameToId(cx->names().reason);
+ }
+ if (!NativeDefineDataProperty(cx, obj, id, valueOrReason, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // Step 12. Set values[index] to obj.
+ RootedValue objVal(cx, ObjectValue(*obj));
+ if (!values.setElement(cx, index, objVal)) {
+ return false;
+ }
+
+ // (reordered)
+ // Step 8. Let remainingElementsCount be F.[[RemainingElements]].
+ //
+ // Step 13. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] - 1.
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ // Step 14. If remainingElementsCount.[[Value]] is 0, then
+ if (remainingCount == 0) {
+ // Step 14.a. Let valuesArray be ! CreateArrayFromList(values).
+ // (already performed)
+
+ // (reordered)
+ // Step 7. Let promiseCapability be F.[[Capability]].
+ //
+ // Step 14.b. Return
+ // ? Call(promiseCapability.[[Resolve]], undefined,
+ // « valuesArray »).
+ RootedObject resolveAllFun(cx, data->resolveOrRejectObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+ if (!CallPromiseResolveFunction(cx, resolveAllFun, values.value(),
+ promiseObj)) {
+ return false;
+ }
+ }
+
+ // Step 15. Return undefined.
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.any ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.any
+ */
+static bool Promise_static_any(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CommonPromiseCombinator(cx, args, CombinatorKind::Any);
+}
+
+static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
+ Value* vp);
+
+static void ThrowAggregateError(JSContext* cx,
+ Handle<PromiseCombinatorElements> errors,
+ HandleObject promise);
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.any ( iterable )
+ * https://tc39.es/ecma262/#sec-promise.any
+ * PerformPromiseAny ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseany
+ */
+[[nodiscard]] static bool PerformPromiseAny(
+ JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve,
+ bool* done) {
+ *done = false;
+
+ // Step 1. Let C be the this value.
+ MOZ_ASSERT(C->isConstructor());
+
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ // (omitted).
+
+ // Step 3. Let promiseResolve be GetPromiseResolve(C).
+ Rooted<PromiseCombinatorElements> errors(cx);
+ if (!NewPromiseCombinatorElements(cx, resultCapability, &errors)) {
+ return false;
+ }
+
+ // Step 4.
+ // Create our data holder that holds all the things shared across every step
+ // of the iterator. In particular, this holds the remainingElementsCount (as
+ // an integer reserved slot), the array of errors, and the reject function
+ // from our PromiseCapability.
+ Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
+ dataHolder = PromiseCombinatorDataHolder::New(
+ cx, resultCapability.promise(), errors, resultCapability.reject());
+ if (!dataHolder) {
+ return false;
+ }
+
+ // PerformPromiseAny
+ // Step 3. Let index be 0.
+ uint32_t index = 0;
+
+ auto getResolveAndReject = [cx, &resultCapability, &errors, &dataHolder,
+ &index](MutableHandleValue resolveFunVal,
+ MutableHandleValue rejectFunVal) {
+ // Step 4.h. Append undefined to errors.
+ if (!errors.pushUndefined(cx)) {
+ return false;
+ }
+
+ // Steps 4.j-q.
+ JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
+ cx, PromiseAnyRejectElementFunction, dataHolder, index);
+ if (!rejectFunc) {
+ return false;
+ }
+
+ // Step 4.r. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] + 1.
+ dataHolder->increaseRemainingCount();
+
+ // Step 4.t. Set index to index + 1.
+ index++;
+ MOZ_ASSERT(index > 0);
+
+ resolveFunVal.setObject(*resultCapability.resolve());
+ rejectFunVal.setObject(*rejectFunc);
+ return true;
+ };
+
+ // BlockOnPromise fast path requires the passed onFulfilled function doesn't
+ // return an object value, because otherwise the skipped promise creation is
+ // detectable due to missing property lookups.
+ bool isDefaultResolveFn =
+ IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction);
+
+ // Steps 4.
+ if (!CommonPerformPromiseCombinator(
+ cx, iterator, C, resultCapability.promise(), promiseResolve, done,
+ isDefaultResolveFn, getResolveAndReject)) {
+ return false;
+ }
+
+ // Step 4.d.ii. Set remainingElementsCount.[[Value]] to
+ // remainingElementsCount.[[Value]] - 1.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Step 4.d.iii. If remainingElementsCount.[[Value]] is 0, then
+ if (remainingCount == 0) {
+ ThrowAggregateError(cx, errors, resultCapability.promise());
+ return false;
+ }
+
+ // Step 4.d.iv. Return resultCapability.[[Promise]].
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.any Reject Element Functions
+ * https://tc39.es/ecma262/#sec-promise.any-reject-element-functions
+ */
+static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue xVal = args.get(0);
+
+ // Steps 1-5.
+ Rooted<PromiseCombinatorDataHolder*> data(cx);
+ uint32_t index;
+ if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 6.
+ Rooted<PromiseCombinatorElements> errors(cx);
+ if (!GetPromiseCombinatorElements(cx, data, &errors)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!errors.setElement(cx, index, xVal)) {
+ return false;
+ }
+
+ // Steps 8, 10.
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ // Step 11.
+ if (remainingCount == 0) {
+ // Step 7 (Adapted to work with PromiseCombinatorDataHolder's layout).
+ RootedObject rejectFun(cx, data->resolveOrRejectObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+
+ ThrowAggregateError(cx, errors, promiseObj);
+
+ RootedValue reason(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!MaybeGetAndClearExceptionAndStack(cx, &reason, &stack)) {
+ return false;
+ }
+
+ if (!CallPromiseRejectFunction(cx, rejectFun, reason, promiseObj, stack,
+ UnhandledRejectionBehavior::Report)) {
+ return false;
+ }
+ }
+
+ // Step 12.
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseAny ( iteratorRecord, constructor, resultCapability,
+ * promiseResolve )
+ * https://tc39.es/ecma262/#sec-performpromiseany
+ *
+ * Steps 4.d.iii.1-3
+ */
+static void ThrowAggregateError(JSContext* cx,
+ Handle<PromiseCombinatorElements> errors,
+ HandleObject promise) {
+ MOZ_ASSERT(!cx->isExceptionPending());
+
+ // Create the AggregateError in the same realm as the array object.
+ AutoRealm ar(cx, errors.unwrappedArray());
+
+ RootedObject allocationSite(cx);
+ mozilla::Maybe<JS::AutoSetAsyncStackForNewCalls> asyncStack;
+
+ // Provide a more useful error stack if possible: This function is typically
+ // called from Promise job queue, which doesn't have any JS frames on the
+ // stack. So when we create the AggregateError below, its stack property will
+ // be set to the empty string, which makes it harder to debug the error cause.
+ // To avoid this situation set-up an async stack based on the Promise
+ // allocation site, which should point to calling site of |Promise.any|.
+ if (promise->is<PromiseObject>()) {
+ allocationSite = promise->as<PromiseObject>().allocationSite();
+ if (allocationSite) {
+ asyncStack.emplace(
+ cx, allocationSite, "Promise.any",
+ JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
+ }
+ }
+
+ // Step 4.d.iii.1. Let error be a newly created AggregateError object.
+ //
+ // AutoSetAsyncStackForNewCalls requires a new activation before it takes
+ // effect, so call into the self-hosting helper to set-up new call frames.
+ RootedValue error(cx);
+ if (!GetAggregateError(cx, JSMSG_PROMISE_ANY_REJECTION, &error)) {
+ return;
+ }
+
+ // Step 4.d.iii.2. Perform ! DefinePropertyOrThrow(
+ // error, "errors", PropertyDescriptor {
+ // [[Configurable]]: true, [[Enumerable]]: false,
+ // [[Writable]]: true,
+ // [[Value]]: ! CreateArrayFromList(errors) }).
+ //
+ // |error| isn't guaranteed to be an AggregateError in case of OOM or stack
+ // overflow.
+ Rooted<SavedFrame*> stack(cx);
+ if (error.isObject() && error.toObject().is<ErrorObject>()) {
+ Rooted<ErrorObject*> errorObj(cx, &error.toObject().as<ErrorObject>());
+ if (errorObj->type() == JSEXN_AGGREGATEERR) {
+ RootedValue errorsVal(cx, JS::ObjectValue(*errors.unwrappedArray()));
+ if (!NativeDefineDataProperty(cx, errorObj, cx->names().errors, errorsVal,
+ 0)) {
+ return;
+ }
+
+ // Adopt the existing saved frames when present.
+ if (JSObject* errorStack = errorObj->stack()) {
+ stack = &errorStack->as<SavedFrame>();
+ }
+ }
+ }
+
+ // Step 4.d.iii.3. Return ThrowCompletion(error).
+ cx->setPendingException(error, stack);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Unified implementation of
+ *
+ * Promise.reject ( r )
+ * https://tc39.es/ecma262/#sec-promise.reject
+ * NewPromiseCapability ( C )
+ * https://tc39.es/ecma262/#sec-newpromisecapability
+ * Promise.resolve ( x )
+ * https://tc39.es/ecma262/#sec-promise.resolve
+ * PromiseResolve ( C, x )
+ * https://tc39.es/ecma262/#sec-promise-resolve
+ */
+[[nodiscard]] static JSObject* CommonStaticResolveRejectImpl(
+ JSContext* cx, HandleValue thisVal, HandleValue argVal,
+ ResolutionMode mode) {
+ // Promise.reject
+ // Step 1. Let C be the this value.
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ //
+ // Promise.reject => NewPromiseCapability
+ // Step 1. If IsConstructor(C) is false, throw a TypeError exception.
+ //
+ // Promise.resolve
+ // Step 1. Let C be the this value.
+ // Step 2. If Type(C) is not Object, throw a TypeError exception.
+ if (!thisVal.isObject()) {
+ const char* msg = mode == ResolveMode ? "Receiver of Promise.resolve call"
+ : "Receiver of Promise.reject call";
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_OBJECT_REQUIRED, msg);
+ return nullptr;
+ }
+ RootedObject C(cx, &thisVal.toObject());
+
+ // Promise.resolve
+ // Step 3. Return ? PromiseResolve(C, x).
+ //
+ // PromiseResolve
+ // Step 1. Assert: Type(C) is Object.
+ // (implicit)
+ if (mode == ResolveMode && argVal.isObject()) {
+ RootedObject xObj(cx, &argVal.toObject());
+ bool isPromise = false;
+ if (xObj->is<PromiseObject>()) {
+ isPromise = true;
+ } else if (IsWrapper(xObj)) {
+ // Treat instances of Promise from other compartments as Promises
+ // here, too.
+ // It's important to do the GetProperty for the `constructor`
+ // below through the wrapper, because wrappers can change the
+ // outcome, so instead of unwrapping and then performing the
+ // GetProperty, just check here and then operate on the original
+ // object again.
+ if (xObj->canUnwrapAs<PromiseObject>()) {
+ isPromise = true;
+ }
+ }
+
+ // PromiseResolve
+ // Step 2. If IsPromise(x) is true, then
+ if (isPromise) {
+ // Step 2.a. Let xConstructor be ? Get(x, "constructor").
+ RootedValue ctorVal(cx);
+ if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal)) {
+ return nullptr;
+ }
+
+ // Step 2.b. If SameValue(xConstructor, C) is true, return x.
+ if (ctorVal == thisVal) {
+ return xObj;
+ }
+ }
+ }
+
+ // Promise.reject
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ // PromiseResolve
+ // Step 3. Let promiseCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseCapability> capability(cx);
+ if (!NewPromiseCapability(cx, C, &capability, true)) {
+ return nullptr;
+ }
+
+ HandleObject promise = capability.promise();
+ if (mode == ResolveMode) {
+ // PromiseResolve
+ // Step 4. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
+ if (!CallPromiseResolveFunction(cx, capability.resolve(), argVal,
+ promise)) {
+ return nullptr;
+ }
+ } else {
+ // Promise.reject
+ // Step 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
+ if (!CallPromiseRejectFunction(cx, capability.reject(), argVal, promise,
+ nullptr,
+ UnhandledRejectionBehavior::Report)) {
+ return nullptr;
+ }
+ }
+
+ // Promise.reject
+ // Step 4. Return promiseCapability.[[Promise]].
+ // PromiseResolve
+ // Step 5. Return promiseCapability.[[Promise]].
+ return promise;
+}
+
+[[nodiscard]] JSObject* js::PromiseResolve(JSContext* cx,
+ HandleObject constructor,
+ HandleValue value) {
+ RootedValue C(cx, ObjectValue(*constructor));
+ return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.reject ( r )
+ * https://tc39.es/ecma262/#sec-promise.reject
+ */
+static bool Promise_reject(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue thisVal = args.thisv();
+ HandleValue argVal = args.get(0);
+ JSObject* result =
+ CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode);
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.reject ( r )
+ * https://tc39.es/ecma262/#sec-promise.reject
+ *
+ * Unforgeable version.
+ */
+/* static */
+PromiseObject* PromiseObject::unforgeableReject(JSContext* cx,
+ HandleValue value) {
+ cx->check(value);
+
+ // Step 1. Let C be the this value.
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseObject*> promise(
+ cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+ MOZ_ASSERT(IsPromiseWithDefaultResolvingFunction(promise));
+
+ // Step 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
+ if (!RejectPromiseInternal(cx, promise, value)) {
+ return nullptr;
+ }
+
+ // Step 4. Return promiseCapability.[[Promise]].
+ return promise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.resolve ( x )
+ * https://tc39.es/ecma262/#sec-promise.resolve
+ */
+bool js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue thisVal = args.thisv();
+ HandleValue argVal = args.get(0);
+ JSObject* result =
+ CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode);
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.resolve ( x )
+ * https://tc39.es/ecma262/#sec-promise.resolve
+ *
+ * Unforgeable version.
+ */
+/* static */
+JSObject* PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value) {
+ JSObject* promiseCtor = JS::GetPromiseConstructor(cx);
+ if (!promiseCtor) {
+ return nullptr;
+ }
+ RootedValue cVal(cx, ObjectValue(*promiseCtor));
+ return CommonStaticResolveRejectImpl(cx, cVal, value, ResolveMode);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.resolve ( x )
+ * https://tc39.es/ecma262/#sec-promise.resolve
+ * PromiseResolve ( C, x )
+ * https://tc39.es/ecma262/#sec-promise-resolve
+ *
+ * Unforgeable version, where `x` is guaranteed not to be a promise.
+ */
+/* static */
+PromiseObject* PromiseObject::unforgeableResolveWithNonPromise(
+ JSContext* cx, HandleValue value) {
+ cx->check(value);
+
+#ifdef DEBUG
+ auto IsPromise = [](HandleValue value) {
+ if (!value.isObject()) {
+ return false;
+ }
+
+ JSObject* obj = &value.toObject();
+ if (obj->is<PromiseObject>()) {
+ return true;
+ }
+
+ if (!IsWrapper(obj)) {
+ return false;
+ }
+
+ return obj->canUnwrapAs<PromiseObject>();
+ };
+ MOZ_ASSERT(!IsPromise(value), "must use unforgeableResolve with this value");
+#endif
+
+ // Promise.resolve
+ // Step 3. Return ? PromiseResolve(C, x).
+
+ // PromiseResolve
+ // Step 2. Let promiseCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseObject*> promise(
+ cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+ MOZ_ASSERT(IsPromiseWithDefaultResolvingFunction(promise));
+
+ // PromiseResolve
+ // Step 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
+ if (!ResolvePromiseInternal(cx, promise, value)) {
+ return nullptr;
+ }
+
+ // PromiseResolve
+ // Step 4. Return promiseCapability.[[Promise]].
+ return promise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * get Promise [ @@species ]
+ * https://tc39.es/ecma262/#sec-get-promise-@@species
+ */
+bool js::Promise_static_species(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. Return the this value.
+ args.rval().set(args.thisv());
+ return true;
+}
+
+enum class IncumbentGlobalObject {
+ // Do not use the incumbent global, this is a special case used by the
+ // debugger.
+ No,
+
+ // Use incumbent global, this is the normal operation.
+ Yes
+};
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseThen ( promise, onFulfilled, onRejected
+ * [ , resultCapability ] )
+ * https://tc39.es/ecma262/#sec-performpromisethen
+ *
+ * Steps 7-8 for creating PromiseReaction record.
+ * We use single object for both fulfillReaction and rejectReaction.
+ */
+static PromiseReactionRecord* NewReactionRecord(
+ JSContext* cx, Handle<PromiseCapability> resultCapability,
+ HandleValue onFulfilled, HandleValue onRejected,
+ IncumbentGlobalObject incumbentGlobalObjectOption) {
+#ifdef DEBUG
+ if (resultCapability.promise()) {
+ if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) {
+ if (resultCapability.promise()->is<PromiseObject>()) {
+ // If `resultCapability.promise` is a Promise object,
+ // `resultCapability.{resolve,reject}` may be optimized out,
+ // but if they're not, they should be callable.
+ MOZ_ASSERT_IF(resultCapability.resolve(),
+ IsCallable(resultCapability.resolve()));
+ MOZ_ASSERT_IF(resultCapability.reject(),
+ IsCallable(resultCapability.reject()));
+ } else {
+ // If `resultCapability.promise` is a non-Promise object
+ // (including wrapped Promise object),
+ // `resultCapability.{resolve,reject}` should be callable.
+ MOZ_ASSERT(resultCapability.resolve());
+ MOZ_ASSERT(IsCallable(resultCapability.resolve()));
+ MOZ_ASSERT(resultCapability.reject());
+ MOZ_ASSERT(IsCallable(resultCapability.reject()));
+ }
+ } else {
+ // For debugger usage, `resultCapability.promise` should be a
+ // maybe-wrapped Promise object. The other fields are not used.
+ //
+ // This is the only case where we allow `resolve` and `reject` to
+ // be null when the `promise` field is not a PromiseObject.
+ JSObject* unwrappedPromise = UncheckedUnwrap(resultCapability.promise());
+ MOZ_ASSERT(unwrappedPromise->is<PromiseObject>());
+ MOZ_ASSERT(!resultCapability.resolve());
+ MOZ_ASSERT(!resultCapability.reject());
+ }
+ } else {
+ // `resultCapability.promise` is null for the following cases:
+ // * resulting Promise is known to be unused
+ // * Async Function
+ // * Async Generator
+ // In any case, other fields are also not used.
+ MOZ_ASSERT(!resultCapability.resolve());
+ MOZ_ASSERT(!resultCapability.reject());
+ MOZ_ASSERT(incumbentGlobalObjectOption == IncumbentGlobalObject::Yes);
+ }
+#endif
+
+ // Ensure the onFulfilled handler has the expected type.
+ MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull());
+ MOZ_ASSERT_IF(onFulfilled.isObject(), IsCallable(onFulfilled));
+ MOZ_ASSERT_IF(onFulfilled.isInt32(),
+ 0 <= onFulfilled.toInt32() &&
+ onFulfilled.toInt32() < int32_t(PromiseHandler::Limit));
+
+ // Ensure the onRejected handler has the expected type.
+ MOZ_ASSERT(onRejected.isInt32() || onRejected.isObjectOrNull());
+ MOZ_ASSERT_IF(onRejected.isObject(), IsCallable(onRejected));
+ MOZ_ASSERT_IF(onRejected.isInt32(),
+ 0 <= onRejected.toInt32() &&
+ onRejected.toInt32() < int32_t(PromiseHandler::Limit));
+
+ // Handlers must either both be present or both be absent.
+ MOZ_ASSERT(onFulfilled.isNull() == onRejected.isNull());
+
+ RootedObject incumbentGlobalObject(cx);
+ if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) {
+ if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobalObject)) {
+ return nullptr;
+ }
+ }
+
+ PromiseReactionRecord* reaction =
+ NewBuiltinClassInstance<PromiseReactionRecord>(cx);
+ if (!reaction) {
+ return nullptr;
+ }
+
+ cx->check(resultCapability.promise());
+ cx->check(onFulfilled);
+ cx->check(onRejected);
+ cx->check(resultCapability.resolve());
+ cx->check(resultCapability.reject());
+ cx->check(incumbentGlobalObject);
+
+ // Step 7. Let fulfillReaction be the PromiseReaction
+ // { [[Capability]]: resultCapability, [[Type]]: Fulfill,
+ // [[Handler]]: onFulfilledJobCallback }.
+ // Step 8. Let rejectReaction be the PromiseReaction
+ // { [[Capability]]: resultCapability, [[Type]]: Reject,
+ // [[Handler]]: onRejectedJobCallback }.
+
+ // See comments for ReactionRecordSlots for the relation between
+ // spec record fields and PromiseReactionRecord slots.
+ reaction->setFixedSlot(ReactionRecordSlot_Promise,
+ ObjectOrNullValue(resultCapability.promise()));
+ // We set [[Type]] in EnqueuePromiseReactionJob, by calling
+ // setTargetStateAndHandlerArg.
+ reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0));
+ reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled);
+ reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected);
+ reaction->setFixedSlot(ReactionRecordSlot_Resolve,
+ ObjectOrNullValue(resultCapability.resolve()));
+ reaction->setFixedSlot(ReactionRecordSlot_Reject,
+ ObjectOrNullValue(resultCapability.reject()));
+ reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject,
+ ObjectOrNullValue(incumbentGlobalObject));
+
+ return reaction;
+}
+
+static bool IsPromiseSpecies(JSContext* cx, JSFunction* species) {
+ return species->maybeNative() == Promise_static_species;
+}
+
+// Whether to create a promise as the return value of Promise#{then,catch}.
+// If the return value is known to be unused, and if the operation is known
+// to be unobservable, we can skip creating the promise.
+enum class CreateDependentPromise { Always, SkipIfCtorUnobservable };
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ *
+ * Steps 3-4.
+ */
+static bool PromiseThenNewPromiseCapability(
+ JSContext* cx, HandleObject promiseObj,
+ CreateDependentPromise createDependent,
+ MutableHandle<PromiseCapability> resultCapability) {
+ // Step 3. Let C be ? SpeciesConstructor(promise, %Promise%).
+ RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise,
+ IsPromiseSpecies));
+ if (!C) {
+ return false;
+ }
+
+ if (createDependent != CreateDependentPromise::Always &&
+ IsNativeFunction(C, PromiseConstructor)) {
+ return true;
+ }
+
+ // Step 4. Let resultCapability be ? NewPromiseCapability(C).
+ if (!NewPromiseCapability(cx, C, resultCapability, true)) {
+ return false;
+ }
+
+ RootedObject unwrappedPromise(cx, promiseObj);
+ if (IsWrapper(promiseObj)) {
+ unwrappedPromise = UncheckedUnwrap(promiseObj);
+ }
+ RootedObject unwrappedNewPromise(cx, resultCapability.promise());
+ if (IsWrapper(resultCapability.promise())) {
+ unwrappedNewPromise = UncheckedUnwrap(resultCapability.promise());
+ }
+ if (unwrappedPromise->is<PromiseObject>() &&
+ unwrappedNewPromise->is<PromiseObject>()) {
+ unwrappedNewPromise->as<PromiseObject>().copyUserInteractionFlagsFrom(
+ *unwrappedPromise.as<PromiseObject>());
+ }
+
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ *
+ * Steps 3-5.
+ */
+[[nodiscard]] PromiseObject* js::OriginalPromiseThen(JSContext* cx,
+ HandleObject promiseObj,
+ HandleObject onFulfilled,
+ HandleObject onRejected) {
+ cx->check(promiseObj);
+ cx->check(onFulfilled);
+ cx->check(onRejected);
+
+ RootedValue promiseVal(cx, ObjectValue(*promiseObj));
+ Rooted<PromiseObject*> unwrappedPromise(
+ cx,
+ UnwrapAndTypeCheckValue<PromiseObject>(cx, promiseVal, [cx, promiseObj] {
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
+ JSMSG_INCOMPATIBLE_PROTO, "Promise", "then",
+ promiseObj->getClass()->name);
+ }));
+ if (!unwrappedPromise) {
+ return nullptr;
+ }
+
+ // Step 3. Let C be ? SpeciesConstructor(promise, %Promise%).
+ // Step 4. Let resultCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseObject*> newPromise(
+ cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+ if (!newPromise) {
+ return nullptr;
+ }
+ newPromise->copyUserInteractionFlagsFrom(*unwrappedPromise);
+
+ Rooted<PromiseCapability> resultCapability(cx);
+ resultCapability.promise().set(newPromise);
+
+ // Step 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+ // resultCapability).
+ {
+ RootedValue onFulfilledVal(cx, ObjectOrNullValue(onFulfilled));
+ RootedValue onRejectedVal(cx, ObjectOrNullValue(onRejected));
+ if (!PerformPromiseThen(cx, unwrappedPromise, onFulfilledVal, onRejectedVal,
+ resultCapability)) {
+ return nullptr;
+ }
+ }
+
+ return newPromise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ *
+ * Steps 3-5.
+ */
+[[nodiscard]] static bool OriginalPromiseThenWithoutSettleHandlers(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseObject*> promiseToResolve) {
+ cx->check(promise);
+
+ // Step 3. Let C be ? SpeciesConstructor(promise, %Promise%).
+ // Step 4. Let resultCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseCapability> resultCapability(cx);
+ if (!PromiseThenNewPromiseCapability(
+ cx, promise, CreateDependentPromise::SkipIfCtorUnobservable,
+ &resultCapability)) {
+ return false;
+ }
+
+ // Step 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+ // resultCapability).
+ return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve,
+ resultCapability);
+}
+
+[[nodiscard]] static bool PerformPromiseThenWithReaction(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseReactionRecord*> reaction);
+
+[[nodiscard]] bool js::ReactToUnwrappedPromise(
+ JSContext* cx, Handle<PromiseObject*> unwrappedPromise,
+ HandleObject onFulfilled_, HandleObject onRejected_,
+ UnhandledRejectionBehavior behavior) {
+ cx->check(onFulfilled_);
+ cx->check(onRejected_);
+
+ MOZ_ASSERT_IF(onFulfilled_, IsCallable(onFulfilled_));
+ MOZ_ASSERT_IF(onRejected_, IsCallable(onRejected_));
+
+ RootedValue onFulfilled(
+ cx, onFulfilled_ ? ObjectValue(*onFulfilled_)
+ : Int32Value(int32_t(PromiseHandler::Identity)));
+
+ RootedValue onRejected(
+ cx, onRejected_ ? ObjectValue(*onRejected_)
+ : Int32Value(int32_t(PromiseHandler::Thrower)));
+
+ Rooted<PromiseCapability> resultCapability(cx);
+ MOZ_ASSERT(!resultCapability.promise());
+
+ Rooted<PromiseReactionRecord*> reaction(
+ cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected,
+ IncumbentGlobalObject::Yes));
+ if (!reaction) {
+ return false;
+ }
+
+ if (behavior == UnhandledRejectionBehavior::Ignore) {
+ reaction->setShouldIgnoreUnhandledRejection();
+ }
+
+ return PerformPromiseThenWithReaction(cx, unwrappedPromise, reaction);
+}
+
+static bool CanCallOriginalPromiseThenBuiltin(JSContext* cx,
+ HandleValue promise) {
+ return promise.isObject() && promise.toObject().is<PromiseObject>() &&
+ cx->realm()->promiseLookup.isDefaultInstance(
+ cx, &promise.toObject().as<PromiseObject>());
+}
+
+static MOZ_ALWAYS_INLINE bool IsPromiseThenOrCatchRetValImplicitlyUsed(
+ JSContext* cx, PromiseObject* promise) {
+ // Embedding requires the return value of then/catch as
+ // `enqueuePromiseJob` parameter, to propaggate the user-interaction.
+ // We cannot optimize out the return value if the flag is set by embedding.
+ if (promise->requiresUserInteractionHandling()) {
+ return true;
+ }
+
+ // The returned promise of Promise#then and Promise#catch contains
+ // stack info if async stack is enabled. Even if their return value is not
+ // used explicitly in the script, the stack info is observable in devtools
+ // and profilers. We shouldn't apply the optimization not to allocate the
+ // returned Promise object if the it's implicitly used by them.
+ if (!cx->options().asyncStack()) {
+ return false;
+ }
+
+ // If devtools is opened, the current realm will become debuggee.
+ if (cx->realm()->isDebuggee()) {
+ return true;
+ }
+
+ // There are 2 profilers, and they can be independently enabled.
+ if (cx->runtime()->geckoProfiler().enabled()) {
+ return true;
+ }
+ if (JS::IsProfileTimelineRecordingEnabled()) {
+ return true;
+ }
+
+ // The stack is also observable from Error#stack, but we don't care since
+ // it's nonstandard feature.
+ return false;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ *
+ * Steps 3-5.
+ */
+static bool OriginalPromiseThenBuiltin(JSContext* cx, HandleValue promiseVal,
+ HandleValue onFulfilled,
+ HandleValue onRejected,
+ MutableHandleValue rval,
+ bool rvalExplicitlyUsed) {
+ cx->check(promiseVal, onFulfilled, onRejected);
+ MOZ_ASSERT(CanCallOriginalPromiseThenBuiltin(cx, promiseVal));
+
+ Rooted<PromiseObject*> promise(cx,
+ &promiseVal.toObject().as<PromiseObject>());
+
+ bool rvalUsed = rvalExplicitlyUsed ||
+ IsPromiseThenOrCatchRetValImplicitlyUsed(cx, promise);
+
+ // Step 3. Let C be ? SpeciesConstructor(promise, %Promise%).
+ // Step 4. Let resultCapability be ? NewPromiseCapability(C).
+ Rooted<PromiseCapability> resultCapability(cx);
+ if (rvalUsed) {
+ PromiseObject* resultPromise =
+ CreatePromiseObjectWithoutResolutionFunctions(cx);
+ if (!resultPromise) {
+ return false;
+ }
+
+ resultPromise->copyUserInteractionFlagsFrom(
+ promiseVal.toObject().as<PromiseObject>());
+ resultCapability.promise().set(resultPromise);
+ }
+
+ // Step 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+ // resultCapability).
+ if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected,
+ resultCapability)) {
+ return false;
+ }
+
+ if (rvalUsed) {
+ rval.setObject(*resultCapability.promise());
+ } else {
+ rval.setUndefined();
+ }
+ return true;
+}
+
+[[nodiscard]] bool js::RejectPromiseWithPendingError(
+ JSContext* cx, Handle<PromiseObject*> promise) {
+ cx->check(promise);
+
+ if (!cx->isExceptionPending()) {
+ // Reject the promise, but also propagate this uncatchable error.
+ (void)PromiseObject::reject(cx, promise, UndefinedHandleValue);
+ return false;
+ }
+
+ RootedValue exn(cx);
+ if (!GetAndClearException(cx, &exn)) {
+ return false;
+ }
+ return PromiseObject::reject(cx, promise, exn);
+}
+
+// Some async/await functions are implemented here instead of
+// js/src/builtin/AsyncFunction.cpp, to call Promise internal functions.
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Runtime Semantics: EvaluateAsyncFunctionBody
+ * AsyncFunctionBody : FunctionBody
+ * https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncfunctionbody
+ *
+ * Runtime Semantics: EvaluateAsyncConciseBody
+ * AsyncConciseBody : ExpressionBody
+ * https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncconcisebody
+ */
+[[nodiscard]] PromiseObject* js::CreatePromiseObjectForAsync(JSContext* cx) {
+ // Step 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
+ PromiseObject* promise = CreatePromiseObjectWithoutResolutionFunctions(cx);
+ if (!promise) {
+ return nullptr;
+ }
+
+ AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC);
+ return promise;
+}
+
+bool js::IsPromiseForAsyncFunctionOrGenerator(JSObject* promise) {
+ return promise->is<PromiseObject>() &&
+ PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_ASYNC);
+}
+
+[[nodiscard]] PromiseObject* js::CreatePromiseObjectForAsyncGenerator(
+ JSContext* cx) {
+ PromiseObject* promise = CreatePromiseObjectWithoutResolutionFunctions(cx);
+ if (!promise) {
+ return nullptr;
+ }
+
+ AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC);
+ return promise;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * AsyncFunctionStart ( promiseCapability, asyncFunctionBody )
+ * https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
+ *
+ * Steps 4.f-g.
+ */
+[[nodiscard]] bool js::AsyncFunctionThrown(JSContext* cx,
+ Handle<PromiseObject*> resultPromise,
+ HandleValue reason) {
+ if (resultPromise->state() != JS::PromiseState::Pending) {
+ // OOM after resolving promise.
+ // Report a warning and ignore the result.
+ if (!WarnNumberASCII(cx, JSMSG_UNHANDLABLE_PROMISE_REJECTION_WARNING)) {
+ if (cx->isExceptionPending()) {
+ cx->clearPendingException();
+ }
+ }
+ return true;
+ }
+
+ // Step 4.f. Else,
+ // Step 4.f.i. Assert: result.[[Type]] is throw.
+ // Step 4.f.ii. Perform
+ // ! Call(promiseCapability.[[Reject]], undefined,
+ // « result.[[Value]] »).
+ // Step 4.g. Return.
+ return RejectPromiseInternal(cx, resultPromise, reason);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * AsyncFunctionStart ( promiseCapability, asyncFunctionBody )
+ * https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
+ *
+ * Steps 4.e, 4.g.
+ */
+[[nodiscard]] bool js::AsyncFunctionReturned(
+ JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value) {
+ // Step 4.e. Else if result.[[Type]] is return, then
+ // Step 4.e.i. Perform
+ // ! Call(promiseCapability.[[Resolve]], undefined,
+ // « result.[[Value]] »).
+ return ResolvePromiseInternal(cx, resultPromise, value);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Await
+ * https://tc39.github.io/ecma262/#await
+ *
+ * Helper function that performs Await(promise) steps 2-7.
+ * The same steps are also used in a few other places in the spec.
+ */
+template <typename T>
+[[nodiscard]] static bool InternalAwait(JSContext* cx, HandleValue value,
+ HandleObject resultPromise,
+ PromiseHandler onFulfilled,
+ PromiseHandler onRejected,
+ T extraStep) {
+ // Step 2. Let promise be ? PromiseResolve(%Promise%, value).
+ RootedObject promise(cx, PromiseObject::unforgeableResolve(cx, value));
+ if (!promise) {
+ return false;
+ }
+
+ // This downcast is safe because unforgeableResolve either returns `value`
+ // (only if it is already a possibly-wrapped promise) or creates a new
+ // promise using the Promise constructor.
+ Rooted<PromiseObject*> unwrappedPromise(
+ cx, UnwrapAndDowncastObject<PromiseObject>(cx, promise));
+ if (!unwrappedPromise) {
+ return false;
+ }
+
+ // Steps 3-6 for creating onFulfilled/onRejected are done by caller.
+
+ // Step 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
+ RootedValue onFulfilledValue(cx, Int32Value(int32_t(onFulfilled)));
+ RootedValue onRejectedValue(cx, Int32Value(int32_t(onRejected)));
+ Rooted<PromiseCapability> resultCapability(cx);
+ resultCapability.promise().set(resultPromise);
+ Rooted<PromiseReactionRecord*> reaction(
+ cx, NewReactionRecord(cx, resultCapability, onFulfilledValue,
+ onRejectedValue, IncumbentGlobalObject::Yes));
+ if (!reaction) {
+ return false;
+ }
+ extraStep(reaction);
+ return PerformPromiseThenWithReaction(cx, unwrappedPromise, reaction);
+}
+
+[[nodiscard]] bool js::InternalAsyncGeneratorAwait(
+ JSContext* cx, JS::Handle<AsyncGeneratorObject*> generator,
+ JS::Handle<JS::Value> value, PromiseHandler onFulfilled,
+ PromiseHandler onRejected) {
+ auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
+ reaction->setIsAsyncGenerator(generator);
+ };
+ return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Await
+ * https://tc39.es/ecma262/#await
+ */
+[[nodiscard]] JSObject* js::AsyncFunctionAwait(
+ JSContext* cx, Handle<AsyncFunctionGeneratorObject*> genObj,
+ HandleValue value) {
+ auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
+ reaction->setIsAsyncFunction(genObj);
+ };
+ if (!InternalAwait(cx, value, nullptr,
+ PromiseHandler::AsyncFunctionAwaitedFulfilled,
+ PromiseHandler::AsyncFunctionAwaitedRejected, extra)) {
+ return nullptr;
+ }
+ return genObj->promise();
+}
+
+// https://tc39.github.io/ecma262/#sec-%asyncfromsynciteratorprototype%.next
+// 25.1.4.2.1 %AsyncFromSyncIteratorPrototype%.next
+// 25.1.4.2.2 %AsyncFromSyncIteratorPrototype%.return
+// 25.1.4.2.3 %AsyncFromSyncIteratorPrototype%.throw
+bool js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args,
+ CompletionKind completionKind) {
+ // Step 1: Let O be the this value.
+ HandleValue thisVal = args.thisv();
+
+ // Step 2: Let promiseCapability be ! NewPromiseCapability(%Promise%).
+ Rooted<PromiseObject*> resultPromise(
+ cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+ if (!resultPromise) {
+ return false;
+ }
+
+ // Step 3: If Type(O) is not Object, or if O does not have a
+ // [[SyncIteratorRecord]] internal slot, then
+ if (!thisVal.isObject() ||
+ !thisVal.toObject().is<AsyncFromSyncIteratorObject>()) {
+ // NB: See https://github.com/tc39/proposal-async-iteration/issues/105
+ // for why this check shouldn't be necessary as long as we can ensure
+ // the Async-from-Sync iterator can't be accessed directly by user
+ // code.
+
+ // Step 3.a: Let invalidIteratorError be a newly created TypeError object.
+ RootedValue badGeneratorError(cx);
+ if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_ITERATOR, &badGeneratorError)) {
+ return false;
+ }
+
+ // Step 3.b: Perform ! Call(promiseCapability.[[Reject]], undefined,
+ // « invalidIteratorError »).
+ if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) {
+ return false;
+ }
+
+ // Step 3.c: Return promiseCapability.[[Promise]].
+ args.rval().setObject(*resultPromise);
+ return true;
+ }
+
+ Rooted<AsyncFromSyncIteratorObject*> asyncIter(
+ cx, &thisVal.toObject().as<AsyncFromSyncIteratorObject>());
+
+ // Step 4: Let syncIteratorRecord be O.[[SyncIteratorRecord]].
+ RootedObject iter(cx, asyncIter->iterator());
+
+ RootedValue func(cx);
+ if (completionKind == CompletionKind::Normal) {
+ // next() preparing for steps 5-6.
+ func.set(asyncIter->nextMethod());
+ } else if (completionKind == CompletionKind::Return) {
+ // return() steps 5-7.
+ // Step 5: Let return be GetMethod(syncIterator, "return").
+ // Step 6: IfAbruptRejectPromise(return, promiseCapability).
+ if (!GetProperty(cx, iter, iter, cx->names().return_, &func)) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // Step 7: If return is undefined, then
+ // (Note: GetMethod contains a step that changes `null` to `undefined`;
+ // we omit that step above, and check for `null` here instead.)
+ if (func.isNullOrUndefined()) {
+ // Step 7.a: Let iterResult be ! CreateIterResultObject(value, true).
+ PlainObject* resultObj = CreateIterResultObject(cx, args.get(0), true);
+ if (!resultObj) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ RootedValue resultVal(cx, ObjectValue(*resultObj));
+
+ // Step 7.b: Perform ! Call(promiseCapability.[[Resolve]], undefined,
+ // « iterResult »).
+ if (!ResolvePromiseInternal(cx, resultPromise, resultVal)) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // Step 7.c: Return promiseCapability.[[Promise]].
+ args.rval().setObject(*resultPromise);
+ return true;
+ }
+ } else {
+ // noexcept(true) steps 5-7.
+ MOZ_ASSERT(completionKind == CompletionKind::Throw);
+
+ // Step 5: Let throw be GetMethod(syncIterator, "throw").
+ // Step 6: IfAbruptRejectPromise(throw, promiseCapability).
+ if (!GetProperty(cx, iter, iter, cx->names().throw_, &func)) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // Step 7: If throw is undefined, then
+ // (Note: GetMethod contains a step that changes `null` to `undefined`;
+ // we omit that step above, and check for `null` here instead.)
+ if (func.isNullOrUndefined()) {
+ // Step 7.a: Perform ! Call(promiseCapability.[[Reject]], undefined, «
+ // value »).
+ if (!RejectPromiseInternal(cx, resultPromise, args.get(0))) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // Step 7.b: Return promiseCapability.[[Promise]].
+ args.rval().setObject(*resultPromise);
+ return true;
+ }
+ }
+
+ // next() steps 5-6.
+ // Step 5: Let result be IteratorNext(syncIteratorRecord, value).
+ // Step 6: IfAbruptRejectPromise(result, promiseCapability).
+ // return/throw() steps 8-9.
+ // Step 8: Let result be Call(throw, syncIterator, « value »).
+ // Step 9: IfAbruptRejectPromise(result, promiseCapability).
+ //
+ // Including the changes from: https://github.com/tc39/ecma262/pull/1776
+ RootedValue iterVal(cx, ObjectValue(*iter));
+ RootedValue resultVal(cx);
+ bool ok;
+ if (args.length() == 0) {
+ ok = Call(cx, func, iterVal, &resultVal);
+ } else {
+ ok = Call(cx, func, iterVal, args[0], &resultVal);
+ }
+ if (!ok) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // next() step 5 -> IteratorNext Step 3:
+ // If Type(result) is not Object, throw a TypeError exception.
+ // Followed by IfAbruptRejectPromise in step 6.
+ //
+ // return/throw() Step 10: If Type(result) is not Object, then
+ // Step 10.a: Perform ! Call(promiseCapability.[[Reject]], undefined,
+ // « a newly created TypeError object »).
+ // Step 10.b: Return promiseCapability.[[Promise]].
+ if (!resultVal.isObject()) {
+ CheckIsObjectKind kind;
+ switch (completionKind) {
+ case CompletionKind::Normal:
+ kind = CheckIsObjectKind::IteratorNext;
+ break;
+ case CompletionKind::Throw:
+ kind = CheckIsObjectKind::IteratorThrow;
+ break;
+ case CompletionKind::Return:
+ kind = CheckIsObjectKind::IteratorReturn;
+ break;
+ }
+ MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, kind));
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ RootedObject resultObj(cx, &resultVal.toObject());
+
+ // next() Step 7, return/throw() Step 11: Return
+ // ! AsyncFromSyncIteratorContinuation(result, promiseCapability).
+ //
+ // The step numbers below are for
+ // 25.1.4.4 AsyncFromSyncIteratorContinuation ( result, promiseCapability ).
+
+ // Step 1: Let done be IteratorComplete(result).
+ // Step 2: IfAbruptRejectPromise(done, promiseCapability).
+ RootedValue doneVal(cx);
+ if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+ bool done = ToBoolean(doneVal);
+
+ // Step 3: Let value be IteratorValue(result).
+ // Step 4: IfAbruptRejectPromise(value, promiseCapability).
+ RootedValue value(cx);
+ if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // Step numbers below include the changes in
+ // <https://github.com/tc39/ecma262/pull/1470>, which inserted a new step 6.
+ //
+ // Steps 7-9 (reordered).
+ // Step 7: Let steps be the algorithm steps defined in Async-from-Sync
+ // Iterator Value Unwrap Functions.
+ // Step 8: Let onFulfilled be CreateBuiltinFunction(steps, « [[Done]] »).
+ // Step 9: Set onFulfilled.[[Done]] to done.
+ PromiseHandler onFulfilled =
+ done ? PromiseHandler::AsyncFromSyncIteratorValueUnwrapDone
+ : PromiseHandler::AsyncFromSyncIteratorValueUnwrapNotDone;
+ PromiseHandler onRejected = PromiseHandler::Thrower;
+
+ // Steps 5 and 10 are identical to some steps in Await; we have a utility
+ // function InternalAwait() that implements the idiom.
+ //
+ // Step 5: Let valueWrapper be PromiseResolve(%Promise%, « value »).
+ // Step 6: IfAbruptRejectPromise(valueWrapper, promiseCapability).
+ // Step 10: Perform ! PerformPromiseThen(valueWrapper, onFulfilled,
+ // undefined, promiseCapability).
+ auto extra = [](Handle<PromiseReactionRecord*> reaction) {};
+ if (!InternalAwait(cx, value, resultPromise, onFulfilled, onRejected,
+ extra)) {
+ return AbruptRejectPromise(cx, args, resultPromise, nullptr);
+ }
+
+ // Step 11: Return promiseCapability.[[Promise]].
+ args.rval().setObject(*resultPromise);
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.catch ( onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.catch
+ */
+static bool Promise_catch_impl(JSContext* cx, unsigned argc, Value* vp,
+ bool rvalExplicitlyUsed) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. Let promise be the this value.
+ HandleValue thisVal = args.thisv();
+ HandleValue onFulfilled = UndefinedHandleValue;
+ HandleValue onRejected = args.get(0);
+
+ // Fast path when the default Promise state is intact.
+ if (CanCallOriginalPromiseThenBuiltin(cx, thisVal)) {
+ return OriginalPromiseThenBuiltin(cx, thisVal, onFulfilled, onRejected,
+ args.rval(), rvalExplicitlyUsed);
+ }
+
+ // Step 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
+ RootedValue thenVal(cx);
+ if (!GetProperty(cx, thisVal, cx->names().then, &thenVal)) {
+ return false;
+ }
+
+ if (IsNativeFunction(thenVal, &Promise_then) &&
+ thenVal.toObject().nonCCWRealm() == cx->realm()) {
+ return Promise_then_impl(cx, thisVal, onFulfilled, onRejected, args.rval(),
+ rvalExplicitlyUsed);
+ }
+
+ return Call(cx, thenVal, thisVal, UndefinedHandleValue, onRejected,
+ args.rval());
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.catch ( onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.catch
+ */
+static bool Promise_catch_noRetVal(JSContext* cx, unsigned argc, Value* vp) {
+ return Promise_catch_impl(cx, argc, vp, false);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.catch ( onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.catch
+ */
+static bool Promise_catch(JSContext* cx, unsigned argc, Value* vp) {
+ return Promise_catch_impl(cx, argc, vp, true);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ */
+static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal,
+ HandleValue onFulfilled, HandleValue onRejected,
+ MutableHandleValue rval,
+ bool rvalExplicitlyUsed) {
+ // Step 1. Let promise be the this value.
+ // (implicit)
+
+ // Step 2. If IsPromise(promise) is false, throw a TypeError exception.
+ if (!promiseVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_OBJECT_REQUIRED,
+ "Receiver of Promise.prototype.then call");
+ return false;
+ }
+
+ // Fast path when the default Promise state is intact.
+ if (CanCallOriginalPromiseThenBuiltin(cx, promiseVal)) {
+ // Steps 3-5.
+ return OriginalPromiseThenBuiltin(cx, promiseVal, onFulfilled, onRejected,
+ rval, rvalExplicitlyUsed);
+ }
+
+ RootedObject promiseObj(cx, &promiseVal.toObject());
+ Rooted<PromiseObject*> unwrappedPromise(
+ cx,
+ UnwrapAndTypeCheckValue<PromiseObject>(cx, promiseVal, [cx, &promiseVal] {
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
+ JSMSG_INCOMPATIBLE_PROTO, "Promise", "then",
+ InformalValueTypeName(promiseVal));
+ }));
+ if (!unwrappedPromise) {
+ return false;
+ }
+
+ bool rvalUsed =
+ rvalExplicitlyUsed ||
+ IsPromiseThenOrCatchRetValImplicitlyUsed(cx, unwrappedPromise);
+
+ // Step 3. Let C be ? SpeciesConstructor(promise, %Promise%).
+ // Step 4. Let resultCapability be ? NewPromiseCapability(C).
+ CreateDependentPromise createDependent =
+ rvalUsed ? CreateDependentPromise::Always
+ : CreateDependentPromise::SkipIfCtorUnobservable;
+ Rooted<PromiseCapability> resultCapability(cx);
+ if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent,
+ &resultCapability)) {
+ return false;
+ }
+
+ // Step 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+ // resultCapability).
+ if (!PerformPromiseThen(cx, unwrappedPromise, onFulfilled, onRejected,
+ resultCapability)) {
+ return false;
+ }
+
+ if (rvalUsed) {
+ rval.setObject(*resultCapability.promise());
+ } else {
+ rval.setUndefined();
+ }
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ */
+bool Promise_then_noRetVal(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1),
+ args.rval(), false);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * Promise.prototype.then ( onFulfilled, onRejected )
+ * https://tc39.es/ecma262/#sec-promise.prototype.then
+ */
+bool js::Promise_then(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1),
+ args.rval(), true);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseThen ( promise, onFulfilled, onRejected
+ * [ , resultCapability ] )
+ * https://tc39.es/ecma262/#sec-performpromisethen
+ *
+ * Steps 1-12.
+ */
+[[nodiscard]] static bool PerformPromiseThen(
+ JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
+ HandleValue onRejected_, Handle<PromiseCapability> resultCapability) {
+ // Step 1. Assert: IsPromise(promise) is true.
+ // Step 2. If resultCapability is not present, then
+ // Step 2. a. Set resultCapability to undefined.
+ // (implicit)
+
+ // (reordered)
+ // Step 4. Else,
+ // Step 4. a. Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled).
+ RootedValue onFulfilled(cx, onFulfilled_);
+
+ // Step 3. If IsCallable(onFulfilled) is false, then
+ if (!IsCallable(onFulfilled)) {
+ // Step 3. a. Let onFulfilledJobCallback be empty.
+ onFulfilled = Int32Value(int32_t(PromiseHandler::Identity));
+ }
+
+ // (reordered)
+ // Step 6. Else,
+ // Step 6. a. Let onRejectedJobCallback be HostMakeJobCallback(onRejected).
+ RootedValue onRejected(cx, onRejected_);
+
+ // Step 5. If IsCallable(onRejected) is false, then
+ if (!IsCallable(onRejected)) {
+ // Step 5. a. Let onRejectedJobCallback be empty.
+ onRejected = Int32Value(int32_t(PromiseHandler::Thrower));
+ }
+
+ // Step 7. Let fulfillReaction be the PromiseReaction
+ // { [[Capability]]: resultCapability, [[Type]]: Fulfill,
+ // [[Handler]]: onFulfilledJobCallback }.
+ // Step 8. Let rejectReaction be the PromiseReaction
+ // { [[Capability]]: resultCapability, [[Type]]: Reject,
+ // [[Handler]]: onRejectedJobCallback }.
+ //
+ // NOTE: We use single object for both reactions.
+ Rooted<PromiseReactionRecord*> reaction(
+ cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected,
+ IncumbentGlobalObject::Yes));
+ if (!reaction) {
+ return false;
+ }
+
+ // Steps 9-14.
+ return PerformPromiseThenWithReaction(cx, promise, reaction);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseThen ( promise, onFulfilled, onRejected
+ * [ , resultCapability ] )
+ * https://tc39.es/ecma262/#sec-performpromisethen
+ */
+[[nodiscard]] static bool PerformPromiseThenWithoutSettleHandlers(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseObject*> promiseToResolve,
+ Handle<PromiseCapability> resultCapability) {
+ // Step 1. Assert: IsPromise(promise) is true.
+ // Step 2. If resultCapability is not present, then
+ // (implicit)
+
+ // Step 3. If IsCallable(onFulfilled) is false, then
+ // Step 3.a. Let onFulfilledJobCallback be empty.
+ HandleValue onFulfilled = NullHandleValue;
+
+ // Step 5. If IsCallable(onRejected) is false, then
+ // Step 5.a. Let onRejectedJobCallback be empty.
+ HandleValue onRejected = NullHandleValue;
+
+ // Step 7. Let fulfillReaction be the PromiseReaction
+ // { [[Capability]]: resultCapability, [[Type]]: Fulfill,
+ // [[Handler]]: onFulfilledJobCallback }.
+ // Step 8. Let rejectReaction be the PromiseReaction
+ // { [[Capability]]: resultCapability, [[Type]]: Reject,
+ // [[Handler]]: onRejectedJobCallback }.
+ Rooted<PromiseReactionRecord*> reaction(
+ cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected,
+ IncumbentGlobalObject::Yes));
+ if (!reaction) {
+ return false;
+ }
+
+ reaction->setIsDefaultResolvingHandler(promiseToResolve);
+
+ // Steps 9-12.
+ return PerformPromiseThenWithReaction(cx, promise, reaction);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseThen ( promise, onFulfilled, onRejected
+ * [ , resultCapability ] )
+ * https://tc39.github.io/ecma262/#sec-performpromisethen
+ *
+ * Steps 9-12.
+ */
+[[nodiscard]] static bool PerformPromiseThenWithReaction(
+ JSContext* cx, Handle<PromiseObject*> unwrappedPromise,
+ Handle<PromiseReactionRecord*> reaction) {
+ // Step 9. If promise.[[PromiseState]] is pending, then
+ JS::PromiseState state = unwrappedPromise->state();
+ int32_t flags = unwrappedPromise->flags();
+ if (state == JS::PromiseState::Pending) {
+ // Step 9.a. Append fulfillReaction as the last element of the List that is
+ // promise.[[PromiseFulfillReactions]].
+ // Step 9.b. Append rejectReaction as the last element of the List that is
+ // promise.[[PromiseRejectReactions]].
+ //
+ // Instead of creating separate reaction records for fulfillment and
+ // rejection, we create a combined record. All places we use the record
+ // can handle that.
+ if (!AddPromiseReaction(cx, unwrappedPromise, reaction)) {
+ return false;
+ }
+ }
+
+ // Steps 10-11.
+ else {
+ // Step 11.a. Assert: The value of promise.[[PromiseState]] is rejected.
+ MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled,
+ state == JS::PromiseState::Rejected);
+
+ // Step 10.a. Let value be promise.[[PromiseResult]].
+ // Step 11.b. Let reason be promise.[[PromiseResult]].
+ RootedValue valueOrReason(cx, unwrappedPromise->valueOrReason());
+
+ // We might be operating on a promise from another compartment. In that
+ // case, we need to wrap the result/reason value before using it.
+ if (!cx->compartment()->wrap(cx, &valueOrReason)) {
+ return false;
+ }
+
+ // Step 11.c. If promise.[[PromiseIsHandled]] is false,
+ // perform HostPromiseRejectionTracker(promise, "handle").
+ if (state == JS::PromiseState::Rejected &&
+ !(flags & PROMISE_FLAG_HANDLED)) {
+ cx->runtime()->removeUnhandledRejectedPromise(cx, unwrappedPromise);
+ }
+
+ // Step 10.b. Let fulfillJob be
+ // NewPromiseReactionJob(fulfillReaction, value).
+ // Step 10.c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]],
+ // fulfillJob.[[Realm]]).
+ // Step 11.d. Let rejectJob be
+ // NewPromiseReactionJob(rejectReaction, reason).
+ // Step 11.e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]],
+ // rejectJob.[[Realm]]).
+ if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) {
+ return false;
+ }
+ }
+
+ // Step 12. Set promise.[[PromiseIsHandled]] to true.
+ unwrappedPromise->setHandled();
+
+ return true;
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * PerformPromiseThen ( promise, onFulfilled, onRejected
+ * [ , resultCapability ] )
+ * https://tc39.github.io/ecma262/#sec-performpromisethen
+ *
+ * Steps 9.a-b.
+ */
+[[nodiscard]] static bool AddPromiseReaction(
+ JSContext* cx, Handle<PromiseObject*> unwrappedPromise,
+ Handle<PromiseReactionRecord*> reaction) {
+ MOZ_RELEASE_ASSERT(reaction->is<PromiseReactionRecord>());
+ RootedValue reactionVal(cx, ObjectValue(*reaction));
+
+ // The code that creates Promise reactions can handle wrapped Promises,
+ // unwrapping them as needed. That means that the `promise` and `reaction`
+ // objects we have here aren't necessarily from the same compartment. In
+ // order to store the reaction on the promise, we have to ensure that it
+ // is properly wrapped.
+ mozilla::Maybe<AutoRealm> ar;
+ if (unwrappedPromise->compartment() != cx->compartment()) {
+ ar.emplace(cx, unwrappedPromise);
+ if (!cx->compartment()->wrap(cx, &reactionVal)) {
+ return false;
+ }
+ }
+ Handle<PromiseObject*> promise = unwrappedPromise;
+
+ // Step 9.a. Append fulfillReaction as the last element of the List that is
+ // promise.[[PromiseFulfillReactions]].
+ // Step 9.b. Append rejectReaction as the last element of the List that is
+ // promise.[[PromiseRejectReactions]].
+ RootedValue reactionsVal(cx, promise->reactions());
+
+ if (reactionsVal.isUndefined()) {
+ // If no reactions existed so far, just store the reaction record directly.
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal);
+ return true;
+ }
+
+ RootedObject reactionsObj(cx, &reactionsVal.toObject());
+
+ // If only a single reaction exists, it's stored directly instead of in a
+ // list. In that case, `reactionsObj` might be a wrapper, which we can
+ // always safely unwrap.
+ if (IsProxy(reactionsObj)) {
+ reactionsObj = UncheckedUnwrap(reactionsObj);
+ if (JS_IsDeadWrapper(reactionsObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(reactionsObj->is<PromiseReactionRecord>());
+ }
+
+ if (reactionsObj->is<PromiseReactionRecord>()) {
+ // If a single reaction existed so far, create a list and store the
+ // old and the new reaction in it.
+ ArrayObject* reactions = NewDenseFullyAllocatedArray(cx, 2);
+ if (!reactions) {
+ return false;
+ }
+
+ reactions->setDenseInitializedLength(2);
+ reactions->initDenseElement(0, reactionsVal);
+ reactions->initDenseElement(1, reactionVal);
+
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult,
+ ObjectValue(*reactions));
+ } else {
+ // Otherwise, just store the new reaction.
+ MOZ_RELEASE_ASSERT(reactionsObj->is<NativeObject>());
+ Handle<NativeObject*> reactions = reactionsObj.as<NativeObject>();
+ uint32_t len = reactions->getDenseInitializedLength();
+ DenseElementResult result = reactions->ensureDenseElements(cx, len, 1);
+ if (result != DenseElementResult::Success) {
+ MOZ_ASSERT(result == DenseElementResult::Failure);
+ return false;
+ }
+ reactions->setDenseElement(len, reactionVal);
+ }
+
+ return true;
+}
+
+[[nodiscard]] static bool AddDummyPromiseReactionForDebugger(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ HandleObject dependentPromise) {
+ if (promise->state() != JS::PromiseState::Pending) {
+ return true;
+ }
+
+ // `dependentPromise` should be a maybe-wrapped Promise.
+ MOZ_ASSERT(UncheckedUnwrap(dependentPromise)->is<PromiseObject>());
+
+ // Leave resolve and reject as null.
+ Rooted<PromiseCapability> capability(cx);
+ capability.promise().set(dependentPromise);
+
+ Rooted<PromiseReactionRecord*> reaction(
+ cx, NewReactionRecord(cx, capability, NullHandleValue, NullHandleValue,
+ IncumbentGlobalObject::No));
+ if (!reaction) {
+ return false;
+ }
+
+ reaction->setIsDebuggerDummy();
+
+ return AddPromiseReaction(cx, promise, reaction);
+}
+
+uint64_t PromiseObject::getID() { return PromiseDebugInfo::id(this); }
+
+double PromiseObject::lifetime() {
+ return MillisecondsSinceStartup() - allocationTime();
+}
+
+/**
+ * Returns all promises that directly depend on this one. That means those
+ * created by calling `then` on this promise, or the promise returned by
+ * `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise
+ * being a member of the passed-in `iterable`.
+ *
+ * Per spec, we should have separate lists of reaction records for the
+ * fulfill and reject cases. As an optimization, we have only one of those,
+ * containing the required data for both cases. So we just walk that list
+ * and extract the dependent promises from all reaction records.
+ */
+bool PromiseObject::dependentPromises(JSContext* cx,
+ MutableHandle<GCVector<Value>> values) {
+ if (state() != JS::PromiseState::Pending) {
+ return true;
+ }
+
+ uint32_t valuesIndex = 0;
+ RootedValue reactionsVal(cx, reactions());
+
+ return ForEachReaction(cx, reactionsVal, [&](MutableHandleObject obj) {
+ if (IsProxy(obj)) {
+ obj.set(UncheckedUnwrap(obj));
+ }
+
+ if (JS_IsDeadWrapper(obj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(obj->is<PromiseReactionRecord>());
+ Rooted<PromiseReactionRecord*> reaction(cx,
+ &obj->as<PromiseReactionRecord>());
+
+ // Not all reactions have a Promise on them.
+ RootedObject promiseObj(cx, reaction->promise());
+ if (promiseObj) {
+ if (!values.growBy(1)) {
+ return false;
+ }
+
+ values[valuesIndex++].setObject(*promiseObj);
+ }
+ return true;
+ });
+}
+
+bool PromiseObject::forEachReactionRecord(
+ JSContext* cx, PromiseReactionRecordBuilder& builder) {
+ if (state() != JS::PromiseState::Pending) {
+ // Promise was resolved, so no reaction records are present.
+ return true;
+ }
+
+ RootedValue reactionsVal(cx, reactions());
+ if (reactionsVal.isNullOrUndefined()) {
+ // No reaction records are attached to this promise.
+ return true;
+ }
+
+ return ForEachReaction(cx, reactionsVal, [&](MutableHandleObject obj) {
+ if (IsProxy(obj)) {
+ obj.set(UncheckedUnwrap(obj));
+ }
+
+ if (JS_IsDeadWrapper(obj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEAD_OBJECT);
+ return false;
+ }
+
+ Rooted<PromiseReactionRecord*> reaction(cx,
+ &obj->as<PromiseReactionRecord>());
+ MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
+
+ if (reaction->isAsyncFunction()) {
+ Rooted<AsyncFunctionGeneratorObject*> generator(
+ cx, reaction->asyncFunctionGenerator());
+ if (!builder.asyncFunction(cx, generator)) {
+ return false;
+ }
+ } else if (reaction->isAsyncGenerator()) {
+ Rooted<AsyncGeneratorObject*> generator(cx, reaction->asyncGenerator());
+ if (!builder.asyncGenerator(cx, generator)) {
+ return false;
+ }
+ } else if (reaction->isDefaultResolvingHandler()) {
+ Rooted<PromiseObject*> promise(cx, reaction->defaultResolvingPromise());
+ if (!builder.direct(cx, promise)) {
+ return false;
+ }
+ } else {
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+ RootedObject result(cx, reaction->promise());
+
+ Value v = reaction->getFixedSlot(ReactionRecordSlot_OnFulfilled);
+ if (v.isObject()) {
+ resolve = &v.toObject();
+ }
+
+ v = reaction->getFixedSlot(ReactionRecordSlot_OnRejected);
+ if (v.isObject()) {
+ reject = &v.toObject();
+ }
+
+ if (!builder.then(cx, resolve, reject, result)) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+}
+
+/**
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * Promise Reject Functions
+ * https://tc39.es/ecma262/#sec-promise-reject-functions
+ */
+static bool CallDefaultPromiseResolveFunction(JSContext* cx,
+ Handle<PromiseObject*> promise,
+ HandleValue resolutionValue) {
+ MOZ_ASSERT(IsPromiseWithDefaultResolvingFunction(promise));
+
+ // Steps 1-3.
+ // (implicit)
+
+ // Step 4. Let alreadyResolved be F.[[AlreadyResolved]].
+ // Step 5. If alreadyResolved.[[Value]] is true, return undefined.
+ if (IsAlreadyResolvedPromiseWithDefaultResolvingFunction(promise)) {
+ return true;
+ }
+
+ // Step 6. Set alreadyResolved.[[Value]] to true.
+ SetAlreadyResolvedPromiseWithDefaultResolvingFunction(promise);
+
+ // Steps 7-15.
+ // (implicit) Step 16. Return undefined.
+ return ResolvePromiseInternal(cx, promise, resolutionValue);
+}
+
+/* static */
+bool PromiseObject::resolve(JSContext* cx, Handle<PromiseObject*> promise,
+ HandleValue resolutionValue) {
+ MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC));
+ if (promise->state() != JS::PromiseState::Pending) {
+ return true;
+ }
+
+ if (IsPromiseWithDefaultResolvingFunction(promise)) {
+ return CallDefaultPromiseResolveFunction(cx, promise, resolutionValue);
+ }
+
+ JSFunction* resolveFun = GetResolveFunctionFromPromise(promise);
+ if (!resolveFun) {
+ return true;
+ }
+
+ RootedValue funVal(cx, ObjectValue(*resolveFun));
+
+ // For xray'd Promises, the resolve fun may have been created in another
+ // compartment. For the call below to work in that case, wrap the
+ // function into the current compartment.
+ if (!cx->compartment()->wrap(cx, &funVal)) {
+ return false;
+ }
+
+ RootedValue dummy(cx);
+ return Call(cx, funVal, UndefinedHandleValue, resolutionValue, &dummy);
+}
+
+/**
+ * ES2023 draft rev 714fa3dd1e8237ae9c666146270f81880089eca5
+ *
+ * Promise Reject Functions
+ * https://tc39.es/ecma262/#sec-promise-reject-functions
+ */
+static bool CallDefaultPromiseRejectFunction(
+ JSContext* cx, Handle<PromiseObject*> promise, HandleValue rejectionValue,
+ JS::Handle<SavedFrame*> unwrappedRejectionStack /* = nullptr */) {
+ MOZ_ASSERT(IsPromiseWithDefaultResolvingFunction(promise));
+
+ // Steps 1-3.
+ // (implicit)
+
+ // Step 4. Let alreadyResolved be F.[[AlreadyResolved]].
+ // Step 5. If alreadyResolved.[[Value]] is true, return undefined.
+ if (IsAlreadyResolvedPromiseWithDefaultResolvingFunction(promise)) {
+ return true;
+ }
+
+ // Step 6. Set alreadyResolved.[[Value]] to true.
+ SetAlreadyResolvedPromiseWithDefaultResolvingFunction(promise);
+
+ return RejectPromiseInternal(cx, promise, rejectionValue,
+ unwrappedRejectionStack);
+}
+
+/* static */
+bool PromiseObject::reject(JSContext* cx, Handle<PromiseObject*> promise,
+ HandleValue rejectionValue) {
+ MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC));
+ if (promise->state() != JS::PromiseState::Pending) {
+ return true;
+ }
+
+ if (IsPromiseWithDefaultResolvingFunction(promise)) {
+ return CallDefaultPromiseRejectFunction(cx, promise, rejectionValue);
+ }
+
+ RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction));
+ MOZ_ASSERT(IsCallable(funVal));
+
+ RootedValue dummy(cx);
+ return Call(cx, funVal, UndefinedHandleValue, rejectionValue, &dummy);
+}
+
+/**
+ * ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
+ *
+ * RejectPromise ( promise, reason )
+ * https://tc39.es/ecma262/#sec-rejectpromise
+ *
+ * Step 7.
+ */
+/* static */
+void PromiseObject::onSettled(JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<SavedFrame*> unwrappedRejectionStack) {
+ PromiseDebugInfo::setResolutionInfo(cx, promise, unwrappedRejectionStack);
+
+ // Step 7. If promise.[[PromiseIsHandled]] is false, perform
+ // HostPromiseRejectionTracker(promise, "reject").
+ if (promise->state() == JS::PromiseState::Rejected &&
+ promise->isUnhandled()) {
+ cx->runtime()->addUnhandledRejectedPromise(cx, promise);
+ }
+
+ DebugAPI::onPromiseSettled(cx, promise);
+}
+
+void PromiseObject::setRequiresUserInteractionHandling(bool state) {
+ if (state) {
+ AddPromiseFlags(*this, PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
+ } else {
+ RemovePromiseFlags(*this, PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
+ }
+}
+
+void PromiseObject::setHadUserInteractionUponCreation(bool state) {
+ if (state) {
+ AddPromiseFlags(*this, PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
+ } else {
+ RemovePromiseFlags(*this, PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
+ }
+}
+
+void PromiseObject::copyUserInteractionFlagsFrom(PromiseObject& rhs) {
+ setRequiresUserInteractionHandling(rhs.requiresUserInteractionHandling());
+ setHadUserInteractionUponCreation(rhs.hadUserInteractionUponCreation());
+}
+
+// We can skip `await` with an already resolved value only if the current frame
+// is the topmost JS frame and the current job is the last job in the job queue.
+// This guarantees that any new job enqueued in the current turn will be
+// executed immediately after the current job.
+//
+// Currently we only support skipping jobs when the async function is resumed
+// at least once.
+[[nodiscard]] static bool IsTopMostAsyncFunctionCall(JSContext* cx) {
+ FrameIter iter(cx);
+
+ // The current frame should be the async function.
+ if (iter.done()) {
+ return false;
+ }
+
+ if (!iter.isFunctionFrame() && iter.isModuleFrame()) {
+ // The iterator is not a function frame, it is a module frame.
+ // Ignore this optimization for now.
+ return true;
+ }
+
+ MOZ_ASSERT(iter.calleeTemplate()->isAsync());
+
+#ifdef DEBUG
+ bool isGenerator = iter.calleeTemplate()->isGenerator();
+#endif
+
+ ++iter;
+
+ // The parent frame should be the `next` function of the generator that is
+ // internally called in AsyncFunctionResume resp. AsyncGeneratorResume.
+ if (iter.done()) {
+ return false;
+ }
+ // The initial call into an async function can happen from top-level code, so
+ // the parent frame isn't required to be a function frame. Contrary to that,
+ // the parent frame for an async generator function is always a function
+ // frame, because async generators can't directly fall through to an `await`
+ // expression from their initial call.
+ if (!iter.isFunctionFrame()) {
+ MOZ_ASSERT(!isGenerator);
+ return false;
+ }
+
+ // Always skip InterpretGeneratorResume if present.
+ JSFunction* fun = iter.calleeTemplate();
+ if (IsSelfHostedFunctionWithName(fun, cx->names().InterpretGeneratorResume)) {
+ ++iter;
+
+ if (iter.done()) {
+ return false;
+ }
+
+ MOZ_ASSERT(iter.isFunctionFrame());
+ fun = iter.calleeTemplate();
+ }
+
+ if (!IsSelfHostedFunctionWithName(fun, cx->names().AsyncFunctionNext) &&
+ !IsSelfHostedFunctionWithName(fun, cx->names().AsyncGeneratorNext)) {
+ return false;
+ }
+
+ ++iter;
+
+ // There should be no more frames.
+ if (iter.done()) {
+ return true;
+ }
+
+ return false;
+}
+
+[[nodiscard]] bool js::CanSkipAwait(JSContext* cx, HandleValue val,
+ bool* canSkip) {
+ if (!cx->canSkipEnqueuingJobs) {
+ *canSkip = false;
+ return true;
+ }
+
+ if (!IsTopMostAsyncFunctionCall(cx)) {
+ *canSkip = false;
+ return true;
+ }
+
+ // Primitive values cannot be 'thenables', so we can trivially skip the
+ // await operation.
+ if (!val.isObject()) {
+ *canSkip = true;
+ return true;
+ }
+
+ JSObject* obj = &val.toObject();
+ if (!obj->is<PromiseObject>()) {
+ *canSkip = false;
+ return true;
+ }
+
+ PromiseObject* promise = &obj->as<PromiseObject>();
+
+ if (promise->state() == JS::PromiseState::Pending) {
+ *canSkip = false;
+ return true;
+ }
+
+ PromiseLookup& promiseLookup = cx->realm()->promiseLookup;
+ if (!promiseLookup.isDefaultInstance(cx, promise)) {
+ *canSkip = false;
+ return true;
+ }
+
+ if (promise->state() == JS::PromiseState::Rejected) {
+ // We don't optimize rejected Promises for now.
+ *canSkip = false;
+ return true;
+ }
+
+ *canSkip = true;
+ return true;
+}
+
+[[nodiscard]] bool js::ExtractAwaitValue(JSContext* cx, HandleValue val,
+ MutableHandleValue resolved) {
+// Ensure all callers of this are jumping past the
+// extract if it's not possible to extract.
+#ifdef DEBUG
+ bool canSkip;
+ if (!CanSkipAwait(cx, val, &canSkip)) {
+ return false;
+ }
+ MOZ_ASSERT(canSkip == true);
+#endif
+
+ // Primitive values cannot be 'thenables', so we can trivially skip the
+ // await operation.
+ if (!val.isObject()) {
+ resolved.set(val);
+ return true;
+ }
+
+ JSObject* obj = &val.toObject();
+ PromiseObject* promise = &obj->as<PromiseObject>();
+ resolved.set(promise->value());
+
+ return true;
+}
+
+JS::AutoDebuggerJobQueueInterruption::AutoDebuggerJobQueueInterruption()
+ : cx(nullptr) {}
+
+JS::AutoDebuggerJobQueueInterruption::~AutoDebuggerJobQueueInterruption() {
+ MOZ_ASSERT_IF(initialized(), cx->jobQueue->empty());
+}
+
+bool JS::AutoDebuggerJobQueueInterruption::init(JSContext* cx) {
+ MOZ_ASSERT(cx->jobQueue);
+ this->cx = cx;
+ saved = cx->jobQueue->saveJobQueue(cx);
+ return !!saved;
+}
+
+void JS::AutoDebuggerJobQueueInterruption::runJobs() {
+ JS::AutoSaveExceptionState ases(cx);
+ cx->jobQueue->runJobs(cx);
+}
+
+const JSJitInfo promise_then_info = {
+ {(JSJitGetterOp)Promise_then_noRetVal},
+ {0}, /* unused */
+ {0}, /* unused */
+ JSJitInfo::IgnoresReturnValueNative,
+ JSJitInfo::AliasEverything,
+ JSVAL_TYPE_UNDEFINED,
+};
+
+const JSJitInfo promise_catch_info = {
+ {(JSJitGetterOp)Promise_catch_noRetVal},
+ {0}, /* unused */
+ {0}, /* unused */
+ JSJitInfo::IgnoresReturnValueNative,
+ JSJitInfo::AliasEverything,
+ JSVAL_TYPE_UNDEFINED,
+};
+
+static const JSFunctionSpec promise_methods[] = {
+ JS_FNINFO("then", js::Promise_then, &promise_then_info, 2, 0),
+ JS_FNINFO("catch", Promise_catch, &promise_catch_info, 1, 0),
+ JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0), JS_FS_END};
+
+static const JSPropertySpec promise_properties[] = {
+ JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY), JS_PS_END};
+
+static const JSFunctionSpec promise_static_methods[] = {
+ JS_FN("all", Promise_static_all, 1, 0),
+ JS_FN("allSettled", Promise_static_allSettled, 1, 0),
+ JS_FN("any", Promise_static_any, 1, 0),
+ JS_FN("race", Promise_static_race, 1, 0),
+ JS_FN("reject", Promise_reject, 1, 0),
+ JS_FN("resolve", js::Promise_static_resolve, 1, 0),
+ JS_FS_END};
+
+static const JSPropertySpec promise_static_properties[] = {
+ JS_SYM_GET(species, js::Promise_static_species, 0), JS_PS_END};
+
+static const ClassSpec PromiseObjectClassSpec = {
+ GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PromiseObject>,
+ promise_static_methods,
+ promise_static_properties,
+ promise_methods,
+ promise_properties};
+
+const JSClass PromiseObject::class_ = {
+ "Promise",
+ JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Promise) |
+ JSCLASS_HAS_XRAYED_CONSTRUCTOR,
+ JS_NULL_CLASS_OPS, &PromiseObjectClassSpec};
+
+const JSClass PromiseObject::protoClass_ = {
+ "Promise.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_Promise),
+ JS_NULL_CLASS_OPS, &PromiseObjectClassSpec};