#include "builtin/Promise.h"
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "mozilla/TimeStamp.h"
#include "jsapi.h"
#include "jsexn.h"
#include "jsfriendapi.h"
#include "gc/Heap.h"
#include "js/Debug.h"
#include "js/PropertySpec.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
#include "vm/Iteration.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/SelfHosting.h"
#include "vm/Compartment-inl.h"
#include "vm/Debugger-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
static double MillisecondsSinceStartup(
const mozilla::Maybe<mozilla::TimeStamp>& maybeNow) {
auto now = maybeNow.isSome() ? maybeNow.ref() : mozilla::TimeStamp::Now();
return (now - mozilla::TimeStamp::ProcessCreation()).ToMilliseconds();
}
enum PromiseHandler {
PromiseHandlerIdentity = 0,
PromiseHandlerThrower,
PromiseHandlerAsyncFunctionAwaitedFulfilled,
PromiseHandlerAsyncFunctionAwaitedRejected,
PromiseHandlerAsyncGeneratorAwaitedFulfilled,
PromiseHandlerAsyncGeneratorAwaitedRejected,
PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled,
PromiseHandlerAsyncGeneratorResumeNextReturnRejected,
PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled,
PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected,
PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone,
PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone,
PromiseHandlerLimit
};
enum ResolutionMode { ResolveMode, RejectMode };
enum ResolveFunctionSlots {
ResolveFunctionSlot_Promise = 0,
ResolveFunctionSlot_RejectFunction,
};
enum RejectFunctionSlots {
RejectFunctionSlot_Promise = 0,
RejectFunctionSlot_ResolveFunction,
};
enum PromiseAllResolveElementFunctionSlots {
PromiseAllResolveElementFunctionSlot_Data = 0,
PromiseAllResolveElementFunctionSlot_ElementIndex,
};
enum ReactionJobSlots {
ReactionJobSlot_ReactionRecord = 0,
};
enum ThenableJobSlots {
ThenableJobSlot_Handler = 0,
ThenableJobSlot_JobData,
};
enum ThenableJobDataIndices {
ThenableJobDataIndex_Promise = 0,
ThenableJobDataIndex_Thenable,
ThenableJobDataLength,
};
enum BuiltinThenableJobSlots {
BuiltinThenableJobSlot_Promise = 0,
BuiltinThenableJobSlot_Thenable,
};
enum PromiseAllDataHolderSlots {
PromiseAllDataHolderSlot_Promise = 0,
PromiseAllDataHolderSlot_RemainingElements,
PromiseAllDataHolderSlot_ValuesArray,
PromiseAllDataHolderSlot_ResolveFunction,
PromiseAllDataHolderSlots,
};
struct PromiseCapability {
JSObject* promise = nullptr;
JSObject* resolve = nullptr;
JSObject* reject = nullptr;
PromiseCapability() = default;
static void trace(PromiseCapability* self, JSTracer* trc) {
self->trace(trc);
}
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);
}
};
}
class PromiseAllDataHolder : public NativeObject {
public:
static const Class class_;
JSObject* promiseObj() {
return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject();
}
JSObject* resolveObj() {
return &getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObject();
}
Value valuesArray() {
return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray);
}
int32_t remainingCount() {
return getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
}
int32_t increaseRemainingCount() {
int32_t remainingCount =
getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
remainingCount++;
setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
Int32Value(remainingCount));
return remainingCount;
}
int32_t decreaseRemainingCount() {
int32_t remainingCount =
getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
remainingCount--;
setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
Int32Value(remainingCount));
return remainingCount;
}
};
const Class PromiseAllDataHolder::class_ = {
"PromiseAllDataHolder",
JSCLASS_HAS_RESERVED_SLOTS(PromiseAllDataHolderSlots)};
static PromiseAllDataHolder* NewPromiseAllDataHolder(JSContext* cx,
HandleObject resultPromise,
HandleValue valuesArray,
HandleObject resolve) {
PromiseAllDataHolder* dataHolder =
NewBuiltinClassInstance<PromiseAllDataHolder>(cx);
if (!dataHolder) {
return nullptr;
}
cx->check(resultPromise);
cx->check(valuesArray);
cx->check(resolve);
dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise,
ObjectValue(*resultPromise));
dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
Int32Value(1));
dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray);
dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction,
ObjectValue(*resolve));
return dataHolder;
}
namespace {
mozilla::Atomic<uint64_t> gIDGenerator(0);
}
static MOZ_ALWAYS_INLINE bool ShouldCaptureDebugInfo(JSContext* cx) {
return cx->options().asyncStack() || cx->realm()->isDebuggee();
}
static mozilla::Maybe<mozilla::TimeStamp> MaybeNow() {
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return mozilla::Some(mozilla::TimeStamp::Now());
}
return mozilla::Nothing();
}
class PromiseDebugInfo : public NativeObject {
private:
enum Slots {
Slot_AllocationSite,
Slot_ResolutionSite,
Slot_AllocationTime,
Slot_ResolutionTime,
Slot_Id,
SlotCount
};
public:
static const Class class_;
static PromiseDebugInfo* create(
JSContext* cx, Handle<PromiseObject*> promise,
const mozilla::Maybe<mozilla::TimeStamp>& maybeNow) {
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(maybeNow)));
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;
}
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();
}
static void setResolutionInfo(JSContext* cx, Handle<PromiseObject*> promise) {
mozilla::Maybe<mozilla::TimeStamp> maybeNow = MaybeNow();
if (!ShouldCaptureDebugInfo(cx)) {
return;
}
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
Rooted<PromiseDebugInfo*> debugInfo(cx, FromPromise(promise));
if (!debugInfo) {
RootedValue idVal(cx, promise->getFixedSlot(PromiseSlot_DebugInfo));
debugInfo = create(cx, promise, maybeNow);
if (!debugInfo) {
cx->clearPendingException();
return;
}
debugInfo->setFixedSlot(Slot_ResolutionSite,
debugInfo->getFixedSlot(Slot_AllocationSite));
debugInfo->setFixedSlot(Slot_AllocationSite, NullValue());
debugInfo->setFixedSlot(Slot_ResolutionTime,
debugInfo->getFixedSlot(Slot_AllocationTime));
debugInfo->setFixedSlot(Slot_Id, idVal);
return;
}
RootedObject stack(cx);
if (!JS::CaptureCurrentStack(cx, &stack,
JS::StackCapture(JS::AllFrames()))) {
cx->clearPendingException();
return;
}
debugInfo->setFixedSlot(Slot_ResolutionSite, ObjectOrNullValue(stack));
debugInfo->setFixedSlot(Slot_ResolutionTime,
DoubleValue(MillisecondsSinceStartup(maybeNow)));
}
};
const Class 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;
}
static bool MaybeGetAndClearException(JSContext* cx, MutableHandleValue rval) {
if (!cx->isExceptionPending()) {
return false;
}
return GetAndClearException(cx, rval);
}
static MOZ_MUST_USE bool RunResolutionFunction(JSContext* cx,
HandleObject resolutionFun,
HandleValue result,
ResolutionMode mode,
HandleObject promiseObj);
static bool AbruptRejectPromise(JSContext* cx, CallArgs& args,
HandleObject promiseObj, HandleObject reject) {
RootedValue reason(cx);
if (!MaybeGetAndClearException(cx, &reason)) {
return false;
}
if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj)) {
return false;
}
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 {
ReactionRecordSlot_Promise = 0,
ReactionRecordSlot_OnFulfilled,
ReactionRecordSlot_OnRejectedArg = ReactionRecordSlot_OnFulfilled,
ReactionRecordSlot_OnRejected,
ReactionRecordSlot_OnFulfilledArg = ReactionRecordSlot_OnRejected,
ReactionRecordSlot_Resolve,
ReactionRecordSlot_Reject,
ReactionRecordSlot_IncumbentGlobalObject,
ReactionRecordSlot_Flags,
ReactionRecordSlot_GeneratorOrPromiseToResolve,
ReactionRecordSlots,
};
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;
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 Class class_;
JSObject* promise() {
return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull();
}
int32_t flags() { 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 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 Class 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 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);
if (JS_IsDeadWrapper(promise)) {
return false;
}
}
return promise->as<PromiseObject>().state() != JS::PromiseState::Pending;
}
static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext* cx,
HandleObject promiseObj,
HandleValue reason);
static MOZ_MUST_USE bool RejectPromiseInternal(JSContext* cx,
Handle<PromiseObject*> promise,
HandleValue reason);
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);
const Value& promiseVal = reject->getExtendedSlot(RejectFunctionSlot_Promise);
if (promiseVal.isUndefined()) {
args.rval().setUndefined();
return true;
}
RootedObject promise(cx, &promiseVal.toObject());
ClearResolutionFunctionSlots(reject);
if (IsSettledMaybeWrappedPromise(promise)) {
args.rval().setUndefined();
return true;
}
if (!RejectMaybeWrappedPromise(cx, promise, reasonVal)) {
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(JSContext* cx, unsigned argc, Value* vp);
static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal,
HandleValue onFulfilled, HandleValue onRejected,
MutableHandleValue rval, bool rvalUsed);
static MOZ_MUST_USE bool ResolvePromiseInternal(JSContext* cx,
HandleObject promise,
HandleValue resolutionVal) {
cx->check(promise, resolutionVal);
MOZ_ASSERT(!IsSettledMaybeWrappedPromise(promise));
if (!resolutionVal.isObject()) {
return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
}
RootedObject resolution(cx, &resolutionVal.toObject());
if (resolution == promise) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
RootedValue selfResolutionError(cx);
if (!MaybeGetAndClearException(cx, &selfResolutionError)) {
return false;
}
return RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
}
RootedValue thenVal(cx);
bool status =
GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
RootedValue error(cx);
if (!status) {
if (!MaybeGetAndClearException(cx, &error)) {
return false;
}
}
if (IsSettledMaybeWrappedPromise(promise)) {
return true;
}
if (!status) {
return RejectMaybeWrappedPromise(cx, promise, error);
}
if (!IsCallable(thenVal)) {
return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
}
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;
}
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);
if (!resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
.isObject()) {
args.rval().setUndefined();
return true;
}
RootedObject promise(
cx, &resolve->getExtendedSlot(ResolveFunctionSlot_Promise).toObject());
ClearResolutionFunctionSlots(resolve);
if (IsSettledMaybeWrappedPromise(promise)) {
args.rval().setUndefined();
return true;
}
if (!ResolvePromiseInternal(cx, promise, resolutionVal)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
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);
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()) {
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;
}
}
MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
cx->check(handlerArg);
reaction->setTargetStateAndHandlerArg(targetState, handlerArg);
RootedValue reactionVal(cx, ObjectValue(*reaction));
RootedValue handler(cx, reaction->handler());
mozilla::Maybe<AutoRealm> ar2;
if (handler.isObject()) {
JSObject* handlerObj = UncheckedUnwrap(&handler.toObject());
MOZ_ASSERT(handlerObj);
ar2.emplace(cx, handlerObj);
if (!cx->compartment()->wrap(cx, &reactionVal)) {
return false;
}
}
HandlePropertyName 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);
RootedObject promise(cx, reaction->promise());
if (promise) {
if (promise->is<PromiseObject>()) {
if (!cx->compartment()->wrap(cx, &promise)) {
return false;
}
} else if (IsWrapper(promise)) {
JSObject* unwrappedPromise = UncheckedUnwrap(promise);
if (unwrappedPromise->is<PromiseObject>()) {
if (!cx->compartment()->wrap(cx, &promise)) {
return false;
}
} else {
promise = nullptr;
}
} else {
promise = nullptr;
}
}
Rooted<GlobalObject*> global(cx);
if (JSObject* objectFromIncumbentGlobal =
reaction->getAndClearIncumbentGlobalObject()) {
objectFromIncumbentGlobal = CheckedUnwrapStatic(objectFromIncumbentGlobal);
MOZ_ASSERT(objectFromIncumbentGlobal);
global = &objectFromIncumbentGlobal->nonCCWGlobal();
}
return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
}
static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx,
HandleValue reactionsVal,
JS::PromiseState state,
HandleValue valueOrReason);
static MOZ_MUST_USE bool ResolvePromise(JSContext* cx,
Handle<PromiseObject*> promise,
HandleValue valueOrReason,
JS::PromiseState state) {
MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
MOZ_ASSERT(state == JS::PromiseState::Fulfilled ||
state == JS::PromiseState::Rejected);
RootedValue reactionsVal(cx, promise->reactions());
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason);
int32_t flags = promise->flags();
flags |= PROMISE_FLAG_RESOLVED;
if (state == JS::PromiseState::Fulfilled) {
flags |= PROMISE_FLAG_FULFILLED;
}
promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags));
promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue());
PromiseObject::onSettled(cx, promise);
if (reactionsVal.isObject()) {
return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason);
}
return true;
}
static MOZ_MUST_USE bool RejectPromiseInternal(JSContext* cx,
Handle<PromiseObject*> promise,
HandleValue reason) {
return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
}
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) {
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
if (!promise) {
return nullptr;
}
if (!CreateResolvingFunctions(cx, promise, resolve, reject)) {
return nullptr;
}
promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*reject));
return promise;
}
static MOZ_MUST_USE bool NewPromiseCapability(
JSContext* cx, HandleObject C, MutableHandle<PromiseCapability> capability,
bool canOmitResolutionFunctions) {
RootedValue cVal(cx, ObjectValue(*C));
if (!IsConstructor(C)) {
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_SEARCH_STACK, cVal,
nullptr);
return false;
}
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;
}
HandlePropertyName funName = cx->names().empty;
RootedFunction executor(
cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!executor) {
return false;
}
FixedConstructArgs<1> cargs(cx);
cargs[0].setObject(*executor);
if (!Construct(cx, cVal, cargs, cVal, capability.promise())) {
return false;
}
const Value& resolveVal =
executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve);
if (!IsCallable(resolveVal)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
return false;
}
const Value& rejectVal =
executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject);
if (!IsCallable(rejectVal)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
return false;
}
capability.resolve().set(&resolveVal.toObject());
capability.reject().set(&rejectVal.toObject());
return true;
}
static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSFunction* F = &args.callee().as<JSFunction>();
if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() ||
!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
return false;
}
F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0));
F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1));
args.rval().setUndefined();
return true;
}
static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext* cx,
HandleObject promiseObj,
HandleValue reason_) {
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);
if (!cx->compartment()->wrap(cx, &reason)) {
return false;
}
if (reason.isObject() && !CheckedUnwrapStatic(&reason.toObject())) {
JSObject* realReason = UncheckedUnwrap(&reason.toObject());
RootedValue realReasonVal(cx, ObjectValue(*realReason));
Rooted<GlobalObject*> realGlobal(cx, &realReason->nonCCWGlobal());
ReportErrorToGlobal(cx, realGlobal, realReasonVal);
if (!GetInternalError(cx, JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,
&reason)) {
return false;
}
}
}
return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
}
static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx,
HandleValue reactionsVal,
JS::PromiseState state,
HandleValue valueOrReason) {
MOZ_ASSERT(state == JS::PromiseState::Fulfilled ||
state == JS::PromiseState::Rejected);
RootedObject reactions(cx, &reactionsVal.toObject());
if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions) ||
JS_IsDeadWrapper(reactions)) {
return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state);
}
HandleNativeObject reactionsList = reactions.as<NativeObject>();
uint32_t reactionsCount = reactionsList->getDenseInitializedLength();
MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
RootedObject reaction(cx);
for (uint32_t i = 0; i < reactionsCount; i++) {
const Value& reactionVal = reactionsList->getDenseElement(i);
MOZ_RELEASE_ASSERT(reactionVal.isObject());
reaction = &reactionVal.toObject();
if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) {
return false;
}
}
return true;
}
static MOZ_MUST_USE bool DefaultResolvingPromiseReactionJob(
JSContext* cx, Handle<PromiseReactionRecord*> reaction,
MutableHandleValue rval) {
MOZ_ASSERT(reaction->targetState() != JS::PromiseState::Pending);
Rooted<PromiseObject*> promiseToResolve(cx,
reaction->defaultResolvingPromise());
ResolutionMode resolutionMode = ResolveMode;
RootedValue handlerResult(cx, UndefinedValue());
if (promiseToResolve->state() == JS::PromiseState::Pending) {
RootedValue argument(cx, reaction->handlerArg());
bool ok;
if (reaction->targetState() == JS::PromiseState::Fulfilled) {
ok = ResolvePromiseInternal(cx, promiseToResolve, argument);
} else {
ok = RejectPromiseInternal(cx, promiseToResolve, argument);
}
if (!ok) {
resolutionMode = RejectMode;
if (!MaybeGetAndClearException(cx, &handlerResult)) {
return false;
}
}
}
uint32_t hookSlot = resolutionMode == RejectMode ? ReactionRecordSlot_Reject
: ReactionRecordSlot_Resolve;
RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
RootedObject promiseObj(cx, reaction->promise());
if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode,
promiseObj)) {
return false;
}
rval.setUndefined();
return true;
}
static MOZ_MUST_USE bool AsyncFunctionPromiseReactionJob(
JSContext* cx, Handle<PromiseReactionRecord*> reaction,
MutableHandleValue rval) {
MOZ_ASSERT(reaction->isAsyncFunction());
RootedValue handlerVal(cx, reaction->handler());
RootedValue argument(cx, reaction->handlerArg());
Rooted<AsyncFunctionGeneratorObject*> generator(
cx, reaction->asyncFunctionGenerator());
int32_t handlerNum = handlerVal.toInt32();
if (handlerNum == PromiseHandlerAsyncFunctionAwaitedFulfilled) {
if (!AsyncFunctionAwaitedFulfilled(cx, generator, argument)) {
return false;
}
} else {
MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFunctionAwaitedRejected);
if (!AsyncFunctionAwaitedRejected(cx, generator, argument)) {
return false;
}
}
rval.setUndefined();
return true;
}
static MOZ_MUST_USE bool AsyncGeneratorPromiseReactionJob(
JSContext* cx, Handle<PromiseReactionRecord*> reaction,
MutableHandleValue rval) {
MOZ_ASSERT(reaction->isAsyncGenerator());
RootedValue handlerVal(cx, reaction->handler());
RootedValue argument(cx, reaction->handlerArg());
Rooted<AsyncGeneratorObject*> asyncGenObj(cx, reaction->asyncGenerator());
int32_t handlerNum = handlerVal.toInt32();
if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedFulfilled) {
if (!AsyncGeneratorAwaitedFulfilled(cx, asyncGenObj, argument)) {
return false;
}
} else if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedRejected) {
if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument)) {
return false;
}
} else if (handlerNum ==
PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled) {
asyncGenObj->setCompleted();
if (!AsyncGeneratorResolve(cx, asyncGenObj, argument, true)) {
return false;
}
} else if (handlerNum ==
PromiseHandlerAsyncGeneratorResumeNextReturnRejected) {
asyncGenObj->setCompleted();
if (!AsyncGeneratorReject(cx, asyncGenObj, argument)) {
return false;
}
} else if (handlerNum ==
PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled) {
asyncGenObj->setExecuting();
if (!AsyncGeneratorYieldReturnAwaitedFulfilled(cx, asyncGenObj, argument)) {
return false;
}
} else {
MOZ_ASSERT(handlerNum ==
PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected);
asyncGenObj->setExecuting();
if (!AsyncGeneratorYieldReturnAwaitedRejected(cx, asyncGenObj, argument)) {
return false;
}
}
rval.setUndefined();
return true;
}
static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction job(cx, &args.callee().as<JSFunction>());
RootedObject reactionObj(
cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject());
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);
}
Handle<PromiseReactionRecord*> reaction =
reactionObj.as<PromiseReactionRecord>();
if (reaction->isDefaultResolvingHandler()) {
return DefaultResolvingPromiseReactionJob(cx, reaction, args.rval());
}
if (reaction->isAsyncFunction()) {
return AsyncFunctionPromiseReactionJob(cx, reaction, args.rval());
}
if (reaction->isAsyncGenerator()) {
return AsyncGeneratorPromiseReactionJob(cx, reaction, args.rval());
}
if (reaction->isDebuggerDummy()) {
return true;
}
RootedValue handlerVal(cx, reaction->handler());
RootedValue argument(cx, reaction->handlerArg());
RootedValue handlerResult(cx);
ResolutionMode resolutionMode = ResolveMode;
if (handlerVal.isInt32()) {
int32_t handlerNum = handlerVal.toInt32();
if (handlerNum == PromiseHandlerIdentity) {
handlerResult = argument;
} else if (handlerNum == PromiseHandlerThrower) {
resolutionMode = RejectMode;
handlerResult = argument;
} else {
MOZ_ASSERT(
handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone ||
handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone);
bool done =
handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone;
JSObject* resultObj = CreateIterResultObject(cx, argument, done);
if (!resultObj) {
return false;
}
handlerResult = ObjectValue(*resultObj);
}
} else {
MOZ_ASSERT(handlerVal.isObject());
MOZ_ASSERT(IsCallable(handlerVal));
if (!Call(cx, handlerVal, UndefinedHandleValue, argument, &handlerResult)) {
resolutionMode = RejectMode;
if (!MaybeGetAndClearException(cx, &handlerResult)) {
return false;
}
}
}
uint32_t hookSlot = resolutionMode == RejectMode ? ReactionRecordSlot_Reject
: ReactionRecordSlot_Resolve;
RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
RootedObject promiseObj(cx, reaction->promise());
if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode,
promiseObj)) {
return false;
}
args.rval().setUndefined();
return true;
}
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());
MOZ_ASSERT(!IsWrapper(&then.toObject()));
RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData)
.toObject()
.as<NativeObject>());
RootedObject promise(
cx, &jobArgs->getDenseElement(ThenableJobDataIndex_Promise).toObject());
RootedValue thenable(cx,
jobArgs->getDenseElement(ThenableJobDataIndex_Thenable));
RootedObject resolveFn(cx);
RootedObject rejectFn(cx);
if (!CreateResolvingFunctions(cx, promise, &resolveFn, &rejectFn)) {
return false;
}
FixedInvokeArgs<2> args2(cx);
args2[0].setObject(*resolveFn);
args2[1].setObject(*rejectFn);
RootedValue rval(cx);
if (Call(cx, then, thenable, args2, &rval)) {
return true;
}
if (!MaybeGetAndClearException(cx, &rval)) {
return false;
}
RootedValue rejectVal(cx, ObjectValue(*rejectFn));
return Call(cx, rejectVal, UndefinedHandleValue, rval, &rval);
}
static MOZ_MUST_USE bool OriginalPromiseThenWithoutSettleHandlers(
JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseObject*> promiseToResolve);
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>());
if (OriginalPromiseThenWithoutSettleHandlers(cx, thenable.as<PromiseObject>(),
promise.as<PromiseObject>())) {
return true;
}
RootedValue exception(cx);
if (!MaybeGetAndClearException(cx, &exception)) {
return false;
}
if (promise->as<PromiseObject>().state() != JS::PromiseState::Pending) {
return true;
}
return RejectPromiseInternal(cx, promise.as<PromiseObject>(), exception);
}
static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(
JSContext* cx, HandleValue promiseToResolve_, HandleValue thenable_,
HandleValue thenVal) {
RootedValue promiseToResolve(cx, promiseToResolve_);
RootedValue thenable(cx, thenable_);
RootedObject then(cx, CheckedUnwrapStatic(&thenVal.toObject()));
AutoRealm ar(cx, then);
if (!cx->compartment()->wrap(cx, &promiseToResolve)) {
return false;
}
MOZ_ASSERT(thenable.isObject());
if (!cx->compartment()->wrap(cx, &thenable)) {
return false;
}
HandlePropertyName funName = cx->names().empty;
RootedFunction job(
cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!job) {
return false;
}
job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
RootedArrayObject data(
cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
if (!data) {
return false;
}
data->setDenseInitializedLength(ThenableJobDataLength);
data->initDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
data->initDenseElement(ThenableJobDataIndex_Thenable, thenable);
job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
RootedObject promise(cx, &promiseToResolve.toObject());
Rooted<GlobalObject*> incumbentGlobal(cx,
cx->runtime()->getIncumbentGlobal(cx));
return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
}
static MOZ_MUST_USE bool EnqueuePromiseResolveThenableBuiltinJob(
JSContext* cx, HandleObject promiseToResolve, HandleObject thenable) {
cx->check(promiseToResolve, thenable);
MOZ_ASSERT(promiseToResolve->is<PromiseObject>());
MOZ_ASSERT(thenable->is<PromiseObject>());
HandlePropertyName funName = cx->names().empty;
RootedFunction job(
cx, NewNativeFunction(cx, PromiseResolveBuiltinThenableJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!job) {
return false;
}
job->setExtendedSlot(BuiltinThenableJobSlot_Promise,
ObjectValue(*promiseToResolve));
job->setExtendedSlot(BuiltinThenableJobSlot_Thenable, ObjectValue(*thenable));
Rooted<GlobalObject*> incumbentGlobal(cx,
cx->runtime()->getIncumbentGlobal(cx));
return cx->runtime()->enqueuePromiseJob(cx, job, promiseToResolve,
incumbentGlobal);
}
static MOZ_MUST_USE bool AddDummyPromiseReactionForDebugger(
JSContext* cx, Handle<PromiseObject*> promise,
HandleObject dependentPromise);
static MOZ_MUST_USE 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();
if (IsWrapper(rejectFunObj)) {
rejectFunObj = UncheckedUnwrap(rejectFunObj);
}
if (!rejectFunObj->is<JSFunction>()) {
return nullptr;
}
JSFunction* rejectFun = &rejectFunObj->as<JSFunction>();
if (rejectFun->maybeNative() != &RejectPromiseFunction) {
return nullptr;
}
if (rejectFun->getExtendedSlot(RejectFunctionSlot_ResolveFunction)
.isUndefined()) {
return nullptr;
}
return GetResolveFunctionFromReject(rejectFun);
}
static void ClearResolutionFunctionSlots(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());
}
static MOZ_MUST_USE MOZ_ALWAYS_INLINE PromiseObject*
CreatePromiseObjectInternal(JSContext* cx, HandleObject proto ,
bool protoIsWrapped ,
bool informDebugger ) {
mozilla::Maybe<AutoRealm> ar;
if (protoIsWrapped) {
ar.emplace(cx, proto);
}
PromiseObject* promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
if (!promise) {
return nullptr;
}
promise->initFixedSlot(PromiseSlot_Flags, Int32Value(0));
mozilla::Maybe<mozilla::TimeStamp> maybeNow = MaybeNow();
if (MOZ_LIKELY(!ShouldCaptureDebugInfo(cx))) {
return promise;
}
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
Rooted<PromiseObject*> promiseRoot(cx, promise);
PromiseDebugInfo* debugInfo =
PromiseDebugInfo::create(cx, promiseRoot, maybeNow);
if (!debugInfo) {
return nullptr;
}
if (informDebugger) {
Debugger::onNewPromise(cx, promiseRoot);
}
return promiseRoot;
}
static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "Promise")) {
return false;
}
HandleValue executorVal = args.get(0);
if (!IsCallable(executorVal)) {
return ReportIsNotFunction(cx, executorVal);
}
RootedObject executor(cx, &executorVal.toObject());
RootedObject newTarget(cx, &args.newTarget().toObject());
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();
JSFunction* promiseCtor =
GlobalObject::getOrCreatePromiseConstructor(cx, global);
if (!promiseCtor) {
return false;
}
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;
}
args.rval().setObject(*promise);
if (needsWrapping) {
return cx->compartment()->wrap(cx, args.rval());
}
return true;
}
PromiseObject* PromiseObject::create(JSContext* cx, HandleObject executor,
HandleObject proto ,
bool needsWrapping ) {
MOZ_ASSERT(executor->isCallable());
RootedObject usedProto(cx, proto);
if (needsWrapping) {
MOZ_ASSERT(proto);
usedProto = CheckedUnwrapStatic(proto);
if (!usedProto) {
ReportAccessDenied(cx);
return nullptr;
}
}
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;
}
RootedObject resolveFn(cx);
RootedObject rejectFn(cx);
if (!CreateResolvingFunctions(cx, promiseObj, &resolveFn, &rejectFn)) {
return nullptr;
}
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));
}
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);
}
if (!success) {
RootedValue exceptionVal(cx);
if (!MaybeGetAndClearException(cx, &exceptionVal)) {
return nullptr;
}
RootedValue calleeOrRval(cx, ObjectValue(*rejectFn));
if (!Call(cx, calleeOrRval, UndefinedHandleValue, exceptionVal,
&calleeOrRval)) {
return nullptr;
}
}
Debugger::onNewPromise(cx, promise);
return promise;
}
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);
}
};
static MOZ_MUST_USE bool PerformPromiseAll(
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
Handle<PromiseCapability> resultCapability, bool* done);
static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue iterable = args.get(0);
HandleValue CVal = args.thisv();
if (!CVal.isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_NONNULL_OBJECT,
"Receiver of Promise.all call");
return false;
}
RootedObject C(cx, &CVal.toObject());
Rooted<PromiseCapability> promiseCapability(cx);
if (!NewPromiseCapability(cx, C, &promiseCapability, false)) {
return false;
}
PromiseForOfIterator iter(cx);
if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
return AbruptRejectPromise(cx, args, promiseCapability);
}
if (!iter.valueIsIterable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
"Argument of Promise.all");
return AbruptRejectPromise(cx, args, promiseCapability);
}
bool done;
bool result = PerformPromiseAll(cx, iter, C, promiseCapability, &done);
if (!result) {
if (!done) {
iter.closeThrow();
}
return AbruptRejectPromise(cx, args, promiseCapability);
}
args.rval().setObject(*promiseCapability.promise());
return true;
}
static MOZ_MUST_USE bool PerformPromiseThen(
JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
HandleValue onRejected_, Handle<PromiseCapability> resultCapability);
static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(
JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseObject*> promiseToResolve,
Handle<PromiseCapability> resultCapability);
static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
Value* vp);
MOZ_MUST_USE JSObject* js::GetWaitForAllPromise(
JSContext* cx, const JS::AutoObjectVector& 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
RootedObject C(cx,
GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
if (!C) {
return nullptr;
}
Rooted<PromiseCapability> resultCapability(cx);
if (!NewPromiseCapability(cx, C, &resultCapability, false)) {
return nullptr;
}
{
uint32_t promiseCount = promises.length();
RootedNativeObject valuesArray(
cx, NewDenseFullyAllocatedArray(cx, promiseCount));
if (!valuesArray) {
return nullptr;
}
valuesArray->ensureDenseInitializedLength(cx, 0, promiseCount);
RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
Rooted<PromiseAllDataHolder*> dataHolder(cx);
dataHolder =
NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal,
resultCapability.resolve());
if (!dataHolder) {
return nullptr;
}
Rooted<PromiseCapability> resultCapabilityWithoutResolving(cx);
resultCapabilityWithoutResolving.promise().set(resultCapability.promise());
for (uint32_t index = 0; index < promiseCount; index++) {
valuesArray->setDenseElement(index, UndefinedHandleValue);
RootedObject nextPromiseObj(cx, promises[index]);
RootedFunction resolveFunc(
cx,
NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!resolveFunc) {
return nullptr;
}
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data,
ObjectValue(*dataHolder));
resolveFunc->setExtendedSlot(
PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index));
dataHolder->increaseRemainingCount();
RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject()));
Rooted<PromiseObject*> nextPromise(cx);
nextPromise = &UncheckedUnwrap(nextPromiseObj)->as<PromiseObject>();
if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal,
resultCapabilityWithoutResolving)) {
return nullptr;
}
}
int32_t remainingCount = dataHolder->decreaseRemainingCount();
if (remainingCount == 0) {
RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
if (!ResolvePromiseInternal(cx, resultCapability.promise(),
valuesArrayVal)) {
return nullptr;
}
}
}
return resultCapability.promise();
}
static MOZ_MUST_USE bool RunResolutionFunction(JSContext* cx,
HandleObject resolutionFun,
HandleValue result,
ResolutionMode mode,
HandleObject promiseObj) {
cx->check(resolutionFun);
cx->check(result);
cx->check(promiseObj);
if (resolutionFun) {
RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun));
return Call(cx, calleeOrRval, UndefinedHandleValue, result, &calleeOrRval);
}
if (!promiseObj) {
if (mode == RejectMode) {
Rooted<PromiseObject*> temporaryPromise(cx);
temporaryPromise = CreatePromiseObjectWithoutResolutionFunctions(cx);
if (!temporaryPromise) {
cx->clearPendingException();
return true;
}
return RejectPromiseInternal(cx, temporaryPromise, result);
}
return true;
}
Handle<PromiseObject*> promise = promiseObj.as<PromiseObject>();
if (promise->state() != JS::PromiseState::Pending) {
return true;
}
if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) {
return true;
}
if (mode == ResolveMode) {
return ResolvePromiseInternal(cx, promise, result);
}
return RejectPromiseInternal(cx, promise, result);
}
static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(
JSContext* cx, HandleValue thisVal, HandleValue argVal,
ResolutionMode mode);
static bool IsPromiseSpecies(JSContext* cx, JSFunction* species);
template <typename T>
static MOZ_MUST_USE bool CommonPerformPromiseAllRace(
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
Handle<PromiseCapability> resultCapability, bool* done,
bool resolveReturnsUndefined, T getResolveFun) {
RootedObject promiseCtor(
cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
if (!promiseCtor) {
return false;
}
bool iterationMayHaveSideEffects = !iterator.isOptimizedDenseArrayIteration();
bool isDefaultPromiseState = C == promiseCtor;
bool validatePromiseState = true;
PromiseLookup& promiseLookup = cx->realm()->promiseLookup;
RootedValue CVal(cx, ObjectValue(*C));
HandleObject resultPromise = resultCapability.promise();
RootedValue resolveFunVal(cx);
RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject()));
RootedValue nextValueOrNextPromise(cx);
RootedObject nextPromiseObj(cx);
RootedValue resolveOrThen(cx);
RootedObject thenSpeciesOrBlockedPromise(cx);
Rooted<PromiseCapability> thenCapability(cx);
while (true) {
RootedValue& nextValue = nextValueOrNextPromise;
if (!iterator.next(&nextValue, done)) {
*done = true;
return false;
}
if (*done) {
return true;
}
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)) {
validatePromiseState = iterationMayHaveSideEffects;
MOZ_ASSERT(&nextPromise.toObject() == nextValuePromise);
getThen = false;
} else {
validatePromiseState = true;
JSObject* res =
CommonStaticResolveRejectImpl(cx, CVal, nextValue, ResolveMode);
if (!res) {
return false;
}
nextPromise.setObject(*res);
}
} else {
RootedValue& staticResolve = resolveOrThen;
if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) {
return false;
}
if (!Call(cx, staticResolve, CVal, nextValue, &nextPromise)) {
return false;
}
}
JSObject* resolveFun = getResolveFun();
if (!resolveFun) {
return false;
}
resolveFunVal.setObject(*resolveFun);
nextPromiseObj = ToObject(cx, nextPromise);
if (!nextPromiseObj) {
return false;
}
RootedValue& thenVal = resolveOrThen;
bool isBuiltinThen;
if (getThen) {
if (!GetProperty(cx, nextPromiseObj, nextPromise, cx->names().then,
&thenVal)) {
return false;
}
isBuiltinThen = nextPromiseObj->is<PromiseObject>() &&
IsNativeFunction(thenVal, Promise_then);
} else {
isBuiltinThen = true;
}
bool addToDependent = true;
if (isBuiltinThen) {
MOZ_ASSERT(nextPromise.isObject());
MOZ_ASSERT(&nextPromise.toObject() == nextPromiseObj);
RootedObject& thenSpecies = thenSpeciesOrBlockedPromise;
if (getThen) {
thenSpecies = SpeciesConstructor(cx, nextPromiseObj, JSProto_Promise,
IsPromiseSpecies);
if (!thenSpecies) {
return false;
}
} else {
thenSpecies = promiseCtor;
}
thenCapability.resolve().set(nullptr);
thenCapability.reject().set(nullptr);
if (thenSpecies == promiseCtor && resolveReturnsUndefined &&
resultPromise->is<PromiseObject>() &&
!PromiseHasAnyFlag(resultPromise->as<PromiseObject>(),
PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) {
thenCapability.promise().set(resultPromise);
addToDependent = false;
} else {
if (!NewPromiseCapability(cx, thenSpecies, &thenCapability, true)) {
return false;
}
}
Handle<PromiseObject*> promise = nextPromiseObj.as<PromiseObject>();
if (!PerformPromiseThen(cx, promise, resolveFunVal, rejectFunVal,
thenCapability)) {
return false;
}
} else {
RootedValue& ignored = thenVal;
if (!Call(cx, thenVal, nextPromise, resolveFunVal, rejectFunVal,
&ignored)) {
return false;
}
if (!nextPromise.isObject()) {
addToDependent = false;
}
}
if (addToDependent) {
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 (nextPromiseObj->is<PromiseObject>() &&
resultPromise->is<PromiseObject>()) {
Handle<PromiseObject*> promise = nextPromiseObj.as<PromiseObject>();
if (!AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise)) {
return false;
}
}
}
}
}
static MOZ_MUST_USE bool PerformPromiseAll(
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
Handle<PromiseCapability> resultCapability, bool* done) {
*done = false;
MOZ_ASSERT(C->isConstructor());
RootedArrayObject valuesArray(cx);
RootedValue valuesArrayVal(cx);
if (IsWrapper(resultCapability.promise())) {
JSObject* unwrappedPromiseObj =
CheckedUnwrapStatic(resultCapability.promise());
MOZ_ASSERT(unwrappedPromiseObj);
{
AutoRealm ar(cx, unwrappedPromiseObj);
valuesArray = NewDenseEmptyArray(cx);
if (!valuesArray) {
return false;
}
}
valuesArrayVal.setObject(*valuesArray);
if (!cx->compartment()->wrap(cx, &valuesArrayVal)) {
return false;
}
} else {
valuesArray = NewDenseEmptyArray(cx);
if (!valuesArray) {
return false;
}
valuesArrayVal.setObject(*valuesArray);
}
Rooted<PromiseAllDataHolder*> dataHolder(cx);
dataHolder =
NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal,
resultCapability.resolve());
if (!dataHolder) {
return false;
}
uint32_t index = 0;
auto getResolve = [cx, &valuesArray, &dataHolder, &index]() -> JSObject* {
{ AutoRealm ar(cx, valuesArray);
if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) {
return nullptr;
}
}
JSFunction* resolveFunc =
NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject);
if (!resolveFunc) {
return nullptr;
}
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data,
ObjectValue(*dataHolder));
resolveFunc->setExtendedSlot(
PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index));
dataHolder->increaseRemainingCount();
index++;
MOZ_ASSERT(index > 0);
return resolveFunc;
};
if (!CommonPerformPromiseAllRace(cx, iterator, C, resultCapability, done,
true, getResolve)) {
return false;
}
int32_t remainingCount = dataHolder->decreaseRemainingCount();
if (remainingCount == 0) {
return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal,
ResolveMode, resultCapability.promise());
}
return true;
}
static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSFunction* resolve = &args.callee().as<JSFunction>();
RootedValue xVal(cx, args.get(0));
const Value& dataVal =
resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data);
if (dataVal.isUndefined()) {
args.rval().setUndefined();
return true;
}
Rooted<PromiseAllDataHolder*> data(
cx, &dataVal.toObject().as<PromiseAllDataHolder>());
resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data,
UndefinedValue());
int32_t index =
resolve
->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
.toInt32();
RootedValue valuesVal(cx, data->valuesArray());
RootedObject valuesObj(cx, &valuesVal.toObject());
if (IsProxy(valuesObj)) {
valuesObj = UncheckedUnwrap(valuesObj);
if (JS_IsDeadWrapper(valuesObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEAD_OBJECT);
return false;
}
AutoRealm ar(cx, valuesObj);
if (!cx->compartment()->wrap(cx, &xVal)) {
return false;
}
}
HandleNativeObject values = valuesObj.as<NativeObject>();
MOZ_ASSERT(values->getDenseElement(index).isUndefined());
values->setDenseElement(index, xVal);
uint32_t remainingCount = data->decreaseRemainingCount();
if (remainingCount == 0) {
RootedObject resolveAllFun(cx, data->resolveObj());
RootedObject promiseObj(cx, data->promiseObj());
if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode,
promiseObj)) {
return false;
}
}
args.rval().setUndefined();
return true;
}
static MOZ_MUST_USE bool PerformPromiseRace(
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
Handle<PromiseCapability> resultCapability, bool* done);
static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue iterable = args.get(0);
HandleValue CVal = args.thisv();
if (!CVal.isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_NONNULL_OBJECT,
"Receiver of Promise.race call");
return false;
}
RootedObject C(cx, &CVal.toObject());
Rooted<PromiseCapability> promiseCapability(cx);
if (!NewPromiseCapability(cx, C, &promiseCapability, false)) {
return false;
}
PromiseForOfIterator iter(cx);
if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
return AbruptRejectPromise(cx, args, promiseCapability);
}
if (!iter.valueIsIterable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
"Argument of Promise.race");
return AbruptRejectPromise(cx, args, promiseCapability);
}
bool done;
bool result = PerformPromiseRace(cx, iter, C, promiseCapability, &done);
if (!result) {
if (!done) {
iter.closeThrow();
}
return AbruptRejectPromise(cx, args, promiseCapability);
}
args.rval().setObject(*promiseCapability.promise());
return true;
}
static MOZ_MUST_USE bool PerformPromiseRace(
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
Handle<PromiseCapability> resultCapability, bool* done) {
*done = false;
MOZ_ASSERT(C->isConstructor());
bool isDefaultResolveFn =
IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction);
auto getResolve = [&resultCapability]() -> JSObject* {
return resultCapability.resolve();
};
return CommonPerformPromiseAllRace(cx, iterator, C, resultCapability, done,
isDefaultResolveFn, getResolve);
}
static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(
JSContext* cx, HandleValue thisVal, HandleValue argVal,
ResolutionMode mode) {
if (!thisVal.isObject()) {
const char* msg = mode == ResolveMode ? "Receiver of Promise.resolve call"
: "Receiver of Promise.reject call";
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_NONNULL_OBJECT, msg);
return nullptr;
}
RootedObject C(cx, &thisVal.toObject());
if (mode == ResolveMode && argVal.isObject()) {
RootedObject xObj(cx, &argVal.toObject());
bool isPromise = false;
if (xObj->is<PromiseObject>()) {
isPromise = true;
} else if (IsWrapper(xObj)) {
if (xObj->canUnwrapAs<PromiseObject>()) {
isPromise = true;
}
}
if (isPromise) {
RootedValue ctorVal(cx);
if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal)) {
return nullptr;
}
if (ctorVal == thisVal) {
return xObj;
}
}
}
Rooted<PromiseCapability> capability(cx);
if (!NewPromiseCapability(cx, C, &capability, true)) {
return nullptr;
}
if (!RunResolutionFunction(
cx, mode == ResolveMode ? capability.resolve() : capability.reject(),
argVal, mode, capability.promise())) {
return nullptr;
}
return capability.promise();
}
MOZ_MUST_USE JSObject* js::PromiseResolve(JSContext* cx,
HandleObject constructor,
HandleValue value) {
RootedValue C(cx, ObjectValue(*constructor));
return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode);
}
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;
}
JSObject* PromiseObject::unforgeableReject(JSContext* cx, HandleValue value) {
JSObject* promiseCtor = JS::GetPromiseConstructor(cx);
if (!promiseCtor) {
return nullptr;
}
RootedValue cVal(cx, ObjectValue(*promiseCtor));
return CommonStaticResolveRejectImpl(cx, cVal, value, RejectMode);
}
static bool 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;
}
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);
}
static bool Promise_static_species(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().set(args.thisv());
return true;
}
enum class IncumbentGlobalObject {
No,
Yes
};
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>()) {
MOZ_ASSERT_IF(resultCapability.resolve(),
IsCallable(resultCapability.resolve()));
MOZ_ASSERT_IF(resultCapability.reject(),
IsCallable(resultCapability.reject()));
} else {
MOZ_ASSERT(resultCapability.resolve());
MOZ_ASSERT(IsCallable(resultCapability.resolve()));
MOZ_ASSERT(resultCapability.reject());
MOZ_ASSERT(IsCallable(resultCapability.reject()));
}
} else {
JSObject* unwrappedPromise = UncheckedUnwrap(resultCapability.promise());
MOZ_ASSERT(unwrappedPromise->is<PromiseObject>());
MOZ_ASSERT(!resultCapability.resolve());
MOZ_ASSERT(!resultCapability.reject());
}
} else {
MOZ_ASSERT(!resultCapability.resolve());
MOZ_ASSERT(!resultCapability.reject());
MOZ_ASSERT(incumbentGlobalObjectOption == IncumbentGlobalObject::Yes);
}
#endif
MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull());
MOZ_ASSERT_IF(onFulfilled.isObject(), IsCallable(onFulfilled));
MOZ_ASSERT_IF(onFulfilled.isInt32(),
0 <= onFulfilled.toInt32() &&
onFulfilled.toInt32() < PromiseHandlerLimit);
MOZ_ASSERT(onRejected.isInt32() || onRejected.isObjectOrNull());
MOZ_ASSERT_IF(onRejected.isObject(), IsCallable(onRejected));
MOZ_ASSERT_IF(
onRejected.isInt32(),
0 <= onRejected.toInt32() && onRejected.toInt32() < PromiseHandlerLimit);
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);
reaction->setFixedSlot(ReactionRecordSlot_Promise,
ObjectOrNullValue(resultCapability.promise()));
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;
}
static bool PromiseThenNewPromiseCapability(
JSContext* cx, HandleObject promiseObj,
CreateDependentPromise createDependent,
MutableHandle<PromiseCapability> resultCapability) {
if (createDependent != CreateDependentPromise::Never) {
RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise,
IsPromiseSpecies));
if (!C) {
return false;
}
if (createDependent == CreateDependentPromise::Always ||
!IsNativeFunction(C, PromiseConstructor)) {
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;
}
MOZ_MUST_USE bool js::OriginalPromiseThen(
JSContext* cx, HandleObject promiseObj, HandleValue onFulfilled,
HandleValue onRejected, MutableHandleObject dependent,
CreateDependentPromise createDependent) {
RootedValue promiseVal(cx, ObjectValue(*promiseObj));
Rooted<PromiseObject*> promise(
cx,
UnwrapAndTypeCheckValue<PromiseObject>(cx, promiseVal, [cx, promiseObj] {
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
JSMSG_INCOMPATIBLE_PROTO, "Promise", "then",
promiseObj->getClass()->name);
}));
if (!promise) {
return false;
}
Rooted<PromiseCapability> resultCapability(cx);
if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent,
&resultCapability)) {
return false;
}
if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected,
resultCapability)) {
return false;
}
dependent.set(resultCapability.promise());
return true;
}
static MOZ_MUST_USE bool OriginalPromiseThenWithoutSettleHandlers(
JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseObject*> promiseToResolve) {
cx->check(promise);
Rooted<PromiseCapability> resultCapability(cx);
if (!PromiseThenNewPromiseCapability(
cx, promise, CreateDependentPromise::SkipIfCtorUnobservable,
&resultCapability)) {
return false;
}
return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve,
resultCapability);
}
static bool CanCallOriginalPromiseThenBuiltin(JSContext* cx,
HandleValue promise) {
return promise.isObject() && promise.toObject().is<PromiseObject>() &&
cx->realm()->promiseLookup.isDefaultInstance(
cx, &promise.toObject().as<PromiseObject>());
}
static bool OriginalPromiseThenBuiltin(JSContext* cx, HandleValue promiseVal,
HandleValue onFulfilled,
HandleValue onRejected,
MutableHandleValue rval, bool rvalUsed) {
cx->check(promiseVal, onFulfilled, onRejected);
MOZ_ASSERT(CanCallOriginalPromiseThenBuiltin(cx, promiseVal));
Rooted<PromiseObject*> promise(cx,
&promiseVal.toObject().as<PromiseObject>());
Rooted<PromiseCapability> resultCapability(cx);
if (rvalUsed) {
PromiseObject* resultPromise =
CreatePromiseObjectWithoutResolutionFunctions(cx);
if (!resultPromise) {
return false;
}
resultPromise->copyUserInteractionFlagsFrom(
promiseVal.toObject().as<PromiseObject>());
resultCapability.promise().set(resultPromise);
}
if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected,
resultCapability)) {
return false;
}
if (rvalUsed) {
rval.setObject(*resultCapability.promise());
} else {
rval.setUndefined();
}
return true;
}
MOZ_MUST_USE bool js::RejectPromiseWithPendingError(
JSContext* cx, Handle<PromiseObject*> promise) {
if (!cx->isExceptionPending()) {
mozilla::Unused << PromiseObject::reject(cx, promise, UndefinedHandleValue);
return false;
}
RootedValue exn(cx);
if (!GetAndClearException(cx, &exn)) {
return false;
}
return PromiseObject::reject(cx, promise, exn);
}
static MOZ_MUST_USE bool PerformPromiseThenWithReaction(
JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseReactionRecord*> reaction);
MOZ_MUST_USE PromiseObject* js::CreatePromiseObjectForAsync(JSContext* cx) {
PromiseObject* promise = CreatePromiseObjectWithoutResolutionFunctions(cx);
if (!promise) {
return nullptr;
}
AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC);
return promise;
}
bool js::IsPromiseForAsync(JSObject* promise) {
return promise->is<PromiseObject>() &&
PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_ASYNC);
}
MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx,
Handle<PromiseObject*> resultPromise,
HandleValue reason) {
return RejectPromiseInternal(cx, resultPromise, reason);
}
MOZ_MUST_USE bool js::AsyncFunctionReturned(
JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value) {
return ResolvePromiseInternal(cx, resultPromise, value);
}
template <typename T>
static MOZ_MUST_USE bool InternalAwait(JSContext* cx, HandleValue value,
HandleObject resultPromise,
HandleValue onFulfilled,
HandleValue onRejected, T extraStep) {
MOZ_ASSERT(onFulfilled.isInt32());
MOZ_ASSERT(onRejected.isInt32());
Rooted<PromiseObject*> promise(
cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
if (!promise) {
return false;
}
if (!ResolvePromiseInternal(cx, promise, value)) {
return false;
}
Rooted<PromiseCapability> resultCapability(cx);
resultCapability.promise().set(resultPromise);
Rooted<PromiseReactionRecord*> reaction(
cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected,
IncumbentGlobalObject::Yes));
if (!reaction) {
return false;
}
extraStep(reaction);
return PerformPromiseThenWithReaction(cx, promise, reaction);
}
MOZ_MUST_USE JSObject* js::AsyncFunctionAwait(
JSContext* cx, Handle<AsyncFunctionGeneratorObject*> genObj,
HandleValue value) {
RootedValue onFulfilled(
cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedFulfilled));
RootedValue onRejected(
cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncFunction(genObj);
};
if (!InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra)) {
return nullptr;
}
return genObj->promise();
}
MOZ_MUST_USE bool js::AsyncGeneratorAwait(
JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
HandleValue value) {
RootedValue onFulfilled(
cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedFulfilled));
RootedValue onRejected(
cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(asyncGenObj);
};
return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
}
bool js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args,
CompletionKind completionKind) {
HandleValue thisVal = args.thisv();
Rooted<PromiseObject*> resultPromise(
cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
if (!resultPromise) {
return false;
}
if (!thisVal.isObject() ||
!thisVal.toObject().is<AsyncFromSyncIteratorObject>()) {
RootedValue badGeneratorError(cx);
if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_ITERATOR, &badGeneratorError)) {
return false;
}
if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) {
return false;
}
args.rval().setObject(*resultPromise);
return true;
}
Rooted<AsyncFromSyncIteratorObject*> asyncIter(
cx, &thisVal.toObject().as<AsyncFromSyncIteratorObject>());
RootedObject iter(cx, asyncIter->iterator());
RootedValue func(cx);
if (completionKind == CompletionKind::Normal) {
func.set(asyncIter->nextMethod());
} else if (completionKind == CompletionKind::Return) {
if (!GetProperty(cx, iter, iter, cx->names().return_, &func)) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
if (func.isNullOrUndefined()) {
JSObject* resultObj = CreateIterResultObject(cx, args.get(0), true);
if (!resultObj) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
RootedValue resultVal(cx, ObjectValue(*resultObj));
if (!ResolvePromiseInternal(cx, resultPromise, resultVal)) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
args.rval().setObject(*resultPromise);
return true;
}
} else {
MOZ_ASSERT(completionKind == CompletionKind::Throw);
if (!GetProperty(cx, iter, iter, cx->names().throw_, &func)) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
if (func.isNullOrUndefined()) {
if (!RejectPromiseInternal(cx, resultPromise, args.get(0))) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
args.rval().setObject(*resultPromise);
return true;
}
}
RootedValue iterVal(cx, ObjectValue(*iter));
RootedValue resultVal(cx);
if (!Call(cx, func, iterVal, args.get(0), &resultVal)) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
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());
RootedValue doneVal(cx);
if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
bool done = ToBoolean(doneVal);
RootedValue value(cx);
if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) {
return AbruptRejectPromise(cx, args, resultPromise, nullptr);
}
RootedValue onFulfilled(
cx,
Int32Value(done ? PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone
: PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone));
RootedValue onRejected(cx, Int32Value(PromiseHandlerThrower));
auto extra = [](Handle<PromiseReactionRecord*> reaction) {};
if (!InternalAwait(cx, value, resultPromise, onFulfilled, onRejected,
extra)) {
return false;
}
args.rval().setObject(*resultPromise);
return true;
}
enum class ResumeNextKind { Enqueue, Reject, Resolve };
static MOZ_MUST_USE bool AsyncGeneratorResumeNext(
JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
ResumeNextKind kind, HandleValue valueOrException = UndefinedHandleValue,
bool done = false);
MOZ_MUST_USE bool js::AsyncGeneratorResolve(
JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value,
bool done) {
return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Resolve,
value, done);
}
MOZ_MUST_USE bool js::AsyncGeneratorReject(
JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
HandleValue exception) {
return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Reject,
exception);
}
static MOZ_MUST_USE bool AsyncGeneratorResumeNext(
JSContext* cx, Handle<AsyncGeneratorObject*> unwrappedGenerator,
ResumeNextKind kind,
HandleValue valueOrException_ ,
bool done ) {
RootedValue valueOrException(cx, valueOrException_);
while (true) {
switch (kind) {
case ResumeNextKind::Enqueue:
break;
case ResumeNextKind::Reject: {
HandleValue exception = valueOrException;
MOZ_ASSERT(!unwrappedGenerator->isQueueEmpty());
AsyncGeneratorRequest* request =
AsyncGeneratorObject::dequeueRequest(cx, unwrappedGenerator);
if (!request) {
return false;
}
Rooted<PromiseObject*> resultPromise(cx, request->promise());
unwrappedGenerator->cacheRequest(request);
if (!RejectPromiseInternal(cx, resultPromise, exception)) {
return false;
}
break;
}
case ResumeNextKind::Resolve: {
HandleValue value = valueOrException;
MOZ_ASSERT(!unwrappedGenerator->isQueueEmpty());
AsyncGeneratorRequest* request =
AsyncGeneratorObject::dequeueRequest(cx, unwrappedGenerator);
if (!request) {
return false;
}
Rooted<PromiseObject*> resultPromise(cx, request->promise());
unwrappedGenerator->cacheRequest(request);
JSObject* resultObj = CreateIterResultObject(cx, value, done);
if (!resultObj) {
return false;
}
RootedValue resultValue(cx, ObjectValue(*resultObj));
if (!ResolvePromiseInternal(cx, resultPromise, resultValue)) {
return false;
}
break;
}
}
MOZ_ASSERT(!unwrappedGenerator->isExecuting());
if (unwrappedGenerator->isAwaitingYieldReturn() ||
unwrappedGenerator->isAwaitingReturn()) {
return true;
}
if (unwrappedGenerator->isQueueEmpty()) {
return true;
}
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::peekRequest(unwrappedGenerator));
if (!request) {
return false;
}
CompletionKind completionKind = request->completionKind();
if (completionKind != CompletionKind::Normal) {
if (unwrappedGenerator->isSuspendedStart()) {
unwrappedGenerator->setCompleted();
}
if (unwrappedGenerator->isCompleted()) {
RootedValue value(cx, request->completionValue());
if (completionKind == CompletionKind::Return) {
unwrappedGenerator->setAwaitingReturn();
static constexpr int32_t ResumeNextReturnFulfilled =
PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled;
static constexpr int32_t ResumeNextReturnRejected =
PromiseHandlerAsyncGeneratorResumeNextReturnRejected;
RootedValue onFulfilled(cx, Int32Value(ResumeNextReturnFulfilled));
RootedValue onRejected(cx, Int32Value(ResumeNextReturnRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(unwrappedGenerator);
};
return InternalAwait(cx, value, nullptr, onFulfilled, onRejected,
extra);
}
MOZ_ASSERT(completionKind == CompletionKind::Throw);
kind = ResumeNextKind::Reject;
valueOrException.set(value);
continue;
}
} else if (unwrappedGenerator->isCompleted()) {
kind = ResumeNextKind::Resolve;
valueOrException.setUndefined();
done = true;
continue;
}
MOZ_ASSERT(unwrappedGenerator->isSuspendedStart() ||
unwrappedGenerator->isSuspendedYield());
unwrappedGenerator->setExecuting();
RootedValue argument(cx, request->completionValue());
if (completionKind == CompletionKind::Return) {
unwrappedGenerator->setAwaitingYieldReturn();
static constexpr int32_t YieldReturnAwaitedFulfilled =
PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled;
static constexpr int32_t YieldReturnAwaitedRejected =
PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected;
RootedValue onFulfilled(cx, Int32Value(YieldReturnAwaitedFulfilled));
RootedValue onRejected(cx, Int32Value(YieldReturnAwaitedRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(unwrappedGenerator);
};
return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected,
extra);
}
return AsyncGeneratorResume(cx, unwrappedGenerator, completionKind,
argument);
}
}
MOZ_MUST_USE bool js::AsyncGeneratorEnqueue(JSContext* cx,
HandleValue asyncGenVal,
CompletionKind completionKind,
HandleValue completionValue,
MutableHandleValue result) {
Rooted<PromiseObject*> resultPromise(
cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
if (!resultPromise) {
return false;
}
if (!asyncGenVal.isObject() ||
!asyncGenVal.toObject().is<AsyncGeneratorObject>()) {
RootedValue badGeneratorError(cx);
if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_GENERATOR, &badGeneratorError)) {
return false;
}
if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) {
return false;
}
result.setObject(*resultPromise);
return true;
}
Rooted<AsyncGeneratorObject*> asyncGenObj(
cx, &asyncGenVal.toObject().as<AsyncGeneratorObject>());
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::createRequest(cx, asyncGenObj, completionKind,
completionValue, resultPromise));
if (!request) {
return false;
}
if (!AsyncGeneratorObject::enqueueRequest(cx, asyncGenObj, request)) {
return false;
}
if (!asyncGenObj->isExecuting()) {
if (!AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Enqueue)) {
return false;
}
}
result.setObject(*resultPromise);
return true;
}
static bool Promise_catch_impl(JSContext* cx, unsigned argc, Value* vp,
bool rvalUsed) {
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue thisVal = args.thisv();
HandleValue onFulfilled = UndefinedHandleValue;
HandleValue onRejected = args.get(0);
if (CanCallOriginalPromiseThenBuiltin(cx, thisVal)) {
return OriginalPromiseThenBuiltin(cx, thisVal, onFulfilled, onRejected,
args.rval(), rvalUsed);
}
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(),
rvalUsed);
}
return Call(cx, thenVal, thisVal, UndefinedHandleValue, onRejected,
args.rval());
}
static MOZ_ALWAYS_INLINE bool IsPromiseThenOrCatchRetValImplicitlyUsed(
JSContext* cx) {
if (!cx->options().asyncStack()) {
return false;
}
if (cx->realm()->isDebuggee()) {
return true;
}
if (cx->runtime()->geckoProfiler().enabled()) {
return true;
}
if (JS::IsProfileTimelineRecordingEnabled()) {
return true;
}
return false;
}
static bool Promise_catch_noRetVal(JSContext* cx, unsigned argc, Value* vp) {
return Promise_catch_impl(cx, argc, vp,
IsPromiseThenOrCatchRetValImplicitlyUsed(cx));
}
static bool Promise_catch(JSContext* cx, unsigned argc, Value* vp) {
return Promise_catch_impl(cx, argc, vp, true);
}
static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal,
HandleValue onFulfilled, HandleValue onRejected,
MutableHandleValue rval, bool rvalUsed) {
if (!promiseVal.isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_NONNULL_OBJECT,
"Receiver of Promise.prototype.then call");
return false;
}
if (CanCallOriginalPromiseThenBuiltin(cx, promiseVal)) {
return OriginalPromiseThenBuiltin(cx, promiseVal, onFulfilled, onRejected,
rval, rvalUsed);
}
RootedObject promiseObj(cx, &promiseVal.toObject());
if (!promiseObj->is<PromiseObject>()) {
JSObject* unwrappedPromiseObj = CheckedUnwrapStatic(promiseObj);
if (!unwrappedPromiseObj) {
ReportAccessDenied(cx);
return false;
}
if (!unwrappedPromiseObj->is<PromiseObject>()) {
JS_ReportErrorNumberLatin1(
cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, "Promise",
"then", InformalValueTypeName(ObjectValue(*promiseObj)));
return false;
}
}
CreateDependentPromise createDependent =
rvalUsed ? CreateDependentPromise::Always
: CreateDependentPromise::SkipIfCtorUnobservable;
RootedObject resultPromise(cx);
if (!OriginalPromiseThen(cx, promiseObj, onFulfilled, onRejected,
&resultPromise, createDependent)) {
return false;
}
if (rvalUsed) {
rval.setObject(*resultPromise);
} else {
rval.setUndefined();
}
return true;
}
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(),
IsPromiseThenOrCatchRetValImplicitlyUsed(cx));
}
static bool 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);
}
static MOZ_MUST_USE bool PerformPromiseThen(
JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
HandleValue onRejected_, Handle<PromiseCapability> resultCapability) {
RootedValue onFulfilled(cx, onFulfilled_);
if (!IsCallable(onFulfilled)) {
onFulfilled = Int32Value(PromiseHandlerIdentity);
}
RootedValue onRejected(cx, onRejected_);
if (!IsCallable(onRejected)) {
onRejected = Int32Value(PromiseHandlerThrower);
}
Rooted<PromiseReactionRecord*> reaction(
cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected,
IncumbentGlobalObject::Yes));
if (!reaction) {
return false;
}
return PerformPromiseThenWithReaction(cx, promise, reaction);
}
static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(
JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseObject*> promiseToResolve,
Handle<PromiseCapability> resultCapability) {
HandleValue onFulfilled = NullHandleValue;
HandleValue onRejected = NullHandleValue;
Rooted<PromiseReactionRecord*> reaction(
cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected,
IncumbentGlobalObject::Yes));
if (!reaction) {
return false;
}
reaction->setIsDefaultResolvingHandler(promiseToResolve);
return PerformPromiseThenWithReaction(cx, promise, reaction);
}
static MOZ_MUST_USE bool PerformPromiseThenWithReaction(
JSContext* cx, Handle<PromiseObject*> unwrappedPromise,
Handle<PromiseReactionRecord*> reaction) {
JS::PromiseState state = unwrappedPromise->state();
int32_t flags = unwrappedPromise->flags();
if (state == JS::PromiseState::Pending) {
if (!AddPromiseReaction(cx, unwrappedPromise, reaction)) {
return false;
}
}
else {
MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled,
state == JS::PromiseState::Rejected);
RootedValue valueOrReason(cx, unwrappedPromise->valueOrReason());
if (!cx->compartment()->wrap(cx, &valueOrReason)) {
return false;
}
if (state == JS::PromiseState::Rejected &&
!(flags & PROMISE_FLAG_HANDLED)) {
cx->runtime()->removeUnhandledRejectedPromise(cx, unwrappedPromise);
}
if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) {
return false;
}
}
unwrappedPromise->setFixedSlot(PromiseSlot_Flags,
Int32Value(flags | PROMISE_FLAG_HANDLED));
return true;
}
static MOZ_MUST_USE bool AddPromiseReaction(
JSContext* cx, Handle<PromiseObject*> unwrappedPromise,
Handle<PromiseReactionRecord*> reaction) {
MOZ_RELEASE_ASSERT(reaction->is<PromiseReactionRecord>());
RootedValue reactionVal(cx, ObjectValue(*reaction));
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;
RootedValue reactionsVal(cx, promise->reactions());
if (reactionsVal.isUndefined()) {
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal);
return true;
}
RootedObject reactionsObj(cx, &reactionsVal.toObject());
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>()) {
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 {
MOZ_RELEASE_ASSERT(reactionsObj->is<NativeObject>());
HandleNativeObject 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;
}
static MOZ_MUST_USE bool AddDummyPromiseReactionForDebugger(
JSContext* cx, Handle<PromiseObject*> promise,
HandleObject dependentPromise) {
if (promise->state() != JS::PromiseState::Pending) {
return true;
}
MOZ_ASSERT(UncheckedUnwrap(dependentPromise)->is<PromiseObject>());
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(mozilla::Some(mozilla::TimeStamp::Now())) -
allocationTime();
}
bool PromiseObject::dependentPromises(JSContext* cx,
MutableHandle<GCVector<Value>> values) {
if (state() != JS::PromiseState::Pending) {
return true;
}
RootedValue reactionsVal(cx, reactions());
if (reactionsVal.isNullOrUndefined()) {
return true;
}
RootedObject reactionsObj(cx, &reactionsVal.toObject());
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>()) {
RootedObject promiseObj(
cx, reactionsObj->as<PromiseReactionRecord>().promise());
if (!promiseObj) {
return true;
}
if (!values.growBy(1)) {
return false;
}
values[0].setObject(*promiseObj);
return true;
}
MOZ_RELEASE_ASSERT(reactionsObj->is<NativeObject>());
HandleNativeObject reactions = reactionsObj.as<NativeObject>();
uint32_t len = reactions->getDenseInitializedLength();
MOZ_ASSERT(len >= 2);
uint32_t valuesIndex = 0;
Rooted<PromiseReactionRecord*> reaction(cx);
for (uint32_t i = 0; i < len; i++) {
JSObject* element = &reactions->getDenseElement(i).toObject();
if (IsProxy(element)) {
element = UncheckedUnwrap(element);
if (JS_IsDeadWrapper(element)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEAD_OBJECT);
return false;
}
}
MOZ_RELEASE_ASSERT(element->is<PromiseReactionRecord>());
reaction = &element->as<PromiseReactionRecord>();
RootedObject promiseObj(cx, reaction->promise());
if (!promiseObj) {
continue;
}
if (!values.growBy(1)) {
return false;
}
values[valuesIndex++].setObject(*promiseObj);
}
return true;
}
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 (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) {
return ResolvePromiseInternal(cx, promise, resolutionValue);
}
JSFunction* resolveFun = GetResolveFunctionFromPromise(promise);
if (!resolveFun) {
return true;
}
RootedValue funVal(cx, ObjectValue(*resolveFun));
if (!cx->compartment()->wrap(cx, &funVal)) {
return false;
}
RootedValue dummy(cx);
return Call(cx, funVal, UndefinedHandleValue, resolutionValue, &dummy);
}
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 (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) {
return ResolvePromise(cx, promise, rejectionValue,
JS::PromiseState::Rejected);
}
RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction));
MOZ_ASSERT(IsCallable(funVal));
RootedValue dummy(cx);
return Call(cx, funVal, UndefinedHandleValue, rejectionValue, &dummy);
}
void PromiseObject::onSettled(JSContext* cx, Handle<PromiseObject*> promise) {
PromiseDebugInfo::setResolutionInfo(cx, promise);
if (promise->state() == JS::PromiseState::Rejected &&
promise->isUnhandled()) {
cx->runtime()->addUnhandledRejectedPromise(cx, promise);
}
Debugger::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());
}
JSFunction* js::PromiseLookup::getPromiseConstructor(JSContext* cx) {
const Value& val = cx->global()->getConstructor(JSProto_Promise);
return val.isObject() ? &val.toObject().as<JSFunction>() : nullptr;
}
NativeObject* js::PromiseLookup::getPromisePrototype(JSContext* cx) {
const Value& val = cx->global()->getPrototype(JSProto_Promise);
return val.isObject() ? &val.toObject().as<NativeObject>() : nullptr;
}
bool js::PromiseLookup::isDataPropertyNative(JSContext* cx, NativeObject* obj,
uint32_t slot, JSNative native) {
JSFunction* fun;
if (!IsFunctionObject(obj->getSlot(slot), &fun)) {
return false;
}
return fun->maybeNative() == native && fun->realm() == cx->realm();
}
bool js::PromiseLookup::isAccessorPropertyNative(JSContext* cx, Shape* shape,
JSNative native) {
JSObject* getter = shape->getterObject();
return getter && IsNativeFunction(getter, native) &&
getter->as<JSFunction>().realm() == cx->realm();
}
void js::PromiseLookup::initialize(JSContext* cx) {
MOZ_ASSERT(state_ == State::Uninitialized);
NativeObject* promiseProto = getPromisePrototype(cx);
if (!promiseProto) {
return;
}
JSFunction* promiseCtor = getPromiseConstructor(cx);
MOZ_ASSERT(promiseCtor,
"The Promise constructor is initialized iff Promise.prototype is "
"initialized");
state_ = State::Disabled;
Shape* ctorShape = promiseProto->lookup(cx, cx->names().constructor);
if (!ctorShape || !ctorShape->isDataProperty()) {
return;
}
JSFunction* ctorFun;
if (!IsFunctionObject(promiseProto->getSlot(ctorShape->slot()), &ctorFun)) {
return;
}
if (ctorFun != promiseCtor) {
return;
}
Shape* thenShape = promiseProto->lookup(cx, cx->names().then);
if (!thenShape || !thenShape->isDataProperty()) {
return;
}
if (!isDataPropertyNative(cx, promiseProto, thenShape->slot(),
Promise_then)) {
return;
}
Shape* speciesShape =
promiseCtor->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
if (!speciesShape || !speciesShape->hasGetterObject()) {
return;
}
if (!isAccessorPropertyNative(cx, speciesShape, Promise_static_species)) {
return;
}
Shape* resolveShape = promiseCtor->lookup(cx, cx->names().resolve);
if (!resolveShape || !resolveShape->isDataProperty()) {
return;
}
if (!isDataPropertyNative(cx, promiseCtor, resolveShape->slot(),
Promise_static_resolve)) {
return;
}
MOZ_ASSERT(!IsInsideNursery(promiseCtor->lastProperty()));
MOZ_ASSERT(!IsInsideNursery(speciesShape));
MOZ_ASSERT(!IsInsideNursery(promiseProto->lastProperty()));
state_ = State::Initialized;
promiseConstructorShape_ = promiseCtor->lastProperty();
#ifdef DEBUG
promiseSpeciesShape_ = speciesShape;
#endif
promiseProtoShape_ = promiseProto->lastProperty();
promiseResolveSlot_ = resolveShape->slot();
promiseProtoConstructorSlot_ = ctorShape->slot();
promiseProtoThenSlot_ = thenShape->slot();
}
void js::PromiseLookup::reset() {
AlwaysPoison(this, 0xBB, sizeof(*this), MemCheckKind::MakeUndefined);
state_ = State::Uninitialized;
}
bool js::PromiseLookup::isPromiseStateStillSane(JSContext* cx) {
MOZ_ASSERT(state_ == State::Initialized);
NativeObject* promiseProto = getPromisePrototype(cx);
MOZ_ASSERT(promiseProto);
NativeObject* promiseCtor = getPromiseConstructor(cx);
MOZ_ASSERT(promiseCtor);
if (promiseProto->lastProperty() != promiseProtoShape_) {
return false;
}
if (promiseCtor->lastProperty() != promiseConstructorShape_) {
return false;
}
if (promiseProto->getSlot(promiseProtoConstructorSlot_) !=
ObjectValue(*promiseCtor)) {
return false;
}
if (!isDataPropertyNative(cx, promiseProto, promiseProtoThenSlot_,
Promise_then)) {
return false;
}
#ifdef DEBUG
MOZ_ASSERT(isAccessorPropertyNative(cx, promiseSpeciesShape_,
Promise_static_species));
#endif
if (!isDataPropertyNative(cx, promiseCtor, promiseResolveSlot_,
Promise_static_resolve)) {
return false;
}
return true;
}
bool js::PromiseLookup::ensureInitialized(JSContext* cx,
Reinitialize reinitialize) {
if (state_ == State::Uninitialized) {
initialize(cx);
} else if (state_ == State::Initialized) {
if (reinitialize == Reinitialize::Allowed) {
if (!isPromiseStateStillSane(cx)) {
reset();
initialize(cx);
}
} else {
MOZ_ASSERT(isPromiseStateStillSane(cx));
}
}
if (state_ != State::Initialized) {
return false;
}
MOZ_ASSERT(isPromiseStateStillSane(cx));
return true;
}
bool js::PromiseLookup::isDefaultPromiseState(JSContext* cx) {
return ensureInitialized(cx, Reinitialize::Allowed);
}
bool js::PromiseLookup::hasDefaultProtoAndNoShadowedProperties(
JSContext* cx, PromiseObject* promise) {
if (promise->staticPrototype() != getPromisePrototype(cx)) {
return false;
}
return promise->lastProperty()->isEmptyShape();
}
bool js::PromiseLookup::isDefaultInstance(JSContext* cx, PromiseObject* promise,
Reinitialize reinitialize) {
if (!ensureInitialized(cx, reinitialize)) {
return false;
}
return hasDefaultProtoAndNoShadowedProperties(cx, promise);
}
static MOZ_MUST_USE bool IsTopMostAsyncFunctionCall(JSContext* cx) {
FrameIter iter(cx);
if (iter.done()) {
return false;
}
MOZ_ASSERT(iter.isFunctionFrame());
MOZ_ASSERT(iter.calleeTemplate()->isAsync());
#ifdef DEBUG
bool isGenerator = iter.calleeTemplate()->isGenerator();
#endif
++iter;
if (iter.done()) {
return false;
}
if (!iter.isFunctionFrame()) {
MOZ_ASSERT(!isGenerator);
return false;
}
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;
if (iter.done()) {
return true;
}
return false;
}
MOZ_MUST_USE bool js::TrySkipAwait(JSContext* cx, HandleValue val,
bool* canSkip, MutableHandleValue resolved) {
if (!cx->canSkipEnqueuingJobs) {
*canSkip = false;
return true;
}
if (!IsTopMostAsyncFunctionCall(cx)) {
*canSkip = false;
return true;
}
if (!val.isObject()) {
resolved.set(val);
*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) {
*canSkip = false;
return true;
}
resolved.set(promise->value());
*canSkip = true;
return true;
}
OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx,
Handle<PromiseObject*> promise)
: runtime_(cx->runtime()), promise_(cx, promise), registered_(false) {
MOZ_ASSERT(runtime_ == promise_->zone()->runtimeFromMainThread());
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());
}
OffThreadPromiseTask::~OffThreadPromiseTask() {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
MOZ_ASSERT(state.initialized());
if (registered_) {
unregister(state);
}
}
bool OffThreadPromiseTask::init(JSContext* cx) {
MOZ_ASSERT(cx->runtime() == runtime_);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
MOZ_ASSERT(state.initialized());
LockGuard<Mutex> lock(state.mutex_);
if (!state.live_.putNew(this)) {
ReportOutOfMemory(cx);
return false;
}
registered_ = true;
return true;
}
void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state) {
MOZ_ASSERT(registered_);
LockGuard<Mutex> lock(state.mutex_);
state.live_.remove(this);
registered_ = false;
}
void OffThreadPromiseTask::run(JSContext* cx,
MaybeShuttingDown maybeShuttingDown) {
MOZ_ASSERT(cx->runtime() == runtime_);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
MOZ_ASSERT(registered_);
OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
MOZ_ASSERT(state.initialized());
unregister(state);
if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) {
AutoRealm ar(cx, promise_);
if (!resolve(cx, promise_)) {
cx->clearPendingException();
}
}
js_delete(this);
}
void OffThreadPromiseTask::dispatchResolveAndDestroy() {
MOZ_ASSERT(registered_);
OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
MOZ_ASSERT(state.initialized());
MOZ_ASSERT((LockGuard<Mutex>(state.mutex_), state.live_.has(this)));
if (state.dispatchToEventLoopCallback_(state.dispatchToEventLoopClosure_,
this)) {
return;
}
LockGuard<Mutex> lock(state.mutex_);
state.numCanceled_++;
if (state.numCanceled_ == state.live_.count()) {
state.allCanceled_.notify_one();
}
}
OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState()
: dispatchToEventLoopCallback_(nullptr),
dispatchToEventLoopClosure_(nullptr),
mutex_(mutexid::OffThreadPromiseState),
numCanceled_(0),
internalDispatchQueueClosed_(false) {}
OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState() {
MOZ_ASSERT(live_.empty());
MOZ_ASSERT(numCanceled_ == 0);
MOZ_ASSERT(internalDispatchQueue_.empty());
MOZ_ASSERT(!initialized());
}
void OffThreadPromiseRuntimeState::init(
JS::DispatchToEventLoopCallback callback, void* closure) {
MOZ_ASSERT(!initialized());
dispatchToEventLoopCallback_ = callback;
dispatchToEventLoopClosure_ = closure;
MOZ_ASSERT(initialized());
}
bool OffThreadPromiseRuntimeState::internalDispatchToEventLoop(
void* closure, JS::Dispatchable* d) {
OffThreadPromiseRuntimeState& state =
*reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
MOZ_ASSERT(state.usingInternalDispatchQueue());
LockGuard<Mutex> lock(state.mutex_);
if (state.internalDispatchQueueClosed_) {
return false;
}
AutoEnterOOMUnsafeRegion noOOM;
if (!state.internalDispatchQueue_.pushBack(d)) {
noOOM.crash("internalDispatchToEventLoop");
}
state.internalDispatchQueueAppended_.notify_one();
return true;
}
bool OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const {
return dispatchToEventLoopCallback_ == internalDispatchToEventLoop;
}
void OffThreadPromiseRuntimeState::initInternalDispatchQueue() {
init(internalDispatchToEventLoop, this);
MOZ_ASSERT(usingInternalDispatchQueue());
}
bool OffThreadPromiseRuntimeState::initialized() const {
return !!dispatchToEventLoopCallback_;
}
void OffThreadPromiseRuntimeState::internalDrain(JSContext* cx) {
MOZ_ASSERT(usingInternalDispatchQueue());
MOZ_ASSERT(!internalDispatchQueueClosed_);
for (;;) {
JS::Dispatchable* d;
{
LockGuard<Mutex> lock(mutex_);
MOZ_ASSERT_IF(!internalDispatchQueue_.empty(), !live_.empty());
if (live_.empty()) {
return;
}
while (internalDispatchQueue_.empty()) {
internalDispatchQueueAppended_.wait(lock);
}
d = internalDispatchQueue_.popCopyFront();
}
d->run(cx, JS::Dispatchable::NotShuttingDown);
}
}
bool OffThreadPromiseRuntimeState::internalHasPending() {
MOZ_ASSERT(usingInternalDispatchQueue());
MOZ_ASSERT(!internalDispatchQueueClosed_);
LockGuard<Mutex> lock(mutex_);
MOZ_ASSERT_IF(!internalDispatchQueue_.empty(), !live_.empty());
return !live_.empty();
}
void OffThreadPromiseRuntimeState::shutdown(JSContext* cx) {
if (!initialized()) {
return;
}
if (usingInternalDispatchQueue()) {
DispatchableFifo dispatchQueue;
{
LockGuard<Mutex> lock(mutex_);
mozilla::Swap(dispatchQueue, internalDispatchQueue_);
MOZ_ASSERT(internalDispatchQueue_.empty());
internalDispatchQueueClosed_ = true;
}
for (JS::Dispatchable* d : dispatchQueue) {
d->run(cx, JS::Dispatchable::ShuttingDown);
}
}
{
LockGuard<Mutex> lock(mutex_);
while (live_.count() != numCanceled_) {
MOZ_ASSERT(numCanceled_ < live_.count());
allCanceled_.wait(lock);
}
}
for (OffThreadPromiseTaskSet::Range r = live_.all(); !r.empty();
r.popFront()) {
OffThreadPromiseTask* task = r.front();
MOZ_ASSERT(task->registered_);
task->registered_ = false;
js_delete(task);
}
live_.clear();
numCanceled_ = 0;
dispatchToEventLoopCallback_ = nullptr;
MOZ_ASSERT(!initialized());
}
JS::AutoDebuggerJobQueueInterruption::AutoDebuggerJobQueueInterruption(
MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: cx(nullptr) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
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},
{0},
JSJitInfo::IgnoresReturnValueNative,
JSJitInfo::AliasEverything,
JSVAL_TYPE_UNDEFINED,
};
const JSJitInfo promise_catch_info = {
{(JSJitGetterOp)Promise_catch_noRetVal},
{0},
{0},
JSJitInfo::IgnoresReturnValueNative,
JSJitInfo::AliasEverything,
JSVAL_TYPE_UNDEFINED,
};
static const JSFunctionSpec promise_methods[] = {
JS_FNINFO("then", 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("race", Promise_static_race, 1, 0),
JS_FN("reject", Promise_reject, 1, 0),
JS_FN("resolve", Promise_static_resolve, 1, 0), JS_FS_END};
static const JSPropertySpec promise_static_properties[] = {
JS_SYM_GET(species, 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 Class 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 Class PromiseObject::protoClass_ = {
"PromiseProto", JSCLASS_HAS_CACHED_PROTO(JSProto_Promise),
JS_NULL_CLASS_OPS, &PromiseObjectClassSpec};