--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -109,27 +109,22 @@ PromiseObject::create(JSContext* cx, Han
promise = NewObjectWithClassProto<PromiseObject>(cx, usedProto);
if (!promise)
return nullptr;
// Step 4.
promise->setFixedSlot(PROMISE_STATE_SLOT, Int32Value(PROMISE_STATE_PENDING));
- // Step 5.
+ // Steps 5-6.
+ // We only have a single list of reaction records.
RootedArrayObject reactions(cx, NewDenseEmptyArray(cx));
if (!reactions)
return nullptr;
- promise->setFixedSlot(PROMISE_FULFILL_REACTIONS_SLOT, ObjectValue(*reactions));
-
- // Step 6.
- reactions = NewDenseEmptyArray(cx);
- if (!reactions)
- return nullptr;
- promise->setFixedSlot(PROMISE_REJECT_REACTIONS_SLOT, ObjectValue(*reactions));
+ promise->setFixedSlot(PROMISE_REACTIONS_SLOT, ObjectValue(*reactions));
// Step 7.
promise->setFixedSlot(PROMISE_IS_HANDLED_SLOT,
Int32Value(PROMISE_IS_HANDLED_STATE_UNHANDLED));
// Store an allocation stack so we can later figure out what the
// control flow was for some unexpected results. Frightfully expensive,
// but oh well.
@@ -232,38 +227,31 @@ PromiseObject::getID()
}
/**
* 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`.
*
- *
- * For the then() case, we have both resolve and reject callbacks that know
- * what the next promise is.
- *
- * For the race() case, likewise.
- *
- * For the all() case, our reject callback knows what the next promise is, but
- * our resolve callback doesn't.
- *
- * So we walk over our _reject_ callbacks and ask each of them what promise
- * its dependent promise is.
+ * 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)
{
- RootedValue rejectReactionsVal(cx, getReservedSlot(PROMISE_REJECT_REACTIONS_SLOT));
- RootedObject rejectReactions(cx, rejectReactionsVal.toObjectOrNull());
- if (!rejectReactions)
+ RootedValue reactionsVal(cx, getReservedSlot(PROMISE_REACTIONS_SLOT));
+ RootedObject reactions(cx, reactionsVal.toObjectOrNull());
+ if (!reactions)
return true;
AutoIdVector keys(cx);
- if (!GetPropertyKeys(cx, rejectReactions, JSITER_OWNONLY, &keys))
+ if (!GetPropertyKeys(cx, reactions, JSITER_OWNONLY, &keys))
return false;
if (keys.length() == 0)
return true;
if (!values.growBy(keys.length()))
return false;
@@ -274,17 +262,17 @@ PromiseObject::dependentPromises(JSConte
// reject: [the `reject` callback content code provided],
// handler: [the internal handler that fulfills/rejects the promise]
// }
//
// 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, rejectReactions, rejectReactions, keys[i], val))
+ 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))
return false;
}
return true;
}
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -1,20 +1,22 @@
/* 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/. */
// ES6, 25.4.1.2.
// This object is used to verify that an object is a PromiseReaction record.
var PromiseReactionRecordProto = {__proto__: null};
-function PromiseReactionRecord(promise, resolve, reject, handler, incumbentGlobal) {
+function PromiseReactionRecord(promise, resolve, reject, fulfillHandler, rejectHandler,
+ incumbentGlobal) {
this.promise = promise;
this.resolve = resolve;
this.reject = reject;
- this.handler = handler;
+ 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-
@@ -120,57 +122,61 @@ function CreateResolvingFunctions(promis
// 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_FULFILL_REACTIONS_SLOT, PROMISE_STATE_FULFILLED);
+ return ResolvePromise(promise, value, PROMISE_STATE_FULFILLED);
}
function FulfillUnwrappedPromise(value) {
- return ResolvePromise(this, value, PROMISE_FULFILL_REACTIONS_SLOT, PROMISE_STATE_FULFILLED);
+ 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, reactionsSlot, state) {
+function ResolvePromise(promise, valueOrReason, state) {
// Step 1.
assert(GetPromiseState(promise) === PROMISE_STATE_PENDING,
"Can't resolve non-pending promise");
- assert(state >= PROMISE_STATE_PENDING && state <= PROMISE_STATE_REJECTED,
- `Invalid Promise state <${state}>`);
+ assert(state >= PROMISE_STATE_FULFILLED && state <= PROMISE_STATE_REJECTED,
+ `Invalid Promise resolution state <${state}>`);
// Step 2.
- var reactions = UnsafeGetObjectFromReservedSlot(promise, reactionsSlot);
+ // 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 = UnsafeGetObjectFromReservedSlot(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);
- // Step 4.
- UnsafeSetReservedSlot(promise, PROMISE_FULFILL_REACTIONS_SLOT, null);
-
- // Step 5.
- UnsafeSetReservedSlot(promise, PROMISE_REJECT_REACTIONS_SLOT, null);
+ // 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.
- return TriggerPromiseReactions(reactions, valueOrReason);
+ return TriggerPromiseReactions(reactions, jobType, valueOrReason);
}
// Used to verify that an object is a PromiseCapability record.
var PromiseCapabilityRecordProto = {__proto__: null};
// ES6, 25.4.1.5.
// Creates PromiseCapability records, see 25.4.1.1.
function NewPromiseCapability(C) {
@@ -214,32 +220,36 @@ function NewPromiseCapability(C) {
reject
};
}
// ES6, 25.4.1.6. is implemented as an intrinsic in SelfHosting.cpp.
// ES2016, February 12 draft, 25.4.1.7.
function RejectPromise(promise, reason) {
- return ResolvePromise(promise, reason, PROMISE_REJECT_REACTIONS_SLOT, PROMISE_STATE_REJECTED);
+ return ResolvePromise(promise, reason, PROMISE_STATE_REJECTED);
}
// ES6, 25.4.1.8.
-function TriggerPromiseReactions(reactions, argument) {
+function TriggerPromiseReactions(reactions, jobType, argument) {
// Step 1.
for (var i = 0, len = reactions.length; i < len; i++)
- EnqueuePromiseReactionJob(reactions[i], argument);
+ EnqueuePromiseReactionJob(reactions[i], jobType, argument);
// Step 2 (implicit).
}
// ES2016, February 12 draft 25.4.1.9, implemented in SelfHosting.cpp.
// ES6, 25.4.2.1.
-function EnqueuePromiseReactionJob(reaction, argument) {
- _EnqueuePromiseReactionJob(reaction.handler,
+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],
argument,
reaction.resolve,
reaction.reject,
reaction.promise,
reaction.incumbentGlobal || null);
}
// ES6, 25.4.2.2. (Implemented in C++).
@@ -606,61 +616,48 @@ function BlockOnPromise(promise, blocked
// 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.
// If the object isn't a maybe-wrapped instance of |Promise|, 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 (IsPromise(promise)) {
- return callFunction(AddPromiseReaction, promise, PROMISE_REJECT_REACTIONS_SLOT,
- blockedPromise);
- }
+ if (IsPromise(promise))
+ return callFunction(AddDependentPromise, promise, blockedPromise);
- if (IsWrappedPromise(promise)) {
- callFunction(CallPromiseMethodIfWrapped, promise, PROMISE_REJECT_REACTIONS_SLOT,
- blockedPromise, "AddPromiseReaction");
- }
+ if (IsWrappedPromise(promise))
+ callFunction(CallPromiseMethodIfWrapped, promise, blockedPromise, "AddDependentPromise");
}
/**
- * Invoked with a Promise as the receiver, AddPromiseReaction adds an entry to
- * the reactions list in `slot`, using the other parameters as values for that
- * reaction.
+ * Invoked with a Promise as the receiver, AddDependentPromise adds an entry
+ * to the reactions list.
*
- * If any of the callback functions aren't specified, they're set to
- * NullFunction. Doing that here is useful in case the call is performed on an
- * unwrapped Promise. Passing in NullFunctions would cause useless compartment
- * switches.
+ * This is only used to make dependent promises visible in the devtools, so no
+ * callbacks are provided. To make handling that case easier elsewhere,
+ * they're all set to NullFunction.
*
* The reason for the target Promise to be passed as the receiver is so the
* same function can be used for wrapped and unwrapped Promise objects.
*/
-function AddPromiseReaction(slot, dependentPromise, onResolve, onReject, handler) {
- assert(IsPromise(this), "AddPromiseReaction expects an unwrapped Promise as the receiver");
- assert(slot === PROMISE_FULFILL_REACTIONS_SLOT || slot === PROMISE_REJECT_REACTIONS_SLOT,
- "Invalid slot");
+function AddDependentPromise(dependentPromise) {
+ assert(IsPromise(this), "AddDependentPromise expects an unwrapped Promise as the receiver");
- if (!onResolve)
- onResolve = NullFunction;
- if (!onReject)
- onReject = NullFunction;
- if (!handler)
- handler = NullFunction;
+ let reaction = new PromiseReactionRecord(dependentPromise, NullFunction, NullFunction,
+ NullFunction, NullFunction, null);
- let reactions = UnsafeGetReservedSlot(this, slot);
+ let reactions = UnsafeGetReservedSlot(this, PROMISE_REACTIONS_SLOT);
// The reactions slot might've been reset because the Promise was resolved.
if (!reactions) {
assert(GetPromiseState(this) !== PROMISE_STATE_PENDING,
"Pending promises must have reactions lists.");
return;
}
- let reaction = new PromiseReactionRecord(promise, reject, resolve, handler, null);
_DefineDataProperty(reactions, reactions.length, reaction);
}
// ES6, 25.4.4.4.
function Promise_static_reject(r) {
// Step 1.
let C = this;
@@ -882,51 +879,43 @@ function PerformPromiseThen(promise, onF
if (!IsCallable(onFulfilled))
onFulfilled = PROMISE_HANDLER_IDENTITY;
// Step 4.
if (!IsCallable(onRejected))
onRejected = PROMISE_HANDLER_THROWER;
let incumbentGlobal = _GetObjectFromIncumbentGlobal();
- // Step 5.
- let fulfillReaction = new PromiseReactionRecord(resultCapability.promise,
- resultCapability.resolve,
- resultCapability.reject,
- onFulfilled,
- incumbentGlobal);
-
- // Step 6.
- let rejectReaction = new PromiseReactionRecord(resultCapability.promise,
- resultCapability.resolve,
- resultCapability.reject,
- onRejected,
- incumbentGlobal);
+ // Steps 5,6.
+ // Instead of creating separate reaction records for fulfillment and
+ // rejection, we create a combined record. All places we use the record
+ // can handle that.
+ let reaction = new PromiseReactionRecord(resultCapability.promise,
+ resultCapability.resolve,
+ resultCapability.reject,
+ onFulfilled,
+ onRejected,
+ incumbentGlobal);
// Step 7.
let state = GetPromiseState(promise);
if (state === PROMISE_STATE_PENDING) {
- // Step 7.a.
- let fulfillReactions = UnsafeGetObjectFromReservedSlot(promise,
- PROMISE_FULFILL_REACTIONS_SLOT);
- _DefineDataProperty(fulfillReactions, fulfillReactions.length, fulfillReaction);
-
- // Step 7.b.
- let rejectReactions = UnsafeGetObjectFromReservedSlot(promise,
- PROMISE_REJECT_REACTIONS_SLOT);
- _DefineDataProperty(rejectReactions, rejectReactions.length, rejectReaction);
+ // Steps 7.a,b.
+ // We only have a single list for fulfill and reject reactions.
+ let reactions = UnsafeGetObjectFromReservedSlot(promise, PROMISE_REACTIONS_SLOT);
+ _DefineDataProperty(reactions, reactions.length, reaction);
}
// Step 8.
else if (state === PROMISE_STATE_FULFILLED) {
// Step 8.a.
let value = UnsafeGetReservedSlot(promise, PROMISE_RESULT_SLOT);
// Step 8.b.
- EnqueuePromiseReactionJob(fulfillReaction, value);
+ EnqueuePromiseReactionJob(reaction, PROMISE_JOB_TYPE_FULFILL, value);
}
// Step 9.
else {
// Step 9.a.
assert(state === PROMISE_STATE_REJECTED, "Invalid Promise state " + state);
// Step 9.b.
@@ -935,17 +924,17 @@ function PerformPromiseThen(promise, onF
// Step 9.c.
if (UnsafeGetInt32FromReservedSlot(promise, PROMISE_IS_HANDLED_SLOT) !==
PROMISE_IS_HANDLED_STATE_HANDLED)
{
HostPromiseRejectionTracker(promise, PROMISE_REJECTION_TRACKER_OPERATION_HANDLE);
}
// Step 9.d.
- EnqueuePromiseReactionJob(rejectReaction, reason);
+ EnqueuePromiseReactionJob(reaction, PROMISE_JOB_TYPE_REJECT, reason);
}
// Step 10.
UnsafeSetReservedSlot(promise, PROMISE_IS_HANDLED_SLOT, PROMISE_IS_HANDLED_STATE_HANDLED);
// Step 11.
return resultCapability.promise;
}