Bug 1313049 - Review feedback. r?arai draft
authorTill Schneidereit <till@tillschneidereit.net>
Fri, 28 Oct 2016 18:12:41 +0200
changeset 431020 429f66ea23172d84235e5d3fea1f01f6d6fed665
parent 431019 3f5d34b0a3fd385938d438b00d6e0d2a70c899a1
child 535343 0c1980f7075514328feaa96e33c27f9558b4efb3
push id33980
push userbmo:till@tillschneidereit.net
push dateFri, 28 Oct 2016 16:14:07 +0000
reviewersarai
bugs1313049
milestone52.0a1
Bug 1313049 - Review feedback. r?arai MozReview-Commit-ID: KlVjpjDEUk8
js/src/builtin/Promise.cpp
js/src/builtin/TestingFunctions.cpp
js/src/jsapi.cpp
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -58,41 +58,76 @@ enum ReactionJobSlots {
     ReactionJobSlot_ReactionRecord = 0,
 };
 
 enum ThenableJobSlots {
     ThenableJobSlot_Handler = 0,
     ThenableJobSlot_JobData,
 };
 
-enum ThenableJobDataSlots {
-    ThenableJobDataSlot_Promise = 0,
-    ThenableJobDataSlot_Thenable,
-    ThenableJobDataSlotsCount,
+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).toObjectOrNull();
+    }
+    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, HandleObject valuesArray,
+                        HandleObject resolve)
+{
+    Rooted<PromiseAllDataHolder*> dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
+    if (!dataHolder)
+        return nullptr;
+
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise));
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1));
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, ObjectValue(*valuesArray));
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectOrNullValue(resolve));
+    return dataHolder;
+}
+
 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.
@@ -103,80 +138,88 @@ AbruptRejectPromise(JSContext *cx, CallA
     RootedValue reason(cx);
     if (!GetAndClearException(cx, &reason))
         return false;
 
     if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj))
         return false;
 
     // Step 1.b.
-    args.rval().set(ObjectValue(*promiseObj));
+    args.rval().setObject(*promiseObj);
     return true;
 }
 
-// ES2016, 25.4.1.2.
-class PromiseReactionRecord : public NativeObject
-{
-  public:
-    static const Class class_;
-};
-
 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
 
-static int32_t
-ReactionFlags(PromiseReactionRecord* reaction)
-{
-    return reaction->getFixedSlot(ReactionRecordSlot_Flags).toInt32();
-}
-
-static void
-SetReactionTargetState(PromiseReactionRecord* reaction, JS::PromiseState state)
+// ES2016, 25.4.1.2.
+class PromiseReactionRecord : public NativeObject
 {
-    int32_t flags = ReactionFlags(reaction);
-    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;
-    reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
-}
-
-static JS::PromiseState
-ReactionTargetState(PromiseReactionRecord* reaction)
-{
-    int32_t flags = ReactionFlags(reaction);
-    if (!(flags & REACTION_FLAG_RESOLVED))
-        return JS::PromiseState::Pending;
-    return flags & REACTION_FLAG_FULFILLED ?
-           JS::PromiseState::Fulfilled :
-           JS::PromiseState::Rejected;
-}
+  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));
+    }
+    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),
-    JS_NULL_CLASS_OPS
+    JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots)
 };
 
 static void
