mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 14:54:25 +00:00
300f97150c
Tag #1287
2794 lines
99 KiB
C++
2794 lines
99 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
#include "builtin/Promise.h"
|
|
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
|
|
#include "jscntxt.h"
|
|
|
|
#include "gc/Heap.h"
|
|
#include "js/Debug.h"
|
|
#include "vm/AsyncFunction.h"
|
|
#include "vm/SelfHosting.h"
|
|
|
|
#include "jsobjinlines.h"
|
|
|
|
#include "vm/NativeObject-inl.h"
|
|
|
|
using namespace js;
|
|
|
|
static double
|
|
MillisecondsSinceStartup()
|
|
{
|
|
auto now = mozilla::TimeStamp::Now();
|
|
bool ignored;
|
|
return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
|
|
}
|
|
|
|
enum PromiseHandler {
|
|
PromiseHandlerIdentity = 0,
|
|
PromiseHandlerThrower,
|
|
PromiseHandlerAwaitFulfilled,
|
|
PromiseHandlerAwaitRejected,
|
|
};
|
|
|
|
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 PromiseAllDataHolderSlots {
|
|
PromiseAllDataHolderSlot_Promise = 0,
|
|
PromiseAllDataHolderSlot_RemainingElements,
|
|
PromiseAllDataHolderSlot_ValuesArray,
|
|
PromiseAllDataHolderSlot_ResolveFunction,
|
|
PromiseAllDataHolderSlots,
|
|
};
|
|
|
|
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)
|
|
{
|
|
Rooted<PromiseAllDataHolder*> dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
|
|
if (!dataHolder)
|
|
return nullptr;
|
|
|
|
assertSameCompartment(cx, resultPromise);
|
|
assertSameCompartment(cx, valuesArray);
|
|
assertSameCompartment(cx, 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;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for GetAndClearException that handles cases where no exception is
|
|
* pending, but an error occurred. This can be the case if an OOM was
|
|
* encountered while throwing the error.
|
|
*/
|
|
static bool
|
|
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);
|
|
|
|
// ES2016, 25.4.1.1.1, Steps 1.a-b.
|
|
// Extracting all of this internal spec algorithm into a helper function would
|
|
// be tedious, so the check in step 1 and the entirety of step 2 aren't
|
|
// included.
|
|
static bool
|
|
AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject)
|
|
{
|
|
// Step 1.a.
|
|
RootedValue reason(cx);
|
|
if (!MaybeGetAndClearException(cx, &reason))
|
|
return false;
|
|
|
|
if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj))
|
|
return false;
|
|
|
|
// Step 1.b.
|
|
args.rval().setObject(*promiseObj);
|
|
return true;
|
|
}
|
|
|
|
enum ReactionRecordSlots {
|
|
ReactionRecordSlot_Promise = 0,
|
|
ReactionRecordSlot_OnFulfilled,
|
|
ReactionRecordSlot_OnRejected,
|
|
ReactionRecordSlot_Resolve,
|
|
ReactionRecordSlot_Reject,
|
|
ReactionRecordSlot_IncumbentGlobalObject,
|
|
ReactionRecordSlot_Flags,
|
|
ReactionRecordSlot_HandlerArg,
|
|
ReactionRecordSlots,
|
|
};
|
|
|
|
#define REACTION_FLAG_RESOLVED 0x1
|
|
#define REACTION_FLAG_FULFILLED 0x2
|
|
#define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4
|
|
#define REACTION_FLAG_AWAIT 0x8
|
|
|
|
// ES2016, 25.4.1.2.
|
|
class PromiseReactionRecord : public NativeObject
|
|
{
|
|
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 setTargetState(JS::PromiseState state) {
|
|
int32_t flags = this->flags();
|
|
MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED));
|
|
MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending.");
|
|
flags |= REACTION_FLAG_RESOLVED;
|
|
if (state == JS::PromiseState::Fulfilled)
|
|
flags |= REACTION_FLAG_FULFILLED;
|
|
setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
|
|
}
|
|
void setIsAwait() {
|
|
int32_t flags = this->flags();
|
|
flags |= REACTION_FLAG_AWAIT;
|
|
setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
|
|
}
|
|
bool isAwait() {
|
|
int32_t flags = this->flags();
|
|
return flags & REACTION_FLAG_AWAIT;
|
|
}
|
|
Value handler() {
|
|
MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
|
|
uint32_t slot = targetState() == JS::PromiseState::Fulfilled
|
|
? ReactionRecordSlot_OnFulfilled
|
|
: ReactionRecordSlot_OnRejected;
|
|
return getFixedSlot(slot);
|
|
}
|
|
Value handlerArg() {
|
|
MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
|
|
return getFixedSlot(ReactionRecordSlot_HandlerArg);
|
|
}
|
|
void setHandlerArg(Value& arg) {
|
|
MOZ_ASSERT(targetState() == JS::PromiseState::Pending);
|
|
setFixedSlot(ReactionRecordSlot_HandlerArg, arg);
|
|
}
|
|
JSObject* incumbentGlobalObject() {
|
|
return getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull();
|
|
}
|
|
};
|
|
|
|
const Class PromiseReactionRecord::class_ = {
|
|
"PromiseReactionRecord",
|
|
JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots)
|
|
};
|
|
|
|
static void
|
|
AddPromiseFlags(PromiseObject& promise, int32_t flag)
|
|
{
|
|
int32_t flags = promise.getFixedSlot(PromiseSlot_Flags).toInt32();
|
|
promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag));
|
|
}
|
|
|
|
static bool
|
|
PromiseHasAnyFlag(PromiseObject& promise, int32_t flag)
|
|
{
|
|
return promise.getFixedSlot(PromiseSlot_Flags).toInt32() & flag;
|
|
}
|
|
|
|
static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp);
|
|
static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp);
|
|
|
|
// ES2016, 25.4.1.3.
|
|
static MOZ_MUST_USE bool
|
|
CreateResolvingFunctions(JSContext* cx, HandleValue promise,
|
|
MutableHandleValue resolveVal,
|
|
MutableHandleValue rejectVal)
|
|
{
|
|
RootedAtom funName(cx, cx->names().empty);
|
|
RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
|
|
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
|
|
if (!resolve)
|
|
return false;
|
|
|
|
RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
|
|
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
|
|
if (!reject)
|
|
return false;
|
|
|
|
resolve->setExtendedSlot(ResolveFunctionSlot_Promise, promise);
|
|
resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*reject));
|
|
|
|
reject->setExtendedSlot(RejectFunctionSlot_Promise, promise);
|
|
reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, ObjectValue(*resolve));
|
|
|
|
resolveVal.setObject(*resolve);
|
|
rejectVal.setObject(*reject);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ClearResolutionFunctionSlots(JSFunction* resolutionFun);
|
|
static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj,
|
|
HandleValue reason);
|
|
|
|
// ES2016, 25.4.1.3.1.
|
|
static bool
|
|
RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedFunction reject(cx, &args.callee().as<JSFunction>());
|
|
RootedValue reasonVal(cx, args.get(0));
|
|
|
|
// Steps 1-2.
|
|
RootedValue promiseVal(cx, reject->getExtendedSlot(RejectFunctionSlot_Promise));
|
|
|
|
// Steps 3-4.
|
|
// If the Promise isn't available anymore, it has been resolved and the
|
|
// reference to it removed to make it eligible for collection.
|
|
if (promiseVal.isUndefined()) {
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
RootedObject promise(cx, &promiseVal.toObject());
|
|
|
|
// In some cases the Promise reference on the resolution function won't
|
|
// have been removed during resolution, so we need to check that here,
|
|
// too.
|
|
if (promise->is<PromiseObject>() &&
|
|
PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED))
|
|
{
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// Step 5.
|
|
// Here, we only remove the Promise reference from the resolution
|
|
// functions. Actually marking it as fulfilled/rejected happens later.
|
|
ClearResolutionFunctionSlots(reject);
|
|
|
|
// Step 6.
|
|
bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal);
|
|
if (result)
|
|
args.rval().setUndefined();
|
|
return result;
|
|
}
|
|
|
|
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);
|
|
|
|
// ES2016, 25.4.1.3.2, steps 7-13.
|
|
static MOZ_MUST_USE bool
|
|
ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal)
|
|
{
|
|
// Step 7.
|
|
if (!resolutionVal.isObject())
|
|
return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
|
|
|
|
RootedObject resolution(cx, &resolutionVal.toObject());
|
|
|
|
// Step 8.
|
|
RootedValue thenVal(cx);
|
|
bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
|
|
|
|
// Step 9.
|
|
if (!status) {
|
|
RootedValue error(cx);
|
|
if (!MaybeGetAndClearException(cx, &error))
|
|
return false;
|
|
|
|
return RejectMaybeWrappedPromise(cx, promise, error);
|
|
}
|
|
|
|
// Step 10 (implicit).
|
|
|
|
// Step 11.
|
|
if (!IsCallable(thenVal))
|
|
return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
|
|
|
|
// Step 12.
|
|
RootedValue promiseVal(cx, ObjectValue(*promise));
|
|
if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
|
|
return false;
|
|
|
|
// Step 13.
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.1.3.2.
|
|
static bool
|
|
ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedFunction resolve(cx, &args.callee().as<JSFunction>());
|
|
RootedValue resolutionVal(cx, args.get(0));
|
|
|
|
// Steps 1-2.
|
|
RootedValue promiseVal(cx, resolve->getExtendedSlot(ResolveFunctionSlot_Promise));
|
|
|
|
// Steps 3-4.
|
|
// If the Promise isn't available anymore, it has been rejected and the
|
|
// reference to it removed to make it eligible for collection.
|
|
if (promiseVal.isUndefined()) {
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
RootedObject promise(cx, &promiseVal.toObject());
|
|
|
|
// In some cases the Promise reference on the resolution function won't
|
|
// have been removed during resolution, so we need to check that here,
|
|
// too.
|
|
if (promise->is<PromiseObject>() &&
|
|
PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED))
|
|
{
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// Step 5.
|
|
// Here, we only remove the Promise reference from the resolution
|
|
// functions. Actually marking it as fulfilled/rejected happens later.
|
|
ClearResolutionFunctionSlots(resolve);
|
|
|
|
// Step 6.
|
|
if (resolutionVal == promiseVal) {
|
|
// Step 6.a.
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
|
|
RootedValue selfResolutionError(cx);
|
|
bool status = GetAndClearException(cx, &selfResolutionError);
|
|
MOZ_ASSERT(status);
|
|
|
|
// Step 6.b.
|
|
status = RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
|
|
if (status)
|
|
args.rval().setUndefined();
|
|
return status;
|
|
}
|
|
|
|
bool status = ResolvePromiseInternal(cx, promise, resolutionVal);
|
|
if (status)
|
|
args.rval().setUndefined();
|
|
return status;
|
|
}
|
|
|
|
static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
|
|
|
|
/**
|
|
* Tells the embedding to enqueue a Promise reaction job, based on
|
|
* three parameters:
|
|
* reactionObj - The reaction record.
|
|
* handlerArg_ - The first and only argument to pass to the handler invoked by
|
|
* the job. This will be stored on the reaction record.
|
|
* targetState - The PromiseState this reaction job targets. This decides
|
|
* whether the onFulfilled or onRejected handler is called.
|
|
*/
|
|
MOZ_MUST_USE static bool
|
|
EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
|
|
HandleValue handlerArg_, JS::PromiseState targetState)
|
|
{
|
|
// The reaction might have been stored on a Promise from another
|
|
// compartment, which means it would've been wrapped in a CCW.
|
|
// To properly handle that case here, unwrap it and enter its
|
|
// compartment, where the job creation should take place anyway.
|
|
Rooted<PromiseReactionRecord*> reaction(cx);
|
|
RootedValue handlerArg(cx, handlerArg_);
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
if (!IsProxy(reactionObj)) {
|
|
MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
|
|
reaction = &reactionObj->as<PromiseReactionRecord>();
|
|
} else {
|
|
if (JS_IsDeadWrapper(UncheckedUnwrap(reactionObj))) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
|
return false;
|
|
}
|
|
reaction = &UncheckedUnwrap(reactionObj)->as<PromiseReactionRecord>();
|
|
MOZ_RELEASE_ASSERT(reaction->is<PromiseReactionRecord>());
|
|
ac.emplace(cx, reaction);
|
|
if (!reaction->compartment()->wrap(cx, &handlerArg))
|
|
return false;
|
|
}
|
|
|
|
// Must not enqueue a reaction job more than once.
|
|
MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
|
|
|
|
assertSameCompartment(cx, handlerArg);
|
|
reaction->setHandlerArg(handlerArg.get());
|
|
|
|
RootedValue reactionVal(cx, ObjectValue(*reaction));
|
|
|
|
reaction->setTargetState(targetState);
|
|
RootedValue handler(cx, reaction->handler());
|
|
|
|
// If we have a handler callback, we enter that handler's compartment so
|
|
// that the promise reaction job function is created in that compartment.
|
|
// That guarantees that the embedding ends up with the right entry global.
|
|
// This is relevant for some html APIs like fetch that derive information
|
|
// from said global.
|
|
mozilla::Maybe<AutoCompartment> ac2;
|
|
if (handler.isObject()) {
|
|
RootedObject handlerObj(cx, &handler.toObject());
|
|
|
|
// The unwrapping has to be unchecked because we specifically want to
|
|
// be able to use handlers with wrappers that would only allow calls.
|
|
// E.g., it's ok to have a handler from a chrome compartment in a
|
|
// reaction to a content compartment's Promise instance.
|
|
handlerObj = UncheckedUnwrap(handlerObj);
|
|
MOZ_ASSERT(handlerObj);
|
|
ac2.emplace(cx, handlerObj);
|
|
|
|
// We need to wrap the reaction to store it on the job function.
|
|
if (!cx->compartment()->wrap(cx, &reactionVal))
|
|
return false;
|
|
}
|
|
|
|
// Create the JS function to call when the job is triggered.
|
|
RootedAtom funName(cx, cx->names().empty);
|
|
RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
|
|
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
|
|
if (!job)
|
|
return false;
|
|
|
|
// Store the reaction on the reaction job.
|
|
job->setExtendedSlot(ReactionJobSlot_ReactionRecord, reactionVal);
|
|
|
|
// When using JS::AddPromiseReactions, no actual promise is created, so we
|
|
// might not have one here.
|
|
// Additionally, we might have an object here that isn't an instance of
|
|
// Promise. This can happen if content overrides the value of
|
|
// Promise[@@species] (or invokes Promise#then on a Promise subclass
|
|
// instance with a non-default @@species value on the constructor) with a
|
|
// function that returns objects that're not Promise (subclass) instances.
|
|
// In that case, we just pretend we didn't have an object in the first
|
|
// place.
|
|
// If after all this we do have an object, wrap it in case we entered the
|
|
// handler's compartment above, because we should pass objects from a
|
|
// single compartment to the enqueuePromiseJob callback.
|
|
RootedObject promise(cx, reaction->promise());
|
|
if (promise && promise->is<PromiseObject>()) {
|
|
if (!cx->compartment()->wrap(cx, &promise))
|
|
return false;
|
|
}
|
|
|
|
// Using objectFromIncumbentGlobal, we can derive the incumbent global by
|
|
// unwrapping and then getting the global. This is very convoluted, but
|
|
// much better than having to store the original global as a private value
|
|
// because we couldn't wrap it to store it as a normal JS value.
|
|
RootedObject global(cx);
|
|
RootedObject objectFromIncumbentGlobal(cx, reaction->incumbentGlobalObject());
|
|
if (objectFromIncumbentGlobal) {
|
|
objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
|
|
MOZ_ASSERT(objectFromIncumbentGlobal);
|
|
global = &objectFromIncumbentGlobal->global();
|
|
}
|
|
|
|
// Note: the global we pass here might be from a different compartment
|
|
// than job and promise. While it's somewhat unusual to pass objects
|
|
// from multiple compartments, in this case we specifically need the
|
|
// global to be unwrapped because wrapping and unwrapping aren't
|
|
// necessarily symmetric for globals.
|
|
return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
|
|
}
|
|
|
|
static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal,
|
|
JS::PromiseState state, HandleValue valueOrReason);
|
|
|
|
// ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
|
|
static MOZ_MUST_USE bool
|
|
ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueOrReason,
|
|
JS::PromiseState state)
|
|
{
|
|
// Step 1.
|
|
MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
|
|
MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected);
|
|
|
|
// Step 2.
|
|
// We only have one list of reactions for both resolution types. So
|
|
// instead of getting the right list of reactions, we determine the
|
|
// resolution type to retrieve the right information from the
|
|
// reaction records.
|
|
RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
|
|
|
|
// Steps 3-5.
|
|
// The same slot is used for the reactions list and the result, so setting
|
|
// the result also removes the reactions list.
|
|
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason);
|
|
|
|
// Step 6.
|
|
int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
|
|
flags |= PROMISE_FLAG_RESOLVED;
|
|
if (state == JS::PromiseState::Fulfilled)
|
|
flags |= PROMISE_FLAG_FULFILLED;
|
|
promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags));
|
|
|
|
// Also null out the resolve/reject functions so they can be GC'd.
|
|
promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue());
|
|
|
|
// Now that everything else is done, do the things the debugger needs.
|
|
// Step 7 of RejectPromise implemented in onSettled.
|
|
PromiseObject::onSettled(cx, promise);
|
|
|
|
// Step 7 of FulfillPromise.
|
|
// Step 8 of RejectPromise.
|
|
if (reactionsVal.isObject())
|
|
return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason);
|
|
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.1.4.
|
|
static MOZ_MUST_USE bool
|
|
FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_)
|
|
{
|
|
Rooted<PromiseObject*> promise(cx);
|
|
RootedValue value(cx, value_);
|
|
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
if (!IsProxy(promiseObj)) {
|
|
promise = &promiseObj->as<PromiseObject>();
|
|
} else {
|
|
if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
|
return false;
|
|
}
|
|
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
|
|
ac.emplace(cx, promise);
|
|
if (!promise->compartment()->wrap(cx, &value))
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
|
|
|
|
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*
|
|
CreatePromiseObjectWithDefaultResolution(JSContext* cx)
|
|
{
|
|
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
|
|
if (!promise)
|
|
return nullptr;
|
|
|
|
AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
|
|
PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
|
|
return promise;
|
|
}
|
|
|
|
// ES2016, 25.4.1.5.
|
|
static MOZ_MUST_USE bool
|
|
NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
|
|
MutableHandleObject resolve, MutableHandleObject reject,
|
|
bool canOmitResolutionFunctions)
|
|
{
|
|
RootedValue cVal(cx, ObjectValue(*C));
|
|
|
|
// Steps 1-2.
|
|
if (!IsConstructor(C)) {
|
|
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr);
|
|
return false;
|
|
}
|
|
|
|
// If we'd call the original Promise constructor and know that the
|
|
// resolve/reject functions won't ever escape to content, we can skip
|
|
// creating and calling the executor function and instead return a Promise
|
|
// marked as having default resolve/reject functions.
|
|
//
|
|
// This can't be used in Promise.all and Promise.race because we have to
|
|
// pass the reject (and resolve, in the race case) function to thenables
|
|
// in the list passed to all/race, which (potentially) means exposing them
|
|
// to content.
|
|
if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) {
|
|
promise.set(CreatePromiseObjectWithDefaultResolution(cx));
|
|
if (!promise)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Step 3 (omitted).
|
|
|
|
// Step 4.
|
|
RootedAtom funName(cx, cx->names().empty);
|
|
RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
|
|
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
|
|
if (!executor)
|
|
return false;
|
|
|
|
// Step 5 (omitted).
|
|
|
|
// Step 6.
|
|
FixedConstructArgs<1> cargs(cx);
|
|
cargs[0].setObject(*executor);
|
|
if (!Construct(cx, cVal, cargs, cVal, promise))
|
|
return false;
|
|
|
|
// Step 7.
|
|
RootedValue resolveVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve));
|
|
if (!IsCallable(resolveVal)) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
|
|
return false;
|
|
}
|
|
|
|
// Step 8.
|
|
RootedValue rejectVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject));
|
|
if (!IsCallable(rejectVal)) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
|
|
return false;
|
|
}
|
|
|
|
// Step 9 (well, the equivalent for all of promiseCapabilities' fields.)
|
|
resolve.set(&resolveVal.toObject());
|
|
reject.set(&rejectVal.toObject());
|
|
|
|
// Step 10.
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.1.5.1.
|
|
static bool
|
|
GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedFunction F(cx, &args.callee().as<JSFunction>());
|
|
|
|
// Steps 1-2 (implicit).
|
|
|
|
// Steps 3-4.
|
|
if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() ||
|
|
!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined())
|
|
{
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
|
|
return false;
|
|
}
|
|
|
|
// Step 5.
|
|
F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0));
|
|
|
|
// Step 6.
|
|
F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1));
|
|
|
|
// Step 7.
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.1.7.
|
|
static MOZ_MUST_USE bool
|
|
RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_)
|
|
{
|
|
Rooted<PromiseObject*> promise(cx);
|
|
RootedValue reason(cx, reason_);
|
|
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
if (!IsProxy(promiseObj)) {
|
|
promise = &promiseObj->as<PromiseObject>();
|
|
} else {
|
|
if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
|
return false;
|
|
}
|
|
promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
|
|
ac.emplace(cx, promise);
|
|
|
|
// The rejection reason might've been created in a compartment with higher
|
|
// privileges than the Promise's. In that case, object-type rejection
|
|
// values might be wrapped into a wrapper that throws whenever the
|
|
// Promise's reaction handler wants to do anything useful with it. To
|
|
// avoid that situation, we synthesize a generic error that doesn't
|
|
// expose any privileged information but can safely be used in the
|
|
// rejection handler.
|
|
if (!promise->compartment()->wrap(cx, &reason))
|
|
return false;
|
|
if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) {
|
|
// Async stacks are only properly adopted if there's at least one
|
|
// interpreter frame active right now. If a thenable job with a
|
|
// throwing `then` function got us here, that'll not be the case,
|
|
// so we add one by throwing the error from self-hosted code.
|
|
FixedInvokeArgs<1> getErrorArgs(cx);
|
|
getErrorArgs[0].set(Int32Value(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON));
|
|
if (!CallSelfHostedFunction(cx, "GetInternalError", reason, getErrorArgs, &reason))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
|
|
|
|
return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
|
|
}
|
|
|
|
// ES2016, 25.4.1.8.
|
|
static MOZ_MUST_USE bool
|
|
TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state,
|
|
HandleValue valueOrReason)
|
|
{
|
|
RootedObject reactions(cx, &reactionsVal.toObject());
|
|
RootedObject reaction(cx);
|
|
|
|
if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions))
|
|
return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state);
|
|
|
|
RootedNativeObject reactionsList(cx, &reactions->as<NativeObject>());
|
|
size_t reactionsCount = reactionsList->getDenseInitializedLength();
|
|
MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
|
|
|
|
for (size_t i = 0; i < reactionsCount; i++) {
|
|
reaction = &reactionsList->getDenseElement(i).toObject();
|
|
if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
AwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
|
|
MutableHandleValue rval)
|
|
{
|
|
MOZ_ASSERT(reaction->isAwait());
|
|
|
|
RootedValue handlerVal(cx, reaction->handler());
|
|
RootedValue argument(cx, reaction->handlerArg());
|
|
Rooted<PromiseObject*> resultPromise(cx, &reaction->promise()->as<PromiseObject>());
|
|
RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator));
|
|
|
|
int32_t handlerNum = int32_t(handlerVal.toNumber());
|
|
MOZ_ASSERT(handlerNum == PromiseHandlerAwaitFulfilled
|
|
|| handlerNum == PromiseHandlerAwaitRejected);
|
|
|
|
// Await's handlers don't return a value, nor throw exception.
|
|
// They fail only on OOM.
|
|
if (handlerNum == PromiseHandlerAwaitFulfilled) {
|
|
if (!AsyncFunctionAwaitedFulfilled(cx, resultPromise, generatorVal, argument))
|
|
return false;
|
|
} else {
|
|
if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument))
|
|
return false;
|
|
}
|
|
|
|
rval.setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.2.1.
|
|
/**
|
|
* Callback triggering the fulfill/reject reaction for a resolved Promise,
|
|
* to be invoked by the embedding during its processing of the Promise job
|
|
* queue.
|
|
*
|
|
* See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
|
|
*
|
|
* A PromiseReactionJob is set as the native function of an extended
|
|
* JSFunction object, with all information required for the job's
|
|
* execution stored in in a reaction record in its first extended slot.
|
|
*/
|
|
static bool
|
|
PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedFunction job(cx, &args.callee().as<JSFunction>());
|
|
|
|
RootedObject reactionObj(cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject());
|
|
|
|
// To ensure that the embedding ends up with the right entry global, we're
|
|
// guaranteeing that the reaction job function gets created in the same
|
|
// compartment as the handler function. That's not necessarily the global
|
|
// that the job was triggered from, though.
|
|
// We can find the triggering global via the job's reaction record. To go
|
|
// back, we check if the reaction is a wrapper and if so, unwrap it and
|
|
// enter its compartment.
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
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>());
|
|
ac.emplace(cx, reactionObj);
|
|
}
|
|
|
|
// Steps 1-2.
|
|
Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
|
|
if (reaction->isAwait())
|
|
return AwaitPromiseReactionJob(cx, reaction, args.rval());
|
|
|
|
// Step 3.
|
|
RootedValue handlerVal(cx, reaction->handler());
|
|
|
|
RootedValue argument(cx, reaction->handlerArg());
|
|
|
|
RootedValue handlerResult(cx);
|
|
ResolutionMode resolutionMode = ResolveMode;
|
|
|
|
// Steps 4-6.
|
|
if (handlerVal.isNumber()) {
|
|
int32_t handlerNum = int32_t(handlerVal.toNumber());
|
|
|
|
// Step 4.
|
|
if (handlerNum == PromiseHandlerIdentity) {
|
|
handlerResult = argument;
|
|
} else {
|
|
// Step 5.
|
|
MOZ_ASSERT(handlerNum == PromiseHandlerThrower);
|
|
resolutionMode = RejectMode;
|
|
handlerResult = argument;
|
|
}
|
|
} else {
|
|
// Step 6.
|
|
FixedInvokeArgs<1> args2(cx);
|
|
args2[0].set(argument);
|
|
if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
|
|
resolutionMode = RejectMode;
|
|
if (!MaybeGetAndClearException(cx, &handlerResult))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Steps 7-9.
|
|
size_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;
|
|
}
|
|
|
|
// ES2016, 25.4.2.2.
|
|
/**
|
|
* Callback for resolving a thenable, to be invoked by the embedding during
|
|
* its processing of the Promise job queue.
|
|
*
|
|
* See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
|
|
*
|
|
* A PromiseResolveThenableJob is set as the native function of an extended
|
|
* JSFunction object, with all information required for the job's
|
|
* execution stored in the function's extended slots.
|
|
*
|
|
* Usage of the function's extended slots is as follows:
|
|
* ThenableJobSlot_Handler: The handler to use as the Promise reaction.
|
|
* This can be PromiseHandlerIdentity,
|
|
* PromiseHandlerThrower, or a callable. In the
|
|
* latter case, it's guaranteed to be an object
|
|
* from the same compartment as the
|
|
* PromiseReactionJob.
|
|
* ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
|
|
* containing data required for proper execution of
|
|
* the reaction.
|
|
*
|
|
* The JobData list has the following entries:
|
|
* ThenableJobDataSlot_Promise: The Promise to resolve using the given
|
|
* thenable.
|
|
* ThenableJobDataSlot_Thenable: The thenable to use as the receiver when
|
|
* calling the `then` function.
|
|
*/
|
|
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(!IsWrapper(&then.toObject()));
|
|
RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData)
|
|
.toObject().as<NativeObject>());
|
|
|
|
RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Promise));
|
|
RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Thenable));
|
|
|
|
// Step 1.
|
|
RootedValue resolveVal(cx);
|
|
RootedValue rejectVal(cx);
|
|
if (!CreateResolvingFunctions(cx, promise, &resolveVal, &rejectVal))
|
|
return false;
|
|
|
|
// Step 2.
|
|
FixedInvokeArgs<2> args2(cx);
|
|
args2[0].set(resolveVal);
|
|
args2[1].set(rejectVal);
|
|
|
|
RootedValue rval(cx);
|
|
|
|
// In difference to the usual pattern, we return immediately on success.
|
|
if (Call(cx, then, thenable, args2, &rval))
|
|
return true;
|
|
|
|
if (!MaybeGetAndClearException(cx, &rval))
|
|
return false;
|
|
|
|
FixedInvokeArgs<1> rejectArgs(cx);
|
|
rejectArgs[0].set(rval);
|
|
|
|
return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval);
|
|
}
|
|
|
|
/**
|
|
* Tells the embedding to enqueue a Promise resolve thenable job, based on
|
|
* three parameters:
|
|
* promiseToResolve_ - The promise to resolve, obviously.
|
|
* thenable_ - The thenable to resolve the Promise with.
|
|
* thenVal - The `then` function to invoke with the `thenable` as the receiver.
|
|
*/
|
|
static MOZ_MUST_USE bool
|
|
EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
|
|
HandleValue thenable_, HandleValue thenVal)
|
|
{
|
|
// Need to re-root these to enable wrapping them below.
|
|
RootedValue promiseToResolve(cx, promiseToResolve_);
|
|
RootedValue thenable(cx, thenable_);
|
|
|
|
// We enter the `then` callable's compartment so that the job function is
|
|
// created in that compartment.
|
|
// That guarantees that the embedding ends up with the right entry global.
|
|
// This is relevant for some html APIs like fetch that derive information
|
|
// from said global.
|
|
RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
|
|
AutoCompartment ac(cx, then);
|
|
|
|
RootedAtom funName(cx, cx->names().empty);
|
|
RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
|
|
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
|
|
if (!job)
|
|
return false;
|
|
|
|
// Store the `then` function on the callback.
|
|
job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
|
|
|
|
// Create a dense array to hold the data needed for the reaction job to
|
|
// work.
|
|
// See the doc comment for PromiseResolveThenableJob for the layout.
|
|
RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
|
|
if (!data ||
|
|
data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Wrap and set the `promiseToResolve` argument.
|
|
if (!cx->compartment()->wrap(cx, &promiseToResolve))
|
|
return false;
|
|
data->setDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
|
|
// At this point the promise is guaranteed to be wrapped into the job's
|
|
// compartment.
|
|
RootedObject promise(cx, &promiseToResolve.toObject());
|
|
|
|
// Wrap and set the `thenable` argument.
|
|
MOZ_ASSERT(thenable.isObject());
|
|
if (!cx->compartment()->wrap(cx, &thenable))
|
|
return false;
|
|
data->setDenseElement(ThenableJobDataIndex_Thenable, thenable);
|
|
|
|
// Store the data array on the reaction job.
|
|
job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
|
|
|
|
RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx));
|
|
return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
|
|
HandleValue onRejected, HandleObject dependentPromise,
|
|
HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal);
|
|
|
|
static MOZ_MUST_USE bool
|
|
AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
|
|
Handle<PromiseReactionRecord*> reaction);
|
|
|
|
static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promise,
|
|
HandleObject blockedPromise,
|
|
HandleValue onFulfilled, HandleValue onRejected);
|
|
|
|
static JSFunction*
|
|
GetResolveFunctionFromReject(JSFunction* reject)
|
|
{
|
|
MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction);
|
|
Value resolveFunVal = reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction);
|
|
MOZ_ASSERT(IsNativeFunction(resolveFunVal, ResolvePromiseFunction));
|
|
return &resolveFunVal.toObject().as<JSFunction>();
|
|
}
|
|
|
|
static JSFunction*
|
|
GetRejectFunctionFromResolve(JSFunction* resolve)
|
|
{
|
|
MOZ_ASSERT(resolve->maybeNative() == ResolvePromiseFunction);
|
|
Value rejectFunVal = resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction);
|
|
MOZ_ASSERT(IsNativeFunction(rejectFunVal, RejectPromiseFunction));
|
|
return &rejectFunVal.toObject().as<JSFunction>();
|
|
}
|
|
|
|
static JSFunction*
|
|
GetResolveFunctionFromPromise(PromiseObject* promise)
|
|
{
|
|
Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction);
|
|
if (rejectFunVal.isUndefined())
|
|
return nullptr;
|
|
JSObject* rejectFunObj = &rejectFunVal.toObject();
|
|
|
|
// We can safely unwrap it because all we want is to get the resolve
|
|
// function.
|
|
if (IsWrapper(rejectFunObj))
|
|
rejectFunObj = UncheckedUnwrap(rejectFunObj);
|
|
|
|
if (!rejectFunObj->is<JSFunction>())
|
|
return nullptr;
|
|
|
|
JSFunction* rejectFun = &rejectFunObj->as<JSFunction>();
|
|
|
|
// Only the original RejectPromiseFunction has a reference to the resolve
|
|
// function.
|
|
if (rejectFun->maybeNative() != &RejectPromiseFunction)
|
|
return nullptr;
|
|
|
|
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());
|
|
}
|
|
|
|
// ES2016, 25.4.3.1. steps 3-7.
|
|
static MOZ_MUST_USE PromiseObject*
|
|
CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
|
|
bool protoIsWrapped /* = false */, bool informDebugger /* = true */)
|
|
{
|
|
// Step 3.
|
|
Rooted<PromiseObject*> promise(cx);
|
|
// Enter the unwrapped proto's compartment, if that's different from
|
|
// the current one.
|
|
// All state stored in a Promise's fixed slots must be created in the
|
|
// same compartment, so we get all of that out of the way here.
|
|
// (Except for the resolution functions, which are created below.)
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
if (protoIsWrapped)
|
|
ac.emplace(cx, proto);
|
|
|
|
promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
|
|
if (!promise)
|
|
return nullptr;
|
|
|
|
// Step 4.
|
|
promise->setFixedSlot(PromiseSlot_Flags, Int32Value(0));
|
|
|
|
// Steps 5-6.
|
|
// Omitted, we allocate our single list of reaction records lazily.
|
|
|
|
// Step 7.
|
|
// Implicit, the handled flag is unset by default.
|
|
|
|
// Store an allocation stack so we can later figure out what the
|
|
// control flow was for some unexpected results. Frightfully expensive,
|
|
// but oh well.
|
|
RootedObject stack(cx);
|
|
if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
|
|
if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
|
|
return nullptr;
|
|
}
|
|
promise->setFixedSlot(PromiseSlot_AllocationSite, ObjectOrNullValue(stack));
|
|
promise->setFixedSlot(PromiseSlot_AllocationTime, DoubleValue(MillisecondsSinceStartup()));
|
|
|
|
// Let the Debugger know about this Promise.
|
|
if (informDebugger)
|
|
JS::dbg::onNewPromise(cx, promise);
|
|
|
|
return promise;
|
|
}
|
|
|
|
// ES2016, 25.4.3.1.
|
|
static bool
|
|
PromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
// Step 1.
|
|
if (!ThrowIfNotConstructing(cx, args, "Promise"))
|
|
return false;
|
|
|
|
// Step 2.
|
|
RootedValue executorVal(cx, args.get(0));
|
|
if (!IsCallable(executorVal))
|
|
return ReportIsNotFunction(cx, executorVal);
|
|
RootedObject executor(cx, &executorVal.toObject());
|
|
|
|
// Steps 3-10.
|
|
RootedObject newTarget(cx, &args.newTarget().toObject());
|
|
RootedObject originalNewTarget(cx, newTarget);
|
|
bool needsWrapping = false;
|
|
|
|
// If the constructor is called via an Xray wrapper, then the newTarget
|
|
// hasn't been unwrapped. We want that because, while the actual instance
|
|
// should be created in the target compartment, the constructor's code
|
|
// should run in the wrapper's compartment.
|
|
//
|
|
// This is so that the resolve and reject callbacks get created in the
|
|
// wrapper's compartment, which is required for code in that compartment
|
|
// to freely interact with it, and, e.g., pass objects as arguments, which
|
|
// it wouldn't be able to if the callbacks were themselves wrapped in Xray
|
|
// wrappers.
|
|
//
|
|
// At the same time, just creating the Promise itself in the wrapper's
|
|
// compartment wouldn't be helpful: if the wrapper forbids interactions
|
|
// with objects except for specific actions, such as calling them, then
|
|
// the code we want to expose it to can't actually treat it as a Promise:
|
|
// calling .then on it would throw, for example.
|
|
//
|
|
// Another scenario where it's important to create the Promise in a
|
|
// different compartment from the resolution functions is when we want to
|
|
// give non-privileged code a Promise resolved with the result of a
|
|
// Promise from privileged code; as a return value of a JS-implemented
|
|
// API, say. If the resolution functions were unprivileged, then resolving
|
|
// with a privileged Promise would cause `resolve` to attempt accessing
|
|
// .then on the passed Promise, which would throw an exception, so we'd
|
|
// just end up with a rejected Promise. Really, we want to chain the two
|
|
// Promises, with the unprivileged one resolved with the resolution of the
|
|
// privileged one.
|
|
if (IsWrapper(newTarget)) {
|
|
newTarget = CheckedUnwrap(newTarget);
|
|
MOZ_ASSERT(newTarget);
|
|
MOZ_ASSERT(newTarget != originalNewTarget);
|
|
{
|
|
AutoCompartment ac(cx, newTarget);
|
|
RootedObject promiseCtor(cx);
|
|
if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
|
|
return false;
|
|
|
|
// Promise subclasses don't get the special Xray treatment, so
|
|
// we only need to do the complex wrapping and unwrapping scheme
|
|
// described above for instances of Promise itself.
|
|
if (newTarget == promiseCtor)
|
|
needsWrapping = true;
|
|
}
|
|
}
|
|
|
|
RootedObject proto(cx);
|
|
if (!GetPrototypeFromConstructor(cx, needsWrapping ? newTarget : originalNewTarget, &proto))
|
|
return false;
|
|
if (needsWrapping && !cx->compartment()->wrap(cx, &proto))
|
|
return false;
|
|
Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, executor, proto, needsWrapping));
|
|
if (!promise)
|
|
return false;
|
|
|
|
// Step 11.
|
|
args.rval().setObject(*promise);
|
|
if (needsWrapping)
|
|
return cx->compartment()->wrap(cx, args.rval());
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.3.1. steps 3-11.
|
|
/* static */ PromiseObject*
|
|
PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */,
|
|
bool needsWrapping /* = false */)
|
|
{
|
|
MOZ_ASSERT(executor->isCallable());
|
|
|
|
RootedObject usedProto(cx, proto);
|
|
// If the proto is wrapped, that means the current function is running
|
|
// with a different compartment active from the one the Promise instance
|
|
// is to be created in.
|
|
// See the comment in PromiseConstructor for details.
|
|
if (needsWrapping) {
|
|
MOZ_ASSERT(proto);
|
|
usedProto = CheckedUnwrap(proto);
|
|
if (!usedProto)
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
// Steps 3-7.
|
|
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, needsWrapping,
|
|
false));
|
|
if (!promise)
|
|
return nullptr;
|
|
|
|
RootedValue promiseVal(cx, ObjectValue(*promise));
|
|
if (needsWrapping && !cx->compartment()->wrap(cx, &promiseVal))
|
|
return nullptr;
|
|
|
|
// Step 8.
|
|
// The resolving functions are created in the compartment active when the
|
|
// (maybe wrapped) Promise constructor was called. They contain checks and
|
|
// can unwrap the Promise if required.
|
|
RootedValue resolveVal(cx);
|
|
RootedValue rejectVal(cx);
|
|
if (!CreateResolvingFunctions(cx, promiseVal, &resolveVal, &rejectVal))
|
|
return nullptr;
|
|
|
|
// Need to wrap the resolution functions before storing them on the Promise.
|
|
if (needsWrapping) {
|
|
AutoCompartment ac(cx, promise);
|
|
RootedValue wrappedRejectVal(cx, rejectVal);
|
|
if (!cx->compartment()->wrap(cx, &wrappedRejectVal))
|
|
return nullptr;
|
|
promise->setFixedSlot(PromiseSlot_RejectFunction, wrappedRejectVal);
|
|
} else {
|
|
promise->setFixedSlot(PromiseSlot_RejectFunction, rejectVal);
|
|
}
|
|
|
|
// Step 9.
|
|
bool success;
|
|
{
|
|
FixedInvokeArgs<2> args(cx);
|
|
|
|
args[0].set(resolveVal);
|
|
args[1].set(rejectVal);
|
|
|
|
RootedValue calleeOrRval(cx, ObjectValue(*executor));
|
|
success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval);
|
|
}
|
|
|
|
// Step 10.
|
|
if (!success) {
|
|
RootedValue exceptionVal(cx);
|
|
if (!MaybeGetAndClearException(cx, &exceptionVal))
|
|
return nullptr;
|
|
|
|
FixedInvokeArgs<1> args(cx);
|
|
|
|
args[0].set(exceptionVal);
|
|
|
|
// |rejectVal| is unused after this, so we can safely write to it.
|
|
if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal))
|
|
return nullptr;
|
|
}
|
|
|
|
// Let the Debugger know about this Promise.
|
|
JS::dbg::onNewPromise(cx, promise);
|
|
|
|
// Step 11.
|
|
return promise;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator,
|
|
HandleObject C, HandleObject promiseObj,
|
|
HandleObject resolve, HandleObject reject,
|
|
bool* done);
|
|
|
|
// ES2016, 25.4.4.1.
|
|
static bool
|
|
Promise_static_all(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedValue iterable(cx, args.get(0));
|
|
|
|
// Step 2 (reordered).
|
|
RootedValue CVal(cx, args.thisv());
|
|
if (!CVal.isObject()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
|
"Receiver of Promise.all call");
|
|
return false;
|
|
}
|
|
|
|
// Step 1.
|
|
RootedObject C(cx, &CVal.toObject());
|
|
|
|
// Step 3.
|
|
RootedObject resultPromise(cx);
|
|
RootedObject resolve(cx);
|
|
RootedObject reject(cx);
|
|
if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
|
|
return false;
|
|
|
|
// Steps 4-5.
|
|
JS::ForOfIterator iter(cx);
|
|
if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
|
|
return AbruptRejectPromise(cx, args, resultPromise, reject);
|
|
|
|
if (!iter.valueIsIterable()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
|
|
"Argument of Promise.all");
|
|
return AbruptRejectPromise(cx, args, resultPromise, reject);
|
|
}
|
|
|
|
// Step 6 (implicit).
|
|
|
|
// Step 7.
|
|
bool done;
|
|
bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject, &done);
|
|
|
|
// Step 8.
|
|
if (!result) {
|
|
// Step 8.a.
|
|
if (!done)
|
|
iter.closeThrow();
|
|
|
|
// Step 8.b.
|
|
return AbruptRejectPromise(cx, args, resultPromise, reject);
|
|
}
|
|
|
|
// Step 9.
|
|
args.rval().setObject(*resultPromise);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
|
|
HandleValue onFulfilled_, HandleValue onRejected_,
|
|
HandleObject resultPromise,
|
|
HandleObject resolve, HandleObject reject);
|
|
|
|
static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp);
|
|
|
|
// Unforgeable version of ES2016, 25.4.4.1.
|
|
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];
|
|
assertSameCompartment(cx, obj);
|
|
MOZ_ASSERT(UncheckedUnwrap(obj)->is<PromiseObject>());
|
|
}
|
|
#endif
|
|
|
|
// Step 1.
|
|
RootedObject C(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
|
|
if (!C)
|
|
return nullptr;
|
|
|
|
// Step 2 (omitted).
|
|
|
|
// Step 3.
|
|
RootedObject resultPromise(cx);
|
|
RootedObject resolve(cx);
|
|
RootedObject reject(cx);
|
|
if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
|
|
return nullptr;
|
|
|
|
// Steps 4-6 (omitted).
|
|
|
|
// Step 7.
|
|
// Implemented as an inlined, simplied version of ES2016 25.4.4.1.1, PerformPromiseAll.
|
|
{
|
|
uint32_t promiseCount = promises.length();
|
|
// Sub-steps 1-2 (omitted).
|
|
|
|
// Sub-step 3.
|
|
RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount));
|
|
if (!valuesArray)
|
|
return nullptr;
|
|
if (valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success)
|
|
return nullptr;
|
|
|
|
// Sub-step 4.
|
|
// Create our data holder that holds all the things shared across
|
|
// every step of the iterator. In particular, this holds the
|
|
// remainingElementsCount (as an integer reserved slot), the array of
|
|
// values, and the resolve function from our PromiseCapability.
|
|
RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
|
|
Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise,
|
|
valuesArrayVal,
|
|
resolve));
|
|
if (!dataHolder)
|
|
return nullptr;
|
|
RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
|
|
|
|
// Sub-step 5 (inline in loop-header below).
|
|
|
|
// Sub-step 6.
|
|
for (uint32_t index = 0; index < promiseCount; index++) {
|
|
// Steps a-c (omitted).
|
|
// Step d (implemented after the loop).
|
|
// Steps e-g (omitted).
|
|
|
|
// Step h.
|
|
valuesArray->setDenseElement(index, UndefinedHandleValue);
|
|
|
|
// Step i, vastly simplified.
|
|
RootedObject nextPromiseObj(cx, promises[index]);
|
|
|
|
// Step j.
|
|
RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
|
|
1, nullptr,
|
|
gc::AllocKind::FUNCTION_EXTENDED,
|
|
GenericObject));
|
|
if (!resolveFunc)
|
|
return nullptr;
|
|
|
|
// Steps k-o.
|
|
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
|
|
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
|
|
Int32Value(index));
|
|
|
|
// Step p.
|
|
dataHolder->increaseRemainingCount();
|
|
|
|
// Step q, very roughly.
|
|
RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
|
|
RootedValue rejectFunVal(cx, ObjectValue(*reject));
|
|
Rooted<PromiseObject*> nextPromise(cx);
|
|
|
|
// GetWaitForAllPromise is used internally only and must not
|
|
// trigger content-observable effects when registering a reaction.
|
|
// It's also meant to work on wrapped Promises, potentially from
|
|
// compartments with principals inaccessible from the current
|
|
// compartment. To make that work, it unwraps promises with
|
|
// UncheckedUnwrap,
|
|
nextPromise = &UncheckedUnwrap(nextPromiseObj)->as<PromiseObject>();
|
|
|
|
if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal,
|
|
resultPromise, nullptr, nullptr))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Step r (inline in loop-header).
|
|
}
|
|
|
|
// Sub-step d.i (implicit).
|
|
// Sub-step d.ii.
|
|
int32_t remainingCount = dataHolder->decreaseRemainingCount();
|
|
|
|
// Sub-step d.iii-iv.
|
|
if (remainingCount == 0) {
|
|
RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
|
|
if (!ResolvePromiseInternal(cx, resultPromise, valuesArrayVal))
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Step 8 (omitted).
|
|
|
|
// Step 9.
|
|
return resultPromise;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result,
|
|
ResolutionMode mode, HandleObject promiseObj)
|
|
{
|
|
// The absence of a resolve/reject function can mean that, as an
|
|
// optimization, those weren't created. In that case, a flag is set on
|
|
// the Promise object. There are also reactions where the Promise
|
|
// itself is missing. For those, there's nothing left to do here.
|
|
assertSameCompartment(cx, resolutionFun);
|
|
assertSameCompartment(cx, result);
|
|
assertSameCompartment(cx, promiseObj);
|
|
if (resolutionFun) {
|
|
RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun));
|
|
FixedInvokeArgs<1> resolveArgs(cx);
|
|
resolveArgs[0].set(result);
|
|
return Call(cx, calleeOrRval, UndefinedHandleValue, resolveArgs, &calleeOrRval);
|
|
}
|
|
|
|
if (!promiseObj)
|
|
return true;
|
|
|
|
Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
|
|
if (promise->state() != JS::PromiseState::Pending)
|
|
return true;
|
|
|
|
if (mode == ResolveMode) {
|
|
if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION))
|
|
return true;
|
|
return ResolvePromiseInternal(cx, promise, result);
|
|
}
|
|
|
|
if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION))
|
|
return true;
|
|
return RejectMaybeWrappedPromise(cx, promiseObj, result);
|
|
}
|
|
|
|
// ES2016, 25.4.4.1.1.
|
|
static MOZ_MUST_USE bool
|
|
PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
|
|
HandleObject promiseObj, HandleObject resolve, HandleObject reject,
|
|
bool* done)
|
|
{
|
|
*done = false;
|
|
|
|
RootedObject unwrappedPromiseObj(cx);
|
|
if (IsWrapper(promiseObj)) {
|
|
unwrappedPromiseObj = CheckedUnwrap(promiseObj);
|
|
MOZ_ASSERT(unwrappedPromiseObj);
|
|
}
|
|
|
|
// Step 1.
|
|
MOZ_ASSERT(C->isConstructor());
|
|
RootedValue CVal(cx, ObjectValue(*C));
|
|
|
|
// Step 2 (omitted).
|
|
|
|
// Step 3.
|
|
// We have to be very careful about which compartments we create things in
|
|
// here. In particular, we have to maintain the invariant that anything
|
|
// stored in a reserved slot is same-compartment with the object whose
|
|
// reserved slot it's in. But we want to create the values array in the
|
|
// Promise's compartment, because that array can get exposed to
|
|
// code that has access to the Promise (in particular code from
|
|
// that compartment), and that should work, even if the Promise
|
|
// compartment is less-privileged than our caller compartment.
|
|
//
|
|
// So the plan is as follows: Create the values array in the promise
|
|
// compartment. Create the PromiseAllResolveElement function
|
|
// and the data holder in our current compartment. Store a
|
|
// cross-compartment wrapper to the values array in the holder. This
|
|
// should be OK because the only things we hand the
|
|
// PromiseAllResolveElement function to are the "then" calls we do and in
|
|
// the case when the Promise's compartment is not the current compartment
|
|
// those are happening over Xrays anyway, which means they get the
|
|
// canonical "then" function and content can't see our
|
|
// PromiseAllResolveElement.
|
|
RootedObject valuesArray(cx);
|
|
if (unwrappedPromiseObj) {
|
|
JSAutoCompartment ac(cx, unwrappedPromiseObj);
|
|
valuesArray = NewDenseFullyAllocatedArray(cx, 0);
|
|
} else {
|
|
valuesArray = NewDenseFullyAllocatedArray(cx, 0);
|
|
}
|
|
if (!valuesArray)
|
|
return false;
|
|
|
|
RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
|
|
if (!cx->compartment()->wrap(cx, &valuesArrayVal))
|
|
return false;
|
|
|
|
// Step 4.
|
|
// Create our data holder that holds all the things shared across
|
|
// every step of the iterator. In particular, this holds the
|
|
// remainingElementsCount (as an integer reserved slot), the array of
|
|
// values, and the resolve function from our PromiseCapability.
|
|
Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj,
|
|
valuesArrayVal, resolve));
|
|
if (!dataHolder)
|
|
return false;
|
|
RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
|
|
|
|
// Step 5.
|
|
uint32_t index = 0;
|
|
|
|
// Step 6.
|
|
RootedValue nextValue(cx);
|
|
RootedId indexId(cx);
|
|
RootedValue rejectFunVal(cx, ObjectValue(*reject));
|
|
|
|
while (true) {
|
|
// Steps a-c, e-g.
|
|
if (!iterator.next(&nextValue, done)) {
|
|
// Steps b, f.
|
|
*done = true;
|
|
|
|
// Steps c, g.
|
|
return false;
|
|
}
|
|
|
|
// Step d.
|
|
if (*done) {
|
|
// Step d.i (implicit).
|
|
|
|
// Step d.ii.
|
|
int32_t remainingCount = dataHolder->decreaseRemainingCount();
|
|
|
|
// Steps d.iii-iv.
|
|
if (remainingCount == 0) {
|
|
return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode,
|
|
promiseObj);
|
|
}
|
|
|
|
// We're all set for now!
|
|
return true;
|
|
}
|
|
|
|
// Step h.
|
|
{ // Scope for the JSAutoCompartment we need to work with valuesArray. We
|
|
// mostly do this for performance; we could go ahead and do the define via
|
|
// a cross-compartment proxy instead...
|
|
JSAutoCompartment ac(cx, valuesArray);
|
|
indexId = INT_TO_JSID(index);
|
|
if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue))
|
|
return false;
|
|
}
|
|
|
|
// Step i.
|
|
// Sadly, because someone could have overridden
|
|
// "resolve" on the canonical Promise constructor.
|
|
RootedValue nextPromise(cx);
|
|
RootedValue staticResolve(cx);
|
|
if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
|
|
return false;
|
|
|
|
FixedInvokeArgs<1> resolveArgs(cx);
|
|
resolveArgs[0].set(nextValue);
|
|
if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise))
|
|
return false;
|
|
|
|
// Step j.
|
|
RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
|
|
1, nullptr,
|
|
gc::AllocKind::FUNCTION_EXTENDED,
|
|
GenericObject));
|
|
if (!resolveFunc)
|
|
return false;
|
|
|
|
// Steps k,m,n.
|
|
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
|
|
|
|
// Step l.
|
|
resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
|
|
Int32Value(index));
|
|
|
|
// Steps o-p.
|
|
dataHolder->increaseRemainingCount();
|
|
|
|
// Step q.
|
|
RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
|
|
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
|
|
return false;
|
|
|
|
// Step r.
|
|
index++;
|
|
MOZ_ASSERT(index > 0);
|
|
}
|
|
}
|
|
|
|
// ES2016, 25.4.4.1.2.
|
|
static bool
|
|
PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedFunction resolve(cx, &args.callee().as<JSFunction>());
|
|
RootedValue xVal(cx, args.get(0));
|
|
|
|
// Step 1.
|
|
RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data));
|
|
|
|
// Step 2.
|
|
// We use the existence of the data holder as a signal for whether the
|
|
// Promise was already resolved. Upon resolution, it's reset to
|
|
// `undefined`.
|
|
if (dataVal.isUndefined()) {
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>());
|
|
|
|
// Step 3.
|
|
resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue());
|
|
|
|
// Step 4.
|
|
int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
|
|
.toInt32();
|
|
|
|
// Step 5.
|
|
RootedValue valuesVal(cx, data->valuesArray());
|
|
RootedObject valuesObj(cx, &valuesVal.toObject());
|
|
bool valuesListIsWrapped = false;
|
|
if (IsWrapper(valuesObj)) {
|
|
valuesListIsWrapped = true;
|
|
// See comment for PerformPromiseAll, step 3 for why we unwrap here.
|
|
valuesObj = UncheckedUnwrap(valuesObj);
|
|
}
|
|
RootedNativeObject values(cx, &valuesObj->as<NativeObject>());
|
|
|
|
// Step 6 (moved under step 10).
|
|
// Step 7 (moved to step 9).
|
|
|
|
// Step 8.
|
|
// The index is guaranteed to be initialized to `undefined`.
|
|
if (valuesListIsWrapped) {
|
|
AutoCompartment ac(cx, values);
|
|
if (!cx->compartment()->wrap(cx, &xVal))
|
|
return false;
|
|
}
|
|
values->setDenseElement(index, xVal);
|
|
|
|
// Steps 7,9.
|
|
uint32_t remainingCount = data->decreaseRemainingCount();
|
|
|
|
// Step 10.
|
|
if (remainingCount == 0) {
|
|
// Step 10.a. (Omitted, happened in PerformPromiseAll.)
|
|
// Step 10.b.
|
|
|
|
// Step 6 (Adapted to work with PromiseAllDataHolder's layout).
|
|
RootedObject resolveAllFun(cx, data->resolveObj());
|
|
RootedObject promiseObj(cx, data->promiseObj());
|
|
if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj))
|
|
return false;
|
|
}
|
|
|
|
// Step 11.
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator,
|
|
HandleObject C, HandleObject promiseObj,
|
|
HandleObject resolve, HandleObject reject,
|
|
bool* done);
|
|
|
|
// ES2016, 25.4.4.3.
|
|
static bool
|
|
Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedValue iterable(cx, args.get(0));
|
|
|
|
// Step 2 (reordered).
|
|
RootedValue CVal(cx, args.thisv());
|
|
if (!CVal.isObject()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
|
"Receiver of Promise.race call");
|
|
return false;
|
|
}
|
|
|
|
// Step 1.
|
|
RootedObject C(cx, &CVal.toObject());
|
|
|
|
// Step 3.
|
|
RootedObject resultPromise(cx);
|
|
RootedObject resolve(cx);
|
|
RootedObject reject(cx);
|
|
if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
|
|
return false;
|
|
|
|
// Steps 4-5.
|
|
JS::ForOfIterator iter(cx);
|
|
if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
|
|
return AbruptRejectPromise(cx, args, resultPromise, reject);
|
|
|
|
if (!iter.valueIsIterable()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
|
|
"Argument of Promise.race");
|
|
return AbruptRejectPromise(cx, args, resultPromise, reject);
|
|
}
|
|
|
|
// Step 6 (implicit).
|
|
|
|
// Step 7.
|
|
bool done;
|
|
bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject, &done);
|
|
|
|
// Step 8.
|
|
if (!result) {
|
|
// Step 8.a.
|
|
if (!done)
|
|
iter.closeThrow();
|
|
|
|
// Step 8.b.
|
|
return AbruptRejectPromise(cx, args, resultPromise, reject);
|
|
}
|
|
|
|
// Step 9.
|
|
args.rval().setObject(*resultPromise);
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.4.3.1.
|
|
static MOZ_MUST_USE bool
|
|
PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
|
|
HandleObject promiseObj, HandleObject resolve, HandleObject reject,
|
|
bool* done)
|
|
{
|
|
*done = false;
|
|
MOZ_ASSERT(C->isConstructor());
|
|
RootedValue CVal(cx, ObjectValue(*C));
|
|
|
|
RootedValue nextValue(cx);
|
|
RootedValue resolveFunVal(cx, ObjectValue(*resolve));
|
|
RootedValue rejectFunVal(cx, ObjectValue(*reject));
|
|
|
|
while (true) {
|
|
// Steps a-c, e-g.
|
|
if (!iterator.next(&nextValue, done)) {
|
|
// Steps b, f.
|
|
*done = true;
|
|
|
|
// Steps c, g.
|
|
return false;
|
|
}
|
|
|
|
// Step d.
|
|
if (*done) {
|
|
// Step d.i (implicit).
|
|
|
|
// Step d.ii.
|
|
return true;
|
|
}
|
|
|
|
// Step h.
|
|
// Sadly, because someone could have overridden
|
|
// "resolve" on the canonical Promise constructor.
|
|
RootedValue nextPromise(cx);
|
|
RootedValue staticResolve(cx);
|
|
if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
|
|
return false;
|
|
|
|
FixedInvokeArgs<1> resolveArgs(cx);
|
|
resolveArgs[0].set(nextValue);
|
|
if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise))
|
|
return false;
|
|
|
|
// Step i.
|
|
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace");
|
|
}
|
|
|
|
// ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5.
|
|
static MOZ_MUST_USE JSObject*
|
|
CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue argVal,
|
|
ResolutionMode mode)
|
|
{
|
|
// Steps 1-2.
|
|
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());
|
|
|
|
// Step 3 of Resolve.
|
|
if (mode == ResolveMode && argVal.isObject()) {
|
|
RootedObject xObj(cx, &argVal.toObject());
|
|
bool isPromise = false;
|
|
if (xObj->is<PromiseObject>()) {
|
|
isPromise = true;
|
|
} else if (IsWrapper(xObj)) {
|
|
// Treat instances of Promise from other compartments as Promises
|
|
// here, too.
|
|
// It's important to do the GetProperty for the `constructor`
|
|
// below through the wrapper, because wrappers can change the
|
|
// outcome, so instead of unwrapping and then performing the
|
|
// GetProperty, just check here and then operate on the original
|
|
// object again.
|
|
RootedObject unwrappedObject(cx, CheckedUnwrap(xObj));
|
|
if (unwrappedObject && unwrappedObject->is<PromiseObject>())
|
|
isPromise = true;
|
|
}
|
|
if (isPromise) {
|
|
RootedValue ctorVal(cx);
|
|
if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal))
|
|
return nullptr;
|
|
if (ctorVal == thisVal)
|
|
return xObj;
|
|
}
|
|
}
|
|
|
|
// Step 4 of Resolve, 3 of Reject.
|
|
RootedObject promise(cx);
|
|
RootedObject resolveFun(cx);
|
|
RootedObject rejectFun(cx);
|
|
if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun, true))
|
|
return nullptr;
|
|
|
|
// Step 5 of Resolve, 4 of Reject.
|
|
if (!RunResolutionFunction(cx, mode == ResolveMode ? resolveFun : rejectFun, argVal, mode,
|
|
promise))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Step 6 of Resolve, 4 of Reject.
|
|
return promise;
|
|
}
|
|
|
|
MOZ_MUST_USE JSObject*
|
|
js::PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value)
|
|
{
|
|
RootedValue C(cx, ObjectValue(*constructor));
|
|
return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode);
|
|
}
|
|
|
|
/**
|
|
* ES2016, 25.4.4.4, Promise.reject.
|
|
*/
|
|
bool
|
|
js::Promise_reject(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedValue thisVal(cx, args.thisv());
|
|
RootedValue argVal(cx, args.get(0));
|
|
JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode);
|
|
if (!result)
|
|
return false;
|
|
args.rval().setObject(*result);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
|
|
*/
|
|
/* static */ JSObject*
|
|
PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
|
|
{
|
|
RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx));
|
|
if (!promiseCtor)
|
|
return nullptr;
|
|
RootedValue cVal(cx, ObjectValue(*promiseCtor));
|
|
return CommonStaticResolveRejectImpl(cx, cVal, value, RejectMode);
|
|
}
|
|
|
|
/**
|
|
* ES2016, 25.4.4.5, Promise.resolve.
|
|
*/
|
|
bool
|
|
js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedValue thisVal(cx, args.thisv());
|
|
RootedValue argVal(cx, args.get(0));
|
|
JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode);
|
|
if (!result)
|
|
return false;
|
|
args.rval().setObject(*result);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
|
|
*/
|
|
/* static */ JSObject*
|
|
PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
|
|
{
|
|
RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx));
|
|
if (!promiseCtor)
|
|
return nullptr;
|
|
RootedValue cVal(cx, ObjectValue(*promiseCtor));
|
|
return CommonStaticResolveRejectImpl(cx, cVal, value, ResolveMode);
|
|
}
|
|
|
|
// ES2016, 25.4.4.6, implemented in Promise.js.
|
|
|
|
// ES2016, 25.4.5.1, implemented in Promise.js.
|
|
|
|
static PromiseReactionRecord*
|
|
NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled,
|
|
HandleValue onRejected, HandleObject resolve, HandleObject reject,
|
|
HandleObject incumbentGlobalObject)
|
|
{
|
|
// Either of the following conditions must be met:
|
|
// * resultPromise is a PromiseObject
|
|
// * resolve and reject are callable
|
|
// except for Async Generator, there resultPromise can be nullptr.
|
|
MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), resolve);
|
|
MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), IsCallable(resolve));
|
|
MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), reject);
|
|
MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), IsCallable(reject));
|
|
|
|
Rooted<PromiseReactionRecord*> reaction(cx, NewObjectWithClassProto<PromiseReactionRecord>(cx));
|
|
if (!reaction)
|
|
return nullptr;
|
|
|
|
assertSameCompartment(cx, resultPromise);
|
|
assertSameCompartment(cx, onFulfilled);
|
|
assertSameCompartment(cx, onRejected);
|
|
assertSameCompartment(cx, resolve);
|
|
assertSameCompartment(cx, reject);
|
|
assertSameCompartment(cx, incumbentGlobalObject);
|
|
|
|
reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultPromise));
|
|
reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0));
|
|
reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled);
|
|
reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected);
|
|
reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve));
|
|
reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject));
|
|
reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject,
|
|
ObjectOrNullValue(incumbentGlobalObject));
|
|
|
|
return reaction;
|
|
}
|
|
|
|
// ES2016, 25.4.5.3., steps 3-5.
|
|
MOZ_MUST_USE bool
|
|
js::OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
|
|
HandleValue onFulfilled, HandleValue onRejected,
|
|
MutableHandleObject dependent, bool createDependent)
|
|
{
|
|
RootedObject promiseObj(cx, promise);
|
|
if (promise->compartment() != cx->compartment()) {
|
|
if (!cx->compartment()->wrap(cx, &promiseObj))
|
|
return false;
|
|
}
|
|
|
|
RootedObject resultPromise(cx);
|
|
RootedObject resolve(cx);
|
|
RootedObject reject(cx);
|
|
|
|
if (createDependent) {
|
|
// Step 3.
|
|
RootedValue ctorVal(cx);
|
|
if (!SpeciesConstructor(cx, promiseObj, JSProto_Promise, &ctorVal))
|
|
return false;
|
|
RootedObject C(cx, &ctorVal.toObject());
|
|
|
|
// Step 4.
|
|
if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true))
|
|
return false;
|
|
}
|
|
|
|
// Step 5.
|
|
if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
|
|
return false;
|
|
|
|
dependent.set(resultPromise);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx,
|
|
Handle<PromiseObject*> promise,
|
|
Handle<PromiseReactionRecord*> reaction);
|
|
|
|
// Some async/await functions are implemented here instead of
|
|
// js/src/builtin/AsyncFunction.cpp, to call Promise internal functions.
|
|
|
|
// Async Functions proposal 1.1.8 and 1.2.14 step 1.
|
|
MOZ_MUST_USE PromiseObject*
|
|
js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal)
|
|
{
|
|
// Step 1.
|
|
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx));
|
|
if (!promise)
|
|
return nullptr;
|
|
|
|
AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC);
|
|
promise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal);
|
|
return promise;
|
|
}
|
|
|
|
// Async Functions proposal 2.2 steps 3.f, 3.g.
|
|
MOZ_MUST_USE bool
|
|
js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise)
|
|
{
|
|
// Step 3.f.
|
|
RootedValue exc(cx);
|
|
if (!MaybeGetAndClearException(cx, &exc))
|
|
return false;
|
|
|
|
if (!RejectMaybeWrappedPromise(cx, resultPromise, exc))
|
|
return false;
|
|
|
|
// Step 3.g.
|
|
return true;
|
|
}
|
|
|
|
// Async Functions proposal 2.2 steps 3.d-e, 3.g.
|
|
MOZ_MUST_USE bool
|
|
js::AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
|
|
{
|
|
// Steps 3.d-e.
|
|
if (!ResolvePromiseInternal(cx, resultPromise, value))
|
|
return false;
|
|
|
|
// Step 3.g.
|
|
return true;
|
|
}
|
|
|
|
// Async Functions proposal 2.3 steps 2-8.
|
|
MOZ_MUST_USE bool
|
|
js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
|
|
{
|
|
// Step 2.
|
|
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx));
|
|
if (!promise)
|
|
return false;
|
|
|
|
// Steps 3.
|
|
if (!ResolvePromiseInternal(cx, promise, value))
|
|
return false;
|
|
|
|
// Steps 4-5.
|
|
RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAwaitFulfilled));
|
|
RootedValue onRejected(cx, Int32Value(PromiseHandlerAwaitRejected));
|
|
|
|
RootedObject incumbentGlobal(cx);
|
|
if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
|
|
return false;
|
|
|
|
// Steps 6-7.
|
|
Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
|
|
onFulfilled, onRejected,
|
|
nullptr, nullptr,
|
|
incumbentGlobal));
|
|
if (!reaction)
|
|
return false;
|
|
|
|
reaction->setIsAwait();
|
|
|
|
// Step 8.
|
|
return PerformPromiseThenWithReaction(cx, promise, reaction);
|
|
}
|
|
|
|
// ES2016, 25.4.5.3.
|
|
bool
|
|
js::Promise_then(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
// Step 1.
|
|
RootedValue promiseVal(cx, args.thisv());
|
|
|
|
RootedValue onFulfilled(cx, args.get(0));
|
|
RootedValue onRejected(cx, args.get(1));
|
|
|
|
// Step 2.
|
|
if (!promiseVal.isObject()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
|
"Receiver of Promise.prototype.then call");
|
|
return false;
|
|
}
|
|
RootedObject promiseObj(cx, &promiseVal.toObject());
|
|
Rooted<PromiseObject*> promise(cx);
|
|
|
|
bool isPromise = promiseObj->is<PromiseObject>();
|
|
if (isPromise) {
|
|
promise = &promiseObj->as<PromiseObject>();
|
|
} else {
|
|
RootedObject unwrappedPromiseObj(cx, CheckedUnwrap(promiseObj));
|
|
if (!unwrappedPromiseObj) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
|
|
return false;
|
|
}
|
|
if (!unwrappedPromiseObj->is<PromiseObject>()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
|
|
"Promise", "then", "value");
|
|
return false;
|
|
}
|
|
promise = &unwrappedPromiseObj->as<PromiseObject>();
|
|
}
|
|
|
|
// Steps 3-5.
|
|
RootedObject resultPromise(cx);
|
|
if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise, true))
|
|
return false;
|
|
|
|
args.rval().setObject(*resultPromise);
|
|
return true;
|
|
}
|
|
|
|
// ES2016, 25.4.5.3.1.
|
|
static MOZ_MUST_USE bool
|
|
PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
|
|
HandleValue onRejected_, HandleObject resultPromise,
|
|
HandleObject resolve, HandleObject reject)
|
|
{
|
|
// Step 1 (implicit).
|
|
// Step 2 (implicit).
|
|
|
|
// Step 3.
|
|
RootedValue onFulfilled(cx, onFulfilled_);
|
|
if (!IsCallable(onFulfilled))
|
|
onFulfilled = Int32Value(PromiseHandlerIdentity);
|
|
|
|
// Step 4.
|
|
RootedValue onRejected(cx, onRejected_);
|
|
if (!IsCallable(onRejected))
|
|
onRejected = Int32Value(PromiseHandlerThrower);
|
|
|
|
RootedObject incumbentGlobal(cx);
|
|
if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
|
|
return false;
|
|
|
|
// Step 7.
|
|
Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
|
|
onFulfilled, onRejected,
|
|
resolve, reject,
|
|
incumbentGlobal));
|
|
if (!reaction)
|
|
return false;
|
|
|
|
return PerformPromiseThenWithReaction(cx, promise, reaction);
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
PerformPromiseThenWithReaction(JSContext* cx, Handle<PromiseObject*> promise,
|
|
Handle<PromiseReactionRecord*> reaction)
|
|
{
|
|
JS::PromiseState state = promise->state();
|
|
int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
|
|
if (state == JS::PromiseState::Pending) {
|
|
// Steps 5,6 (reordered).
|
|
// Instead of creating separate reaction records for fulfillment and
|
|
// rejection, we create a combined record. All places we use the record
|
|
// can handle that.
|
|
if (!AddPromiseReaction(cx, promise, reaction))
|
|
return false;
|
|
}
|
|
|
|
// Steps 8,9.
|
|
else {
|
|
// Step 9.a.
|
|
MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected);
|
|
|
|
// Step 8.a. / 9.b.
|
|
RootedValue valueOrReason(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
|
|
|
|
// We might be operating on a promise from another compartment. In
|
|
// that case, we need to wrap the result/reason value before using it.
|
|
if (!cx->compartment()->wrap(cx, &valueOrReason))
|
|
return false;
|
|
|
|
// Step 9.c.
|
|
if (state == JS::PromiseState::Rejected && !(flags & PROMISE_FLAG_HANDLED))
|
|
cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
|
|
|
|
// Step 8.b. / 9.d.
|
|
if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
|
|
return false;
|
|
}
|
|
|
|
// Step 10.
|
|
promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_HANDLED));
|
|
|
|
// Step 11.
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Calls |promise.then| with the provided hooks and adds |blockedPromise| to
|
|
* its list of dependent promises. Used by |Promise.all| and |Promise.race|.
|
|
*
|
|
* If |promise.then| is the original |Promise.prototype.then| function and
|
|
* the call to |promise.then| would use the original |Promise| constructor to
|
|
* create the resulting promise, this function skips the call to |promise.then|
|
|
* and thus creating a new promise that would not be observable by content.
|
|
*/
|
|
static MOZ_MUST_USE bool
|
|
BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_,
|
|
HandleValue onFulfilled, HandleValue onRejected)
|
|
{
|
|
RootedValue thenVal(cx);
|
|
if (!GetProperty(cx, promiseVal, cx->names().then, &thenVal))
|
|
return false;
|
|
|
|
RootedObject promiseObj(cx);
|
|
if (promiseVal.isObject())
|
|
promiseObj = &promiseVal.toObject();
|
|
|
|
if (promiseObj && promiseObj->is<PromiseObject>() && IsNativeFunction(thenVal, Promise_then)) {
|
|
// |promise| is an unwrapped Promise, and |then| is the original
|
|
// |Promise.prototype.then|, inline it here.
|
|
// 25.4.5.3., step 3.
|
|
RootedObject PromiseCtor(cx);
|
|
if (!GetBuiltinConstructor(cx, JSProto_Promise, &PromiseCtor))
|
|
return false;
|
|
RootedValue PromiseCtorVal(cx, ObjectValue(*PromiseCtor));
|
|
RootedValue CVal(cx);
|
|
if (!SpeciesConstructor(cx, promiseObj, PromiseCtorVal, &CVal))
|
|
return false;
|
|
RootedObject C(cx, &CVal.toObject());
|
|
|
|
RootedObject resultPromise(cx, blockedPromise_);
|
|
RootedObject resolveFun(cx);
|
|
RootedObject rejectFun(cx);
|
|
|
|
// By default, the blocked promise is added as an extra entry to the
|
|
// rejected promises list.
|
|
bool addToDependent = true;
|
|
|
|
if (C == PromiseCtor && resultPromise->is<PromiseObject>()) {
|
|
addToDependent = false;
|
|
} else {
|
|
// 25.4.5.3., step 4.
|
|
if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true))
|
|
return false;
|
|
}
|
|
|
|
// 25.4.5.3., step 5.
|
|
Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
|
|
if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise,
|
|
resolveFun, rejectFun))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!addToDependent)
|
|
return true;
|
|
} else {
|
|
// Optimization failed, do the normal call.
|
|
RootedValue rval(cx);
|
|
if (!Call(cx, thenVal, promiseVal, onFulfilled, onRejected, &rval))
|
|
return false;
|
|
}
|
|
|
|
// In case the value to depend on isn't an object at all, there's nothing
|
|
// more to do here: we can only add reactions to Promise objects
|
|
// (potentially after unwrapping them), and non-object values can't be
|
|
// Promise objects. This can happen if Promise.all is called on an object
|
|
// with a `resolve` method that returns primitives.
|
|
if (!promiseObj)
|
|
return true;
|
|
|
|
// The object created by the |promise.then| call or the inlined version
|
|
// of it above is visible to content (either because |promise.then| was
|
|
// overridden by content and could leak it, or because a constructor
|
|
// other than the original value of |Promise| was used to create it).
|
|
// To have both that object and |blockedPromise| show up as dependent
|
|
// promises in the debugger, add a dummy reaction to the list of reject
|
|
// reactions that contains |blockedPromise|, but otherwise does nothing.
|
|
RootedObject unwrappedPromiseObj(cx, promiseObj);
|
|
RootedObject blockedPromise(cx, blockedPromise_);
|
|
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
if (IsProxy(promiseObj)) {
|
|
unwrappedPromiseObj = CheckedUnwrap(promiseObj);
|
|
if (!unwrappedPromiseObj) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_ACCESS_DENIED);
|
|
return false;
|
|
}
|
|
if (JS_IsDeadWrapper(unwrappedPromiseObj)) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
|
return false;
|
|
}
|
|
ac.emplace(cx, unwrappedPromiseObj);
|
|
if (!cx->compartment()->wrap(cx, &blockedPromise))
|
|
return false;
|
|
}
|
|
|
|
// If either the object to depend on or the object that gets blocked isn't
|
|
// a, maybe-wrapped, Promise instance, we ignore it. All this does is lose
|
|
// some small amount of debug information in scenarios that are highly
|
|
// unlikely to occur in useful code.
|
|
if (!unwrappedPromiseObj->is<PromiseObject>())
|
|
return true;
|
|
if (!blockedPromise_->is<PromiseObject>())
|
|
return true;
|
|
|
|
Rooted<PromiseObject*> promise(cx, &unwrappedPromiseObj->as<PromiseObject>());
|
|
return AddPromiseReaction(cx, promise, UndefinedHandleValue, UndefinedHandleValue,
|
|
blockedPromise, nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
|
|
Handle<PromiseReactionRecord*> reaction)
|
|
{
|
|
RootedValue reactionVal(cx, ObjectValue(*reaction));
|
|
|
|
// The code that creates Promise reactions can handle wrapped Promises,
|
|
// unwrapping them as needed. That means that the `promise` and `reaction`
|
|
// objects we have here aren't necessarily from the same compartment. In
|
|
// order to store the reaction on the promise, we have to ensure that it
|
|
// is properly wrapped.
|
|
mozilla::Maybe<AutoCompartment> ac;
|
|
if (promise->compartment() != cx->compartment()) {
|
|
ac.emplace(cx, promise);
|
|
if (!cx->compartment()->wrap(cx, &reactionVal))
|
|
return false;
|
|
}
|
|
|
|
// 25.4.5.3.1 steps 7.a,b.
|
|
RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
|
|
RootedNativeObject reactions(cx);
|
|
|
|
if (reactionsVal.isUndefined()) {
|
|
// If no reactions existed so far, just store the reaction record directly.
|
|
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal);
|
|
return true;
|
|
}
|
|
|
|
RootedObject reactionsObj(cx, &reactionsVal.toObject());
|
|
|
|
// If only a single reaction exists, it's stored directly instead of in a
|
|
// list. In that case, `reactionsObj` might be a wrapper, which we can
|
|
// always safely unwrap.
|
|
if (IsProxy(reactionsObj)) {
|
|
reactionsObj = UncheckedUnwrap(reactionsObj);
|
|
if (JS_IsDeadWrapper(reactionsObj)) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(reactionsObj->is<PromiseReactionRecord>());
|
|
}
|
|
|
|
if (reactionsObj->is<PromiseReactionRecord>()) {
|
|
// If a single reaction existed so far, create a list and store the
|
|
// old and the new reaction in it.
|
|
reactions = NewDenseFullyAllocatedArray(cx, 2);
|
|
if (!reactions)
|
|
return false;
|
|
if (reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success)
|
|
return false;
|
|
|
|
reactions->setDenseElement(0, reactionsVal);
|
|
reactions->setDenseElement(1, reactionVal);
|
|
|
|
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions));
|
|
} else {
|
|
// Otherwise, just store the new reaction.
|
|
reactions = &reactionsObj->as<NativeObject>();
|
|
uint32_t len = reactions->getDenseInitializedLength();
|
|
if (reactions->ensureDenseElements(cx, 0, len + 1) != DenseElementResult::Success)
|
|
return false;
|
|
reactions->setDenseElement(len, reactionVal);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
|
|
HandleValue onRejected, HandleObject dependentPromise,
|
|
HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal)
|
|
{
|
|
if (promise->state() != JS::PromiseState::Pending)
|
|
return true;
|
|
|
|
Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, dependentPromise,
|
|
onFulfilled, onRejected,
|
|
resolve, reject,
|
|
incumbentGlobal));
|
|
if (!reaction)
|
|
return false;
|
|
return AddPromiseReaction(cx, promise, reaction);
|
|
}
|
|
|
|
namespace {
|
|
// Generator used by PromiseObject::getID.
|
|
mozilla::Atomic<uint64_t> gIDGenerator(0);
|
|
} // namespace
|
|
|
|
double
|
|
PromiseObject::lifetime()
|
|
{
|
|
return MillisecondsSinceStartup() - allocationTime();
|
|
}
|
|
|
|
uint64_t
|
|
PromiseObject::getID()
|
|
{
|
|
Value idVal(getFixedSlot(PromiseSlot_Id));
|
|
if (idVal.isUndefined()) {
|
|
idVal.setDouble(++gIDGenerator);
|
|
setFixedSlot(PromiseSlot_Id, idVal);
|
|
}
|
|
return uint64_t(idVal.toNumber());
|
|
}
|
|
|
|
/**
|
|
* Returns all promises that directly depend on this one. That means those
|
|
* created by calling `then` on this promise, or the promise returned by
|
|
* `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise
|
|
* being a member of the passed-in `iterable`.
|
|
*
|
|
* Per spec, we should have separate lists of reaction records for the
|
|
* fulfill and reject cases. As an optimization, we have only one of those,
|
|
* containing the required data for both cases. So we just walk that list
|
|
* and extract the dependent promises from all reaction records.
|
|
*/
|
|
bool
|
|
PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values)
|
|
{
|
|
if (state() != JS::PromiseState::Pending)
|
|
return true;
|
|
|
|
RootedValue reactionsVal(cx, getFixedSlot(PromiseSlot_ReactionsOrResult));
|
|
|
|
// If no reactions are pending, we don't have list and are done.
|
|
if (reactionsVal.isNullOrUndefined())
|
|
return true;
|
|
|
|
RootedNativeObject reactions(cx, &reactionsVal.toObject().as<NativeObject>());
|
|
|
|
// If only a single reaction is pending, it's stored directly.
|
|
if (reactions->is<PromiseReactionRecord>()) {
|
|
// Not all reactions have a Promise on them.
|
|
RootedObject promiseObj(cx, reactions->as<PromiseReactionRecord>().promise());
|
|
if (!promiseObj)
|
|
return true;
|
|
|
|
if (!values.growBy(1))
|
|
return false;
|
|
|
|
values[0].setObject(*promiseObj);
|
|
return true;
|
|
}
|
|
|
|
uint32_t len = reactions->getDenseInitializedLength();
|
|
MOZ_ASSERT(len >= 2);
|
|
|
|
size_t valuesIndex = 0;
|
|
Rooted<PromiseReactionRecord*> reaction(cx);
|
|
for (size_t i = 0; i < len; i++) {
|
|
reaction = &reactions->getDenseElement(i).toObject().as<PromiseReactionRecord>();
|
|
|
|
// Not all reactions have a Promise on them.
|
|
RootedObject promiseObj(cx, reaction->promise());
|
|
if (!promiseObj)
|
|
continue;
|
|
if (!values.growBy(1))
|
|
return false;
|
|
|
|
values[valuesIndex++].setObject(*promiseObj);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */ bool
|
|
PromiseObject::resolve(JSContext* cx, Handle<PromiseObject*> promise, HandleValue resolutionValue)
|
|
{
|
|
MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC));
|
|
if (promise->state() != JS::PromiseState::Pending)
|
|
return true;
|
|
|
|
RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise));
|
|
RootedValue funVal(cx, ObjectValue(*resolveFun));
|
|
|
|
// For xray'd Promises, the resolve fun may have been created in another
|
|
// compartment. For the call below to work in that case, wrap the
|
|
// function into the current compartment.
|
|
if (!cx->compartment()->wrap(cx, &funVal))
|
|
return false;
|
|
|
|
FixedInvokeArgs<1> args(cx);
|
|
args[0].set(resolutionValue);
|
|
|
|
RootedValue dummy(cx);
|
|
return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
|
|
}
|
|
|
|
/* static */ bool
|
|
PromiseObject::reject(JSContext* cx, Handle<PromiseObject*> promise, HandleValue rejectionValue)
|
|
{
|
|
MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC));
|
|
if (promise->state() != JS::PromiseState::Pending)
|
|
return true;
|
|
|
|
RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction));
|
|
MOZ_ASSERT(IsCallable(funVal));
|
|
|
|
FixedInvokeArgs<1> args(cx);
|
|
args[0].set(rejectionValue);
|
|
|
|
RootedValue dummy(cx);
|
|
return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
|
|
}
|
|
|
|
/* static */ void
|
|
PromiseObject::onSettled(JSContext* cx, Handle<PromiseObject*> promise)
|
|
{
|
|
RootedObject stack(cx);
|
|
if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
|
|
if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) {
|
|
cx->clearPendingException();
|
|
return;
|
|
}
|
|
}
|
|
promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack));
|
|
promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
|
|
|
|
if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
|
|
cx->runtime()->addUnhandledRejectedPromise(cx, promise);
|
|
|
|
JS::dbg::onPromiseSettled(cx, promise);
|
|
}
|
|
|
|
PromiseTask::PromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
|
|
: runtime_(cx),
|
|
promise_(cx, promise)
|
|
{}
|
|
|
|
PromiseTask::~PromiseTask()
|
|
{
|
|
MOZ_ASSERT(CurrentThreadCanAccessZone(promise_->zone()));
|
|
}
|
|
|
|
void
|
|
PromiseTask::finish(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(cx == runtime_);
|
|
{
|
|
// We can't leave a pending exception when returning to the caller so do
|
|
// the same thing as Gecko, which is to ignore the error. This should
|
|
// only happen due to OOM or interruption.
|
|
AutoCompartment ac(cx, promise_);
|
|
if (!finishPromise(cx, promise_))
|
|
cx->clearPendingException();
|
|
}
|
|
js_delete(this);
|
|
}
|
|
|
|
void
|
|
PromiseTask::cancel(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(cx == runtime_);
|
|
js_delete(this);
|
|
}
|
|
|
|
bool
|
|
PromiseTask::executeAndFinish(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(!CanUseExtraThreads());
|
|
execute();
|
|
return finishPromise(cx, promise_);
|
|
}
|
|
|
|
static JSObject*
|
|
CreatePromisePrototype(JSContext* cx, JSProtoKey key)
|
|
{
|
|
return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_);
|
|
}
|
|
|
|
static const JSFunctionSpec promise_methods[] = {
|
|
JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
|
|
JS_FN("then", Promise_then, 2, 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_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
|
|
JS_PS_END
|
|
};
|
|
|
|
static const ClassSpec PromiseObjectClassSpec = {
|
|
GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
|
|
CreatePromisePrototype,
|
|
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
|
|
};
|
|
|
|
static const ClassSpec PromiseObjectProtoClassSpec = {
|
|
DELEGATED_CLASSSPEC(PromiseObject::class_.spec),
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
ClassSpec::IsDelegated
|
|
};
|
|
|
|
const Class PromiseObject::protoClass_ = {
|
|
"PromiseProto",
|
|
JSCLASS_HAS_CACHED_PROTO(JSProto_Promise),
|
|
JS_NULL_CLASS_OPS,
|
|
&PromiseObjectProtoClassSpec
|
|
};
|