зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1289318 - Part 9: Port Promise.resolve and Promise.reject to C++ and optimize various common cases. r=efaust
This adds a lot of C++ code, but it allows us to optimize cases that would be annoying to optimize in JS. MozReview-Commit-ID: CbKWXEs8pMv
This commit is contained in:
Родитель
36380475eb
Коммит
a3a1f7aa3e
|
@ -22,25 +22,6 @@
|
|||
|
||||
using namespace js;
|
||||
|
||||
static const JSFunctionSpec promise_methods[] = {
|
||||
JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
|
||||
JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static const JSFunctionSpec promise_static_methods[] = {
|
||||
JS_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
|
||||
JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
|
||||
JS_SELF_HOSTED_FN("reject", "Promise_static_reject", 1, 0),
|
||||
JS_SELF_HOSTED_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 double
|
||||
MillisecondsSinceStartup()
|
||||
{
|
||||
|
@ -285,6 +266,45 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
|
|||
return result;
|
||||
}
|
||||
|
||||
// ES2016, 25.4.1.3.2, steps 7-13.
|
||||
static 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 (!GetAndClearException(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)
|
||||
|
@ -326,46 +346,10 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
|
|||
return status;
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
if (!resolutionVal.isObject()) {
|
||||
bool status = FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
|
||||
if (status)
|
||||
args.rval().setUndefined();
|
||||
return status;
|
||||
}
|
||||
|
||||
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 (!GetAndClearException(cx, &error))
|
||||
return false;
|
||||
|
||||
return RejectMaybeWrappedPromise(cx, promise, error);
|
||||
}
|
||||
|
||||
// Step 10 (implicit).
|
||||
|
||||
// Step 11.
|
||||
if (!IsCallable(thenVal)) {
|
||||
bool status = FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
|
||||
if (status)
|
||||
args.rval().setUndefined();
|
||||
return status;
|
||||
}
|
||||
|
||||
// Step 12.
|
||||
if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
|
||||
return false;
|
||||
|
||||
// Step 13.
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
bool status = ResolvePromiseInternal(cx, promise, resolutionVal);
|
||||
if (status)
|
||||
args.rval().setUndefined();
|
||||
return status;
|
||||
}
|
||||
|
||||
// ES2016, 25.4.1.3.
|
||||
|
@ -402,7 +386,308 @@ CreateResolvingFunctions(JSContext* cx, HandleValue promise,
|
|||
return true;
|
||||
}
|
||||
|
||||
static PromiseObject*
|
||||
CreatePromiseObjectInternal(JSContext* cx, HandleObject proto, bool protoIsWrapped)
|
||||
{
|
||||
// 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(PROMISE_FLAGS_SLOT, 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(PROMISE_ALLOCATION_SITE_SLOT, ObjectOrNullValue(stack));
|
||||
promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT,
|
||||
DoubleValue(MillisecondsSinceStartup()));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
|
||||
*/
|
||||
/* static */ JSObject*
|
||||
PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
|
||||
{
|
||||
// Steps 1-2 (omitted).
|
||||
|
||||
// Roughly step 3.
|
||||
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
|
||||
if (!promise)
|
||||
return nullptr;
|
||||
|
||||
// Let the Debugger know about this Promise.
|
||||
JS::dbg::onNewPromise(cx, promise);
|
||||
|
||||
// Roughly step 4.
|
||||
if (!ResolvePromise(cx, promise, value, JS::PromiseState::Rejected))
|
||||
return nullptr;
|
||||
|
||||
// Step 5.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
|
||||
*/
|
||||
/* static */ JSObject*
|
||||
PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
|
||||
{
|
||||
// Steps 1-2 (omitted).
|
||||
|
||||
// Step 3.
|
||||
if (value.isObject()) {
|
||||
JSObject* obj = &value.toObject();
|
||||
if (IsWrapper(obj))
|
||||
obj = CheckedUnwrap(obj);
|
||||
// Instead of getting the `constructor` property, do an unforgeable
|
||||
// check.
|
||||
if (obj && obj->is<PromiseObject>())
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
|
||||
if (!promise)
|
||||
return nullptr;
|
||||
|
||||
// Let the Debugger know about this Promise.
|
||||
JS::dbg::onNewPromise(cx, promise);
|
||||
|
||||
// Steps 5.
|
||||
if (!ResolvePromiseInternal(cx, promise, value))
|
||||
return nullptr;
|
||||
|
||||
// Step 6.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// ES6, 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(0).isUndefined() || !F->getExtendedSlot(1).isUndefined()) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5.
|
||||
F->setExtendedSlot(0, args.get(0));
|
||||
|
||||
// Step 6.
|
||||
F->setExtendedSlot(1, args.get(1));
|
||||
|
||||
// Step 7.
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2016, 25.4.1.5.
|
||||
// Creates PromiseCapability records, see 25.4.1.1.
|
||||
static bool
|
||||
NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
|
||||
MutableHandleObject resolve, MutableHandleObject reject)
|
||||
{
|
||||
RootedValue cVal(cx, ObjectValue(*C));
|
||||
|
||||
// Steps 1-2.
|
||||
if (!IsConstructor(C)) {
|
||||
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3 (omitted).
|
||||
|
||||
// Step 4.
|
||||
RootedAtom funName(cx, cx->names().empty);
|
||||
RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
|
||||
gc::AllocKind::FUNCTION_EXTENDED));
|
||||
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(0));
|
||||
if (!IsCallable(resolveVal)) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 8.
|
||||
RootedValue rejectVal(cx, executor->getExtendedSlot(1));
|
||||
if (!IsCallable(rejectVal)) {
|
||||
JS_ReportErrorNumber(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;
|
||||
}
|
||||
|
||||
enum ResolveOrRejectMode {
|
||||
ResolveMode,
|
||||
RejectMode
|
||||
};
|
||||
|
||||
static bool
|
||||
CommonStaticResolveRejectImpl(JSContext* cx, unsigned argc, Value* vp, ResolveOrRejectMode mode)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
RootedValue x(cx, args.get(0));
|
||||
|
||||
// Steps 1-2.
|
||||
if (!args.thisv().isObject()) {
|
||||
const char* msg = mode == ResolveMode
|
||||
? "Receiver of Promise.resolve call"
|
||||
: "Receiver of Promise.reject call";
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg);
|
||||
return false;
|
||||
}
|
||||
RootedValue cVal(cx, args.thisv());
|
||||
RootedObject C(cx, &cVal.toObject());
|
||||
|
||||
// Step 3 of Resolve.
|
||||
if (mode == ResolveMode && x.isObject()) {
|
||||
RootedObject xObj(cx, &x.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 false;
|
||||
if (ctorVal == cVal) {
|
||||
args.rval().set(x);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 4-5 of Resolve, 3-4 of Reject.
|
||||
RootedObject promiseCtor(cx);
|
||||
if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
|
||||
return false;
|
||||
RootedObject promise(cx);
|
||||
|
||||
// If the current constructor is the original Promise constructor, we can
|
||||
// optimize things by skipping the creation and invocation of the resolve
|
||||
// and reject callbacks, directly creating and resolving the new Promise.
|
||||
if (promiseCtor == C) {
|
||||
// Roughly step 4 of Resolve, 3 of Reject.
|
||||
promise = CreatePromiseObjectInternal(cx, nullptr, false);
|
||||
if (!promise)
|
||||
return false;
|
||||
|
||||
// Let the Debugger know about this Promise.
|
||||
JS::dbg::onNewPromise(cx, promise);
|
||||
|
||||
// Roughly step 5 of Resolve.
|
||||
if (mode == ResolveMode) {
|
||||
if (!ResolvePromiseInternal(cx, promise, x))
|
||||
return false;
|
||||
} else {
|
||||
// Roughly step 4 of Reject.
|
||||
Rooted<PromiseObject*> promiseObj(cx, &promise->as<PromiseObject>());
|
||||
if (!ResolvePromise(cx, promiseObj, x, JS::PromiseState::Rejected))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Step 4 of Resolve, 3 of Reject.
|
||||
RootedObject resolveFun(cx);
|
||||
RootedObject rejectFun(cx);
|
||||
if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun))
|
||||
return false;
|
||||
|
||||
// Step 5 of Resolve, 4 of Reject.
|
||||
FixedInvokeArgs<1> args2(cx);
|
||||
args2[0].set(x);
|
||||
RootedValue calleeOrRval(cx, ObjectValue(mode == ResolveMode ? *resolveFun : *rejectFun));
|
||||
if (!Call(cx, calleeOrRval, UndefinedHandleValue, args2, &calleeOrRval))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6 of Resolve, 4 of Reject.
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ES2016, 25.4.4.4, Promise.reject.
|
||||
*/
|
||||
static bool
|
||||
Promise_reject(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
return CommonStaticResolveRejectImpl(cx, argc, vp, RejectMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* ES2016, 25.4.4.5, Promise.resolve.
|
||||
*/
|
||||
static bool
|
||||
Promise_resolve(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
return CommonStaticResolveRejectImpl(cx, argc, vp, ResolveMode);
|
||||
}
|
||||
|
||||
// ES2016, February 12 draft, 25.4.3.1. steps 3-11.
|
||||
/* static */
|
||||
PromiseObject*
|
||||
PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
|
||||
{
|
||||
|
@ -422,43 +707,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
|
|||
}
|
||||
|
||||
|
||||
// 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 (wrappedProto)
|
||||
ac.emplace(cx, usedProto);
|
||||
|
||||
promise = NewObjectWithClassProto<PromiseObject>(cx, usedProto);
|
||||
if (!promise)
|
||||
return nullptr;
|
||||
|
||||
// Step 4.
|
||||
promise->setFixedSlot(PROMISE_FLAGS_SLOT, 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(PROMISE_ALLOCATION_SITE_SLOT, ObjectOrNullValue(stack));
|
||||
promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT,
|
||||
DoubleValue(MillisecondsSinceStartup()));
|
||||
}
|
||||
// Steps 3-7.
|
||||
Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, wrappedProto));
|
||||
|
||||
RootedValue promiseVal(cx, ObjectValue(*promise));
|
||||
if (wrappedProto && !cx->compartment()->wrap(cx, &promiseVal))
|
||||
|
@ -1141,6 +1391,25 @@ CreatePromisePrototype(JSContext* cx, JSProtoKey key)
|
|||
return cx->global()->createBlankPrototype(cx, &PromiseObject::protoClass_);
|
||||
}
|
||||
|
||||
static const JSFunctionSpec promise_methods[] = {
|
||||
JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
|
||||
JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static const JSFunctionSpec promise_static_methods[] = {
|
||||
JS_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
|
||||
JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
|
||||
JS_FN("reject", Promise_reject, 1, 0),
|
||||
JS_FN("resolve", Promise_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,
|
||||
|
|
|
@ -23,6 +23,9 @@ class PromiseObject : public NativeObject
|
|||
static PromiseObject* create(JSContext* cx, HandleObject executor,
|
||||
HandleObject proto = nullptr);
|
||||
|
||||
static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
|
||||
static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
|
||||
|
||||
JS::PromiseState state() {
|
||||
int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
|
||||
if (!(flags & PROMISE_FLAG_RESOLVED)) {
|
||||
|
|
|
@ -495,47 +495,9 @@ function AddDependentPromise(dependentPromise) {
|
|||
_DefineDataProperty(reactions, reactions.length, reaction);
|
||||
}
|
||||
|
||||
// ES6, 25.4.4.4.
|
||||
function Promise_static_reject(r) {
|
||||
// Step 1.
|
||||
let C = this;
|
||||
// ES2016, 25.4.4.4 (implemented in C++).
|
||||
|
||||
// Step 2.
|
||||
if (!IsObject(C))
|
||||
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.reject call");
|
||||
|
||||
// Steps 3-4.
|
||||
let promiseCapability = NewPromiseCapability(C);
|
||||
|
||||
// Steps 5-6.
|
||||
callContentFunction(promiseCapability.reject, undefined, r);
|
||||
|
||||
// Step 7.
|
||||
return promiseCapability.promise;
|
||||
}
|
||||
|
||||
// ES6, 25.4.4.5.
|
||||
function Promise_static_resolve(x) {
|
||||
// Step 1.
|
||||
let C = this;
|
||||
|
||||
// Step 2.
|
||||
if (!IsObject(C))
|
||||
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.resolve call");
|
||||
|
||||
// Step 3.
|
||||
if (IsObject(x) && (IsPromise(x) || IsWrappedPromise(x)) && x.constructor === C)
|
||||
return x;
|
||||
|
||||
// Steps 4-5.
|
||||
let promiseCapability = NewPromiseCapability(C);
|
||||
|
||||
// Steps 6-7.
|
||||
callContentFunction(promiseCapability.resolve, undefined, x);
|
||||
|
||||
// Step 8.
|
||||
return promiseCapability.promise;
|
||||
}
|
||||
// ES2016, 25.4.4.5 (implemented in C++).
|
||||
|
||||
// ES6, 25.4.4.6.
|
||||
function Promise_static_get_species() {
|
||||
|
|
|
@ -4725,26 +4725,9 @@ JS::CallOriginalPromiseResolve(JSContext* cx, JS::HandleValue resolutionValue)
|
|||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, resolutionValue);
|
||||
|
||||
RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
|
||||
if (!promiseCtor)
|
||||
return nullptr;
|
||||
|
||||
JSObject* obj;
|
||||
{
|
||||
FixedInvokeArgs<1> args(cx);
|
||||
|
||||
args[0].set(resolutionValue);
|
||||
|
||||
RootedValue thisvOrRval(cx, ObjectValue(*promiseCtor));
|
||||
if (!CallSelfHostedFunction(cx, "Promise_static_resolve", thisvOrRval, args, &thisvOrRval))
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT(thisvOrRval.isObject());
|
||||
obj = &thisvOrRval.toObject();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(obj->is<PromiseObject>());
|
||||
return obj;
|
||||
RootedObject promise(cx, PromiseObject::unforgeableResolve(cx, resolutionValue));
|
||||
MOZ_ASSERT_IF(promise, promise->is<PromiseObject>());
|
||||
return promise;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSObject*)
|
||||
|
@ -4754,26 +4737,9 @@ JS::CallOriginalPromiseReject(JSContext* cx, JS::HandleValue rejectionValue)
|
|||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, rejectionValue);
|
||||
|
||||
RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
|
||||
if (!promiseCtor)
|
||||
return nullptr;
|
||||
|
||||
JSObject* obj;
|
||||
{
|
||||
FixedInvokeArgs<1> args(cx);
|
||||
|
||||
args[0].set(rejectionValue);
|
||||
|
||||
RootedValue thisvOrRval(cx, ObjectValue(*promiseCtor));
|
||||
if (!CallSelfHostedFunction(cx, "Promise_static_reject", thisvOrRval, args, &thisvOrRval))
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT(thisvOrRval.isObject());
|
||||
obj = &thisvOrRval.toObject();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(obj->is<PromiseObject>());
|
||||
return obj;
|
||||
RootedObject promise(cx, PromiseObject::unforgeableReject(cx, rejectionValue));
|
||||
MOZ_ASSERT_IF(promise, promise->is<PromiseObject>());
|
||||
return promise;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
|
|
|
@ -24,6 +24,7 @@ new Promise((res, rej)=> {res('result'); rej('rejection'); })
|
|||
new Promise((res, rej)=> { rej('rejection'); res('result'); })
|
||||
.catch(val=>{results.push('catch after reject+resolve with val: ' + val);})
|
||||
|
||||
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(results.length, 6);
|
||||
|
@ -34,6 +35,23 @@ assertEq(results[3], 'chained then with val: first then rval');
|
|||
assertEq(results[4], 'then after catch with val: 2');
|
||||
assertEq(results[5], 'then after resolve+reject with val: result');
|
||||
|
||||
results = [];
|
||||
|
||||
Promise.resolve('resolution').then(res=>results.push(res),
|
||||
rej=>{ throw new Error("mustn't be called"); });
|
||||
|
||||
let thenCalled = false;
|
||||
Promise.reject('rejection').then(_=>{thenCalled = true},
|
||||
rej=>results.push(rej));
|
||||
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(thenCalled, false);
|
||||
assertEq(results.length, 2);
|
||||
assertEq(results[0], 'resolution');
|
||||
assertEq(results[1], 'rejection');
|
||||
|
||||
|
||||
function callback() {}
|
||||
|
||||
// Calling the executor function with content functions shouldn't assert:
|
||||
|
@ -42,17 +60,30 @@ Promise.reject.call(function(exec) { exec(callback, callback); });
|
|||
Promise.all.call(function(exec) { exec(callback, callback); });
|
||||
Promise.race.call(function(exec) { exec(callback, callback); });
|
||||
|
||||
let resolveResult = undefined;
|
||||
function resolveFun() {resolveResult = "resolveCalled";}
|
||||
Promise.resolve.call(function(exec) { exec(resolveFun, callback); });
|
||||
assertEq(resolveResult, "resolveCalled");
|
||||
|
||||
let rejectResult = undefined;
|
||||
function rejectFun() {rejectResult = "rejectCalled";}
|
||||
Promise.reject.call(function(exec) { exec(callback, rejectFun); });
|
||||
assertEq(rejectResult, "rejectCalled");
|
||||
|
||||
// These should throw:
|
||||
var wasCalled = false;
|
||||
var hasThrown = false;
|
||||
try {
|
||||
// Calling the executor function twice, providing a resolve callback both times.
|
||||
Promise.resolve.call(function(executor) {
|
||||
wasCalled = true;
|
||||
executor(callback, undefined);
|
||||
executor(callback, callback);
|
||||
});
|
||||
} catch (e) {
|
||||
hasThrown = true;
|
||||
}
|
||||
assertEq(wasCalled, true);
|
||||
assertEq(hasThrown, true);
|
||||
|
||||
var hasThrown = false;
|
||||
|
|
Загрузка…
Ссылка в новой задаче