-SetPromiseFlags(PromiseObject& promise, int32_t flag)
+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)
 {
@@ -375,19 +418,19 @@ ResolvePromiseFunction(JSContext* cx, un
     return status;
 }
 
 static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
 
 /**
  * Tells the embedding to enqueue a Promise reaction job, based on
  * three parameters:
- * reaction - The reaction record.
- * handlerArg - The first and only argument to pass to the handler invoked by
- *              the job. This is stored on the reaction record.
+ * 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
@@ -405,28 +448,25 @@ EnqueuePromiseReactionJob(JSContext* cx,
         reaction = &unwrappedReactionObj->as<PromiseReactionRecord>();
         if (!cx->compartment()->wrap(cx, &handlerArg))
             return false;
     } else {
         reaction = &reactionObj->as<PromiseReactionRecord>();
     }
 
     // Must not enqueue a reaction job more than once.
-    MOZ_ASSERT(ReactionTargetState(reaction) == JS::PromiseState::Pending);
-
-    SetReactionTargetState(reaction, targetState);
+    MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
+
     assertSameCompartment(cx, handlerArg);
-    reaction->setFixedSlot(ReactionRecordSlot_HandlerArg, handlerArg);
+    reaction->setHandlerArg(handlerArg);
 
     RootedValue reactionVal(cx, ObjectValue(*reaction));
 
-    uint32_t handlerSlot = targetState == JS::PromiseState::Fulfilled
-                                          ? ReactionRecordSlot_OnFulfilled
-                                          : ReactionRecordSlot_OnRejected;
-    RootedValue handler(cx, reaction->getFixedSlot(handlerSlot));
+    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()) {
@@ -462,30 +502,28 @@ EnqueuePromiseReactionJob(JSContext* cx,
     // 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->getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull());
+    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);
-    JSObject* obj = reaction->getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject)
-                    .toObjectOrNull();
-    RootedObject objectFromIncumbentGlobal(cx, obj);
+    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
@@ -509,17 +547,17 @@ ResolvePromise(JSContext* cx, Handle<Pro
 
     // 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));
 
-    // Step 3-5.
+    // 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)
@@ -569,16 +607,21 @@ FulfillMaybeWrappedPromise(JSContext *cx
 
 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
+};
+
 // 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));
 
@@ -596,17 +639,17 @@ NewPromiseCapability(JSContext* cx, Hand
     // 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(CreatePromiseObjectInternal(cx));
         if (!promise)
             return false;
-        SetPromiseFlags(promise->as<PromiseObject>(), PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
+        AddPromiseFlags(promise->as<PromiseObject>(), PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
                                                       PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
 
         return true;
     }
 
     // Step 3 (omitted).
 
     // Step 4.
@@ -620,25 +663,25 @@ NewPromiseCapability(JSContext* cx, Hand
 
     // 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));
+    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(1));
+    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());
@@ -653,27 +696,29 @@ 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()) {
+    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(0, args.get(0));
+    F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0));
 
     // Step 6.
-    F->setExtendedSlot(1, args.get(1));
+    F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1));
 
     // Step 7.
     args.rval().setUndefined();
     return true;
 }
 
 // ES2016, 25.4.1.7.
 static MOZ_MUST_USE bool
@@ -738,23 +783,23 @@ TriggerPromiseReactions(JSContext* cx, H
         reaction = &reactionsList->getDenseElement(i).toObject();
         if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
             return false;
     }
 
     return true;
 }
 
-// ES6, 25.4.2.1.
+// 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/6.0/index.html#sec-jobs-and-job-queues
+ * 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)
 {
@@ -772,34 +817,31 @@ PromiseReactionJob(JSContext* cx, unsign
     // back, we check if the reaction is a wrapper and if so, unwrap it and
     // enter its compartment.
     mozilla::Maybe<AutoCompartment> ac;
     if (IsWrapper(reactionObj)) {
         reactionObj = UncheckedUnwrap(reactionObj);
         ac.emplace(cx, reactionObj);
     }
 
+    // Steps 1-2.
     Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
 
-    JS:: PromiseState targetState = ReactionTargetState(reaction);
-    uint32_t handlerSlot = JS::PromiseState(targetState) == JS::PromiseState::Fulfilled
-                           ? ReactionRecordSlot_OnFulfilled
-                           : ReactionRecordSlot_OnRejected;
-    RootedValue handlerVal(cx, reaction->getFixedSlot(handlerSlot));
-    RootedValue argument(cx, reaction->getFixedSlot(ReactionRecordSlot_HandlerArg));
-
-    // Step 1 (omitted).
-
-    // Steps 2-3.
+    // Step 3.
+    RootedValue handlerVal(cx, reaction->handler());
+
+    RootedValue argument(cx, reaction->handlerArg());
+
     RootedValue handlerResult(cx);
     ResolutionMode resolutionMode = ResolveMode;
 
-    // Steps 4-7.
+    // Steps 4-6.
     if (handlerVal.isNumber()) {
         int32_t handlerNum = int32_t(handlerVal.toNumber());
+
         // Step 4.
         if (handlerNum == PROMISE_HANDLER_IDENTITY) {
             handlerResult = argument;
         } else {
             // Step 5.
             MOZ_ASSERT(handlerNum == PROMISE_HANDLER_THROWER);
             resolutionMode = RejectMode;
             handlerResult = argument;
@@ -817,31 +859,30 @@ PromiseReactionJob(JSContext* cx, unsign
         }
     }
 
     // Steps 7-9.
     size_t hookSlot = resolutionMode == RejectMode
                       ? ReactionRecordSlot_Reject
                       : ReactionRecordSlot_Resolve;
     RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
-    RootedObject promiseObj(cx, reaction->getFixedSlot(ReactionRecordSlot_Promise)
-                                .toObjectOrNull());
+    RootedObject promiseObj(cx, reaction->promise());
     if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj))
         return false;
 
     args.rval().setUndefined();
     return true;
 }
 
-// ES6, 25.4.2.2.
+// 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/6.0/index.html#sec-jobs-and-job-queues
+ * 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 PROMISE_HANDLER_IDENTITY,
@@ -865,18 +906,18 @@ PromiseResolveThenableJob(JSContext* cx,
     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(ThenableJobDataSlot_Promise));
-    RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataSlot_Thenable));
+    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.
@@ -897,19 +938,19 @@ PromiseResolveThenableJob(JSContext* 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.
- * then - The `then` function to invoke with the `thenable` as the receiver.
+ * 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_);
@@ -929,36 +970,36 @@ EnqueuePromiseResolveThenableJob(JSConte
         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, ThenableJobDataSlotsCount));
+    RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
     if (!data ||
-        data->ensureDenseElements(cx, 0, ThenableJobDataSlotsCount) != DenseElementResult::Success)
+        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(ThenableJobDataSlot_Promise, promiseToResolve);
+    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(ThenableJobDataSlot_Thenable, thenable);
+    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);
 }
 
@@ -973,23 +1014,23 @@ AddPromiseReaction(JSContext* cx, Handle
 
 static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleObject promise,
                                         HandleObject blockedPromise,
                                         HandleValue onFulfilled, HandleValue onRejected);
 
 static JSFunction*
 GetResolveFunctionFromReject(JSFunction* reject)
 {
+    MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction);
     Value resolveFunVal = reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction);
     if (IsNativeFunction(resolveFunVal, ResolvePromiseFunction))
         return &resolveFunVal.toObject().as<JSFunction>();
 
     PromiseAllDataHolder* resolveFunObj = &resolveFunVal.toObject().as<PromiseAllDataHolder>();
-    return &resolveFunObj->getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction)
-            .toObject().as<JSFunction>();
+    return &resolveFunObj->resolveObj()->as<JSFunction>();
 }
 
 static JSFunction*
 GetResolveFunctionFromPromise(PromiseObject* promise)
 {
     Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction);
     if (rejectFunVal.isUndefined())
         return nullptr;
@@ -1008,29 +1049,32 @@ GetResolveFunctionFromPromise(PromiseObj
         return nullptr;
 
     return GetResolveFunctionFromReject(rejectFun);
 }
 
 static void
 ClearResolutionFunctionSlots(JSFunction* resolutionFun)
 {
-    JSFunction* otherFun;
+    JSFunction* resolve;
+    JSFunction* reject;
     if (resolutionFun->maybeNative() == ResolvePromiseFunction) {
-        otherFun = &resolutionFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
-                    .toObject().as<JSFunction>();
+        resolve = resolutionFun;
+        reject = &resolutionFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
+                  .toObject().as<JSFunction>();
     } else {
-        otherFun = GetResolveFunctionFromReject(resolutionFun);
+        resolve = GetResolveFunctionFromReject(resolutionFun);
+        reject = resolutionFun;
     }
 
-    resolutionFun->setExtendedSlot(0, UndefinedValue());
-    resolutionFun->setExtendedSlot(1, UndefinedValue());
-
-    otherFun->setExtendedSlot(0, UndefinedValue());
-    otherFun->setExtendedSlot(1, UndefinedValue());
+    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.
@@ -1070,17 +1114,17 @@ CreatePromiseObjectInternal(JSContext* c
 
     // Let the Debugger know about this Promise.
     if (informDebugger)
         JS::dbg::onNewPromise(cx, promise);
 
     return promise;
 }
 
-// ES6, 25.4.3.1.
+// 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;
@@ -1249,17 +1293,17 @@ static MOZ_MUST_USE bool PerformPromiseA
 
 // 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.
+    // 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.
@@ -1293,17 +1337,17 @@ Promise_static_all(JSContext* cx, unsign
         // Step 8.a.
         // TODO: implement iterator closing.
 
         // Step 8.b.
         return AbruptRejectPromise(cx, args, resultPromise, reject);
     }
 
     // Step 9.
-    args.rval().set(ObjectValue(*resultPromise));
+    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);
 
@@ -1333,17 +1377,17 @@ js::GetWaitForAllPromise(JSContext* 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 PerformPromiseAll.
+    // 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 ||
             valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success)
@@ -1351,84 +1395,79 @@ js::GetWaitForAllPromise(JSContext* cx, 
             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.
-        RootedNativeObject dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
+        Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise,
+                                                                             valuesArray, resolve));
         if (!dataHolder)
             return nullptr;
         RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
 
-        int32_t remainingCount = 0;
-        dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise));
-        dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
-                                 Int32Value(remainingCount));
-        dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, ObjectValue(*valuesArray));
-        dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction,
-                                 ObjectOrNullValue(resolve));
-
         // 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.
+            // Step d (implemented after the loop).
             // Steps e-g (omitted).
 
             // Step h.
             valuesArray->setDenseElement(index, UndefinedHandleValue);
 
-            // Steps i-j, vastly simplified.
+            // Step i, vastly simplified.
             RootedObject nextPromiseObj(cx, promises[index]);
 
-            // Step k.
+            // Step j.
             RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
                                                              1, nullptr,
                                                              gc::AllocKind::FUNCTION_EXTENDED,
                                                              GenericObject));
             if (!resolveFunc)
                 return nullptr;
 
-            // Steps l-p.
+            // Steps k-o.
             resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
             resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
                                          Int32Value(index));
 
-            // Step q.
-            remainingCount++;
-            dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
-                                     Int32Value(remainingCount));
-
-            // Steps r-s, very roughly.
+            // Step p.
+            dataHolder->increaseRemainingCount();
+
+            // Step q, very roughly.
             RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
-            RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
+            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 t (inline in loop-header).
+            // Step r (inline in loop-header).
         }
 
-        // Sub-step 6.d.
+        // 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).
@@ -1467,17 +1506,17 @@ RunResolutionFunction(JSContext *cx, Han
     }
 
     if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION))
         return true;
     return RejectMaybeWrappedPromise(cx, promiseObj, result);
 
 }
 
-// ES2016, 25.4.4.1.1
+// 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)
 {
     RootedObject unwrappedPromiseObj(cx);
     if (IsWrapper(promiseObj)) {
         unwrappedPromiseObj = CheckedUnwrap(promiseObj);
         MOZ_ASSERT(unwrappedPromiseObj);
@@ -1523,28 +1562,22 @@ PerformPromiseAll(JSContext *cx, JS::For
     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.
-    RootedNativeObject dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
+    Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj,
+                                                                         valuesArray, resolve));
     if (!dataHolder)
         return false;
     RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
 
-    int32_t remainingCount = 1;
-    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*promiseObj));
-    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
-                             Int32Value(remainingCount));
-    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArrayVal);
-    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectOrNullValue(resolve));
-
     // Step 5.
     uint32_t index = 0;
 
     // Step 6.
     RootedValue nextValue(cx);
     RootedId indexId(cx);
     RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
 
@@ -1553,21 +1586,17 @@ PerformPromiseAll(JSContext *cx, JS::For
         // Steps a, b, c, e, f, g.
         if (!iterator.next(&nextValue, &done))
             return false;
 
         // Step d.
         if (done) {
             // Step d.i (implicit).
             // Step d.ii.
-            remainingCount = dataHolder->getFixedSlot(PromiseAllDataHolderSlot_RemainingElements)
-                             .toInt32();
-            remainingCount--;
-            dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
-                                     Int32Value(remainingCount));
+            int32_t remainingCount = dataHolder->decreaseRemainingCount();
 
             // Steps d.iii-iv.
             if (remainingCount == 0) {
                 if (resolve) {
                     return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode,
                                                  promiseObj);
                 }
                 return ResolvePromiseInternal(cx, promiseObj, valuesArrayVal);
@@ -1611,21 +1640,17 @@ PerformPromiseAll(JSContext *cx, JS::For
         // Steps k,m,n.
         resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
 
         // Step l.
         resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
                                      Int32Value(index));
 
         // Steps o-p.
-        remainingCount = dataHolder->getFixedSlot(PromiseAllDataHolderSlot_RemainingElements)
-                         .toInt32();
-        remainingCount++;
-        dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
-                                 Int32Value(remainingCount));
+        dataHolder->increaseRemainingCount();
 
         // Step q.
         RootedObject nextPromiseObj(cx, &nextPromise.toObject());
         RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
         if (!BlockOnPromise(cx, nextPromiseObj, promiseObj, resolveFunVal, rejectFunVal))
             return false;
 
         // Step r.
@@ -1660,56 +1685,49 @@ PromiseAllResolveElementFunction(JSConte
     // Step 3.
     resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue());
 
     // Step 4.
     int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
                     .toInt32();
 
     // Step 5.
-    RootedValue valuesVal(cx, data->getFixedSlot(PromiseAllDataHolderSlot_ValuesArray));
+    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 unter step 10).
-
-    // Step 7.
-    int32_t remainingElementsCount = data->getFixedSlot(PromiseAllDataHolderSlot_RemainingElements)
-                                     .toInt32();
+    // 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);
 
-    // Step 9.
-    remainingElementsCount--;
-    data->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements,
-                       Int32Value(remainingElementsCount));
+    // Steps 7,9.
+    uint32_t remainingCount = data->decreaseRemainingCount();
 
     // Step 10.
-    if (remainingElementsCount == 0) {
+    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->getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction)
-                                       .toObjectOrNull());
-        RootedObject promiseObj(cx, data->getFixedSlot(PromiseAllDataHolderSlot_Promise)
-                                    .toObjectOrNull());
+        RootedObject resolveAllFun(cx, data->resolveObj());
+        RootedObject promiseObj(cx, data->promiseObj());
         if (!resolveAllFun) {
             if (!FulfillMaybeWrappedPromise(cx, promiseObj, valuesVal))
                 return false;
         } else {
             if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj))
                 return false;
         }
     }
@@ -1725,17 +1743,17 @@ static MOZ_MUST_USE bool PerformPromiseR
 
 // 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.
+    // 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.
@@ -1769,32 +1787,28 @@ Promise_static_race(JSContext* cx, unsig
         // Step 8.a.
         // TODO: implement iterator closing.
 
         // Step 8.b.
         return AbruptRejectPromise(cx, args, resultPromise, reject);
     }
 
     // Step 9.
-    args.rval().set(ObjectValue(*resultPromise));
+    args.rval().setObject(*resultPromise);
     return true;
 }
 
-// ES2016, 25.4.4.3.1
+// 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)
 {
-    // Step 1.
     MOZ_ASSERT(C->isConstructor());
     RootedValue CVal(cx, ObjectValue(*C));
 
-    // Step 2 (omitted).
-
-    // Step 3.
     RootedValue nextValue(cx);
     RootedValue resolveFunVal(cx, ObjectOrNullValue(resolve));
     RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
     bool done;
 
     while (true) {
         // Steps a-c, e-g.
         if (!iterator.next(&nextValue, &done))
@@ -2003,29 +2017,30 @@ js::OriginalPromiseThen(JSContext* cx, H
                         HandleValue onRejected)
 {
     RootedObject promiseObj(cx, promise);
     if (promise->compartment() != cx->compartment()) {
         if (!cx->compartment()->wrap(cx, &promiseObj))
             return nullptr;
     }
 
+    // Step 3.
     RootedValue ctorVal(cx);
     if (!SpeciesConstructor(cx, promiseObj, JSProto_Promise, &ctorVal))
         return nullptr;
     RootedObject C(cx, &ctorVal.toObject());
 
-    // Steps 5-6.
+    // Step 4.
     RootedObject resultPromise(cx);
     RootedObject resolve(cx);
     RootedObject reject(cx);
     if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true))
         return nullptr;
 
-    // Step 7.
+    // Step 5.
     if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
         return nullptr;
 
     return resultPromise;
 }
 
 // ES2016, 25.4.5.3.
 static bool
@@ -2155,62 +2170,63 @@ PerformPromiseThen(JSContext* cx, Handle
 static MOZ_MUST_USE bool
 BlockOnPromise(JSContext* cx, HandleObject promiseObj, HandleObject blockedPromise_,
                HandleValue onFulfilled, HandleValue onRejected)
 {
     RootedValue thenVal(cx);
     if (!GetProperty(cx, promiseObj, promiseObj, cx->names().then, &thenVal))
         return false;
 
-    // By default, the blocked promise is added as an extra entry to the
-    // rejected promises list.
-    bool addToDependent = true;
     if (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., steps 3-4.
+        // 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) {
             addToDependent = false;
         } else {
-            // 25.4.5.3., steps 5-6.
+            // 25.4.5.3., step 4.
             if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true))
                 return false;
         }
 
-        // 25.4.5.3., step 7.
+        // 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 promiseVal(cx, ObjectValue(*promiseObj));
         RootedValue rval(cx);
         if (!Call(cx, thenVal, promiseVal, onFulfilled, onRejected, &rval))
             return false;
     }
 
-    if (!addToDependent)
-        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);
@@ -2251,17 +2267,17 @@ AddPromiseReaction(JSContext* cx, Handle
     // is properly wrapped.
     mozilla::Maybe<AutoCompartment> ac;
     if (promise->compartment() != cx->compartment()) {
         ac.emplace(cx, promise);
         if (!cx->compartment()->wrap(cx, &reactionVal))
             return false;
     }
 
-    // Steps 7.a,b.
+    // 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;
     }
@@ -2275,21 +2291,25 @@ AddPromiseReaction(JSContext* cx, Handle
         reactionsObj = UncheckedUnwrap(reactionsObj);
         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 || reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success)
+        if (!reactions)
             return false;
-        promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions));
+        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);
     }
@@ -2323,20 +2343,20 @@ double
 PromiseObject::lifetime()
 {
     return MillisecondsSinceStartup() - allocationTime();
 }
 
 uint64_t
 PromiseObject::getID()
 {
-    Value idVal(getReservedSlot(PromiseSlot_Id));
+    Value idVal(getFixedSlot(PromiseSlot_Id));
     if (idVal.isUndefined()) {
         idVal.setDouble(++gIDGenerator);
-        setReservedSlot(PromiseSlot_Id, idVal);
+        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
@@ -2348,51 +2368,59 @@ PromiseObject::getID()
  * 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, getReservedSlot(PromiseSlot_ReactionsOrResult));
+    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;
-        RootedValue promiseVal(cx, reactions->getFixedSlot(ReactionRecordSlot_Promise));
-        Rooted<PromiseObject*> promise(cx, &promiseVal.toObject().as<PromiseObject>());
-        values[0].set(reactions->getFixedSlot(ReactionRecordSlot_Promise));
+
+        values[0].setObject(*promiseObj);
         return true;
     }
 
     uint32_t len = reactions->getDenseInitializedLength();
     MOZ_ASSERT(len >= 2);
 
-    if (!values.growBy(len))
-        return false;
-
+    size_t valuesIndex = 0;
     Rooted<PromiseReactionRecord*> reaction(cx);
     for (size_t i = 0; i < len; i++) {
         reaction = &reactions->getDenseElement(i).toObject().as<PromiseReactionRecord>();
-        values[i].set(reaction->getFixedSlot(ReactionRecordSlot_Promise));
+
+        // 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;
 }
 
-namespace js {
-
 bool
 PromiseObject::resolve(JSContext* cx, HandleValue resolutionValue)
 {
     if (state() != JS::PromiseState::Pending)
         return true;
 
     RootedObject resolveFun(cx, GetResolveFunctionFromPromise(this));
     RootedValue funVal(cx, ObjectValue(*resolveFun));
@@ -2411,17 +2439,17 @@ PromiseObject::resolve(JSContext* cx, Ha
 }
 
 bool
 PromiseObject::reject(JSContext* cx, HandleValue rejectionValue)
 {
     if (state() != JS::PromiseState::Pending)
         return true;
 
-    RootedValue funVal(cx, this->getReservedSlot(PromiseSlot_RejectFunction));
+    RootedValue funVal(cx, this->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);
 }
@@ -2442,19 +2470,19 @@ PromiseObject::onSettled(JSContext* cx)
 
     if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
         cx->runtime()->addUnhandledRejectedPromise(cx, promise);
 
     JS::dbg::onPromiseSettled(cx, promise);
 }
 
 MOZ_MUST_USE bool
-EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
-                        HandleObject dependentPromise,
-                        HandleValue onFulfilled, HandleValue onRejected)
+js::EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
+                            HandleObject dependentPromise,
+                            HandleValue onFulfilled, HandleValue onRejected)
 {
     MOZ_ASSERT_IF(dependentPromise, dependentPromise->is<PromiseObject>());
     return PerformPromiseThen(cx, promise, onFulfilled, onRejected, dependentPromise,
                               nullptr, nullptr);
 }
 
 PromiseTask::PromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
   : runtime_(cx),
@@ -2477,24 +2505,22 @@ PromiseTask::finish(JSContext* cx)
         AutoCompartment ac(cx, promise_);
         if (!finishPromise(cx, promise_))
             cx->clearPendingException();
     }
     js_delete(this);
 }
 
 void
-js::PromiseTask::cancel(JSContext* cx)
+PromiseTask::cancel(JSContext* cx)
 {
     MOZ_ASSERT(cx == runtime_);
     js_delete(this);
 }
 
-} // namespace js
-
 static JSObject*
 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),
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1461,18 +1461,18 @@ SettlePromiseNow(JSContext* cx, unsigned
 }
 
 static bool
 GetWaitForAllPromise(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.requireAtLeast(cx, "getWaitForAllPromise", 1))
         return false;
-    if (!args[0].isObject() || !args[0].toObject().is<NativeObject>()) {
-        JS_ReportErrorASCII(cx, "first argument must be a Promise object");
+    if (!args[0].isObject() || !IsPackedArray(&args[0].toObject())) {
+        JS_ReportErrorASCII(cx, "first argument must be a dense Array of Promise objects");
         return false;
     }
     RootedNativeObject list(cx, &args[0].toObject().as<NativeObject>());
     AutoObjectVector promises(cx);
     uint32_t count = list->getDenseInitializedLength();
     if (!promises.resize(count))
         return false;
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4788,28 +4788,28 @@ JS::RejectPromise(JSContext* cx, JS::Han
     assertSameCompartment(cx, promise, rejectionValue);
 
     MOZ_ASSERT(promise->is<PromiseObject>());
     return promise->as<PromiseObject>().reject(cx, rejectionValue);
 }
 
 JS_PUBLIC_API(JSObject*)
 JS::CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promiseObj,
-                            JS::HandleObject onResolve, JS::HandleObject onReject)
-{
-    AssertHeapIsIdle(cx);
-    CHECK_REQUEST(cx);
-    assertSameCompartment(cx, promiseObj, onResolve, onReject);
-
-    MOZ_ASSERT_IF(onResolve, IsCallable(onResolve));
-    MOZ_ASSERT_IF(onReject, IsCallable(onReject));
+                            JS::HandleObject onResolveObj, JS::HandleObject onRejectObj)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, promiseObj, onResolveObj, onRejectObj);
+
+    MOZ_ASSERT_IF(onResolveObj, IsCallable(onResolveObj));
+    MOZ_ASSERT_IF(onRejectObj, IsCallable(onRejectObj));
 
     Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
-    RootedValue onFulfilled(cx, ObjectOrNullValue(onResolve));
-    RootedValue onRejected(cx, ObjectOrNullValue(onReject));
+    RootedValue onFulfilled(cx, ObjectOrNullValue(onResolveObj));
+    RootedValue onRejected(cx, ObjectOrNullValue(onRejectObj));
     return OriginalPromiseThen(cx, promise, onFulfilled, onRejected);
 }
 
 JS_PUBLIC_API(bool)
 JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj,
                         JS::HandleObject onResolvedObj, JS::HandleObject onRejectedObj)
 {
     AssertHeapIsIdle(cx);
@@ -4828,61 +4828,18 @@ JS::AddPromiseReactions(JSContext* cx, J
 /**
  * Unforgeable version of Promise.all for internal use.
  *
  * Takes a dense array of Promise objects and returns a promise that's
  * resolved with an array of resolution values when all those promises ahve
  * been resolved, or rejected with the rejection value of the first rejected
  * promise.
  *
- * Asserts if the array isn't dense or one of the entries isn't a Promise.
+ * Asserts that the array is dense and all entries are Promise objects.
  */
-//static bool
-//GetWaitForAllPromise(JSContext*cx, const JS::AutoObjectVector& promises) {
-//    let resultCapability = NewPromiseCapability(GetBuiltinConstructor('Promise'));
-//    let allPromise = resultCapability.promise;
-//
-//    // Step 3.
-//    // Immediately create an Array instead of a List, so we can skip step 6.d.iii.1.
-//    let values = [];
-//    let valuesCount = 0;
-//
-//    // Step 4.
-//    let remainingElementsCount = {value: 0};
-//
-//    // Step 6.
-//    for (let i = 0; i < promises.length; i++) {
-//        // Parts of step 6 for deriving next promise, vastly simplified.
-//        assert(callFunction(std_Object_hasOwnProperty, promises, i),
-//               "GetWaitForAllPromise must be called with a dense array of promises");
-//        let nextPromise = promises[i];
-//        assert(IsPromise(nextPromise) || IsWrappedPromise(nextPromise),
-//               "promises list must only contain possibly wrapped promises");
-//
-//        // Step 6.h.
-//        _DefineDataProperty(values, valuesCount++, undefined);
-//
-//        // Steps 6.k-p.
-//        let resolveElement = CreatePromiseAllResolveElementFunction(i, values,
-//                                                                    resultCapability,
-//                                                                    remainingElementsCount);
-//
-//        // Step 6.q.
-//        remainingElementsCount.value++;
-//
-//        // Steps 6.r-s, very roughly.
-//        EnqueuePromiseReactions(nextPromise, allPromise, resolveElement, resultCapability.reject);
-//    }
-//
-//    if (remainingElementsCount.value === 0)
-//        callFunction(resultCapability.resolve, undefined, values);
-//
-//    return allPromise;
-//}
-
 JS_PUBLIC_API(JSObject*)
 JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     return js::GetWaitForAllPromise(cx, promises);
 }