Source code

Revision control

Other Tools

/* -*- 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/Debug.h"
#include "js/experimental/JitInfo.h" // JSJitGetterOp, JSJitInfo
#include "js/ForOfIterator.h" // JS::ForOfIterator
#include "js/PropertySpec.h"
#include "vm/ArrayObject.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/ErrorObject.h"
#include "vm/GeneratorObject.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::ProcessCreation()).ToMilliseconds();
}
enum PromiseHandler {
PromiseHandlerIdentity = 0,
PromiseHandlerThrower,
// ES 2018 draft 25.5.5.4-5.
PromiseHandlerAsyncFunctionAwaitedFulfilled,
PromiseHandlerAsyncFunctionAwaitedRejected,
// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c
// 6.2.3.1.1 Await Fulfilled Functions
// 6.2.3.1.2 Await Rejected Functions
PromiseHandlerAsyncGeneratorAwaitedFulfilled,
PromiseHandlerAsyncGeneratorAwaitedRejected,
// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c
// 25.5.3.5.1 AsyncGeneratorResumeNext Return Processor Fulfilled Functions
// 25.5.3.5.2 AsyncGeneratorResumeNext Return Processor Rejected Functions
PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled,
PromiseHandlerAsyncGeneratorResumeNextReturnRejected,
// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c
// 25.5.3.7 AsyncGeneratorYield, steps 8.c-e.
PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled,
PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected,
// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c
// 25.1.4.2.5 Async-from-Sync Iterator Value Unwrap Functions
//
// Async-from-Sync iterator handlers take the resolved value and create new
// iterator objects. To do so it needs to forward whether the iterator is
// done. In spec, this is achieved via the [[Done]] internal slot. We
// enumerate both true and false cases here.
PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone,
PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone,
// One past the maximum allowed PromiseHandler value.
PromiseHandlerLimit
};
enum ResolutionMode { ResolveMode, RejectMode };
enum ResolveFunctionSlots {
ResolveFunctionSlot_Promise = 0,
ResolveFunctionSlot_RejectFunction,
};
enum RejectFunctionSlots {
RejectFunctionSlot_Promise = 0,
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);
}
HandleArrayObject unwrappedArray() const {
return HandleArrayObject::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;
}
MOZ_MUST_USE 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());
HandleArrayObject 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.
MOZ_MUST_USE 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,
HandleSavedFrame 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) {
return debugInfo->resolutionSite();
}
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,
MutableHandleSavedFrame stack) {
if (!cx->isExceptionPending()) {
return false;
}
return GetAndClearExceptionAndStack(cx, rval, stack);
}
static MOZ_MUST_USE bool RunRejectFunction(
JSContext* cx, HandleObject onRejectedFunc, HandleValue result,
HandleObject promiseObj, HandleSavedFrame unwrappedRejectionStack,
UnhandledRejectionBehavior behavior);
// ES2016, 25.4.1.1.1, 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.
RootedValue reason(cx);
RootedSavedFrame stack(cx);
if (!MaybeGetAndClearExceptionAndStack(cx, &reason, &stack)) {
return false;
}
if (!RunRejectFunction(cx, reject, reason, promiseObj, stack,
UnhandledRejectionBehavior::Report)) {
return false;
}
// Step 1.b.
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,
};
// ES2016, 25.4.1.2.
class PromiseReactionRecord : public NativeObject {
static constexpr uint32_t REACTION_FLAG_RESOLVED = 0x1;
static constexpr uint32_t REACTION_FLAG_FULFILLED = 0x2;
static constexpr uint32_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4;
static constexpr uint32_t REACTION_FLAG_ASYNC_FUNCTION = 0x8;
static constexpr uint32_t REACTION_FLAG_ASYNC_GENERATOR = 0x10;
static constexpr uint32_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20;
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* asyncGenObj) {
setFlagOnInitialState(REACTION_FLAG_ASYNC_GENERATOR);
setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve,
ObjectValue(*asyncGenObj));
}
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);
// ES2016, 25.4.1.3.
static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool CreateResolvingFunctions(
JSContext* cx, HandleObject promise, MutableHandleObject resolveFn,
MutableHandleObject rejectFn) {
HandlePropertyName funName = cx->names().empty;
resolveFn.set(NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
gc::AllocKind::FUNCTION_EXTENDED,
GenericObject));
if (!resolveFn) {
return false;
}
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>();
resolveFun->initExtendedSlot(ResolveFunctionSlot_Promise,
ObjectValue(*promise));
resolveFun->initExtendedSlot(ResolveFunctionSlot_RejectFunction,
ObjectValue(*rejectFun));
rejectFun->initExtendedSlot(RejectFunctionSlot_Promise,
ObjectValue(*promise));
rejectFun->initExtendedSlot(RejectFunctionSlot_ResolveFunction,
ObjectValue(*resolveFun));
return true;
}
static void ClearResolutionFunctionSlots(JSFunction* resolutionFun);
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;
}
// ES2016, 25.4.1.7.
static MOZ_MUST_USE bool RejectMaybeWrappedPromise(
JSContext* cx, HandleObject promiseObj, HandleValue reason,
HandleSavedFrame unwrappedRejectionStack);
// ES2016, 25.4.1.7.
static MOZ_MUST_USE bool RejectPromiseInternal(
JSContext* cx, Handle<PromiseObject*> promise, HandleValue reason,
HandleSavedFrame unwrappedRejectionStack = nullptr);
// ES2016, 25.4.1.3.1.
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);
// Steps 1-2.
const Value& promiseVal = reject->getExtendedSlot(RejectFunctionSlot_Promise);
// Steps 3-4.
// If the Promise isn't available anymore, it has been resolved and the
// reference to it removed to make it eligible for collection.
if (promiseVal.isUndefined()) {
args.rval().setUndefined();
return true;
}
// Store the promise value in |promise| before ClearResolutionFunctionSlots
// removes the reference.
RootedObject promise(cx, &promiseVal.toObject());
// Step 5.
// Here, we only remove the Promise reference from the resolution
// functions. Actually marking it as fulfilled/rejected happens later.
ClearResolutionFunctionSlots(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 6.
if (!RejectMaybeWrappedPromise(cx, promise, reasonVal, nullptr)) {
return false;
}
args.rval().setUndefined();
return true;
}
static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext* cx,
HandleObject promiseObj,
HandleValue value_);
static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(
JSContext* cx, HandleValue promiseToResolve, HandleValue thenable,
HandleValue thenVal);
static MOZ_MUST_USE bool EnqueuePromiseResolveThenableBuiltinJob(
JSContext* cx, HandleObject promiseToResolve, HandleObject thenable);
static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal,
HandleValue onFulfilled, HandleValue onRejected,
MutableHandleValue rval, bool rvalUsed);
// ES2016, 25.4.1.3.2, steps 6-13.
static MOZ_MUST_USE bool ResolvePromiseInternal(JSContext* cx,
HandleObject promise,
HandleValue resolutionVal) {
cx->check(promise, resolutionVal);
MOZ_ASSERT(!IsSettledMaybeWrappedPromise(promise));
// Step 7 (reordered).
if (!resolutionVal.isObject()) {
return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
}
RootedObject resolution(cx, &resolutionVal.toObject());
// Step 6.
if (resolution == promise) {
// Step 6.a.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
RootedValue selfResolutionError(cx);
RootedSavedFrame stack(cx);
if (!MaybeGetAndClearExceptionAndStack(cx, &selfResolutionError, &stack)) {
return false;
}
// Step 6.b.
return RejectMaybeWrappedPromise(cx, promise, selfResolutionError, stack);
}
// Step 8.
RootedValue thenVal(cx);
bool status =
GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
RootedValue error(cx);
RootedSavedFrame errorStack(cx);
if (!status) {
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 9.
if (!status) {
return RejectMaybeWrappedPromise(cx, promise, error, errorStack);
}
// Step 10 (implicit).
// Step 11.
if (!IsCallable(thenVal)) {
return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
}
// 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;
}
// Step 12.
if (!isBuiltinThen) {
RootedValue promiseVal(cx, ObjectValue(*promise));
if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal,
thenVal)) {
return false;
}
} else {
if (!EnqueuePromiseResolveThenableBuiltinJob(cx, promise, resolution)) {
return false;
}
}
// Step 13.
return true;
}
// ES2016, 25.4.1.3.2.
static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSFunction* resolve = &args.callee().as<JSFunction>();
HandleValue resolutionVal = args.get(0);
// Steps 3-4 (reordered).
// We use the reference to the reject function as a signal for whether
// the resolve or reject function was already called, at which point
// the references on each of the functions are cleared.
if (!resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
.isObject()) {
args.rval().setUndefined();
return true;
}
// Steps 1-2 (reordered).
RootedObject promise(
cx, &resolve->getExtendedSlot(ResolveFunctionSlot_Promise).toObject());
// Step 5.
// Here, we only remove the Promise reference from the resolution
// functions. Actually marking it as fulfilled/rejected happens later.
ClearResolutionFunctionSlots(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 6-13.
if (!ResolvePromiseInternal(cx, promise, resolutionVal)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
/**
* 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.
*/
MOZ_MUST_USE 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);
cx->check(handlerArg);
reaction->setTargetStateAndHandlerArg(targetState, handlerArg);
RootedValue reactionVal(cx, ObjectValue(*reaction));
RootedValue handler(cx, reaction->handler());
// If we have a handler callback, we enter that handler's compartment so
// that the promise reaction job function is created in that compartment.
// That guarantees that the embedding ends up with the right entry global.
// This is relevant for some html APIs like fetch that derive information
// from said global.
mozilla::Maybe<AutoRealm> ar2;
if (handler.isObject()) {
// The unwrapping has to be unchecked because we specifically want to
// be able to use handlers with wrappers that would only allow calls.
// E.g., it's ok to have a handler from a chrome compartment in a
// reaction to a content compartment's Promise instance.
JSObject* handlerObj = UncheckedUnwrap(&handler.toObject());
MOZ_ASSERT(handlerObj);
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;
}
}
// Create the JS function to call when the job is triggered.
HandlePropertyName funName = cx->names().empty;
RootedFunction job(
cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!job) {
return false;
}
// Store the reaction on the reaction job.
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();
}
// 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);
}
static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx,
HandleValue reactionsVal,
JS::PromiseState state,
HandleValue valueOrReason);
// ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
//
// 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.
static MOZ_MUST_USE bool ResolvePromise(
JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueOrReason,
JS::PromiseState state,
HandleSavedFrame unwrappedRejectionStack = nullptr) {
// Step 1.
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);
// Step 2.
// 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());
// Steps 3-5.
// 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);
// Step 6.
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.
// Step 7 of RejectPromise implemented in onSettled.
PromiseObject::onSettled(cx, promise, unwrappedRejectionStack);
// Step 7 of FulfillPromise.
// Step 8 of RejectPromise.
return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason);
}
// ES2016, 25.4.1.7.
static MOZ_MUST_USE bool RejectPromiseInternal(
JSContext* cx, Handle<PromiseObject*> promise, HandleValue reason,
HandleSavedFrame unwrappedRejectionStack) {
return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected,
unwrappedRejectionStack);
}
// ES2016, 25.4.1.4.
static MOZ_MUST_USE 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);
static MOZ_MUST_USE PromiseObject* CreatePromiseObjectInternal(
JSContext* cx, HandleObject proto = nullptr, bool protoIsWrapped = false,
bool informDebugger = true);
enum GetCapabilitiesExecutorSlots {
GetCapabilitiesExecutorSlots_Resolve,
GetCapabilitiesExecutorSlots_Reject
};
static MOZ_MUST_USE PromiseObject*
CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) {
PromiseObject* promise = CreatePromiseObjectInternal(cx);
if (!promise) {
return nullptr;
}
AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS);
return promise;
}
static MOZ_MUST_USE PromiseObject* CreatePromiseWithDefaultResolutionFunctions(
JSContext* cx, MutableHandleObject resolve, MutableHandleObject reject) {
// ES2016, 25.4.3.1., as if called with GetCapabilitiesExecutor as the
// executor argument.
// Steps 1-2 (Not applicable).
// Steps 3-7.
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
if (!promise) {
return nullptr;
}
// Step 8.
if (!CreateResolvingFunctions(cx, promise, resolve, reject)) {
return nullptr;
}
promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*reject));
// Steps 9-10 (Not applicable).
// Step 11.
return promise;
}
// ES2016, 25.4.1.5.
static MOZ_MUST_USE bool NewPromiseCapability(
JSContext* cx, HandleObject C, MutableHandle<PromiseCapability> capability,
bool canOmitResolutionFunctions) {
RootedValue cVal(cx, ObjectValue(*C));
// Steps 1-2.
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;
}
capability.promise().set(promise);
return true;
}
// Step 3 (omitted).
// Step 4.
HandlePropertyName funName = cx->names().empty;
RootedFunction executor(
cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!executor) {
return false;
}
// Step 5 (omitted).
// Step 6.
FixedConstructArgs<1> cargs(cx);
cargs[0].setObject(*executor);
if (!Construct(cx, cVal, cargs, cVal, capability.promise())) {
return false;
}
// Step 7.
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.
const Value& rejectVal =
executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject);
if (!IsCallable(rejectVal)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
return false;
}
// Step 9 (well, the equivalent for all of promiseCapabilities' fields.)
capability.resolve().set(&resolveVal.toObject());
capability.reject().set(&rejectVal.toObject());
// Step 10.
return true;
}
// ES2016, 25.4.1.5.1.
static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSFunction* F = &args.callee().as<JSFunction>();
// Steps 1-2 (implicit).
// Steps 3-4.
if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() ||
!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);