--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -44,38 +44,358 @@ static const JSPropertySpec promise_stat
static double
MillisecondsSinceStartup()
{
auto now = mozilla::TimeStamp::Now();
bool ignored;
return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
}
+enum ResolutionFunctionSlots {
+ ResolutionFunctionSlot_Promise = 0,
+ ResolutionFunctionSlot_OtherFunction,
+};
+
+// 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());
+
+ AutoIdVector keys(cx);
+ if (!GetPropertyKeys(cx, reactions, JSITER_OWNONLY, &keys))
+ return false;
+ MOZ_ASSERT(keys.length() > 0, "Reactions list should be created lazily");
+
+ RootedPropertyName handlerName(cx);
+ handlerName = state == JS::PromiseState::Fulfilled
+ ? cx->names().fulfillHandler
+ : cx->names().rejectHandler;
+
+ // Each reaction is an internally-created object with the structure:
+ // {
+ // promise: [the promise this reaction resolves],
+ // resolve: [the `resolve` callback content code provided],
+ // reject: [the `reject` callback content code provided],
+ // fulfillHandler: [the internal handler that fulfills the promise]
+ // rejectHandler: [the internal handler that rejects the promise]
+ // incumbentGlobal: [an object from the global that was incumbent when
+ // the reaction was created]
+ // }
+ RootedValue val(cx);
+ RootedObject reaction(cx);
+ RootedValue handler(cx);
+ RootedObject promise(cx);
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+ RootedObject objectFromIncumbentGlobal(cx);
+
+ for (size_t i = 0; i < keys.length(); i++) {
+ if (!GetProperty(cx, reactions, reactions, keys[i], &val))
+ return false;
+ reaction = &val.toObject();
+
+ if (!GetProperty(cx, reaction, reaction, cx->names().promise, &val))
+ return false;
+
+ // The jsapi function AddPromiseReactions can add reaction records
+ // without a `promise` object. See comment for EnqueuePromiseReactions
+ // in Promise.js.
+ promise = val.toObjectOrNull();
+
+ if (!GetProperty(cx, reaction, reaction, handlerName, &handler))
+ return false;
+ MOZ_ASSERT((handler.isNumber() &&
+ (handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
+ handler.toNumber() == PROMISE_HANDLER_THROWER)) ||
+ handler.toObject().isCallable());
+
+ if (!GetProperty(cx, reaction, reaction, cx->names().resolve, &val))
+ return false;
+ resolve = &val.toObject();
+ MOZ_ASSERT(IsCallable(resolve));
+
+ if (!GetProperty(cx, reaction, reaction, cx->names().reject, &val))
+ return false;
+ reject = &val.toObject();
+ MOZ_ASSERT(IsCallable(reject));
+
+ if (!GetProperty(cx, reaction, reaction, cx->names().incumbentGlobal, &val))
+ return false;
+ objectFromIncumbentGlobal = val.toObjectOrNull();
+
+ if (!EnqueuePromiseReactionJob(cx, handler, valueOrReason, resolve, reject, promise,
+ objectFromIncumbentGlobal))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// 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(PROMISE_REACTIONS_SLOT));
+
+ // Step 3.
+ promise->setFixedSlot(PROMISE_RESULT_SLOT, valueOrReason);
+
+ // Steps 4-5.
+ promise->setFixedSlot(PROMISE_REACTIONS_SLOT, UndefinedValue());
+
+ // Step 6.
+ promise->setFixedSlot(PROMISE_STATE_SLOT, Int32Value(int32_t(state)));
+
+ // Also null out the resolve/reject functions so they can be GC'd.
+ promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, UndefinedValue());
+ promise->setFixedSlot(PROMISE_REJECT_FUNCTION_SLOT, UndefinedValue());
+
+ // Now that everything else is done, do the things the debugger needs.
+ // Step 7 of RejectPromise implemented in onSettled.
+ promise->onSettled(cx);
+
+ // 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 (!IsWrapper(promiseObj)) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ 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);
+}
+
+// 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 (!IsWrapper(promiseObj)) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ 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);
+}
+
+static void
+ClearResolutionFunctionSlots(JSFunction* resolutionFun)
+{
+ JSFunction* otherFun = &resolutionFun->getExtendedSlot(ResolutionFunctionSlot_OtherFunction)
+ .toObject().as<JSFunction>();
+ resolutionFun->setExtendedSlot(ResolutionFunctionSlot_Promise, UndefinedValue());
+ otherFun->setExtendedSlot(ResolutionFunctionSlot_Promise, UndefinedValue());
+
+ // Also reset the reference to the resolve function so it can be collected.
+ resolutionFun->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, UndefinedValue());
+ otherFun->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, UndefinedValue());
+}
+// 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(ResolutionFunctionSlot_Promise));
+
+ // Steps 3-4.
+ // We use the existence of the Promise a a signal for whether it was
+ // already resolved. Upon resolution, it's reset to `undefined`.
+ if (promiseVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedObject promise(cx, &promiseVal.toObject());
+
+ // Step 5.
+ ClearResolutionFunctionSlots(reject);
+
+ // Step 6.
+ bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal);
+ if (result)
+ args.rval().setUndefined();
+ return result;
+}
+
+// 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(ResolutionFunctionSlot_Promise));
+
+ // Steps 3-4.
+ // We use the existence of the Promise a a signal for whether it was
+ // already resolved. Upon resolution, it's reset to `undefined`.
+ if (promiseVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedObject promise(cx, &promiseVal.toObject());
+
+ // Step 5.
+ ClearResolutionFunctionSlots(resolve);
+
+ // Step 6.
+ if (resolutionVal == promiseVal) {
+ // Step 6.a.
+ JS_ReportErrorNumber(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;
+ }
+
+ // 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;
+}
+
+// ES2016, 25.4.1.3.
+static MOZ_MUST_USE bool
CreateResolvingFunctions(JSContext* cx, HandleValue promise,
MutableHandleValue resolveVal,
MutableHandleValue rejectVal)
{
- FixedInvokeArgs<1> args(cx);
- args[0].set(promise);
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
+ gc::AllocKind::FUNCTION_EXTENDED));
+ if (!resolve)
+ return false;
- RootedValue rval(cx);
-
- if (!CallSelfHostedFunction(cx, cx->names().CreateResolvingFunctions, UndefinedHandleValue,
- args, &rval))
- {
+ RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
+ gc::AllocKind::FUNCTION_EXTENDED));
+ if (!reject)
return false;
- }
+
+ // The resolving functions are trusted, so self-hosted code should be able
+ // to call them using callFunction instead of callContentFunction.
+ resolve->setFlags(resolve->flags() | JSFunction::SELF_HOSTED);
+ reject->setFlags(reject->flags() | JSFunction::SELF_HOSTED);
- RootedArrayObject resolvingFunctions(cx, &args.rval().toObject().as<ArrayObject>());
- resolveVal.set(resolvingFunctions->getDenseElement(0));
- rejectVal.set(resolvingFunctions->getDenseElement(1));
+ resolve->setExtendedSlot(ResolutionFunctionSlot_Promise, promise);
+ resolve->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*reject));
- MOZ_ASSERT(IsCallable(resolveVal));
- MOZ_ASSERT(IsCallable(rejectVal));
+ reject->setExtendedSlot(ResolutionFunctionSlot_Promise, promise);
+ reject->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*resolve));
+
+ resolveVal.setObject(*resolve);
+ rejectVal.setObject(*reject);
return true;
}
// ES2016, February 12 draft, 25.4.3.1. steps 3-11.
PromiseObject*
PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
{
@@ -232,46 +552,49 @@ PromiseObject::getID()
* 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)
{
RootedValue reactionsVal(cx, getReservedSlot(PROMISE_REACTIONS_SLOT));
- RootedObject reactions(cx, reactionsVal.toObjectOrNull());
- if (!reactions)
+ if (reactionsVal.isNullOrUndefined())
return true;
+ RootedObject reactions(cx, &reactionsVal.toObject());
AutoIdVector keys(cx);
if (!GetPropertyKeys(cx, reactions, JSITER_OWNONLY, &keys))
return false;
if (keys.length() == 0)
return true;
if (!values.growBy(keys.length()))
return false;
// Each reaction is an internally-created object with the structure:
// {
// promise: [the promise this reaction resolves],
// resolve: [the `resolve` callback content code provided],
// reject: [the `reject` callback content code provided],
- // handler: [the internal handler that fulfills/rejects the promise]
+ // fulfillHandler: [the internal handler that fulfills the promise]
+ // rejectHandler: [the internal handler that rejects the promise]
+ // incumbentGlobal: [an object from the global that was incumbent when
+ // the reaction was created]
// }
//
// In the following loop we collect the `capabilities.promise` values for
// each reaction.
for (size_t i = 0; i < keys.length(); i++) {
MutableHandleValue val = values[i];
if (!GetProperty(cx, reactions, reactions, keys[i], val))
return false;
RootedObject reaction(cx, &val.toObject());
- if (!GetProperty(cx, reaction, reaction, cx->runtime()->commonNames->promise, val))
+ if (!GetProperty(cx, reaction, reaction, cx->names().promise, val))
return false;
}
return true;
}
namespace js {
@@ -732,18 +1055,16 @@ EnqueuePromiseResolveThenableJob(JSConte
// 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);
- if (!funName)
- return false;
RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED));
if (!job)
return false;
// Store the `then` function on the callback.
job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -12,178 +12,24 @@ function PromiseReactionRecord(promise,
this.reject = reject;
this.fulfillHandler = fulfillHandler;
this.rejectHandler = rejectHandler;
this.incumbentGlobal = incumbentGlobal;
}
MakeConstructible(PromiseReactionRecord, PromiseReactionRecordProto);
-// ES6, 25.4.1.3.
-function CreateResolvingFunctions(promise) {
- // The callbacks created here can deal with Promises wrapped in cross-
- // compartment wrappers. That's required because in some circumstances,
- // they're created in a higher-privileged compartment from the Promise,
- // so they can be invoked seamlessly by code in that compartment.
- //
- // See the comment in PromiseConstructor (in builtin/Promise.cpp) for more
- // details.
- let unwrap = false;
- if (!IsPromise(promise)) {
- assert(IsWrappedPromise(promise),
- "CreateResolvingFunctions expects arg0 to be a - maybe wrapped - promise");
- unwrap = true;
- }
-
- // Step 1.
- let alreadyResolved = false;
-
- // Steps 2-4.
- // ES6, 25.4.1.3.2. Inlined here so we can use an upvar instead of a slot to
- // store promise and alreadyResolved, and share the latter with reject below.
- function resolve(resolution) {
- // Steps 1-3 (implicit).
-
- // Step 4.
- if (alreadyResolved)
- return undefined;
-
- // Step 5.
- alreadyResolved = true;
-
- // Step 6.
- // We know |promise| is an object, so using strict equality instead of
- // SameValue is fine.
- if (resolution === promise) {
- // Step 6.a.
- let selfResolutionError = GetTypeError(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
-
- // Step 6.b.
- if (unwrap) {
- return RejectUnwrappedPromise(promise, selfResolutionError);
- }
- return RejectPromise(promise, selfResolutionError);
- }
-
- // Step 7.
- if (!IsObject(resolution)) {
- if (unwrap) {
- return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
- "FulfillUnwrappedPromise");
- }
- return FulfillPromise(promise, resolution);
- }
-
- // Steps 8-9.
- let then;
- try {
- then = resolution.then;
- } catch (e) {
- if (unwrap) {
- return RejectUnwrappedPromise(promise, e);
- }
- return RejectPromise(promise, e);
- }
-
- // Step 10 (implicit).
-
- // Step 11.
- if (!IsCallable(then)) {
- if (unwrap) {
- return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
- "FulfillUnwrappedPromise");
- }
- return FulfillPromise(promise, resolution);
- }
-
- // Step 12.
- _EnqueuePromiseResolveThenableJob(promise, resolution, then);
-
- // Step 13.
- return undefined;
- }
-
- // Steps 5-7.
- // ES6, 25.4.1.3.2.
- function reject(reason) {
- // Steps 1-3 (implicit).
-
- // Step 4.
- if (alreadyResolved)
- return undefined;
-
- // Step 5.
- alreadyResolved = true;
-
- // Step 6.
- if (unwrap) {
- return RejectUnwrappedPromise(promise, reason);
- }
- return RejectPromise(promise, reason);
- }
-
- // Return an array instead of an object with resolve/reject properties
- // to make value extraction from C++ easier.
- return [resolve, reject];
-}
-
-// ES6, 25.4.1.4.
-function FulfillPromise(promise, value) {
- return ResolvePromise(promise, value, PROMISE_STATE_FULFILLED);
-}
-function FulfillUnwrappedPromise(value) {
- return ResolvePromise(this, value, PROMISE_STATE_FULFILLED);
-}
-
-// Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
-// ES2016 February 12 draft.
-function ResolvePromise(promise, valueOrReason, state) {
- // Step 1.
- assert(GetPromiseState(promise) === PROMISE_STATE_PENDING,
- "Can't resolve non-pending promise");
- assert(state >= PROMISE_STATE_FULFILLED && state <= PROMISE_STATE_REJECTED,
- `Invalid Promise resolution state <${state}>`);
-
- // 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.
- var reactions = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_SLOT);
- let jobType = state === PROMISE_STATE_FULFILLED
- ? PROMISE_JOB_TYPE_FULFILL
- : PROMISE_JOB_TYPE_REJECT;
-
- // Step 3.
- UnsafeSetReservedSlot(promise, PROMISE_RESULT_SLOT, valueOrReason);
-
- // Steps 4-5.
- UnsafeSetReservedSlot(promise, PROMISE_REACTIONS_SLOT, null);
-
- // Step 6.
- UnsafeSetReservedSlot(promise, PROMISE_STATE_SLOT, state);
-
- // Also null out the resolve/reject functions so they can be GC'd.
- UnsafeSetReservedSlot(promise, PROMISE_RESOLVE_FUNCTION_SLOT, null);
- UnsafeSetReservedSlot(promise, PROMISE_REJECT_FUNCTION_SLOT, null);
-
- // Now that everything else is done, do the things the debugger needs.
- // Step 7 of RejectPromise implemented in the debugger intrinsic.
- _dbg_onPromiseSettled(promise);
-
- // Step 7 of FulfillPromise.
- // Step 8 of RejectPromise.
- if (reactions)
- TriggerPromiseReactions(reactions, jobType, valueOrReason);
-}
-
// Used to verify that an object is a PromiseCapability record.
var PromiseCapabilityRecordProto = {__proto__: null};
-// ES6, 25.4.1.5.
+// ES2016, 25.4.1.3, implemented in Promise.cpp.
+
+// ES2016, 25.4.1.4, implemented in Promise.cpp.
+
+// ES2016, 25.4.1.5.
// Creates PromiseCapability records, see 25.4.1.1.
function NewPromiseCapability(C) {
// Steps 1-2.
if (!IsConstructor(C))
ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, 0);
// Step 3. Replaced by individual fields, combined in step 11.
let resolve;
@@ -217,32 +63,23 @@ function NewPromiseCapability(C) {
return {
__proto__: PromiseCapabilityRecordProto,
promise,
resolve,
reject
};
}
-// ES6, 25.4.1.6. is implemented as an intrinsic in SelfHosting.cpp.
+// ES2016, 25.4.1.6, implemented in SelfHosting.cpp.
-// ES2016, February 12 draft, 25.4.1.7.
-function RejectPromise(promise, reason) {
- return ResolvePromise(promise, reason, PROMISE_STATE_REJECTED);
-}
+// ES2016, 25.4.1.7, implemented in Promise.cpp.
-// ES6, 25.4.1.8.
-function TriggerPromiseReactions(reactions, jobType, argument) {
- // Step 1.
- for (var i = 0, len = reactions.length; i < len; i++)
- EnqueuePromiseReactionJob(reactions[i], jobType, argument);
- // Step 2 (implicit).
-}
+// ES2016, 25.4.1.8, implemented in Promise.cpp.
-// ES2016, February 12 draft 25.4.1.9, implemented in SelfHosting.cpp.
+// ES2016, 25.4.1.9, implemented in SelfHosting.cpp.
// ES6, 25.4.2.1.
function EnqueuePromiseReactionJob(reaction, jobType, argument) {
// Reaction records contain handlers for both fulfillment and rejection.
// The `jobType` parameter allows us to retrieves the right one.
assert(jobType === PROMISE_JOB_TYPE_FULFILL || jobType === PROMISE_JOB_TYPE_REJECT,
"Invalid job type");
_EnqueuePromiseReactionJob(reaction[jobType],
@@ -252,17 +89,17 @@ function EnqueuePromiseReactionJob(react
reaction.promise,
reaction.incumbentGlobal || null);
}
// ES6, 25.4.2.2. (Implemented in C++).
// ES6, 25.4.3.1. (Implemented in C++).
-// ES7 2016-01-21 draft, 25.4.4.1.
+// ES2016, 25.4.4.1.
function Promise_static_all(iterable) {
// Step 1.
let C = this;
// Step 2.
if (!IsObject(C))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.all call");
@@ -472,17 +309,17 @@ function CreatePromiseAllResolveElementF
// Step 10.b.
return callContentFunction(promiseCapability.resolve, undefined, values);
}
// Step 11 (implicit).
};
}
-// ES7, 2016-01-21 draft, 25.4.4.3.
+// ES2016, 25.4.4.3.
function Promise_static_race(iterable) {
// Step 1.
let C = this;
// Step 2.
if (!IsObject(C))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.race call");
@@ -510,17 +347,17 @@ function Promise_static_race(iterable) {
// TODO: implement iterator closing.
// Step 8.b.
callContentFunction(promiseCapability.reject, undefined, e);
return promiseCapability.promise;
}
}
-// ES7, 2016-01-21 draft, 25.4.4.3.1.
+// ES2016, 25.4.4.3.1.
function PerformPromiseRace(iteratorRecord, resultCapability, C) {
assert(IsConstructor(C), "PerformPromiseRace called with non-constructor");
assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
// Step 1.
let iterator = iteratorRecord.iterator;
let racePromise = resultCapability.promise;
let next;
@@ -695,17 +532,17 @@ function Promise_static_resolve(x) {
// Steps 6-7.
callContentFunction(promiseCapability.resolve, undefined, x);
// Step 8.
return promiseCapability.promise;
}
-//ES6, 25.4.4.6.
+// ES6, 25.4.4.6.
function Promise_static_get_species() {
// Step 1.
return this;
}
_SetCanonicalName(Promise_static_get_species, "get [Symbol.species]");
// ES6, 25.4.5.1.
function Promise_catch(onRejected) {
@@ -864,17 +701,17 @@ function UnwrappedPerformPromiseThen(ful
function onRejected(argument) {
return UnsafeCallWrappedFunction(rejectedHandler, undefined, argument);
}
return PerformPromiseThen(this, IsCallable(fulfilledHandler) ? onFulfilled : fulfilledHandler,
IsCallable(rejectedHandler) ? onRejected : rejectedHandler,
resultCapability);
}
-// ES2016, March 1, 2016 draft, 25.4.5.3.1.
+// ES2016, 25.4.5.3.1.
function PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability) {
// Step 1.
assert(IsPromise(promise), "Can't call PerformPromiseThen on non-Promise objects");
// Step 2.
assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
// Step 3.
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -202,16 +202,25 @@ function GetTypeError(msg) {
try {
FUN_APPLY(ThrowTypeError, undefined, arguments);
} catch (e) {
return e;
}
assert(false, "the catch block should've returned from this function.");
}
+function GetInternalError(msg) {
+ try {
+ FUN_APPLY(ThrowInternalError, undefined, arguments);
+ } catch (e) {
+ return e;
+ }
+ assert(false, "the catch block should've returned from this function.");
+}
+
// To be used when a function is required but calling it shouldn't do anything.
function NullFunction() {}
/*************************************** Testing functions ***************************************/
function outer() {
return function inner() {
return "foo";
}
--- a/js/src/tests/ecma_6/Promise/promise-basics.js
+++ b/js/src/tests/ecma_6/Promise/promise-basics.js
@@ -11,23 +11,33 @@ new Promise(res=>res('result'))
.then(val=>{results.push('then ' + val); return 'first then rval';})
.then(val=>results.push('chained then with val: ' + val));
new Promise((res, rej)=>rej('rejection'))
.catch(val=>{results.push('catch ' + val); return results.length;})
.then(val=>results.push('then after catch with val: ' + val),
val=>{throw new Error("mustn't be called")});
+new Promise((res, rej)=> {res('result'); rej('rejection'); })
+ .catch(val=>{throw new Error("mustn't be called");})
+ .then(val=>results.push('then after resolve+reject with val: ' + val),
+ val=>{throw new Error("mustn't be called")});
+
+new Promise((res, rej)=> { rej('rejection'); res('result'); })
+ .catch(val=>{results.push('catch after reject+resolve with val: ' + val);})
+
drainJobQueue();
-assertEq(results.length, 4);
+assertEq(results.length, 6);
assertEq(results[0], 'then result');
assertEq(results[1], 'catch rejection');
-assertEq(results[2], 'chained then with val: first then rval');
-assertEq(results[3], 'then after catch with val: 2');
+assertEq(results[2], 'catch after reject+resolve with val: rejection');
+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');
function callback() {}
// Calling the executor function with content functions shouldn't assert:
Promise.resolve.call(function(exec) { exec(callback, callback); });
Promise.reject.call(function(exec) { exec(callback, callback); });
Promise.all.call(function(exec) { exec(callback, callback); });
Promise.race.call(function(exec) { exec(callback, callback); });
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -219,18 +219,23 @@
macro(ownKeys, ownKeys, "ownKeys") \
macro(parseFloat, parseFloat, "parseFloat") \
macro(parseInt, parseInt, "parseInt") \
macro(pattern, pattern, "pattern") \
macro(preventExtensions, preventExtensions, "preventExtensions") \
macro(promise, promise, "promise") \
macro(state, state, "state") \
macro(pending, pending, "pending") \
+ macro(fulfillHandler, fulfillHandler, "fulfillHandler") \
macro(fulfilled, fulfilled, "fulfilled") \
+ macro(reject, reject, "reject") \
macro(rejected, rejected, "rejected") \
+ macro(rejectHandler, rejectHandler, "rejectHandler") \
+ macro(resolve, resolve, "resolve") \
+ macro(incumbentGlobal, incumbentGlobal, "incumbentGlobal") \
macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
macro(proto, proto, "__proto__") \
macro(prototype, prototype, "prototype") \
macro(proxy, proxy, "proxy") \
macro(reason, reason, "reason") \
macro(Reify, Reify, "Reify") \
macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \
macro(resumeGenerator, resumeGenerator, "resumeGenerator") \
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -205,17 +205,17 @@ intrinsic_UnsafeCallWrappedFunction(JSCo
MOZ_ASSERT(IsCallable(args[0]));
MOZ_ASSERT(IsWrapper(&args[0].toObject()));
MOZ_ASSERT(args[1].isObject() || args[1].isUndefined());
MOZ_RELEASE_ASSERT(args[0].isObject());
RootedObject wrappedFun(cx, &args[0].toObject());
RootedObject fun(cx, UncheckedUnwrap(wrappedFun));
MOZ_RELEASE_ASSERT(fun->is<JSFunction>());
- MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedBuiltin());
+ MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedOrIntrinsic());
InvokeArgs args2(cx);
if (!args2.init(args.length() - 2))
return false;
args2.setThis(args[1]);
for (size_t i = 0; i < args2.length(); i++)
@@ -1830,35 +1830,16 @@ intrinsic_EnqueuePromiseReactionJob(JSCo
objectFromIncumbentGlobal))
{
return false;
}
args.rval().setUndefined();
return true;
}
-static bool
-intrinsic_EnqueuePromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
-
- MOZ_ASSERT(args.length() == 3);
- MOZ_ASSERT(IsCallable(args[2]));
-
- RootedValue promiseToResolve(cx, args[0]);
- RootedValue thenable(cx, args[1]);
- RootedValue then(cx, args[2]);
-
- if (!EnqueuePromiseResolveThenableJob(cx, promiseToResolve, thenable, then))
- return false;
-
- args.rval().setUndefined();
- return true;
-}
-
// ES2016, February 12 draft, 25.4.1.9.
static bool
intrinsic_HostPromiseRejectionTracker(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].toObject().is<PromiseObject>());
@@ -1983,30 +1964,16 @@ intrinsic_ConstructorForTypedArray(JSCon
RootedObject ctor(cx);
if (!GetBuiltinConstructor(cx, protoKey, &ctor))
return false;
args.rval().setObject(*ctor);
return true;
}
-static bool
-intrinsic_OriginalPromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 0);
-
- JSObject* obj = GlobalObject::getOrCreatePromiseConstructor(cx, cx->global());
- if (!obj)
- return false;
-
- args.rval().setObject(*obj);
- return true;
-}
-
/**
* Returns an object created in the embedding-provided incumbent global.
*
* Really, we want the incumbent global itself so we can pass it to other
* embedding hooks which need it. Specifically, the enqueue promise hook
* takes an incumbent global so it can set that on the PromiseCallbackJob
* it creates.
*
@@ -2040,57 +2007,16 @@ intrinsic_GetObjectFromIncumbentGlobal(J
if (obj && !cx->compartment()->wrap(cx, &objVal))
return false;
args.rval().set(objVal);
return true;
}
static bool
-intrinsic_RejectUnwrappedPromise(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 2);
-
- RootedObject obj(cx, &args[0].toObject());
- MOZ_ASSERT(IsWrapper(obj));
- Rooted<PromiseObject*> promise(cx, &UncheckedUnwrap(obj)->as<PromiseObject>());
- AutoCompartment ac(cx, promise);
- RootedValue reasonVal(cx, args[1]);
-
- // 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, &reasonVal))
- return false;
- if (reasonVal.isObject() && !CheckedUnwrap(&reasonVal.toObject())) {
- JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
- JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON);
- if (!GetAndClearException(cx, &reasonVal))
- return false;
- }
-
- RootedAtom atom(cx, Atomize(cx, "RejectPromise", strlen("RejectPromise")));
- if (!atom)
- return false;
- RootedPropertyName name(cx, atom->asPropertyName());
-
- FixedInvokeArgs<2> args2(cx);
-
- args2[0].setObject(*promise);
- args2[1].set(reasonVal);
-
- return CallSelfHostedFunction(cx, name, UndefinedHandleValue, args2, args.rval());
-}
-
-static bool
intrinsic_IsWrappedPromiseObject(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedObject obj(cx, &args[0].toObject());
MOZ_ASSERT(!obj->is<PromiseObject>(),
"Unwrapped promises should be filtered out in inlineable code");
@@ -2237,34 +2163,16 @@ intrinsic_ModuleNamespaceExports(JSConte
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleNamespaceObject namespace_(cx, &args[0].toObject().as<ModuleNamespaceObject>());
args.rval().setObject(namespace_->exports());
return true;
}
-/**
- * Intrinsic used to tell the debugger about settled promises.
- *
- * This is invoked both when resolving and rejecting promises, after the
- * resulting state has been set on the promise, and it's up to the debugger
- * to act on this signal in whichever way it wants.
- */
-static bool
-intrinsic_onPromiseSettled(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 1);
- Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
- promise->onSettled(cx);
- args.rval().setUndefined();
- return true;
-}
-
// The self-hosting global isn't initialized with the normal set of builtins.
// Instead, individual C++-implemented functions that're required by
// self-hosted code are defined as global functions. Accessing these
// functions via a content compartment's builtins would be unsafe, because
// content script might have changed the builtins' prototypes' members.
// Installing the whole set of builtins in the self-hosting compartment, OTOH,
// would be wasteful: it increases memory usage and initialization time for
// self-hosting compartment.
@@ -2519,20 +2427,17 @@ static const JSFunctionSpec intrinsic_fu
JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin<WeakSetObject>, 1,0),
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
JS_FN("_GetObjectFromIncumbentGlobal", intrinsic_GetObjectFromIncumbentGlobal, 0, 0),
JS_FN("IsPromise", intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1,0),
JS_FN("IsWrappedPromise", intrinsic_IsWrappedPromiseObject, 1, 0),
JS_FN("_EnqueuePromiseReactionJob", intrinsic_EnqueuePromiseReactionJob, 2, 0),
- JS_FN("_EnqueuePromiseResolveThenableJob", intrinsic_EnqueuePromiseResolveThenableJob, 2, 0),
JS_FN("HostPromiseRejectionTracker", intrinsic_HostPromiseRejectionTracker,2, 0),
- JS_FN("_GetOriginalPromiseConstructor", intrinsic_OriginalPromiseConstructor, 0, 0),
- JS_FN("RejectUnwrappedPromise", intrinsic_RejectUnwrappedPromise, 2, 0),
JS_FN("CallPromiseMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<PromiseObject>>, 2,0),
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0),
JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0),
JS_FN("TypedObjectBuffer", TypedObject::GetBuffer, 1, 0),
JS_FN("TypedObjectByteOffset", TypedObject::GetByteOffset, 1, 0),
@@ -2632,18 +2537,16 @@ static const JSFunctionSpec intrinsic_fu
intrinsic_InstantiateModuleFunctionDeclarations, 1, 0),
JS_FN("SetModuleState", intrinsic_SetModuleState, 1, 0),
JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
JS_FN("IsModuleNamespace", intrinsic_IsInstanceOfBuiltin<ModuleNamespaceObject>, 1, 0),
JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0),
JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
- JS_FN("_dbg_onPromiseSettled", intrinsic_onPromiseSettled, 1, 0),
-
JS_FS_END
};
void
js::FillSelfHostingCompileOptions(CompileOptions& options)
{
/*
* In self-hosting mode, scripts use JSOP_GETINTRINSIC instead of