Backed out 2 changesets (bug 1313049) for various promise-related failures a=backout CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Fri, 28 Oct 2016 16:15:11 -0700
changeset 320087 b171726c465e305411429b29012f48e890598b3f
parent 320086 2777fabbe62a3a55a3514d724303977bac7260fb
child 320088 b727c67b91a3268e893f4f63057f6cb7fae1b4ba
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1313049
milestone52.0a1
backs out8c08e1aca9ea4953d610ead9b7d1cd4eb0d822d3
72764ba31b81562e3069f6935da33cae06fe6b00
Backed out 2 changesets (bug 1313049) for various promise-related failures a=backout CLOSED TREE Backed out changeset 8c08e1aca9ea (bug 1313049) Backed out changeset 72764ba31b81 (bug 1313049)
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/builtin/Promise.js
js/src/builtin/SelfHostingDefines.h
js/src/builtin/TestingFunctions.cpp
js/src/jsapi.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/tests/ecma_6/Promise/get-wait-for-all-promise.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Runtime.cpp
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -25,569 +25,151 @@ using namespace js;
 static double
 MillisecondsSinceStartup()
 {
     auto now = mozilla::TimeStamp::Now();
     bool ignored;
     return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
 }
 
-#define PROMISE_HANDLER_IDENTITY 0
-#define PROMISE_HANDLER_THROWER  1
-
-enum ResolutionMode {
-    ResolveMode,
-    RejectMode
-};
-
-enum ResolveFunctionSlots {
-    ResolveFunctionSlot_Promise = 0,
-    ResolveFunctionSlot_RejectFunction,
-};
-
-enum RejectFunctionSlots {
-    RejectFunctionSlot_Promise = ResolveFunctionSlot_Promise,
-    RejectFunctionSlot_ResolveFunction,
-    RejectFunctionSlot_PromiseAllData = RejectFunctionSlot_ResolveFunction,
-};
-
-enum PromiseAllResolveElementFunctionSlots {
-    PromiseAllResolveElementFunctionSlot_Data = 0,
-    PromiseAllResolveElementFunctionSlot_ElementIndex,
-};
-
-enum ReactionJobSlots {
-    ReactionJobSlot_ReactionRecord = 0,
-};
-
-enum ThenableJobSlots {
-    ThenableJobSlot_Handler = 0,
-    ThenableJobSlot_JobData,
-};
-
-enum ThenableJobDataIndices {
-    ThenableJobDataIndex_Promise = 0,
-    ThenableJobDataIndex_Thenable,
-    ThenableJobDataLength,
-};
-
-enum PromiseAllDataHolderSlots {
-    PromiseAllDataHolderSlot_Promise = 0,
-    PromiseAllDataHolderSlot_RemainingElements,
-    PromiseAllDataHolderSlot_ValuesArray,
-    PromiseAllDataHolderSlot_ResolveFunction,
-    PromiseAllDataHolderSlots,
-};
-
-class PromiseAllDataHolder : public NativeObject
-{
-  public:
-    static const Class class_;
-    JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); }
-    JSObject* resolveObj() {
-        return getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).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)
+enum ResolutionFunctionSlots {
+    ResolutionFunctionSlot_Promise = 0,
+    ResolutionFunctionSlot_OtherFunction,
 };
 
-static PromiseAllDataHolder*
-NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleObject valuesArray,
-                        HandleObject resolve)
+// ES2016, 25.4.1.8.
+static MOZ_MUST_USE bool
+TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state,
+                        HandleValue valueOrReason)
 {
-    Rooted<PromiseAllDataHolder*> dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
-    if (!dataHolder)
-        return nullptr;
+    RootedObject reactions(cx, &reactionsVal.toObject());
 
-    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;
-}
+    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;
 
-static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun,
-                                               HandleValue result, ResolutionMode mode,
-                                               HandleObject promiseObj);
+    // 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);
 
-// ES2016, 25.4.1.1.1, Steps 1.a-b.
-// Extracting all of this internal spec algorithm into a helper function would
-// be tedious, so the check in step 1 and the entirety of step 2 aren't
-// included.
-static bool
-AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject)
-{
-    // Step 1.a.
-    RootedValue reason(cx);
-    if (!GetAndClearException(cx, &reason))
-        return false;
-
-    if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj))
-        return false;
+    for (size_t i = 0; i < keys.length(); i++) {
+        if (!GetProperty(cx, reactions, reactions, keys[i], &val))
+            return false;
+        reaction = &val.toObject();
 
-    // Step 1.b.
-    args.rval().setObject(*promiseObj);
-    return true;
-}
+        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();
 
-enum ReactionRecordSlots {
-    ReactionRecordSlot_Promise = 0,
-    ReactionRecordSlot_OnFulfilled,
-    ReactionRecordSlot_OnRejected,
-    ReactionRecordSlot_Resolve,
-    ReactionRecordSlot_Reject,
-    ReactionRecordSlot_IncumbentGlobalObject,
-    ReactionRecordSlot_Flags,
-    ReactionRecordSlot_HandlerArg,
-    ReactionRecordSlots,
-};
+        if (!GetProperty(cx, reaction, reaction, handlerName, &handler))
+            return false;
+
+#ifdef DEBUG
+        if (handler.isNumber()) {
+            MOZ_ASSERT(handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
+                       handler.toNumber() == PROMISE_HANDLER_THROWER);
+        } else {
+            MOZ_ASSERT(handler.toObject().isCallable());
+        }
+#endif
 
-#define REACTION_FLAG_RESOLVED                  0x1
-#define REACTION_FLAG_FULFILLED                 0x2
-#define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4
+        if (!GetProperty(cx, reaction, reaction, cx->names().resolve, &val))
+            return false;
+        resolve = &val.toObject();
+        MOZ_ASSERT(IsCallable(resolve));
 
-// ES2016, 25.4.1.2.
-class PromiseReactionRecord : public NativeObject
-{
-  public:
-    static const Class class_;
+        if (!GetProperty(cx, reaction, reaction, cx->names().reject, &val))
+            return false;
+        reject = &val.toObject();
+        MOZ_ASSERT(IsCallable(reject));
 
-    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;
+        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;
+        }
     }
-    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)
-};
-
-static void
-AddPromiseFlags(PromiseObject& promise, int32_t flag)
-{
-    int32_t flags = promise.getFixedSlot(PromiseSlot_Flags).toInt32();
-    promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag));
-}
-
-static bool
-PromiseHasAnyFlag(PromiseObject& promise, int32_t flag)
-{
-    return promise.getFixedSlot(PromiseSlot_Flags).toInt32() & flag;
-}
-
-static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp);
-static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp);
-
-// ES2016, 25.4.1.3.
-static MOZ_MUST_USE bool
-CreateResolvingFunctions(JSContext* cx, HandleValue promise,
-                         MutableHandleValue resolveVal,
-                         MutableHandleValue rejectVal)
-{
-    RootedAtom funName(cx, cx->names().empty);
-    RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
-                                                 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
-    if (!resolve)
-        return false;
-
-    RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
-                                                 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
-    if (!reject)
-        return false;
-
-    // TODO: remove this once all self-hosted promise code is gone.
-    // 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);
-
-    resolve->setExtendedSlot(ResolveFunctionSlot_Promise, promise);
-    resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*reject));
-
-    reject->setExtendedSlot(RejectFunctionSlot_Promise, promise);
-    reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, ObjectValue(*resolve));
-
-    resolveVal.setObject(*resolve);
-    rejectVal.setObject(*reject);
 
     return true;
 }
 
-static void ClearResolutionFunctionSlots(JSFunction* resolutionFun);
-static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj,
-                                                   HandleValue reason);
-
-// ES2016, 25.4.1.3.1.
-static bool
-RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedFunction reject(cx, &args.callee().as<JSFunction>());
-    RootedValue reasonVal(cx, args.get(0));
-
-    // Steps 1-2.
-    RootedValue promiseVal(cx, reject->getExtendedSlot(RejectFunctionSlot_Promise));
-
-    // Steps 3-4.
-    // If the Promise isn't available anymore, it has been resolved and the
-    // reference to it removed to make it eligible for collection.
-    if (promiseVal.isUndefined()) {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    RootedObject promise(cx, &promiseVal.toObject());
-
-    // In some cases the Promise reference on the resolution function won't
-    // have been removed during resolution, so we need to check that here,
-    // too.
-    if (promise->is<PromiseObject>() &&
-        PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED))
-    {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    // Step 5.
-    // Here, we only remove the Promise reference from the resolution
-    // functions. Actually marking it as fulfilled/rejected happens later.
-    ClearResolutionFunctionSlots(reject);
-
-    // Step 6.
-    bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal);
-    if (result)
-        args.rval().setUndefined();
-    return result;
-}
-
-static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj,
-                                                    HandleValue value_);
-
-static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx,
-                                                          HandleValue promiseToResolve,
-                                                          HandleValue thenable,
-                                                          HandleValue thenVal);
-
-// ES2016, 25.4.1.3.2, steps 7-13.
-static MOZ_MUST_USE bool
-ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal)
-{
-    // Step 7.
-    if (!resolutionVal.isObject())
-        return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
-
-    RootedObject resolution(cx, &resolutionVal.toObject());
-
-    // Step 8.
-    RootedValue thenVal(cx);
-    bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
-
-    // Step 9.
-    if (!status) {
-        RootedValue error(cx);
-        if (!GetAndClearException(cx, &error))
-            return false;
-
-        return RejectMaybeWrappedPromise(cx, promise, error);
-    }
-
-    // Step 10 (implicit).
-
-    // Step 11.
-    if (!IsCallable(thenVal))
-        return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
-
-    // Step 12.
-    RootedValue promiseVal(cx, ObjectValue(*promise));
-    if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
-        return false;
-
-    // Step 13.
-    return true;
-}
-
-// ES2016, 25.4.1.3.2.
-static bool
-ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedFunction resolve(cx, &args.callee().as<JSFunction>());
-    RootedValue resolutionVal(cx, args.get(0));
-
-    // Steps 1-2.
-    RootedValue promiseVal(cx, resolve->getExtendedSlot(ResolveFunctionSlot_Promise));
-
-    // Steps 3-4.
-    // We use the reference to the reject function as a signal for whether
-    // the resolve or reject function was already called, at which point
-    // the references on each of the functions are cleared.
-    if (!resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction).isObject()) {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    RootedObject promise(cx, &promiseVal.toObject());
-
-    // In some cases the Promise reference on the resolution function won't
-    // have been removed during resolution, so we need to check that here,
-    // too.
-    if (promise->is<PromiseObject>() &&
-        PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED))
-    {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    // Step 5.
-    // Here, we only remove the Promise reference from the resolution
-    // functions. Actually marking it as fulfilled/rejected happens later.
-    ClearResolutionFunctionSlots(resolve);
-
-    // Step 6.
-    if (resolutionVal == promiseVal) {
-        // Step 6.a.
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                  JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
-        RootedValue selfResolutionError(cx);
-        bool status = GetAndClearException(cx, &selfResolutionError);
-        MOZ_ASSERT(status);
-
-        // Step 6.b.
-        status = RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
-        if (status)
-            args.rval().setUndefined();
-        return status;
-    }
-
-    bool status = ResolvePromiseInternal(cx, promise, resolutionVal);
-    if (status)
-        args.rval().setUndefined();
-    return status;
-}
-
-static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
-
-/**
- * Tells the embedding to enqueue a Promise reaction job, based on
- * three parameters:
- * reactionObj - The reaction record.
- * handlerArg_ - The first and only argument to pass to the handler invoked by
- *              the job. This will be stored on the reaction record.
- * targetState - The PromiseState this reaction job targets. This decides
- *               whether the onFulfilled or onRejected handler is called.
- */
-MOZ_MUST_USE static bool
-EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
-                          HandleValue handlerArg_, JS::PromiseState targetState)
-{
-    // The reaction might have been stored on a Promise from another
-    // compartment, which means it would've been wrapped in a CCW.
-    // To properly handle that case here, unwrap it and enter its
-    // compartment, where the job creation should take place anyway.
-    Rooted<PromiseReactionRecord*> reaction(cx);
-    RootedValue handlerArg(cx, handlerArg_);
-    mozilla::Maybe<AutoCompartment> ac;
-    if (IsWrapper(reactionObj)) {
-        RootedObject unwrappedReactionObj(cx, UncheckedUnwrap(reactionObj));
-        if (!unwrappedReactionObj)
-            return false;
-        ac.emplace(cx, unwrappedReactionObj);
-        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(reaction->targetState() == JS::PromiseState::Pending);
-
-    assertSameCompartment(cx, handlerArg);
-    reaction->setHandlerArg(handlerArg.get());
-
-    RootedValue reactionVal(cx, ObjectValue(*reaction));
-
-    reaction->setTargetState(targetState);
-    RootedValue handler(cx, reaction->handler());
-
-    // If we have a handler callback, we enter that handler's compartment so
-    // that the promise reaction job function is created in that compartment.
-    // That guarantees that the embedding ends up with the right entry global.
-    // This is relevant for some html APIs like fetch that derive information
-    // from said global.
-    mozilla::Maybe<AutoCompartment> ac2;
-    if (handler.isObject()) {
-        RootedObject handlerObj(cx, &handler.toObject());
-
-        // The unwrapping has to be unchecked because we specifically want to
-        // be able to use handlers with wrappers that would only allow calls.
-        // E.g., it's ok to have a handler from a chrome compartment in a
-        // reaction to a content compartment's Promise instance.
-        handlerObj = UncheckedUnwrap(handlerObj);
-        MOZ_ASSERT(handlerObj);
-        ac2.emplace(cx, handlerObj);
-
-        // We need to wrap the reaction to store it on the job function.
-        if (!cx->compartment()->wrap(cx, &reactionVal))
-            return false;
-    }
-
-    // Create the JS function to call when the job is triggered.
-    RootedAtom funName(cx, cx->names().empty);
-    RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
-                                             gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
-    if (!job)
-        return false;
-
-    // Store the reaction on the reaction job.
-    job->setExtendedSlot(ReactionJobSlot_ReactionRecord, reactionVal);
-
-    // When using JS::AddPromiseReactions, no actual promise is created, so we
-    // might not have one here.
-    // Additionally, we might have an object here that isn't an instance of
-    // Promise. This can happen if content overrides the value of
-    // Promise[@@species] (or invokes Promise#then on a Promise subclass
-    // instance with a non-default @@species value on the constructor) with a
-    // function that returns objects that're not Promise (subclass) instances.
-    // In that case, we just pretend we didn't have an object in the first
-    // place.
-    // If after all this we do have an object, wrap it in case we entered the
-    // handler's compartment above, because we should pass objects from a
-    // single compartment to the enqueuePromiseJob callback.
-    RootedObject promise(cx, reaction->promise());
-    if (promise && promise->is<PromiseObject>()) {
-      if (!cx->compartment()->wrap(cx, &promise))
-          return false;
-    }
-
-    // Using objectFromIncumbentGlobal, we can derive the incumbent global by
-    // unwrapping and then getting the global. This is very convoluted, but
-    // much better than having to store the original global as a private value
-    // because we couldn't wrap it to store it as a normal JS value.
-    RootedObject global(cx);
-    RootedObject objectFromIncumbentGlobal(cx, reaction->incumbentGlobalObject());
-    if (objectFromIncumbentGlobal) {
-        objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
-        MOZ_ASSERT(objectFromIncumbentGlobal);
-        global = &objectFromIncumbentGlobal->global();
-    }
-
-    // Note: the global we pass here might be from a different compartment
-    // than job and promise. While it's somewhat unusual to pass objects
-    // from multiple compartments, in this case we specifically need the
-    // global to be unwrapped because wrapping and unwrapping aren't
-    // necessarily symmetric for globals.
-    return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
-}
-
-static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal,
-                                                 JS::PromiseState state, HandleValue valueOrReason);
-
 // ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
 static MOZ_MUST_USE bool
 ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueOrReason,
                JS::PromiseState state)
 {
     // Step 1.
     MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
     MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected);
 
     // Step 2.
     // We only have one list of reactions for both resolution types. So
     // instead of getting the right list of reactions, we determine the
     // resolution type to retrieve the right information from the
     // reaction records.
-    RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
+    RootedValue reactionsVal(cx, promise->getFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT));
 
-    // Steps 3-5.
+    // Step 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);
+    promise->setFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT, valueOrReason);
 
     // Step 6.
-    int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
+    int32_t flags = promise->getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
     flags |= PROMISE_FLAG_RESOLVED;
     if (state == JS::PromiseState::Fulfilled)
         flags |= PROMISE_FLAG_FULFILLED;
-    promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags));
+    promise->setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(flags));
 
     // Also null out the resolve/reject functions so they can be GC'd.
-    promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue());
+    promise->setFixedSlot(PROMISE_RESOLVE_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_)
-{
+FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_) {
     Rooted<PromiseObject*> promise(cx);
     RootedValue value(cx, value_);
 
     mozilla::Maybe<AutoCompartment> ac;
     if (!IsProxy(promiseObj)) {
         promise = &promiseObj->as<PromiseObject>();
     } else {
         if (JS_IsDeadWrapper(promiseObj)) {
@@ -600,135 +182,19 @@ FulfillMaybeWrappedPromise(JSContext *cx
             return false;
     }
 
     MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
 
     return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled);
 }
 
-static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp);
-static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp);
-static MOZ_MUST_USE PromiseObject* CreatePromiseObjectInternal(JSContext* cx,
-                                                               HandleObject proto = nullptr,
-                                                               bool protoIsWrapped = false,
-                                                               bool informDebugger = true);
-
-enum GetCapabilitiesExecutorSlots {
-    GetCapabilitiesExecutorSlots_Resolve,
-    GetCapabilitiesExecutorSlots_Reject
-};
-
-// ES2016, 25.4.1.5.
-static MOZ_MUST_USE bool
-NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
-                     MutableHandleObject resolve, MutableHandleObject reject,
-                     bool canOmitResolutionFunctions)
-{
-    RootedValue cVal(cx, ObjectValue(*C));
-
-    // Steps 1-2.
-    if (!IsConstructor(C)) {
-        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr);
-        return false;
-    }
-
-    // If we'd call the original Promise constructor and know that the
-    // resolve/reject functions won't ever escape to content, we can skip
-    // creating and calling the executor function and instead return a Promise
-    // marked as having default resolve/reject functions.
-    //
-    // This can't be used in Promise.all and Promise.race because we have to
-    // pass the reject (and resolve, in the race case) function to thenables
-    // in the list passed to all/race, which (potentially) means exposing them
-    // to content.
-    if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) {
-        promise.set(CreatePromiseObjectInternal(cx));
-        if (!promise)
-            return false;
-        AddPromiseFlags(promise->as<PromiseObject>(), PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
-                                                      PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
-
-        return true;
-    }
-
-    // Step 3 (omitted).
-
-    // Step 4.
-    RootedAtom funName(cx, cx->names().empty);
-    RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
-                                                  gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
-    if (!executor)
-        return false;
-
-    // Step 5 (omitted).
-
-    // Step 6.
-    FixedConstructArgs<1> cargs(cx);
-    cargs[0].setObject(*executor);
-    if (!Construct(cx, cVal, cargs, cVal, promise))
-        return false;
-
-    // Step 7.
-    RootedValue resolveVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve));
-    if (!IsCallable(resolveVal)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                  JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
-        return false;
-    }
-
-    // Step 8.
-    RootedValue rejectVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject));
-    if (!IsCallable(rejectVal)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                  JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
-        return false;
-    }
-
-    // Step 9 (well, the equivalent for all of promiseCapabilities' fields.)
-    resolve.set(&resolveVal.toObject());
-    reject.set(&rejectVal.toObject());
-
-    // Step 10.
-    return true;
-}
-
-// ES2016, 25.4.1.5.1.
-static bool
-GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    RootedFunction F(cx, &args.callee().as<JSFunction>());
-
-    // Steps 1-2 (implicit).
-
-    // Steps 3-4.
-    if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() ||
-        !F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined())
-    {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                  JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
-        return false;
-    }
-
-    // Step 5.
-    F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0));
-
-    // Step 6.
-    F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1));
-
-    // Step 7.
-    args.rval().setUndefined();
-    return true;
-}
-
 // ES2016, 25.4.1.7.
 static MOZ_MUST_USE bool
-RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_)
-{
+RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_) {
     Rooted<PromiseObject*> promise(cx);
     RootedValue reason(cx, reason_);
 
     mozilla::Maybe<AutoCompartment> ac;
     if (!IsProxy(promiseObj)) {
         promise = &promiseObj->as<PromiseObject>();
     } else {
         if (JS_IsDeadWrapper(promiseObj)) {
@@ -759,328 +225,182 @@ RejectMaybeWrappedPromise(JSContext *cx,
         }
     }
 
     MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
 
     return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
 }
 
-// ES2016, 25.4.1.8.
-static MOZ_MUST_USE bool
-TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state,
-                        HandleValue valueOrReason)
+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)
 {
-    RootedObject reactions(cx, &reactionsVal.toObject());
-    RootedObject reaction(cx);
+    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, steps 7-13.
+static bool
+ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal)
+{
+    // Step 7.
+    if (!resolutionVal.isObject())
+        return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+
+    RootedObject resolution(cx, &resolutionVal.toObject());
+
+    // Step 8.
+    RootedValue thenVal(cx);
+    bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
+
+    // Step 9.
+    if (!status) {
+        RootedValue error(cx);
+        if (!GetAndClearException(cx, &error))
+            return false;
+
+        return RejectMaybeWrappedPromise(cx, promise, error);
+    }
+
+    // Step 10 (implicit).
+
+    // Step 11.
+    if (!IsCallable(thenVal)) {
+        return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+    }
+
+    // Step 12.
+    RootedValue promiseVal(cx, ObjectValue(*promise));
+    if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
+        return false;
 
-    if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions))
-        return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state);
+    // Step 13.
+    return true;
+}
+
+// ES2016, 25.4.1.3.2.
+static bool
+ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedFunction resolve(cx, &args.callee().as<JSFunction>());
+    RootedValue resolutionVal(cx, args.get(0));
+
+    // Steps 1-2.
+    RootedValue promiseVal(cx, resolve->getExtendedSlot(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_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                  JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
+        RootedValue selfResolutionError(cx);
+        bool status = GetAndClearException(cx, &selfResolutionError);
+        MOZ_ASSERT(status);
 
-    RootedNativeObject reactionsList(cx, &reactions->as<NativeObject>());
-    size_t reactionsCount = reactionsList->getDenseInitializedLength();
-    MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
+        // Step 6.b.
+        status = RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
+        if (status)
+            args.rval().setUndefined();
+        return status;
+    }
+
+    bool status = ResolvePromiseInternal(cx, promise, resolutionVal);
+    if (status)
+        args.rval().setUndefined();
+    return status;
+}
 
-    for (size_t i = 0; i < reactionsCount; i++) {
-        reaction = &reactionsList->getDenseElement(i).toObject();
-        if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
-            return false;
-    }
+// ES2016, 25.4.1.3.
+static MOZ_MUST_USE bool
+CreateResolvingFunctions(JSContext* cx, HandleValue promise,
+                         MutableHandleValue resolveVal,
+                         MutableHandleValue rejectVal)
+{
+    RootedAtom funName(cx, cx->names().empty);
+    RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
+                                                 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+    if (!resolve)
+        return false;
+
+    RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
+                                                 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+    if (!reject)
+        return false;
+
+    // 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);
+
+    resolve->setExtendedSlot(ResolutionFunctionSlot_Promise, promise);
+    resolve->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*reject));
+
+    reject->setExtendedSlot(ResolutionFunctionSlot_Promise, promise);
+    reject->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*resolve));
+
+    resolveVal.setObject(*resolve);
+    rejectVal.setObject(*reject);
 
     return true;
 }
 
-// ES2016, 25.4.2.1.
-/**
- * Callback triggering the fulfill/reject reaction for a resolved Promise,
- * to be invoked by the embedding during its processing of the Promise job
- * queue.
- *
- * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
- *
- * A PromiseReactionJob is set as the native function of an extended
- * JSFunction object, with all information required for the job's
- * execution stored in in a reaction record in its first extended slot.
- */
-static bool
-PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedFunction job(cx, &args.callee().as<JSFunction>());
-
-    RootedObject reactionObj(cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject());
-
-    // To ensure that the embedding ends up with the right entry global, we're
-    // guaranteeing that the reaction job function gets created in the same
-    // compartment as the handler function. That's not necessarily the global
-    // that the job was triggered from, though.
-    // We can find the triggering global via the job's reaction record. To go
-    // back, we check if the reaction is a wrapper and if so, unwrap it and
-    // enter its compartment.
-    mozilla::Maybe<AutoCompartment> ac;
-    if (IsWrapper(reactionObj)) {
-        reactionObj = UncheckedUnwrap(reactionObj);
-        ac.emplace(cx, reactionObj);
-    }
-
-    // Steps 1-2.
-    Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
-
-    // Step 3.
-    RootedValue handlerVal(cx, reaction->handler());
-
-    RootedValue argument(cx, reaction->handlerArg());
-
-    RootedValue handlerResult(cx);
-    ResolutionMode resolutionMode = ResolveMode;
-
-    // Steps 4-6.
-    if (handlerVal.isNumber()) {
-        int32_t handlerNum = int32_t(handlerVal.toNumber());
-
-        // Step 4.
-        if (handlerNum == PROMISE_HANDLER_IDENTITY) {
-            handlerResult = argument;
-        } else {
-            // Step 5.
-            MOZ_ASSERT(handlerNum == PROMISE_HANDLER_THROWER);
-            resolutionMode = RejectMode;
-            handlerResult = argument;
-        }
-    } else {
-        // Step 6.
-        FixedInvokeArgs<1> args2(cx);
-        args2[0].set(argument);
-        if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
-            resolutionMode = RejectMode;
-            // Not much we can do about uncatchable exceptions, so just bail
-            // for those.
-            if (!cx->isExceptionPending() || !GetAndClearException(cx, &handlerResult))
-                return false;
-        }
-    }
-
-    // Steps 7-9.
-    size_t hookSlot = resolutionMode == RejectMode
-                      ? ReactionRecordSlot_Reject
-                      : ReactionRecordSlot_Resolve;
-    RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
-    RootedObject promiseObj(cx, reaction->promise());
-    if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj))
-        return false;
-
-    args.rval().setUndefined();
-    return true;
-}
-
-// ES2016, 25.4.2.2.
-/**
- * Callback for resolving a thenable, to be invoked by the embedding during
- * its processing of the Promise job queue.
- *
- * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
- *
- * A PromiseResolveThenableJob is set as the native function of an extended
- * JSFunction object, with all information required for the job's
- * execution stored in the function's extended slots.
- *
- * Usage of the function's extended slots is as follows:
- * ThenableJobSlot_Handler: The handler to use as the Promise reaction.
- *                          This can be PROMISE_HANDLER_IDENTITY,
- *                          PROMISE_HANDLER_THROWER, or a callable. In the
- *                          latter case, it's guaranteed to be an object
- *                          from the same compartment as the
- *                          PromiseReactionJob.
- * ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
- *                          containing data required for proper execution of
- *                          the reaction.
- *
- * The JobData list has the following entries:
- * ThenableJobDataSlot_Promise: The Promise to resolve using the given
- *                              thenable.
- * ThenableJobDataSlot_Thenable: The thenable to use as the receiver when
- *                               calling the `then` function.
- */
-static bool
-PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedFunction job(cx, &args.callee().as<JSFunction>());
-    RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler));
-    MOZ_ASSERT(!IsWrapper(&then.toObject()));
-    RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData)
-                                    .toObject().as<NativeObject>());
-
-    RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Promise));
-    RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Thenable));
-
-    // Step 1.
-    RootedValue resolveVal(cx);
-    RootedValue rejectVal(cx);
-    if (!CreateResolvingFunctions(cx, promise, &resolveVal, &rejectVal))
-        return false;
-
-    // Step 2.
-    FixedInvokeArgs<2> args2(cx);
-    args2[0].set(resolveVal);
-    args2[1].set(rejectVal);
-
-    RootedValue rval(cx);
-
-    // In difference to the usual pattern, we return immediately on success.
-    if (Call(cx, then, thenable, args2, &rval))
-        return true;
-
-    if (!GetAndClearException(cx, &rval))
-        return false;
-
-    FixedInvokeArgs<1> rejectArgs(cx);
-    rejectArgs[0].set(rval);
-
-    return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval);
-}
-
-/**
- * Tells the embedding to enqueue a Promise resolve thenable job, based on
- * three parameters:
- * promiseToResolve_ - The promise to resolve, obviously.
- * thenable_ - The thenable to resolve the Promise with.
- * thenVal - The `then` function to invoke with the `thenable` as the receiver.
- */
-static MOZ_MUST_USE bool
-EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
-                                 HandleValue thenable_, HandleValue thenVal)
-{
-    // Need to re-root these to enable wrapping them below.
-    RootedValue promiseToResolve(cx, promiseToResolve_);
-    RootedValue thenable(cx, thenable_);
-
-    // We enter the `then` callable's compartment so that the job function is
-    // created in that compartment.
-    // That guarantees that the embedding ends up with the right entry global.
-    // This is relevant for some html APIs like fetch that derive information
-    // from said global.
-    RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
-    AutoCompartment ac(cx, then);
-
-    RootedAtom funName(cx, cx->names().empty);
-    RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
-                                             gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
-    if (!job)
-        return false;
-
-    // Store the `then` function on the callback.
-    job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
-
-    // Create a dense array to hold the data needed for the reaction job to
-    // work.
-    // See the doc comment for PromiseResolveThenableJob for the layout.
-    RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
-    if (!data ||
-        data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success)
-    {
-        return false;
-    }
-
-    // Wrap and set the `promiseToResolve` argument.
-    if (!cx->compartment()->wrap(cx, &promiseToResolve))
-        return false;
-    data->setDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
-    // At this point the promise is guaranteed to be wrapped into the job's
-    // compartment.
-    RootedObject promise(cx, &promiseToResolve.toObject());
-
-    // Wrap and set the `thenable` argument.
-    MOZ_ASSERT(thenable.isObject());
-    if (!cx->compartment()->wrap(cx, &thenable))
-        return false;
-    data->setDenseElement(ThenableJobDataIndex_Thenable, thenable);
-
-    // Store the data array on the reaction job.
-    job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
-
-    RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx));
-    return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
-}
-
-static MOZ_MUST_USE bool
-AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
-                   HandleValue onRejected, HandleObject dependentPromise,
-                   HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal);
-
-static MOZ_MUST_USE bool
-AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
-                   Handle<PromiseReactionRecord*> reaction);
-
-static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, 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->resolveObj()->as<JSFunction>();
-}
-
-static JSFunction*
-GetResolveFunctionFromPromise(PromiseObject* promise)
-{
-    Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction);
-    if (rejectFunVal.isUndefined())
-        return nullptr;
-    JSObject* rejectFunObj = &rejectFunVal.toObject();
-    if (IsWrapper(rejectFunObj))
-        rejectFunObj = CheckedUnwrap(rejectFunObj);
-
-    if (!rejectFunObj->is<JSFunction>())
-        return nullptr;
-
-    JSFunction* rejectFun = &rejectFunObj->as<JSFunction>();
-
-    // Only the original RejectPromiseFunction has a reference to the resolve
-    // function.
-    if (rejectFun->maybeNative() != &RejectPromiseFunction)
-        return nullptr;
-
-    return GetResolveFunctionFromReject(rejectFun);
-}
-
-static void
-ClearResolutionFunctionSlots(JSFunction* resolutionFun)
-{
-    JSFunction* resolve;
-    JSFunction* reject;
-    if (resolutionFun->maybeNative() == ResolvePromiseFunction) {
-        resolve = resolutionFun;
-        reject = &resolutionFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
-                  .toObject().as<JSFunction>();
-    } else {
-        resolve = GetResolveFunctionFromReject(resolutionFun);
-        reject = resolutionFun;
-    }
-
-    resolve->setExtendedSlot(ResolveFunctionSlot_Promise, UndefinedValue());
-    resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, UndefinedValue());
-
-    reject->setExtendedSlot(RejectFunctionSlot_Promise, UndefinedValue());
-    reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, UndefinedValue());
-}
-
-// ES2016, 25.4.3.1. steps 3-7.
-static MOZ_MUST_USE PromiseObject*
-CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
-                            bool protoIsWrapped /* = false */, bool informDebugger /* = true */)
+static PromiseObject*
+CreatePromiseObjectInternal(JSContext* cx, HandleObject proto, bool protoIsWrapped)
 {
     // Step 3.
     Rooted<PromiseObject*> promise(cx);
     // Enter the unwrapped proto's compartment, if that's different from
     // the current one.
     // All state stored in a Promise's fixed slots must be created in the
     // same compartment, so we get all of that out of the way here.
     // (Except for the resolution functions, which are created below.)
@@ -1088,44 +408,466 @@ CreatePromiseObjectInternal(JSContext* c
     if (protoIsWrapped)
         ac.emplace(cx, proto);
 
     promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
     if (!promise)
         return nullptr;
 
     // Step 4.
-    promise->setFixedSlot(PromiseSlot_Flags, Int32Value(0));
+    promise->setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(0));
 
     // Steps 5-6.
     // Omitted, we allocate our single list of reaction records lazily.
 
     // Step 7.
     // Implicit, the handled flag is unset by default.
 
     // Store an allocation stack so we can later figure out what the
     // control flow was for some unexpected results. Frightfully expensive,
     // but oh well.
     RootedObject stack(cx);
     if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
         if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
             return nullptr;
     }
-    promise->setFixedSlot(PromiseSlot_AllocationSite, ObjectOrNullValue(stack));
-    promise->setFixedSlot(PromiseSlot_AllocationTime, DoubleValue(MillisecondsSinceStartup()));
-
-    // Let the Debugger know about this Promise.
-    if (informDebugger)
-        JS::dbg::onNewPromise(cx, promise);
+    promise->setFixedSlot(PROMISE_ALLOCATION_SITE_SLOT, ObjectOrNullValue(stack));
+    promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT,
+                          DoubleValue(MillisecondsSinceStartup()));
 
     return promise;
 }
 
-// ES2016, 25.4.3.1.
+/**
+ * Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
+ */
+/* static */ JSObject*
+PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
+{
+    // Steps 1-2 (omitted).
+
+    // Roughly step 3.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
+    if (!promise)
+        return nullptr;
+
+    // Let the Debugger know about this Promise.
+    JS::dbg::onNewPromise(cx, promise);
+
+    // Roughly step 4.
+    if (!ResolvePromise(cx, promise, value, JS::PromiseState::Rejected))
+        return nullptr;
+
+    // Step 5.
+    return promise;
+}
+
+/**
+ * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
+ */
+/* static */ JSObject*
+PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
+{
+    // Steps 1-2 (omitted).
+
+    // Step 3.
+    if (value.isObject()) {
+        JSObject* obj = &value.toObject();
+        if (IsWrapper(obj))
+            obj = CheckedUnwrap(obj);
+        // Instead of getting the `constructor` property, do an unforgeable
+        // check.
+        if (obj && obj->is<PromiseObject>())
+            return obj;
+    }
+
+    // Step 4.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
+    if (!promise)
+        return nullptr;
+
+    // Let the Debugger know about this Promise.
+    JS::dbg::onNewPromise(cx, promise);
+
+    // Steps 5.
+    if (!ResolvePromiseInternal(cx, promise, value))
+        return nullptr;
+
+    // Step 6.
+    return promise;
+}
+
+// ES6, 25.4.1.5.1.
+/* static */ bool
+GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedFunction F(cx, &args.callee().as<JSFunction>());
+
+    // Steps 1-2 (implicit).
+
+    // Steps 3-4.
+    if (!F->getExtendedSlot(0).isUndefined() || !F->getExtendedSlot(1).isUndefined()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                  JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
+        return false;
+    }
+
+    // Step 5.
+    F->setExtendedSlot(0, args.get(0));
+
+    // Step 6.
+    F->setExtendedSlot(1, args.get(1));
+
+    // Step 7.
+    args.rval().setUndefined();
+    return true;
+}
+
+// ES2016, 25.4.1.5.
+// Creates PromiseCapability records, see 25.4.1.1.
+static bool
+NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
+                     MutableHandleObject resolve, MutableHandleObject reject)
+{
+    RootedValue cVal(cx, ObjectValue(*C));
+
+    // Steps 1-2.
+    if (!IsConstructor(C)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr);
+        return false;
+    }
+
+    // Step 3 (omitted).
+
+    // Step 4.
+    RootedAtom funName(cx, cx->names().empty);
+    RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
+                                                  gc::AllocKind::FUNCTION_EXTENDED));
+    if (!executor)
+        return false;
+
+    // Step 5 (omitted).
+
+    // Step 6.
+    FixedConstructArgs<1> cargs(cx);
+    cargs[0].setObject(*executor);
+    if (!Construct(cx, cVal, cargs, cVal, promise))
+        return false;
+
+    // Step 7.
+    RootedValue resolveVal(cx, executor->getExtendedSlot(0));
+    if (!IsCallable(resolveVal)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                  JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
+        return false;
+    }
+
+    // Step 8.
+    RootedValue rejectVal(cx, executor->getExtendedSlot(1));
+    if (!IsCallable(rejectVal)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                  JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
+        return false;
+    }
+
+    // Step 9 (well, the equivalent for all of promiseCapabilities' fields.)
+    resolve.set(&resolveVal.toObject());
+    reject.set(&rejectVal.toObject());
+
+    // Step 10.
+    return true;
+}
+
+enum ResolveOrRejectMode {
+    ResolveMode,
+    RejectMode
+};
+
 static bool
+CommonStaticResolveRejectImpl(JSContext* cx, unsigned argc, Value* vp, ResolveOrRejectMode mode)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedValue x(cx, args.get(0));
+
+    // Steps 1-2.
+    if (!args.thisv().isObject()) {
+        const char* msg = mode == ResolveMode
+                          ? "Receiver of Promise.resolve call"
+                          : "Receiver of Promise.reject call";
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg);
+        return false;
+    }
+    RootedValue cVal(cx, args.thisv());
+    RootedObject C(cx, &cVal.toObject());
+
+    // Step 3 of Resolve.
+    if (mode == ResolveMode && x.isObject()) {
+        RootedObject xObj(cx, &x.toObject());
+        bool isPromise = false;
+        if (xObj->is<PromiseObject>()) {
+            isPromise = true;
+        } else if (IsWrapper(xObj)) {
+            // Treat instances of Promise from other compartments as Promises
+            // here, too.
+            // It's important to do the GetProperty for the `constructor`
+            // below through the wrapper, because wrappers can change the
+            // outcome, so instead of unwrapping and then performing the
+            // GetProperty, just check here and then operate on the original
+            // object again.
+            RootedObject unwrappedObject(cx, CheckedUnwrap(xObj));
+            if (unwrappedObject && unwrappedObject->is<PromiseObject>())
+                isPromise = true;
+        }
+        if (isPromise) {
+            RootedValue ctorVal(cx);
+            if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal))
+                return false;
+            if (ctorVal == cVal) {
+                args.rval().set(x);
+                return true;
+            }
+        }
+    }
+
+    // Steps 4-5 of Resolve, 3-4 of Reject.
+    RootedObject promiseCtor(cx);
+    if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
+        return false;
+    RootedObject promise(cx);
+
+    // If the current constructor is the original Promise constructor, we can
+    // optimize things by skipping the creation and invocation of the resolve
+    // and reject callbacks, directly creating and resolving the new Promise.
+    if (promiseCtor == C) {
+        // Roughly step 4 of Resolve, 3 of Reject.
+        promise = CreatePromiseObjectInternal(cx, nullptr, false);
+        if (!promise)
+            return false;
+
+        // Let the Debugger know about this Promise.
+        JS::dbg::onNewPromise(cx, promise);
+
+        // Roughly step 5 of Resolve.
+        if (mode == ResolveMode) {
+            if (!ResolvePromiseInternal(cx, promise, x))
+                return false;
+        } else {
+            // Roughly step 4 of Reject.
+            Rooted<PromiseObject*> promiseObj(cx, &promise->as<PromiseObject>());
+            if (!ResolvePromise(cx, promiseObj, x, JS::PromiseState::Rejected))
+                return false;
+        }
+    } else {
+        // Step 4 of Resolve, 3 of Reject.
+        RootedObject resolveFun(cx);
+        RootedObject rejectFun(cx);
+        if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun))
+            return false;
+
+        // Step 5 of Resolve, 4 of Reject.
+        FixedInvokeArgs<1> args2(cx);
+        args2[0].set(x);
+        RootedValue calleeOrRval(cx, ObjectValue(mode == ResolveMode ? *resolveFun : *rejectFun));
+        if (!Call(cx, calleeOrRval, UndefinedHandleValue, args2, &calleeOrRval))
+            return false;
+    }
+
+    // Step 6 of Resolve, 4 of Reject.
+    args.rval().setObject(*promise);
+    return true;
+}
+
+/**
+ * ES2016, 25.4.4.4, Promise.reject.
+ */
+static bool
+Promise_reject(JSContext* cx, unsigned argc, Value* vp)
+{
+    return CommonStaticResolveRejectImpl(cx, argc, vp, RejectMode);
+}
+
+/**
+ * ES2016, 25.4.4.5, Promise.resolve.
+ */
+static bool
+Promise_resolve(JSContext* cx, unsigned argc, Value* vp)
+{
+    return CommonStaticResolveRejectImpl(cx, argc, vp, ResolveMode);
+}
+
+// ES2016, February 12 draft, 25.4.3.1. steps 3-11.
+/* static */
+PromiseObject*
+PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
+{
+    MOZ_ASSERT(executor->isCallable());
+
+    RootedObject usedProto(cx, proto);
+    bool wrappedProto = false;
+    // If the proto is wrapped, that means the current function is running
+    // with a different compartment active from the one the Promise instance
+    // is to be created in.
+    // See the comment in PromiseConstructor for details.
+    if (proto && IsWrapper(proto)) {
+        wrappedProto = true;
+        usedProto = CheckedUnwrap(proto);
+        if (!usedProto)
+            return nullptr;
+    }
+
+
+    // Steps 3-7.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, wrappedProto));
+    if (!promise)
+        return nullptr;
+
+    RootedValue promiseVal(cx, ObjectValue(*promise));
+    if (wrappedProto && !cx->compartment()->wrap(cx, &promiseVal))
+        return nullptr;
+
+    // Step 8.
+    // The resolving functions are created in the compartment active when the
+    // (maybe wrapped) Promise constructor was called. They contain checks and
+    // can unwrap the Promise if required.
+    RootedValue resolveVal(cx);
+    RootedValue rejectVal(cx);
+    if (!CreateResolvingFunctions(cx, promiseVal, &resolveVal, &rejectVal))
+        return nullptr;
+
+    // Need to wrap the resolution functions before storing them on the Promise.
+    if (wrappedProto) {
+        AutoCompartment ac(cx, promise);
+        RootedValue wrappedResolveVal(cx, resolveVal);
+        if (!cx->compartment()->wrap(cx, &wrappedResolveVal))
+            return nullptr;
+        promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, wrappedResolveVal);
+    } else {
+        promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, resolveVal);
+    }
+
+    // Step 9.
+    bool success;
+    {
+        FixedInvokeArgs<2> args(cx);
+
+        args[0].set(resolveVal);
+        args[1].set(rejectVal);
+
+        RootedValue calleeOrRval(cx, ObjectValue(*executor));
+        success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval);
+    }
+
+    // Step 10.
+    if (!success) {
+        RootedValue exceptionVal(cx);
+        // Not much we can do about uncatchable exceptions, so just bail
+        // for those.
+        if (!cx->isExceptionPending() || !GetAndClearException(cx, &exceptionVal))
+            return nullptr;
+
+        FixedInvokeArgs<1> args(cx);
+
+        args[0].set(exceptionVal);
+
+        // |rejectVal| is unused after this, so we can safely write to it.
+        if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal))
+            return nullptr;
+    }
+
+    // Let the Debugger know about this Promise.
+    JS::dbg::onNewPromise(cx, promise);
+
+    // Step 11.
+    return promise;
+}
+
+namespace {
+// Generator used by PromiseObject::getID.
+mozilla::Atomic<uint64_t> gIDGenerator(0);
+} // namespace
+
+double
+PromiseObject::lifetime()
+{
+    return MillisecondsSinceStartup() - allocationTime();
+}
+
+uint64_t
+PromiseObject::getID()
+{
+    Value idVal(getReservedSlot(PROMISE_ID_SLOT));
+    if (idVal.isUndefined()) {
+        idVal.setDouble(++gIDGenerator);
+        setReservedSlot(PROMISE_ID_SLOT, idVal);
+    }
+    return uint64_t(idVal.toNumber());
+}
+
+/**
+ * Returns all promises that directly depend on this one. That means those
+ * created by calling `then` on this promise, or the promise returned by
+ * `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise
+ * being a member of the passed-in `iterable`.
+ *
+ * Per spec, we should have separate lists of reaction records for the
+ * fulfill and reject cases. As an optimization, we have only one of those,
+ * containing the required data for both cases. So we just walk that list
+ * and extract the dependent promises from all reaction records.
+ */
+bool
+PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values)
+{
+    if (state() != JS::PromiseState::Pending)
+        return true;
+
+    RootedValue reactionsVal(cx, getReservedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT));
+    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],
+    //   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->names().promise, val))
+            return false;
+    }
+
+    return true;
+}
+
+namespace js {
+
+// ES6, 25.4.3.1.
+bool
 PromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     if (!ThrowIfNotConstructing(cx, args, "Promise"))
         return false;
 
@@ -1196,1263 +938,49 @@ PromiseConstructor(JSContext* cx, unsign
 
     // Step 11.
     args.rval().setObject(*promise);
     if (needsWrapping)
         return cx->compartment()->wrap(cx, args.rval());
     return true;
 }
 
-// ES2016, 25.4.3.1. steps 3-11.
-/* static */ PromiseObject*
-PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
-{
-    MOZ_ASSERT(executor->isCallable());
 
-    RootedObject usedProto(cx, proto);
-    bool wrappedProto = false;
-    // If the proto is wrapped, that means the current function is running
-    // with a different compartment active from the one the Promise instance
-    // is to be created in.
-    // See the comment in PromiseConstructor for details.
-    if (proto && IsWrapper(proto)) {
-        wrappedProto = true;
-        usedProto = CheckedUnwrap(proto);
-        if (!usedProto)
-            return nullptr;
-    }
-
-
-    // Steps 3-7.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, wrappedProto,
-                                                                   false));
-    if (!promise)
-        return nullptr;
-
-    RootedValue promiseVal(cx, ObjectValue(*promise));
-    if (wrappedProto && !cx->compartment()->wrap(cx, &promiseVal))
-        return nullptr;
-
-    // Step 8.
-    // The resolving functions are created in the compartment active when the
-    // (maybe wrapped) Promise constructor was called. They contain checks and
-    // can unwrap the Promise if required.
-    RootedValue resolveVal(cx);
-    RootedValue rejectVal(cx);
-    if (!CreateResolvingFunctions(cx, promiseVal, &resolveVal, &rejectVal))
-        return nullptr;
-
-    // Need to wrap the resolution functions before storing them on the Promise.
-    if (wrappedProto) {
-        AutoCompartment ac(cx, promise);
-        RootedValue wrappedRejectVal(cx, rejectVal);
-        if (!cx->compartment()->wrap(cx, &wrappedRejectVal))
-            return nullptr;
-        promise->setFixedSlot(PromiseSlot_RejectFunction, wrappedRejectVal);
-    } else {
-        promise->setFixedSlot(PromiseSlot_RejectFunction, rejectVal);
-    }
-
-    // Step 9.
-    bool success;
-    {
-        FixedInvokeArgs<2> args(cx);
-
-        args[0].set(resolveVal);
-        args[1].set(rejectVal);
-
-        RootedValue calleeOrRval(cx, ObjectValue(*executor));
-        success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval);
-    }
-
-    // Step 10.
-    if (!success) {
-        RootedValue exceptionVal(cx);
-        // Not much we can do about uncatchable exceptions, so just bail
-        // for those.
-        if (!cx->isExceptionPending() || !GetAndClearException(cx, &exceptionVal))
-            return nullptr;
-
-        FixedInvokeArgs<1> args(cx);
-
-        args[0].set(exceptionVal);
-
-        // |rejectVal| is unused after this, so we can safely write to it.
-        if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal))
-            return nullptr;
-    }
-
-    // Let the Debugger know about this Promise.
-    JS::dbg::onNewPromise(cx, promise);
-
-    // Step 11.
-    return promise;
-}
-
-static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator,
-                                           HandleObject C, HandleObject promiseObj,
-                                           HandleObject resolve, HandleObject reject);
-
-// ES2016, 25.4.4.1.
-static bool
-Promise_static_all(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    RootedValue iterable(cx, args.get(0));
-
-    // Step 2 (reordered).
-    RootedValue CVal(cx, args.thisv());
-    if (!CVal.isObject()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
-                                  "Receiver of Promise.all call");
-        return false;
-    }
-
-    // Step 1.
-    RootedObject C(cx, &CVal.toObject());
-
-    // Step 3.
-    RootedObject resultPromise(cx);
-    RootedObject resolve(cx);
-    RootedObject reject(cx);
-    if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
-        return false;
-
-    // Steps 4-5.
-    JS::ForOfIterator iter(cx);
-    if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
-        return AbruptRejectPromise(cx, args, resultPromise, reject);
-
-    if (!iter.valueIsIterable()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
-                                  "Argument of Promise.all");
-        return AbruptRejectPromise(cx, args, resultPromise, reject);
-    }
-
-    // Step 6 (implicit).
-
-    // Step 7.
-    bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject);
-
-    // Step 8.
-    if (!result) {
-        // Step 8.a.
-        // TODO: implement iterator closing.
-
-        // Step 8.b.
-        return AbruptRejectPromise(cx, args, resultPromise, reject);
-    }
-
-    // Step 9.
-    args.rval().setObject(*resultPromise);
-    return true;
-}
-
-static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
-                                            HandleValue onFulfilled_, HandleValue onRejected_,
-                                            HandleObject resultPromise,
-                                            HandleObject resolve, HandleObject reject);
-
-static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp);
-
-// Unforgeable version of ES2016, 25.4.4.1.
-MOZ_MUST_USE JSObject*
-js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
-{
-#ifdef DEBUG
-    for (size_t i = 0, len = promises.length(); i < len; i++) {
-        JSObject* obj = promises[i];
-        assertSameCompartment(cx, obj);
-        MOZ_ASSERT(UncheckedUnwrap(obj)->is<PromiseObject>());
-    }
-#endif
-
-    // Step 1.
-    RootedObject C(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
-    if (!C)
-        return nullptr;
-
-    // Step 2 (omitted).
-
-    // Step 3.
-    RootedObject resultPromise(cx);
-    RootedObject resolve(cx);
-    RootedObject reject(cx);
-    if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
-        return nullptr;
-
-    // Steps 4-6 (omitted).
-
-    // Step 7.
-    // Implemented as an inlined, simplied version of ES2016 25.4.4.1.1, PerformPromiseAll.
-    {
-        uint32_t promiseCount = promises.length();
-        // Sub-steps 1-2 (omitted).
-
-        // Sub-step 3.
-        RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount));
-        if (!valuesArray ||
-            valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success)
-        {
-            return nullptr;
-        }
-
-        // Sub-step 4.
-        // Create our data holder that holds all the things shared across
-        // every step of the iterator.  In particular, this holds the
-        // remainingElementsCount (as an integer reserved slot), the array of
-        // values, and the resolve function from our PromiseCapability.
-        Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise,
-                                                                             valuesArray, resolve));
-        if (!dataHolder)
-            return nullptr;
-        RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
-
-        // Sub-step 5 (inline in loop-header below).
-
-        // Sub-step 6.
-        for (uint32_t index = 0; index < promiseCount; index++) {
-            // Steps a-c (omitted).
-            // Step d (implemented after the loop).
-            // Steps e-g (omitted).
-
-            // Step h.
-            valuesArray->setDenseElement(index, UndefinedHandleValue);
-
-            // Step i, vastly simplified.
-            RootedObject nextPromiseObj(cx, promises[index]);
-
-            // Step j.
-            RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
-                                                             1, nullptr,
-                                                             gc::AllocKind::FUNCTION_EXTENDED,
-                                                             GenericObject));
-            if (!resolveFunc)
-                return nullptr;
-
-            // Steps k-o.
-            resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
-            resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
-                                         Int32Value(index));
-
-            // Step p.
-            dataHolder->increaseRemainingCount();
-
-            // Step q, very roughly.
-            RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
-            RootedValue rejectFunVal(cx, ObjectValue(*reject));
-            Rooted<PromiseObject*> nextPromise(cx);
-
-            // GetWaitForAllPromise is used internally only and must not
-            // trigger content-observable effects when registering a reaction.
-            // It's also meant to work on wrapped Promises, potentially from
-            // compartments with principals inaccessible from the current
-            // compartment. To make that work, it unwraps promises with
-            // UncheckedUnwrap,
-            nextPromise = &UncheckedUnwrap(nextPromiseObj)->as<PromiseObject>();
-
-            if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal,
-                                    resultPromise, nullptr, nullptr))
-            {
-                return nullptr;
-            }
-
-            // Step r (inline in loop-header).
-        }
-
-        // Sub-step d.i (implicit).
-        // Sub-step d.ii.
-        int32_t remainingCount = dataHolder->decreaseRemainingCount();
-
-        // Sub-step d.iii-iv.
-        if (remainingCount == 0) {
-            RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
-            if (!ResolvePromiseInternal(cx, resultPromise, valuesArrayVal))
-                return nullptr;
-        }
-    }
-
-    // Step 8 (omitted).
-
-    // Step 9.
-    return resultPromise;
-}
-
-static MOZ_MUST_USE bool
-RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result,
-                      ResolutionMode mode, HandleObject promiseObj)
-{
-    // The absence of a resolve/reject function can mean that, as an
-    // optimization, those weren't created. In that case, a flag is set on
-    // the Promise object. There are also reactions where the Promise
-    // itself is missing. For those, there's nothing left to do here.
-    if (resolutionFun) {
-        RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun));
-        FixedInvokeArgs<1> resolveArgs(cx);
-        resolveArgs[0].set(result);
-        return Call(cx, calleeOrRval, UndefinedHandleValue, resolveArgs, &calleeOrRval);
-    }
-
-    if (!promiseObj)
-        return true;
-
-    Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
-    if (promise->state() != JS::PromiseState::Pending)
-        return true;
-
-
-    if (mode == ResolveMode) {
-        if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION))
-            return true;
-        return ResolvePromiseInternal(cx, promise, result);
-    }
-
-    if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION))
-        return true;
-    return RejectMaybeWrappedPromise(cx, promiseObj, result);
-
-}
-
-// ES2016, 25.4.4.1.1.
-static MOZ_MUST_USE bool
-PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
-                  HandleObject promiseObj, HandleObject resolve, HandleObject reject)
-{
-    RootedObject unwrappedPromiseObj(cx);
-    if (IsWrapper(promiseObj)) {
-        unwrappedPromiseObj = CheckedUnwrap(promiseObj);
-        MOZ_ASSERT(unwrappedPromiseObj);
-    }
-
-    // Step 1.
-    MOZ_ASSERT(C->isConstructor());
-    RootedValue CVal(cx, ObjectValue(*C));
-
-    // Step 2 (omitted).
-
-    // Step 3.
-    // We have to be very careful about which compartments we create things in
-    // here.  In particular, we have to maintain the invariant that anything
-    // stored in a reserved slot is same-compartment with the object whose
-    // reserved slot it's in.  But we want to create the values array in the
-    // Promise's compartment, because that array can get exposed to
-    // code that has access to the Promise (in particular code from
-    // that compartment), and that should work, even if the Promise
-    // compartment is less-privileged than our caller compartment.
-    //
-    // So the plan is as follows: Create the values array in the promise
-    // compartment.  Create the PromiseAllResolveElement function
-    // and the data holder in our current compartment.  Store a
-    // cross-compartment wrapper to the values array in the holder.  This
-    // should be OK because the only things we hand the
-    // PromiseAllResolveElement function to are the "then" calls we do and in
-    // the case when the Promise's compartment is not the current compartment
-    // those are happening over Xrays anyway, which means they get the
-    // canonical "then" function and content can't see our
-    // PromiseAllResolveElement.
-    RootedObject valuesArray(cx);
-    if (unwrappedPromiseObj) {
-        JSAutoCompartment ac(cx, unwrappedPromiseObj);
-        valuesArray = NewDenseFullyAllocatedArray(cx, 0);
-    } else {
-        valuesArray = NewDenseFullyAllocatedArray(cx, 0);
-    }
-    if (!valuesArray)
-        return false;
-
-    RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
-    if (!cx->compartment()->wrap(cx, &valuesArrayVal))
-        return false;
-
-    // Step 4.
-    // Create our data holder that holds all the things shared across
-    // every step of the iterator.  In particular, this holds the
-    // remainingElementsCount (as an integer reserved slot), the array of
-    // values, and the resolve function from our PromiseCapability.
-    Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj,
-                                                                         valuesArray, resolve));
-    if (!dataHolder)
-        return false;
-    RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
-
-    // Step 5.
-    uint32_t index = 0;
-
-    // Step 6.
-    RootedValue nextValue(cx);
-    RootedId indexId(cx);
-    RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
-
-    while (true) {
-        bool done;
-        // 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.
-            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);
-            }
-
-            // We're all set for now!
-            return true;
-        }
-
-        // Step h.
-        { // Scope for the JSAutoCompartment we need to work with valuesArray.  We
-            // mostly do this for performance; we could go ahead and do the define via
-            // a cross-compartment proxy instead...
-            JSAutoCompartment ac(cx, valuesArray);
-            indexId = INT_TO_JSID(index);
-            if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue))
-                return false;
-        }
-
-        // Step i.
-        // Sadly, because someone could have overridden
-        // "resolve" on the canonical Promise constructor.
-        RootedValue nextPromise(cx);
-        RootedValue staticResolve(cx);
-        if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
-            return false;
-
-        FixedInvokeArgs<1> resolveArgs(cx);
-        resolveArgs[0].set(nextValue);
-        if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise))
-            return false;
-
-        // Step j.
-        RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
-                                                         1, nullptr,
-                                                         gc::AllocKind::FUNCTION_EXTENDED,
-                                                         GenericObject));
-        if (!resolveFunc)
-            return false;
-
-        // Steps k,m,n.
-        resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
-
-        // Step l.
-        resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
-                                     Int32Value(index));
-
-        // Steps o-p.
-        dataHolder->increaseRemainingCount();
-
-        // Step q.
-        RootedObject nextPromiseObj(cx, &nextPromise.toObject());
-        RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
-        if (!BlockOnPromise(cx, nextPromiseObj, promiseObj, resolveFunVal, rejectFunVal))
-            return false;
-
-        // Step r.
-        index++;
-        MOZ_ASSERT(index > 0);
-    }
-}
-
-// ES2016, 25.4.4.1.2.
-static bool
-PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedFunction resolve(cx, &args.callee().as<JSFunction>());
-    RootedValue xVal(cx, args.get(0));
-
-    // Step 1.
-    RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data));
-
-    // Step 2.
-    // We use the existence of the data holder as a signal for whether the
-    // Promise was already resolved. Upon resolution, it's reset to
-    // `undefined`.
-    if (dataVal.isUndefined()) {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>());
-
-    // Step 3.
-    resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue());
-
-    // Step 4.
-    int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
-                    .toInt32();
-
-    // Step 5.
-    RootedValue valuesVal(cx, data->valuesArray());
-    RootedObject valuesObj(cx, &valuesVal.toObject());
-    bool valuesListIsWrapped = false;
-    if (IsWrapper(valuesObj)) {
-        valuesListIsWrapped = true;
-        // See comment for PerformPromiseAll, step 3 for why we unwrap here.
-        valuesObj = UncheckedUnwrap(valuesObj);
-    }
-    RootedNativeObject values(cx, &valuesObj->as<NativeObject>());
-
-    // Step 6 (moved under step 10).
-    // Step 7 (moved to step 9).
-
-    // Step 8.
-    // The index is guaranteed to be initialized to `undefined`.
-    if (valuesListIsWrapped) {
-        AutoCompartment ac(cx, values);
-        if (!cx->compartment()->wrap(cx, &xVal))
-            return false;
-    }
-    values->setDenseElement(index, xVal);
-
-    // Steps 7,9.
-    uint32_t remainingCount = data->decreaseRemainingCount();
-
-    // Step 10.
-    if (remainingCount == 0) {
-        // Step 10.a. (Omitted, happened in PerformPromiseAll.)
-        // Step 10.b.
-
-        // Step 6 (Adapted to work with PromiseAllDataHolder's layout).
-        RootedObject resolveAllFun(cx, data->resolveObj());
-        RootedObject promiseObj(cx, data->promiseObj());
-        if (!resolveAllFun) {
-            if (!FulfillMaybeWrappedPromise(cx, promiseObj, valuesVal))
-                return false;
-        } else {
-            if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj))
-                return false;
-        }
-    }
-
-    // Step 11.
-    args.rval().setUndefined();
-    return true;
-}
-
-static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator,
-                                            HandleObject C, HandleObject promiseObj,
-                                            HandleObject resolve, HandleObject reject);
-
-// ES2016, 25.4.4.3.
-static bool
-Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    RootedValue iterable(cx, args.get(0));
-
-    // Step 2 (reordered).
-    RootedValue CVal(cx, args.thisv());
-    if (!CVal.isObject()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
-                                  "Receiver of Promise.race call");
-        return false;
-    }
-
-    // Step 1.
-    RootedObject C(cx, &CVal.toObject());
-
-    // Step 3.
-    RootedObject resultPromise(cx);
-    RootedObject resolve(cx);
-    RootedObject reject(cx);
-    if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
-        return false;
-
-    // Steps 4-5.
-    JS::ForOfIterator iter(cx);
-    if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
-        return AbruptRejectPromise(cx, args, resultPromise, reject);
-
-    if (!iter.valueIsIterable()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
-                                  "Argument of Promise.race");
-        return AbruptRejectPromise(cx, args, resultPromise, reject);
-    }
-
-    // Step 6 (implicit).
-
-    // Step 7.
-    bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject);
-
-    // Step 8.
-    if (!result) {
-        // Step 8.a.
-        // TODO: implement iterator closing.
-
-        // Step 8.b.
-        return AbruptRejectPromise(cx, args, resultPromise, reject);
-    }
-
-    // Step 9.
-    args.rval().setObject(*resultPromise);
-    return true;
-}
-
-// ES2016, 25.4.4.3.1.
-static MOZ_MUST_USE bool
-PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
-                   HandleObject promiseObj, HandleObject resolve, HandleObject reject)
-{
-    MOZ_ASSERT(C->isConstructor());
-    RootedValue CVal(cx, ObjectValue(*C));
-
-    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))
-            return false;
-
-        // Step d.
-        if (done) {
-            // Step d.i.
-            // TODO: implement iterator closing.
-
-            // Step d.ii.
-            return true;
-        }
-
-        // Step h.
-        // Sadly, because someone could have overridden
-        // "resolve" on the canonical Promise constructor.
-        RootedValue nextPromise(cx);
-        RootedValue staticResolve(cx);
-        if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
-            return false;
-
-        FixedInvokeArgs<1> resolveArgs(cx);
-        resolveArgs[0].set(nextValue);
-        if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise))
-            return false;
-
-        // Step i.
-        RootedObject nextPromiseObj(cx, &nextPromise.toObject());
-        if (!BlockOnPromise(cx, nextPromiseObj, promiseObj, resolveFunVal, rejectFunVal))
-            return false;
-    }
-
-    MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace");
-}
-
-// ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5.
-static MOZ_MUST_USE bool
-CommonStaticResolveRejectImpl(JSContext* cx, unsigned argc, Value* vp, ResolutionMode mode)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    RootedValue x(cx, args.get(0));
-
-    // Steps 1-2.
-    if (!args.thisv().isObject()) {
-        const char* msg = mode == ResolveMode
-                          ? "Receiver of Promise.resolve call"
-                          : "Receiver of Promise.reject call";
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg);
-        return false;
-    }
-    RootedValue cVal(cx, args.thisv());
-    RootedObject C(cx, &cVal.toObject());
-
-    // Step 3 of Resolve.
-    if (mode == ResolveMode && x.isObject()) {
-        RootedObject xObj(cx, &x.toObject());
-        bool isPromise = false;
-        if (xObj->is<PromiseObject>()) {
-            isPromise = true;
-        } else if (IsWrapper(xObj)) {
-            // Treat instances of Promise from other compartments as Promises
-            // here, too.
-            // It's important to do the GetProperty for the `constructor`
-            // below through the wrapper, because wrappers can change the
-            // outcome, so instead of unwrapping and then performing the
-            // GetProperty, just check here and then operate on the original
-            // object again.
-            RootedObject unwrappedObject(cx, CheckedUnwrap(xObj));
-            if (unwrappedObject && unwrappedObject->is<PromiseObject>())
-                isPromise = true;
-        }
-        if (isPromise) {
-            RootedValue ctorVal(cx);
-            if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal))
-                return false;
-            if (ctorVal == cVal) {
-                args.rval().set(x);
-                return true;
-            }
-        }
-    }
-
-    // Step 4 of Resolve, 3 of Reject.
-    RootedObject promise(cx);
-    RootedObject resolveFun(cx);
-    RootedObject rejectFun(cx);
-    if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun, true))
-        return false;
-
-    // Step 5 of Resolve, 4 of Reject.
-    if (!RunResolutionFunction(cx, mode == ResolveMode ? resolveFun : rejectFun, x, mode, promise))
-        return false;
-
-    // Step 6 of Resolve, 4 of Reject.
-    args.rval().setObject(*promise);
-    return true;
-}
-
-/**
- * ES2016, 25.4.4.4, Promise.reject.
- */
-static bool
-Promise_reject(JSContext* cx, unsigned argc, Value* vp)
-{
-    return CommonStaticResolveRejectImpl(cx, argc, vp, RejectMode);
-}
-
-/**
- * Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
- */
-/* static */ JSObject*
-PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
-{
-    // Steps 1-2 (omitted).
-
-    // Roughly step 3.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
-    if (!promise)
-        return nullptr;
-
-    // Roughly step 4.
-    if (!ResolvePromise(cx, promise, value, JS::PromiseState::Rejected))
-        return nullptr;
-
-    // Step 5.
-    return promise;
-}
-
-/**
- * ES2016, 25.4.4.5, Promise.resolve.
- */
-static bool
-Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
-{
-    return CommonStaticResolveRejectImpl(cx, argc, vp, ResolveMode);
-}
-
-/**
- * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
- */
-/* static */ JSObject*
-PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
-{
-    // Steps 1-2 (omitted).
-
-    // Step 3.
-    if (value.isObject()) {
-        JSObject* obj = &value.toObject();
-        if (IsWrapper(obj))
-            obj = CheckedUnwrap(obj);
-        // Instead of getting the `constructor` property, do an unforgeable
-        // check.
-        if (obj && obj->is<PromiseObject>())
-            return obj;
-    }
-
-    // Step 4.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
-    if (!promise)
-        return nullptr;
-
-    // Steps 5.
-    if (!ResolvePromiseInternal(cx, promise, value))
-        return nullptr;
-
-    // Step 6.
-    return promise;
-}
-
-// ES2016, 25.4.4.6, implemented in Promise.js.
-
-// ES2016, 25.4.5.1, implemented in Promise.js.
-
-static PromiseReactionRecord*
-NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled,
-                  HandleValue onRejected, HandleObject resolve, HandleObject reject,
-                  HandleObject incumbentGlobalObject)
-{
-    Rooted<PromiseReactionRecord*> reaction(cx, NewObjectWithClassProto<PromiseReactionRecord>(cx));
-    if (!reaction)
-        return nullptr;
-
-    assertSameCompartment(cx, resultPromise);
-    assertSameCompartment(cx, onFulfilled);
-    assertSameCompartment(cx, onRejected);
-    assertSameCompartment(cx, resolve);
-    assertSameCompartment(cx, reject);
-    assertSameCompartment(cx, incumbentGlobalObject);
-
-    reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultPromise));
-    reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0));
-    reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled);
-    reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected);
-    reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve));
-    reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject));
-    reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject,
-                           ObjectOrNullValue(incumbentGlobalObject));
-
-    return reaction;
-}
-
-// ES2016, 25.4.5.3., steps 3-5.
-MOZ_MUST_USE JSObject*
-js::OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
-                        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());
-
-    // Step 4.
-    RootedObject resultPromise(cx);
-    RootedObject resolve(cx);
-    RootedObject reject(cx);
-    if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true))
-        return nullptr;
-
-    // Step 5.
-    if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
-        return nullptr;
-
-    return resultPromise;
-}
-
-// ES2016, 25.4.5.3.
-static bool
-Promise_then(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    // Step 1.
-    RootedValue promiseVal(cx, args.thisv());
-
-    RootedValue onFulfilled(cx, args.get(0));
-    RootedValue onRejected(cx, args.get(1));
-
-    // Step 2.
-    if (!promiseVal.isObject()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
-                                  "Receiver of Promise.prototype.then call");
-        return false;
-    }
-    RootedObject promiseObj(cx, &promiseVal.toObject());
-    Rooted<PromiseObject*> promise(cx);
-
-    bool isPromise = promiseObj->is<PromiseObject>();
-    if (isPromise) {
-        promise = &promiseObj->as<PromiseObject>();
-    } else {
-        RootedObject unwrappedPromiseObj(cx, CheckedUnwrap(promiseObj));
-        if (!unwrappedPromiseObj) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
-            return false;
-        }
-        if (!unwrappedPromiseObj->is<PromiseObject>()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
-                                      "Promise", "then", "value");
-            return false;
-        }
-        promise = &unwrappedPromiseObj->as<PromiseObject>();
-    }
-
-    // Steps 3-5.
-    RootedObject resultPromise(cx, OriginalPromiseThen(cx, promise, onFulfilled, onRejected));
-    if (!resultPromise)
-        return false;
-
-    args.rval().setObject(*resultPromise);
-    return true;
-}
-
-// ES2016, 25.4.5.3.1.
-static MOZ_MUST_USE bool
-PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
-                   HandleValue onRejected_, HandleObject resultPromise,
-                   HandleObject resolve, HandleObject reject)
-{
-    // Step 1 (implicit).
-    // Step 2 (implicit).
-
-    // Step 3.
-    RootedValue onFulfilled(cx, onFulfilled_);
-    if (!IsCallable(onFulfilled))
-        onFulfilled = Int32Value(PROMISE_HANDLER_IDENTITY);
-
-    // Step 4.
-    RootedValue onRejected(cx, onRejected_);
-    if (!IsCallable(onRejected))
-        onRejected = Int32Value(PROMISE_HANDLER_THROWER);
-
-    RootedObject incumbentGlobal(cx);
-    if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
-        return false;
-
-    // Step 7.
-    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
-                                                                  onFulfilled, onRejected,
-                                                                  resolve, reject,
-                                                                  incumbentGlobal));
-
-    JS::PromiseState state = promise->state();
-    int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
-    if (state == JS::PromiseState::Pending) {
-        // Steps 5,6 (reordered).
-        // Instead of creating separate reaction records for fulfillment and
-        // rejection, we create a combined record. All places we use the record
-        // can handle that.
-        if (!AddPromiseReaction(cx, promise, reaction))
-            return false;
-    }
-
-    // Steps 8,9.
-    else {
-        // Step 9.a.
-        MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected);
-
-        // Step 8.a. / 9.b.
-        RootedValue valueOrReason(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
-
-        // We might be operating on a promise from another compartment. In
-        // that case, we need to wrap the result/reason value before using it.
-        if (!cx->compartment()->wrap(cx, &valueOrReason))
-            return false;
-
-        // Step 9.c.
-        if (state == JS::PromiseState::Rejected && !(flags & PROMISE_FLAG_HANDLED))
-            cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
-
-        // Step 8.b. / 9.d.
-        if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
-            return false;
-    }
-
-    // Step 10.
-    promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_HANDLED));
-
-    // Step 11.
-    return true;
-}
-
-/**
- * Calls |promise.then| with the provided hooks and adds |blockedPromise| to
- * its list of dependent promises. Used by |Promise.all| and |Promise.race|.
- *
- * If |promise.then| is the original |Promise.prototype.then| function and
- * the call to |promise.then| would use the original |Promise| constructor to
- * create the resulting promise, this function skips the call to |promise.then|
- * and thus creating a new promise that would not be observable by content.
- */
-static MOZ_MUST_USE bool
-BlockOnPromise(JSContext* cx, HandleObject promiseObj, HandleObject blockedPromise_,
-               HandleValue onFulfilled, HandleValue onRejected)
-{
-    RootedValue thenVal(cx);
-    if (!GetProperty(cx, promiseObj, promiseObj, cx->names().then, &thenVal))
-        return false;
-
-    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., 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., step 4.
-            if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true))
-                return false;
-        }
-
-        // 25.4.5.3., step 5.
-        Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
-        if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise,
-                                resolveFun, rejectFun))
-        {
-            return false;
-        }
-
-        if (!addToDependent)
-            return true;
-    } else {
-        // Optimization failed, do the normal call.
-        RootedValue promiseVal(cx, ObjectValue(*promiseObj));
-        RootedValue rval(cx);
-        if (!Call(cx, thenVal, promiseVal, onFulfilled, onRejected, &rval))
-            return false;
-    }
-
-    // The object created by the |promise.then| call or the inlined version
-    // of it above is visible to content (either because |promise.then| was
-    // overridden by content and could leak it, or because a constructor
-    // other than the original value of |Promise| was used to create it).
-    // To have both that object and |blockedPromise| show up as dependent
-    // promises in the debugger, add a dummy reaction to the list of reject
-    // reactions that contains |blockedPromise|, but otherwise does nothing.
-    RootedObject unwrappedPromiseObj(cx, promiseObj);
-    RootedObject blockedPromise(cx, blockedPromise_);
-
-    mozilla::Maybe<AutoCompartment> ac;
-    if (IsWrapper(promiseObj)) {
-        unwrappedPromiseObj = CheckedUnwrap(promiseObj);
-        if (!unwrappedPromiseObj)
-            return false;
-        ac.emplace(cx, unwrappedPromiseObj);
-        if (!cx->compartment()->wrap(cx, &blockedPromise))
-            return false;
-    }
-
-    // If the object to depend on isn't a, maybe-wrapped, Promise instance,
-    // we ignore it. All this does is lose some small amount of debug
-    // information in scenarios that are highly unlikely to occur in useful
-    // code.
-    if (!unwrappedPromiseObj->is<PromiseObject>())
-        return true;
-
-    Rooted<PromiseObject*> promise(cx, &unwrappedPromiseObj->as<PromiseObject>());
-    return AddPromiseReaction(cx, promise, UndefinedHandleValue, UndefinedHandleValue,
-                              blockedPromise, nullptr, nullptr, nullptr);
-}
-
-static MOZ_MUST_USE bool
-AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
-                   Handle<PromiseReactionRecord*> reaction)
-{
-    RootedValue reactionVal(cx, ObjectValue(*reaction));
-
-    // The code that creates Promise reactions can handle wrapped Promises,
-    // unwrapping them as needed. That means that the `promise` and `reaction`
-    // objects we have here aren't necessarily from the same compartment. In
-    // order to store the reaction on the promise, we have to ensure that it
-    // is properly wrapped.
-    mozilla::Maybe<AutoCompartment> ac;
-    if (promise->compartment() != cx->compartment()) {
-        ac.emplace(cx, promise);
-        if (!cx->compartment()->wrap(cx, &reactionVal))
-            return false;
-    }
-
-    // 25.4.5.3.1 steps 7.a,b.
-    RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
-    RootedNativeObject reactions(cx);
-
-    if (reactionsVal.isUndefined()) {
-        // If no reactions existed so far, just store the reaction record directly.
-        promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal);
-        return true;
-    }
-
-    RootedObject reactionsObj(cx, &reactionsVal.toObject());
-
-    // If only a single reaction exists, it's stored directly instead of in a
-    // list. In that case, `reactionsObj` might be a wrapper, which we can
-    // always safely unwrap. It is always safe to unwrap it in that case.
-    if (IsWrapper(reactionsObj)) {
-        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)
-            return false;
-        if (reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success)
-            return false;
-
-        reactions->setDenseElement(0, reactionsVal);
-        reactions->setDenseElement(1, reactionVal);
-
-        promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions));
-    } else {
-        // Otherwise, just store the new reaction.
-        reactions = &reactionsObj->as<NativeObject>();
-        uint32_t len = reactions->getDenseInitializedLength();
-        if (reactions->ensureDenseElements(cx, 0, len + 1) != DenseElementResult::Success)
-            return false;
-        reactions->setDenseElement(len, reactionVal);
-    }
-
-    return true;
-}
-
-static MOZ_MUST_USE bool
-AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
-                   HandleValue onRejected, HandleObject dependentPromise,
-                   HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal)
-{
-    if (promise->state() != JS::PromiseState::Pending)
-        return true;
-
-    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, dependentPromise,
-                                                                  onFulfilled, onRejected,
-                                                                  resolve, reject,
-                                                                  incumbentGlobal));
-    if (!reaction)
-        return false;
-    return AddPromiseReaction(cx, promise, reaction);
-}
-
-namespace {
-// Generator used by PromiseObject::getID.
-mozilla::Atomic<uint64_t> gIDGenerator(0);
-} // namespace
-
-double
-PromiseObject::lifetime()
-{
-    return MillisecondsSinceStartup() - allocationTime();
-}
-
-uint64_t
-PromiseObject::getID()
-{
-    Value idVal(getFixedSlot(PromiseSlot_Id));
-    if (idVal.isUndefined()) {
-        idVal.setDouble(++gIDGenerator);
-        setFixedSlot(PromiseSlot_Id, idVal);
-    }
-    return uint64_t(idVal.toNumber());
-}
-
-/**
- * Returns all promises that directly depend on this one. That means those
- * created by calling `then` on this promise, or the promise returned by
- * `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise
- * being a member of the passed-in `iterable`.
- *
- * Per spec, we should have separate lists of reaction records for the
- * fulfill and reject cases. As an optimization, we have only one of those,
- * containing the required data for both cases. So we just walk that list
- * and extract the dependent promises from all reaction records.
- */
-bool
-PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values)
-{
-    if (state() != JS::PromiseState::Pending)
-        return true;
-
-    RootedValue reactionsVal(cx, getFixedSlot(PromiseSlot_ReactionsOrResult));
-
-    // If no reactions are pending, we don't have list and are done.
-    if (reactionsVal.isNullOrUndefined())
-        return true;
-
-    RootedNativeObject reactions(cx, &reactionsVal.toObject().as<NativeObject>());
-
-    // If only a single reaction is pending, it's stored directly.
-    if (reactions->is<PromiseReactionRecord>()) {
-        // Not all reactions have a Promise on them.
-        RootedObject promiseObj(cx, reactions->as<PromiseReactionRecord>().promise());
-        if (!promiseObj)
-            return true;
-
-        if (!values.growBy(1))
-            return false;
-
-        values[0].setObject(*promiseObj);
-        return true;
-    }
-
-    uint32_t len = reactions->getDenseInitializedLength();
-    MOZ_ASSERT(len >= 2);
-
-    size_t valuesIndex = 0;
-    Rooted<PromiseReactionRecord*> reaction(cx);
-    for (size_t i = 0; i < len; i++) {
-        reaction = &reactions->getDenseElement(i).toObject().as<PromiseReactionRecord>();
-
-        // Not all reactions have a Promise on them.
-        RootedObject promiseObj(cx, reaction->promise());
-        if (!promiseObj)
-            continue;
-        if (!values.growBy(1))
-            return false;
-
-        values[valuesIndex++].setObject(*promiseObj);
-    }
-
-    return true;
-}
 
 bool
 PromiseObject::resolve(JSContext* cx, HandleValue resolutionValue)
 {
     if (state() != JS::PromiseState::Pending)
         return true;
 
-    RootedObject resolveFun(cx, GetResolveFunctionFromPromise(this));
-    RootedValue funVal(cx, ObjectValue(*resolveFun));
-
-    // For xray'd Promises, the resolve fun may have been created in another
-    // compartment. For the call below to work in that case, wrap the
-    // function into the current compartment.
-    if (!cx->compartment()->wrap(cx, &funVal))
-        return false;
+    RootedValue funVal(cx, this->getReservedSlot(PROMISE_RESOLVE_FUNCTION_SLOT));
+    // TODO: ensure that this holds for xray'd promises. (It probably doesn't)
+    MOZ_ASSERT(funVal.toObject().is<JSFunction>());
 
     FixedInvokeArgs<1> args(cx);
+
     args[0].set(resolutionValue);
 
     RootedValue dummy(cx);
     return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
 }
 
 bool
 PromiseObject::reject(JSContext* cx, HandleValue rejectionValue)
 {
     if (state() != JS::PromiseState::Pending)
         return true;
 
-    RootedValue funVal(cx, this->getFixedSlot(PromiseSlot_RejectFunction));
-    MOZ_ASSERT(IsCallable(funVal));
+    RootedValue resolveVal(cx, this->getReservedSlot(PROMISE_RESOLVE_FUNCTION_SLOT));
+    RootedFunction resolve(cx, &resolveVal.toObject().as<JSFunction>());
+    RootedValue funVal(cx, resolve->getExtendedSlot(ResolutionFunctionSlot_OtherFunction));
+    MOZ_ASSERT(funVal.toObject().is<JSFunction>());
 
     FixedInvokeArgs<1> args(cx);
+
     args[0].set(rejectionValue);
 
     RootedValue dummy(cx);
     return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
 }
 
 void
 PromiseObject::onSettled(JSContext* cx)
@@ -2460,33 +988,382 @@ PromiseObject::onSettled(JSContext* cx)
     Rooted<PromiseObject*> promise(cx, this);
     RootedObject stack(cx);
     if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
         if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) {
             cx->clearPendingException();
             return;
         }
     }
-    promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack));
-    promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
+    promise->setFixedSlot(PROMISE_RESOLUTION_SITE_SLOT, ObjectOrNullValue(stack));
+    promise->setFixedSlot(PROMISE_RESOLUTION_TIME_SLOT, DoubleValue(MillisecondsSinceStartup()));
 
     if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
         cx->runtime()->addUnhandledRejectedPromise(cx, promise);
 
     JS::dbg::onPromiseSettled(cx, promise);
 }
 
-MOZ_MUST_USE bool
-js::EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
-                            HandleObject dependentPromise,
-                            HandleValue onFulfilled, HandleValue onRejected)
+enum ReactionJobSlots {
+    ReactionJobSlot_Handler = 0,
+    ReactionJobSlot_JobData,
+};
+
+enum ReactionJobDataSlots {
+    ReactionJobDataSlot_HandlerArg = 0,
+    ReactionJobDataSlot_ResolveHook,
+    ReactionJobDataSlot_RejectHook,
+    ReactionJobDataSlotsCount,
+};
+
+// ES6, 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
+ *
+ * A PromiseReactionJob 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:
+ * ReactionJobSlot_Handler: The handler to use as the Promise reaction.
+ *                          This can be PROMISE_HANDLER_IDENTITY,
+ *                          PROMISE_HANDLER_THROWER, or a callable. In the
+ *                          latter case, it's guaranteed to be an object from
+ *                          the same compartment as the PromiseReactionJob.
+ * ReactionJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
+ *                          containing data required for proper execution of
+ *                          the reaction.
+ *
+ * The JobData list has the following entries:
+ * ReactionJobDataSlot_HandlerArg: Value passed as argument when invoking the
+ *                                 reaction handler.
+ * ReactionJobDataSlot_ResolveHook: The Promise's resolve hook, invoked if the
+ *                                  handler is PROMISE_HANDLER_IDENTITY or
+ *                                  upon successful execution of a callable
+ *                                  handler.
+ *  ReactionJobDataSlot_RejectHook: The Promise's reject hook, invoked if the
+ *                                  handler is PROMISE_HANDLER_THROWER or if
+ *                                  execution of a callable handler aborts
+ *                                  abnormally.
+ */
+static bool
+PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedFunction job(cx, &args.callee().as<JSFunction>());
+
+    RootedValue handlerVal(cx, job->getExtendedSlot(ReactionJobSlot_Handler));
+    RootedObject jobDataObj(cx, &job->getExtendedSlot(ReactionJobSlot_JobData).toObject());
+
+    // To ensure that the embedding ends up with the right entry global, we're
+    // guaranteeing that the reaction job function gets created in the same
+    // compartment as the handler function. That's not necessarily the global
+    // that the job was triggered from, though. To be able to find the
+    // triggering global, we always create the jobArgs object in that global
+    // and wrap it into the handler's. So to go back, we check if jobArgsObj
+    // is a wrapper and if so, unwrap it, enter its compartment, and wrap
+    // the handler into that compartment.
+    //
+    // See the doc comment for PromiseReactionJob for how this information is
+    // stored.
+    mozilla::Maybe<AutoCompartment> ac;
+    if (IsWrapper(jobDataObj)) {
+        jobDataObj = UncheckedUnwrap(jobDataObj);
+        ac.emplace(cx, jobDataObj);
+        if (!cx->compartment()->wrap(cx, &handlerVal))
+            return false;
+    }
+    RootedNativeObject jobData(cx, &jobDataObj->as<NativeObject>());
+    RootedValue argument(cx, jobData->getDenseElement(ReactionJobDataSlot_HandlerArg));
+
+    // Step 1 (omitted).
+
+    // Steps 2-3.
+    RootedValue handlerResult(cx);
+    bool shouldReject = false;
+
+    // Steps 4-7.
+    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);
+            shouldReject = true;
+            handlerResult = argument;
+        }
+    } else {
+        // Step 6.
+        FixedInvokeArgs<1> args2(cx);
+        args2[0].set(argument);
+        if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
+            shouldReject = true;
+            // Not much we can do about uncatchable exceptions, so just bail
+            // for those.
+            if (!cx->isExceptionPending() || !GetAndClearException(cx, &handlerResult))
+                return false;
+        }
+    }
+
+    // Steps 7-9.
+    size_t hookSlot = shouldReject
+                      ? ReactionJobDataSlot_RejectHook
+                      : ReactionJobDataSlot_ResolveHook;
+    RootedObject callee(cx, &jobData->getDenseElement(hookSlot).toObject());
+
+    FixedInvokeArgs<1> args2(cx);
+    args2[0].set(handlerResult);
+    RootedValue calleeOrRval(cx, ObjectValue(*callee));
+    bool result = Call(cx, calleeOrRval, UndefinedHandleValue, args2, &calleeOrRval);
+
+    args.rval().set(calleeOrRval);
+    return result;
+}
+
+bool
+EnqueuePromiseReactionJob(JSContext* cx, HandleValue handler_, HandleValue handlerArg,
+                          HandleObject resolve, HandleObject reject,
+                          HandleObject promise_, HandleObject objectFromIncumbentGlobal_)
 {
-    MOZ_ASSERT_IF(dependentPromise, dependentPromise->is<PromiseObject>());
-    return PerformPromiseThen(cx, promise, onFulfilled, onRejected, dependentPromise,
-                              nullptr, nullptr);
+    // Create a dense array to hold the data needed for the reaction job to
+    // work.
+    // See doc comment for PromiseReactionJob for layout details.
+    RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ReactionJobDataSlotsCount));
+    if (!data ||
+        data->ensureDenseElements(cx, 0, ReactionJobDataSlotsCount) != DenseElementResult::Success)
+    {
+        return false;
+    }
+
+    // Store the handler argument.
+    data->setDenseElement(ReactionJobDataSlot_HandlerArg, handlerArg);
+
+    // Store the resolve hook.
+    data->setDenseElement(ReactionJobDataSlot_ResolveHook, ObjectValue(*resolve));
+
+    // Store the reject hook.
+    data->setDenseElement(ReactionJobDataSlot_RejectHook, ObjectValue(*reject));
+
+    RootedValue dataVal(cx, ObjectValue(*data));
+
+    // Re-rooting because we might need to unwrap it.
+    RootedValue handler(cx, 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> ac;
+    if (handler.isObject()) {
+        RootedObject handlerObj(cx, &handler.toObject());
+
+        // The unwrapping has to be unchecked because we specifically want to
+        // be able to use handlers with wrappers that would only allow calls.
+        // E.g., it's ok to have a handler from a chrome compartment in a
+        // reaction to a content compartment's Promise instance.
+        handlerObj = UncheckedUnwrap(handlerObj);
+        MOZ_ASSERT(handlerObj);
+        ac.emplace(cx, handlerObj);
+        handler = ObjectValue(*handlerObj);
+
+        // We need to wrap the |data| array to store it on the job function.
+        if (!cx->compartment()->wrap(cx, &dataVal))
+            return false;
+    }
+
+    // Create the JS function to call when the job is triggered.
+    RootedAtom funName(cx, cx->names().empty);
+    RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
+                                             gc::AllocKind::FUNCTION_EXTENDED));
+    if (!job)
+        return false;
+
+    // Store the handler and the data array on the reaction job.
+    job->setExtendedSlot(ReactionJobSlot_Handler, handler);
+    job->setExtendedSlot(ReactionJobSlot_JobData, dataVal);
+
+    // When using JS::AddPromiseReactions, no actual promise is created, so we
+    // might not have one here.
+    // Additionally, we might have an object here that isn't an instance of
+    // Promise. This can happen if content overrides the value of
+    // Promise[@@species] (or invokes Promise#then on a Promise subclass
+    // instance with a non-default @@species value on the constructor) with a
+    // function that returns objects that're not Promise (subclass) instances.
+    // In that case, we just pretend we didn't have an object in the first
+    // place.
+    // If after all this we do have an object, wrap it in case we entered the
+    // handler's compartment above, because we should pass objects from a
+    // single compartment to the enqueuePromiseJob callback.
+    RootedObject promise(cx);
+    if (promise_ && promise_->is<PromiseObject>()) {
+      promise = promise_;
+      if (!cx->compartment()->wrap(cx, &promise))
+          return false;
+    }
+
+    // Using objectFromIncumbentGlobal, we can derive the incumbent global by
+    // unwrapping and then getting the global. This is very convoluted, but
+    // much better than having to store the original global as a private value
+    // because we couldn't wrap it to store it as a normal JS value.
+    RootedObject global(cx);
+    RootedObject objectFromIncumbentGlobal(cx, objectFromIncumbentGlobal_);
+    if (objectFromIncumbentGlobal) {
+        objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
+        MOZ_ASSERT(objectFromIncumbentGlobal);
+        global = &objectFromIncumbentGlobal->global();
+    }
+
+    // Note: the global we pass here might be from a different compartment
+    // than job and promise. While it's somewhat unusual to pass objects
+    // from multiple compartments, in this case we specifically need the
+    // global to be unwrapped because wrapping and unwrapping aren't
+    // necessarily symmetric for globals.
+    return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
+}
+
+enum ThenableJobSlots {
+    ThenableJobSlot_Handler = 0,
+    ThenableJobSlot_JobData,
+};
+
+enum ThenableJobDataSlots {
+    ThenableJobDataSlot_Promise = 0,
+    ThenableJobDataSlot_Thenable,
+    ThenableJobDataSlotsCount,
+};
+// ES6, 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
+ *
+ * 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,
+ *                          PROMISE_HANDLER_THROWER, or a callable. In the
+ *                          latter case, it's guaranteed to be an object
+ *                          from the same compartment as the
+ *                          PromiseReactionJob.
+ * ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
+ *                          containing data required for proper execution of
+ *                          the reaction.
+ *
+ * The JobData list has the following entries:
+ * ThenableJobDataSlot_Promise: The Promise to resolve using the given
+ *                              thenable.
+ * ThenableJobDataSlot_Thenable: The thenable to use as the receiver when
+ *                               calling the `then` function.
+ */
+static bool
+PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedFunction job(cx, &args.callee().as<JSFunction>());
+    RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler));
+    MOZ_ASSERT(!IsWrapper(&then.toObject()));
+    RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData)
+                                    .toObject().as<NativeObject>());
+
+    RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataSlot_Promise));
+    RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataSlot_Thenable));
+
+    // Step 1.
+    RootedValue resolveVal(cx);
+    RootedValue rejectVal(cx);
+    if (!CreateResolvingFunctions(cx, promise, &resolveVal, &rejectVal))
+        return false;
+
+    // Step 2.
+    FixedInvokeArgs<2> args2(cx);
+    args2[0].set(resolveVal);
+    args2[1].set(rejectVal);
+
+    RootedValue rval(cx);
+
+    // In difference to the usual pattern, we return immediately on success.
+    if (Call(cx, then, thenable, args2, &rval))
+        return true;
+
+    if (!GetAndClearException(cx, &rval))
+        return false;
+
+    FixedInvokeArgs<1> rejectArgs(cx);
+    rejectArgs[0].set(rval);
+
+    return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval);
+}
+
+bool
+EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
+                                 HandleValue thenable_, HandleValue thenVal)
+{
+    // Need to re-root these to enable wrapping them below.
+    RootedValue promiseToResolve(cx, promiseToResolve_);
+    RootedValue thenable(cx, thenable_);
+
+    // We enter the `then` callable's compartment so that the job function is
+    // created in that compartment.
+    // That guarantees that the embedding ends up with the right entry global.
+    // This is relevant for some html APIs like fetch that derive information
+    // from said global.
+    RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
+    AutoCompartment ac(cx, then);
+
+    RootedAtom funName(cx, cx->names().empty);
+    RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
+                                             gc::AllocKind::FUNCTION_EXTENDED));
+    if (!job)
+        return false;
+
+    // Store the `then` function on the callback.
+    job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
+
+    // Create a dense array to hold the data needed for the reaction job to
+    // work.
+    // See the doc comment for PromiseResolveThenableJob for the layout.
+    RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataSlotsCount));
+    if (!data ||
+        data->ensureDenseElements(cx, 0, ThenableJobDataSlotsCount) != DenseElementResult::Success)
+    {
+        return false;
+    }
+
+    // Wrap and set the `promiseToResolve` argument.
+    if (!cx->compartment()->wrap(cx, &promiseToResolve))
+        return false;
+    data->setDenseElement(ThenableJobDataSlot_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);
+
+    // 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);
 }
 
 PromiseTask::PromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
   : runtime_(cx),
     promise_(cx, promise)
 {}
 
 PromiseTask::~PromiseTask()
@@ -2511,38 +1388,40 @@ PromiseTask::finish(JSContext* cx)
 
 void
 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),
-    JS_FN("then", Promise_then, 2, 0),
+    JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
     JS_FS_END
 };
 
 static const JSPropertySpec promise_properties[] = {
     JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY),
     JS_PS_END
 };
 
 static const JSFunctionSpec promise_static_methods[] = {
-    JS_FN("all", Promise_static_all, 1, 0),
-    JS_FN("race", Promise_static_race, 1, 0),
+    JS_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
+    JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
     JS_FN("reject", Promise_reject, 1, 0),
-    JS_FN("resolve", Promise_static_resolve, 1, 0),
+    JS_FN("resolve", Promise_resolve, 1, 0),
     JS_FS_END
 };
 
 static const JSPropertySpec promise_static_properties[] = {
     JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
     JS_PS_END
 };
 
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -7,120 +7,110 @@
 #ifndef builtin_Promise_h
 #define builtin_Promise_h
 
 #include "builtin/SelfHostingDefines.h"
 #include "vm/NativeObject.h"
 
 namespace js {
 
-enum PromiseSlots {
-    PromiseSlot_Flags = 0,
-    PromiseSlot_ReactionsOrResult,
-    PromiseSlot_RejectFunction,
-    PromiseSlot_AllocationSite,
-    PromiseSlot_ResolutionSite,
-    PromiseSlot_AllocationTime,
-    PromiseSlot_ResolutionTime,
-    PromiseSlot_Id,
-    PromiseSlots,
-};
-
-#define PROMISE_FLAG_RESOLVED  0x1
-#define PROMISE_FLAG_FULFILLED 0x2
-#define PROMISE_FLAG_HANDLED   0x4
-#define PROMISE_FLAG_REPORTED  0x8
-#define PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION 0x10
-#define PROMISE_FLAG_DEFAULT_REJECT_FUNCTION  0x20
-
 class AutoSetNewObjectMetadata;
 
 class PromiseObject : public NativeObject
 {
   public:
-    static const unsigned RESERVED_SLOTS = PromiseSlots;
+    static const unsigned RESERVED_SLOTS = 8;
     static const Class class_;
     static const Class protoClass_;
     static PromiseObject* create(JSContext* cx, HandleObject executor,
                                  HandleObject proto = nullptr);
 
     static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
     static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
 
     JS::PromiseState state() {
-        int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
+        int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
         if (!(flags & PROMISE_FLAG_RESOLVED)) {
             MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
             return JS::PromiseState::Pending;
         }
         if (flags & PROMISE_FLAG_FULFILLED)
             return JS::PromiseState::Fulfilled;
         return JS::PromiseState::Rejected;
     }
     Value value()  {
         MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
-        return getFixedSlot(PromiseSlot_ReactionsOrResult);
+        return getFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT);
     }
     Value reason() {
         MOZ_ASSERT(state() == JS::PromiseState::Rejected);
-        return getFixedSlot(PromiseSlot_ReactionsOrResult);
+        return getFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT);
     }
 
     MOZ_MUST_USE bool resolve(JSContext* cx, HandleValue resolutionValue);
     MOZ_MUST_USE bool reject(JSContext* cx, HandleValue rejectionValue);
 
     void onSettled(JSContext* cx);
 
-    double allocationTime() { return getFixedSlot(PromiseSlot_AllocationTime).toNumber(); }
-    double resolutionTime() { return getFixedSlot(PromiseSlot_ResolutionTime).toNumber(); }
+    double allocationTime() { return getFixedSlot(PROMISE_ALLOCATION_TIME_SLOT).toNumber(); }
+    double resolutionTime() { return getFixedSlot(PROMISE_RESOLUTION_TIME_SLOT).toNumber(); }
     JSObject* allocationSite() {
-        return getFixedSlot(PromiseSlot_AllocationSite).toObjectOrNull();
+        return getFixedSlot(PROMISE_ALLOCATION_SITE_SLOT).toObjectOrNull();
     }
     JSObject* resolutionSite() {
-        return getFixedSlot(PromiseSlot_ResolutionSite).toObjectOrNull();
+        return getFixedSlot(PROMISE_RESOLUTION_SITE_SLOT).toObjectOrNull();
     }
     double lifetime();
     double timeToResolution() {
         MOZ_ASSERT(state() != JS::PromiseState::Pending);
         return resolutionTime() - allocationTime();
     }
     MOZ_MUST_USE bool dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values);
     uint64_t getID();
     bool isUnhandled() {
         MOZ_ASSERT(state() == JS::PromiseState::Rejected);
-        return !(getFixedSlot(PromiseSlot_Flags).toInt32() & PROMISE_FLAG_HANDLED);
+        return !(getFixedSlot(PROMISE_FLAGS_SLOT).toInt32() & PROMISE_FLAG_HANDLED);
     }
     void markAsReported() {
         MOZ_ASSERT(isUnhandled());
-        int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
-        setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
+        int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
+        setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(flags | PROMISE_FLAG_REPORTED));
     }
 };
 
 /**
- * Enqueues resolve/reject reactions in the given Promise's reactions lists
- * in a content-invisible way.
- *
- * Used internally to implement DOM functionality.
- *
- * Note: the reactions pushed using this function contain a `promise` field
- * that can contain null. That field is only ever used by devtools, which have
- * to treat these reactions specially.
+ * Tells the embedding to enqueue a Promise reaction job, based on six
+ * parameters:
+ * reaction handler - The callback to invoke for this job.
+   argument - The first and only argument to pass to the handler.
+   resolve - The Promise cabability's resolve hook, called upon normal
+             completion of the handler.
+   reject -  The Promise cabability's reject hook, called if the handler
+             throws.
+   promise - The associated Promise, or null for some internal uses.
+   objectFromIncumbentGlobal - An object from the global that was the
+                               incumbent global when the Promise reaction job
+                               was created (not enqueued). Not the global
+                               itself because unwrapping that might unwrap an
+                               inner to an outer window, which we never want
+                               to happen.
  */
-MOZ_MUST_USE bool
-EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
-                        HandleObject dependentPromise,
-                        HandleValue onFulfilled, HandleValue onRejected);
+bool EnqueuePromiseReactionJob(JSContext* cx, HandleValue handler, HandleValue handlerArg,
+                               HandleObject resolve, HandleObject reject,
+                               HandleObject promise, HandleObject objectFromIncumbentGlobal);
 
-MOZ_MUST_USE JSObject*
-GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises);
-
-MOZ_MUST_USE JSObject*
-OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
-                    HandleValue onRejected);
+/**
+ * Tells the embedding to enqueue a Promise resolve thenable job, based on six
+ * 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.
+ */
+bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve,
+                                      HandleValue thenable, HandleValue then);
 
 /**
  * A PromiseTask represents a task that can be dispatched to a helper thread
  * (via StartPromiseTask), executed (by implementing PromiseTask::execute()),
  * and then resolved back on the original JSContext owner thread.
  * Because it contains a PersistentRooted, a PromiseTask will only be destroyed
  * on the JSContext's owner thread.
  */
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -1,16 +1,762 @@
 /* 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, fulfillHandler, rejectHandler,
+                               incumbentGlobal) {
+    this.promise = promise;
+    this.resolve = resolve;
+    this.reject = reject;
+    this.fulfillHandler = fulfillHandler;
+    this.rejectHandler = rejectHandler;
+    this.incumbentGlobal = incumbentGlobal;
+}
+
+MakeConstructible(PromiseReactionRecord, PromiseReactionRecordProto);
+
+// Used to verify that an object is a PromiseCapability record.
+var PromiseCapabilityRecordProto = {__proto__: null};
+
+// 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;
+    let reject;
+
+    // Steps 4-5.
+    // ES6, 25.4.1.5.1. Inlined here so we can use an upvar instead of a slot to
+    // store promiseCapability.
+    function GetCapabilitiesExecutor(resolve_, reject_) {
+        // Steps 1-2 (implicit).
+
+        // Steps 3-4.
+        if (resolve !== undefined || reject !== undefined)
+            ThrowTypeError(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
+        resolve = resolve_;
+        reject = reject_;
+    }
+
+    // Steps 6-7.
+    let promise = new C(GetCapabilitiesExecutor);
+
+    // Step 8.
+    if (!IsCallable(resolve))
+        ThrowTypeError(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
+
+    // Step 9.
+    if (!IsCallable(reject))
+        ThrowTypeError(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
+
+    // Steps 10-11.
+    return {
+        __proto__: PromiseCapabilityRecordProto,
+        promise,
+        resolve,
+        reject
+    };
+}
+
+// ES2016, 25.4.1.6, implemented in SelfHosting.cpp.
+
+// ES2016, 25.4.1.7, implemented in Promise.cpp.
+
+// ES2016, 25.4.1.8, implemented in Promise.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],
+                               argument,
+                               reaction.resolve,
+                               reaction.reject,
+                               reaction.promise,
+                               reaction.incumbentGlobal || null);
+}
+
+// ES6, 25.4.2.2. (Implemented in C++).
+
+// ES6, 25.4.3.1. (Implemented in C++).
+
+// 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");
+
+    // Step 3.
+    let promiseCapability = NewPromiseCapability(C);
+
+    // Steps 4-5.
+    let iterator;
+    try {
+        iterator = GetIterator(iterable);
+    } catch (e) {
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+
+    // Step 6.
+    let iteratorRecord = {__proto__: null, iterator, done: false};
+
+    // Steps 7-9.
+    try {
+        // Steps 7,9.
+        return PerformPromiseAll(iteratorRecord, C, promiseCapability);
+    } catch (e) {
+        // Step 8.a.
+        // TODO: implement iterator closing.
+
+        // Step 8.b.
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+}
+
+// ES6, 25.4.4.1.1.
+function PerformPromiseAll(iteratorRecord, constructor, resultCapability) {
+    // Step 1.
+    assert(IsConstructor(constructor), "PerformPromiseAll called with non-constructor");
+
+    // Step 2.
+    assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
+
+    // Step 3.
+    // Immediately create an Array instead of a List, so we can skip step 6.d.iii.1.
+    //
+    // We might be dealing with a wrapped instance from another Realm. In that
+    // case, we want to create the `values` array in that other Realm so if
+    // it's less-privileged than the current one, code in that Realm can still
+    // work with the array.
+    let values = IsPromise(resultCapability.promise) || !IsWrappedPromise(resultCapability.promise)
+                 ? []
+                 : NewArrayInCompartment(constructor);
+    let valuesCount = 0;
+
+    // Step 4.
+    let remainingElementsCount = {value: 1};
+
+    // Step 5.
+    let index = 0;
+
+    // Step 6.
+    let iterator = iteratorRecord.iterator;
+    let next;
+    let nextValue;
+    let allPromise = resultCapability.promise;
+    while (true) {
+        try {
+            // Step 6.a.
+            next = callContentFunction(iterator.next, iterator);
+            if (!IsObject(next))
+                ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+        } catch (e) {
+            // Step 6.b.
+            iteratorRecord.done = true;
+
+            // Step 6.c.
+            throw (e);
+        }
+
+        // Step 6.d.
+        if (next.done) {
+            // Step 6.d.i.
+            iteratorRecord.done = true;
+
+            // Step 6.d.ii.
+            remainingElementsCount.value--;
+            assert(remainingElementsCount.value >= 0,
+                   "remainingElementsCount mustn't be negative.");
+
+            // Step 6.d.iii.
+            if (remainingElementsCount.value === 0)
+                callContentFunction(resultCapability.resolve, undefined, values);
+
+            // Step 6.d.iv.
+            return allPromise;
+        }
+        try {
+            // Step 6.e.
+            nextValue = next.value;
+        } catch (e) {
+            // Step 6.f.
+            iteratorRecord.done = true;
+
+            // Step 6.g.
+            throw e;
+        }
+
+        // Step 6.h.
+        _DefineDataProperty(values, valuesCount++, undefined);
+
+        // Steps 6.i-j.
+        let nextPromise = callContentFunction(constructor.resolve, constructor, nextValue);
+
+        // Steps 6.k-p.
+        let resolveElement = CreatePromiseAllResolveElementFunction(index, values,
+                                                                    resultCapability,
+                                                                    remainingElementsCount);
+
+        // Step 6.q.
+        remainingElementsCount.value++;
+
+        // Steps 6.r-s.
+        BlockOnPromise(nextPromise, allPromise, resolveElement, resultCapability.reject);
+
+        // Step 6.t.
+        index++;
+    }
+}
+
+/**
+ * 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.
+ */
+function GetWaitForAllPromise(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;
+}
+
+// ES6, 25.4.4.1.2.
+function CreatePromiseAllResolveElementFunction(index, values, promiseCapability,
+                                                remainingElementsCount)
+{
+    var alreadyCalled = false;
+    return function PromiseAllResolveElementFunction(x) {
+        // Steps 1-2.
+        if (alreadyCalled)
+            return undefined;
+
+        // Step 3.
+        alreadyCalled = true;
+
+        // Steps 4-7 (implicit).
+
+        // Step 8.
+        // Note: this can't throw because the slot was initialized to `undefined` earlier.
+        values[index] = x;
+
+        // Step 9.
+        remainingElementsCount.value--;
+        assert(remainingElementsCount.value >= 0, "remainingElementsCount mustn't be negative.");
+
+        // Step 10.
+        if (remainingElementsCount.value === 0) {
+            // Step 10.a (implicit).
+
+            // Step 10.b.
+            return callContentFunction(promiseCapability.resolve, undefined, values);
+        }
+
+        // Step 11 (implicit).
+    };
+}
+
+// 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");
+
+    // step 3.
+    let promiseCapability = NewPromiseCapability(C);
+
+    // Steps 4-5.
+    let iterator;
+    try {
+        iterator = GetIterator(iterable);
+    } catch (e) {
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+
+    // Step 6.
+    let iteratorRecord = {__proto__: null, iterator, done: false};
+
+    // Steps 7-9.
+    try {
+        // Steps 7,9.
+        return PerformPromiseRace(iteratorRecord, promiseCapability, C);
+    } catch (e) {
+        // Step 8.a.
+        // TODO: implement iterator closing.
+
+        // Step 8.b.
+        callContentFunction(promiseCapability.reject, undefined, e);
+        return promiseCapability.promise;
+    }
+}
+
+// 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;
+    let nextValue;
+    while (true) {
+        try {
+            // Step 1.a.
+            next = callContentFunction(iterator.next, iterator);
+            if (!IsObject(next))
+                ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+        } catch (e) {
+            // Step 1.b.
+            iteratorRecord.done = true;
+
+            // Step 1.c.
+            throw (e);
+        }
+
+        // Step 1.d.
+        if (next.done) {
+            // Step 1.d.i.
+            iteratorRecord.done = true;
+
+            // Step 1.d.ii.
+            return racePromise;
+        }
+        try {
+            // Step 1.e.
+            nextValue = next.value;
+        } catch (e) {
+            // Step 1.f.
+            iteratorRecord.done = true;
+
+            // Step 1.g.
+            throw e;
+        }
+
+        // Step 1.h.
+        let nextPromise = callContentFunction(C.resolve, C, nextValue);
+
+        // Steps 1.i.
+        BlockOnPromise(nextPromise, racePromise, resultCapability.resolve,
+                       resultCapability.reject);
+    }
+    assert(false, "Shouldn't reach the end of PerformPromiseRace");
+}
+
+/**
+ * Calls |promise.then| with the provided hooks and adds |blockedPromise| to
+ * its list of dependent promises. Used by |Promise.all| and |Promise.race|.
+ *
+ * If |promise.then| is the original |Promise.prototype.then| function and
+ * the call to |promise.then| would use the original |Promise| constructor to
+ * create the resulting promise, this function skips the call to |promise.then|
+ * and thus creating a new promise that would not be observable by content.
+ */
+function BlockOnPromise(promise, blockedPromise, onResolve, onReject) {
+    let then = promise.then;
+
+    // By default, the blocked promise is added as an extra entry to the
+    // rejected promises list.
+    let addToDependent = true;
+    if (then === Promise_then && IsObject(promise) && IsPromise(promise)) {
+        // |then| is the original |Promise.prototype.then|, inline it here.
+        // 25.4.5.3., steps 3-4.
+        let PromiseCtor = GetBuiltinConstructor('Promise');
+        let C = SpeciesConstructor(promise, PromiseCtor);
+        let resultCapability;
+
+        if (C === PromiseCtor) {
+            resultCapability = {
+                __proto__: PromiseCapabilityRecordProto,
+                promise: blockedPromise,
+                reject: NullFunction,
+                resolve: NullFunction
+            };
+            addToDependent = false;
+        } else {
+            // 25.4.5.3., steps 5-6.
+            resultCapability = NewPromiseCapability(C);
+        }
+
+        // 25.4.5.3., step 7.
+        PerformPromiseThen(promise, onResolve, onReject, resultCapability);
+    } else {
+        // Optimization failed, do the normal call.
+        callContentFunction(then, promise, onResolve, onReject);
+    }
+    if (!addToDependent)
+        return;
+
+    // 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.
+    // 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(AddDependentPromise, promise, blockedPromise);
+
+    if (IsWrappedPromise(promise))
+        callFunction(CallPromiseMethodIfWrapped, promise, blockedPromise, "AddDependentPromise");
+}
+
+/**
+ * Invoked with a Promise as the receiver, AddDependentPromise adds an entry
+ * to the reactions list.
+ *
+ * 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 AddDependentPromise(dependentPromise) {
+    assert(IsPromise(this), "AddDependentPromise expects an unwrapped Promise as the receiver");
+
+    if (GetPromiseState(this) !== PROMISE_STATE_PENDING)
+        return;
+
+    let reaction = new PromiseReactionRecord(dependentPromise, NullFunction, NullFunction,
+                                             NullFunction, NullFunction, null);
+
+    let reactions = UnsafeGetReservedSlot(this, PROMISE_REACTIONS_OR_RESULT_SLOT);
+
+    // The reactions list might not have been allocated yet.
+    if (!reactions)
+        UnsafeSetReservedSlot(dependentPromise, PROMISE_REACTIONS_OR_RESULT_SLOT, [reaction]);
+    else
+        _DefineDataProperty(reactions, reactions.length, reaction);
+}
+
+// ES2016, 25.4.4.4 (implemented in C++).
+
+// ES2016, 25.4.4.5 (implemented in C++).
+
 // 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) {
     // Steps 1-2.
     return callContentFunction(this.then, this, undefined, onRejected);
 }
+
+// ES6, 25.4.5.3.
+function Promise_then(onFulfilled, onRejected) {
+    // Step 1.
+    let promise = this;
+
+    // Step 2.
+    if (!IsObject(promise))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.prototype.then call");
+
+    let isPromise = IsPromise(promise);
+    let isWrappedPromise = isPromise ? false : IsWrappedPromise(promise);
+    if (!(isPromise || isWrappedPromise))
+        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Promise", "then", "value");
+
+    // Steps 3-4.
+    let C = SpeciesConstructor(promise, GetBuiltinConstructor('Promise'));
+
+    // Steps 5-6.
+    let resultCapability = NewPromiseCapability(C);
+
+    // Step 7.
+    if (isWrappedPromise) {
+        // See comment above GetPromiseHandlerForwarders for why this is needed.
+        let handlerForwarders = GetPromiseHandlerForwarders(onFulfilled, onRejected);
+        return callFunction(CallPromiseMethodIfWrapped, promise,
+                            handlerForwarders[0], handlerForwarders[1],
+                            resultCapability.promise, resultCapability.resolve,
+                            resultCapability.reject, "UnwrappedPerformPromiseThen");
+    }
+    return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability);
+}
+
+/**
+ * Enqueues resolve/reject reactions in the given Promise's reactions lists
+ * in a content-invisible way.
+ *
+ * Used internally to implement DOM functionality.
+ *
+ * Note: the reactions pushed using this function contain a `promise` field
+ * that can contain null. That field is only ever used by devtools, which have
+ * to treat these reactions specially.
+ */
+function EnqueuePromiseReactions(promise, dependentPromise, onFulfilled, onRejected) {
+    let isWrappedPromise = false;
+    if (!IsPromise(promise)) {
+        assert(IsWrappedPromise(promise),
+               "EnqueuePromiseReactions must be provided with a possibly wrapped promise");
+        isWrappedPromise = true;
+    }
+
+    assert(dependentPromise === null || IsPromise(dependentPromise),
+           "EnqueuePromiseReactions's dependentPromise argument must be a Promise or null");
+
+    if (isWrappedPromise) {
+        // See comment above GetPromiseHandlerForwarders for why this is needed.
+        let handlerForwarders = GetPromiseHandlerForwarders(onFulfilled, onRejected);
+        return callFunction(CallPromiseMethodIfWrapped, promise, handlerForwarders[0],
+                            handlerForwarders[1], dependentPromise, NullFunction, NullFunction,
+                            "UnwrappedPerformPromiseThen");
+    }
+    let capability = {
+        __proto__: PromiseCapabilityRecordProto,
+        promise: dependentPromise,
+        resolve: NullFunction,
+        reject: NullFunction
+    };
+    return PerformPromiseThen(promise, onFulfilled, onRejected, capability);
+}
+
+/**
+ * Returns a set of functions that are (1) self-hosted, and (2) exact
+ * forwarders of the passed-in functions, for use by
+ * UnwrappedPerformPromiseThen.
+ *
+ * When calling `then` on an xray-wrapped promise, the receiver isn't
+ * unwrapped. Instead, Promise_then operates on the wrapped Promise. Just
+ * calling PerformPromiseThen from Promise_then as we normally would doesn't
+ * work in this case: PerformPromiseThen can only deal with unwrapped
+ * Promises. Instead, we use the CallPromiseMethodIfWrapped intrinsic to
+ * switch compartments before calling PerformPromiseThen, via
+ * UnwrappedPerformPromiseThen.
+ *
+ * This is almost enough, but there's an additional wrinkle: when calling the
+ * fulfillment and rejection handlers, we might pass in Object-type arguments
+ * from within the xray-ed, lower-privileged compartment. By default, this
+ * doesn't work, because they're wrapped into wrappers that disallow passing
+ * in Object-typed arguments (so the higher-privileged code doesn't
+ * accidentally operate on objects assuming they're higher-privileged, too.)
+ * So instead UnwrappedPerformPromiseThen adds another level of indirection:
+ * it closes over the, by now cross-compartment-wrapped, handler forwarders
+ * created by GetPromiseHandlerForwarders and creates a second set of
+ * forwarders around them, which use UnsafeCallWrappedFunction to call the
+ * initial forwarders.
+
+ * Note that both above-mentioned guarantees are required: while it may seem
+ * as though the original handlers would always be wrappers once they reach
+ * UnwrappedPerformPromiseThen (because the call to `then` originated in the
+ * higher-privileged compartment, and after unwrapping we end up in the
+ * lower-privileged one), that's not necessarily the case. One or both of the
+ * handlers can originate from the lower-privileged compartment, so they'd
+ * actually be unwrapped functions when they reach
+ * UnwrappedPerformPromiseThen.
+ */
+function GetPromiseHandlerForwarders(fulfilledHandler, rejectedHandler) {
+    // If non-callable values are passed, we have to preserve them so steps
+    // 3 and 4 of PerformPromiseThen work as expected.
+    return [
+        IsCallable(fulfilledHandler) ? function onFulfilled(argument) {
+            return callContentFunction(fulfilledHandler, this, argument);
+        } : fulfilledHandler,
+        IsCallable(rejectedHandler) ? function onRejected(argument) {
+            return callContentFunction(rejectedHandler, this, argument);
+        } : rejectedHandler
+    ];
+}
+
+/**
+ * Forwarder used to invoke PerformPromiseThen on an unwrapped Promise, while
+ * wrapping the resolve/reject callbacks into functions that invoke them in
+ * their original compartment. See the comment for GetPromiseHandlerForwarders
+ * for details.
+ */
+function UnwrappedPerformPromiseThen(fulfilledHandler, rejectedHandler, promise, resolve, reject) {
+    let resultCapability = {
+        __proto__: PromiseCapabilityRecordProto,
+        promise,
+        resolve(resolution) {
+            // Under some circumstances, we have an unwrapped `resolve`
+            // function here. One way this happens is if the constructor
+            // passed to `NewPromiseCapability` is from the same global as the
+            // Promise object on which `Promise_then` was called, but where
+            // `Promise_then` is from a different global, so we end up here.
+            // In that case, the `resolve` and `reject` functions aren't
+            // wrappers in the current global.
+            if (IsFunctionObject(resolve))
+                return resolve(resolution);
+            return UnsafeCallWrappedFunction(resolve, undefined, resolution);
+        },
+        reject(reason) {
+            // See comment inside `resolve` above.
+            if (IsFunctionObject(reject))
+                return reject(reason);
+            return UnsafeCallWrappedFunction(reject, undefined, reason);
+        }
+    };
+    function onFulfilled(argument) {
+        return UnsafeCallWrappedFunction(fulfilledHandler, undefined, argument);
+    }
+    function onRejected(argument) {
+        return UnsafeCallWrappedFunction(rejectedHandler, undefined, argument);
+    }
+    return PerformPromiseThen(this, IsCallable(fulfilledHandler) ? onFulfilled : fulfilledHandler,
+                              IsCallable(rejectedHandler) ? onRejected : rejectedHandler,
+                              resultCapability);
+}
+
+// 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.
+    if (!IsCallable(onFulfilled))
+        onFulfilled = PROMISE_HANDLER_IDENTITY;
+
+    // Step 4.
+    if (!IsCallable(onRejected))
+        onRejected = PROMISE_HANDLER_THROWER;
+
+    let incumbentGlobal = _GetObjectFromIncumbentGlobal();
+    // 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);
+    let flags = UnsafeGetInt32FromReservedSlot(promise, PROMISE_FLAGS_SLOT);
+    if (state === PROMISE_STATE_PENDING) {
+        // Steps 7.a,b.
+        // We only have a single list for fulfill and reject reactions.
+        let reactions = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT);
+        if (!reactions)
+            UnsafeSetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT, [reaction]);
+        else
+            _DefineDataProperty(reactions, reactions.length, reaction);
+    }
+
+    // Step 8.
+    else if (state === PROMISE_STATE_FULFILLED) {
+        // Step 8.a.
+        let value = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT);
+
+        // Step 8.b.
+        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.
+        let reason = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_OR_RESULT_SLOT);
+
+        // Step 9.c.
+        if (!(flags & PROMISE_FLAG_HANDLED))
+            HostPromiseRejectionTracker(promise, PROMISE_REJECTION_TRACKER_OPERATION_HANDLE);
+
+        // Step 9.d.
+        EnqueuePromiseReactionJob(reaction, PROMISE_JOB_TYPE_REJECT, reason);
+    }
+
+    // Step 10.
+    UnsafeSetReservedSlot(promise, PROMISE_FLAGS_SLOT, flags | PROMISE_FLAG_HANDLED);
+
+    // Step 11.
+    return resultCapability.promise;
+}
+
+/// Utility functions below.
+function IsPromiseReaction(record) {
+    return std_Reflect_getPrototypeOf(record) === PromiseReactionRecordProto;
+}
+
+function IsPromiseCapability(capability) {
+    return std_Reflect_getPrototypeOf(capability) === PromiseCapabilityRecordProto;
+}
+
+function GetPromiseState(promise) {
+    let flags = UnsafeGetInt32FromReservedSlot(promise, PROMISE_FLAGS_SLOT);
+    if (!(flags & PROMISE_FLAG_RESOLVED)) {
+        assert(!(flags & PROMISE_STATE_FULFILLED), "Fulfilled promises are resolved, too");
+        return PROMISE_STATE_PENDING;
+    }
+    if (flags & PROMISE_FLAG_FULFILLED)
+        return PROMISE_STATE_FULFILLED;
+    return PROMISE_STATE_REJECTED;
+}
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -73,16 +73,43 @@
 #define ITERATOR_SLOT_ITEM_KIND 2
 // Used for ListIterator.
 #define ITERATOR_SLOT_NEXT_METHOD 2
 
 #define ITEM_KIND_KEY 0
 #define ITEM_KIND_VALUE 1
 #define ITEM_KIND_KEY_AND_VALUE 2
 
+#define PROMISE_FLAGS_SLOT               0
+#define PROMISE_REACTIONS_OR_RESULT_SLOT 1
+#define PROMISE_RESOLVE_FUNCTION_SLOT    2
+#define PROMISE_ALLOCATION_SITE_SLOT     3
+#define PROMISE_RESOLUTION_SITE_SLOT     4
+#define PROMISE_ALLOCATION_TIME_SLOT     5
+#define PROMISE_RESOLUTION_TIME_SLOT     6
+#define PROMISE_ID_SLOT                  7
+
+#define PROMISE_STATE_PENDING   0
+#define PROMISE_STATE_FULFILLED 1
+#define PROMISE_STATE_REJECTED  2
+
+#define PROMISE_FLAG_RESOLVED  0x1
+#define PROMISE_FLAG_FULFILLED 0x2
+#define PROMISE_FLAG_HANDLED   0x4
+#define PROMISE_FLAG_REPORTED  0x8
+
+#define PROMISE_HANDLER_IDENTITY 0
+#define PROMISE_HANDLER_THROWER  1
+
+#define PROMISE_REJECTION_TRACKER_OPERATION_REJECT false
+#define PROMISE_REJECTION_TRACKER_OPERATION_HANDLE true
+
+#define PROMISE_JOB_TYPE_FULFILL "fulfillHandler"
+#define PROMISE_JOB_TYPE_REJECT  "rejectHandler"
+
 // NB: keep these in sync with the copy in jsfriendapi.h.
 #define JSITER_OWNONLY    0x8   /* iterate over obj's own properties only */
 #define JSITER_HIDDEN     0x10  /* also enumerate non-enumerable properties */
 #define JSITER_SYMBOLS    0x20  /* also include symbol property keys */
 #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */
 
 
 #define REGEXP_FLAGS_SLOT 2
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1446,52 +1446,25 @@ SettlePromiseNow(JSContext* cx, unsigned
     if (!args.requireAtLeast(cx, "settlePromiseNow", 1))
         return false;
     if (!args[0].isObject() || !args[0].toObject().is<PromiseObject>()) {
         JS_ReportErrorASCII(cx, "first argument must be a Promise object");
         return false;
     }
 
     RootedNativeObject promise(cx, &args[0].toObject().as<NativeObject>());
-    int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
-    promise->setFixedSlot(PromiseSlot_Flags,
+    int32_t flags = promise->getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
+    promise->setFixedSlot(PROMISE_FLAGS_SLOT,
                           Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
-    promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
+    promise->setFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT, UndefinedValue());
 
     JS::dbg::onPromiseSettled(cx, promise);
     return true;
 }
 
-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() || !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;
-
-    for (uint32_t i = 0; i < count; i++)
-        promises[i].set(&list->getDenseElement(i).toObject());
-
-    RootedObject resultPromise(cx, JS::GetWaitForAllPromise(cx, promises));
-    if (!resultPromise)
-        return false;
-
-    args.rval().set(ObjectValue(*resultPromise));
-    return true;
-}
-
 #else
 
 static const js::Class FakePromiseClass = {
     "Promise", JSCLASS_IS_ANONYMOUS
 };
 
 static bool
 MakeFakePromise(JSContext* cx, unsigned argc, Value* vp)
@@ -4133,20 +4106,16 @@ static const JSFunctionSpecWithHelp Test
 
 #ifdef SPIDERMONKEY_PROMISE
     JS_FN_HELP("settlePromiseNow", SettlePromiseNow, 1, 0,
 "settlePromiseNow(promise)",
 "  'Settle' a 'promise' immediately. This just marks the promise as resolved\n"
 "  with a value of `undefined` and causes the firing of any onPromiseSettled\n"
 "  hooks set on Debugger instances that are observing the given promise's\n"
 "  global as a debuggee."),
-    JS_FN_HELP("getWaitForAllPromise", GetWaitForAllPromise, 1, 0,
-"getWaitForAllPromise(densePromisesArray)",
-"  Calls the 'GetWaitForAllPromise' JSAPI function and returns the result\n"
-"  Promise."),
 #else
     JS_FN_HELP("makeFakePromise", MakeFakePromise, 0, 0,
 "makeFakePromise()",
 "  Create an object whose [[Class]] name is 'Promise' and call\n"
 "  JS::dbg::onNewPromise on it before returning it. It doesn't actually have\n"
 "  any of the other behavior associated with promises."),
 
     JS_FN_HELP("settleFakePromise", SettleFakePromise, 1, 0,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4787,66 +4787,107 @@ JS::RejectPromise(JSContext* cx, JS::Han
     CHECK_REQUEST(cx);
     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 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(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);
-    CHECK_REQUEST(cx);
-    assertSameCompartment(cx, promiseObj, onResolvedObj, onRejectedObj);
-
-    MOZ_ASSERT(IsCallable(onResolvedObj));
-    MOZ_ASSERT(IsCallable(onRejectedObj));
-
-    Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
-    RootedValue onResolved(cx, ObjectValue(*onResolvedObj));
-    RootedValue onRejected(cx, ObjectValue(*onRejectedObj));
-    return EnqueuePromiseReactions(cx, promise, nullptr, onResolved, onRejected);
-}
-
-/**
- * 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 that the array is dense and all entries are Promise objects.
- */
+JS::CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promise,
+                            JS::HandleObject onResolve, JS::HandleObject onReject)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, promise, onResolve, onReject);
+
+    MOZ_ASSERT(promise->is<PromiseObject>());
+    MOZ_ASSERT(onResolve == nullptr || IsCallable(onResolve));
+    MOZ_ASSERT(onReject == nullptr || IsCallable(onReject));
+
+    JSObject* obj;
+    {
+        FixedInvokeArgs<2> args(cx);
+
+        args[0].setObjectOrNull(onResolve);
+        args[1].setObjectOrNull(onReject);
+
+        RootedValue thisvOrRval(cx, ObjectValue(*promise));
+        if (!CallSelfHostedFunction(cx, "Promise_then", thisvOrRval, args, &thisvOrRval))
+            return nullptr;
+
+        MOZ_ASSERT(thisvOrRval.isObject());
+        obj = &thisvOrRval.toObject();
+    }
+
+    MOZ_ASSERT(obj->is<PromiseObject>());
+    return obj;
+}
+
+JS_PUBLIC_API(bool)
+JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promise,
+                        JS::HandleObject onResolve, JS::HandleObject onReject)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, promise, onResolve, onReject);
+
+    MOZ_ASSERT(promise->is<PromiseObject>());
+    MOZ_ASSERT(IsCallable(onResolve));
+    MOZ_ASSERT(IsCallable(onReject));
+
+    FixedInvokeArgs<4> args(cx);
+
+    args[0].setObject(*promise);
+    args[1].setNull();
+    args[2].setObject(*onResolve);
+    args[3].setObject(*onReject);
+
+    RootedValue dummy(cx);
+    return CallSelfHostedFunction(cx, "EnqueuePromiseReactions", UndefinedHandleValue, args,
+                                  &dummy);
+}
+
 JS_PUBLIC_API(JSObject*)
 JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
-    return js::GetWaitForAllPromise(cx, promises);
+    RootedArrayObject arr(cx, NewDenseFullyAllocatedArray(cx, promises.length()));
+    if (!arr)
+        return nullptr;
+    arr->ensureDenseInitializedLength(cx, 0, promises.length());
+    for (size_t i = 0, len = promises.length(); i < len; i++) {
+#ifdef DEBUG
+        JSObject* obj = promises[i];
+        assertSameCompartment(cx, obj);
+        if (IsWrapper(obj))
+            obj = UncheckedUnwrap(obj);
+        MOZ_ASSERT(obj->is<PromiseObject>());
+#endif
+        arr->setDenseElement(i, ObjectValue(*promises[i]));
+    }
+
+    JSObject* obj;
+    {
+        FixedInvokeArgs<1> args(cx);
+
+        args[0].setObject(*arr);
+
+        RootedValue thisvOrRval(cx, UndefinedValue());
+        if (!CallSelfHostedFunction(cx, "GetWaitForAllPromise", thisvOrRval, args, &thisvOrRval))
+            return nullptr;
+
+        MOZ_ASSERT(thisvOrRval.isObject());
+        obj = &thisvOrRval.toObject();
+    }
+
+    MOZ_ASSERT(obj->is<PromiseObject>());
+    return obj;
 }
 
 JS_PUBLIC_API(void)
 JS::SetAsyncTaskCallbacks(JSContext* cx, JS::StartAsyncTaskCallback start,
                           JS::FinishAsyncTaskCallback finish)
 {
     cx->startAsyncTaskCallback = start;
     cx->finishAsyncTaskCallback = finish;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1969,57 +1969,16 @@ js::GetBuiltinPrototype(ExclusiveContext
 bool
 js::IsStandardPrototype(JSObject* obj, JSProtoKey key)
 {
     GlobalObject& global = obj->global();
     Value v = global.getPrototype(key);
     return v.isObject() && obj == &v.toObject();
 }
 
-
-/**
- * Returns the original Object.prototype from 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.
- *
- * The reason for not just returning the global itself is that we'd need to
- * wrap it into the current compartment, and later unwrap it. Unwrapping
- * globals is tricky, though: we might accidentally unwrap through an inner
- * to its outer window and end up with the wrong global. Plain objects don't
- * have this problem, so we use the global's Object.prototype. The code using
- * it - e.g. EnqueuePromiseReactionJob - can then unwrap the object and get
- * its global without fear of unwrapping too far.
- */
-bool
-js::GetObjectFromIncumbentGlobal(JSContext* cx, MutableHandleObject obj)
-{
-    RootedObject globalObj(cx, cx->runtime()->getIncumbentGlobal(cx));
-    if (!globalObj) {
-        obj.set(nullptr);
-        return true;
-    }
-
-    {
-        AutoCompartment ac(cx, globalObj);
-        obj.set(globalObj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
-        if (!obj)
-            return false;
-    }
-
-    // The object might be from a different compartment, so wrap it.
-    if (obj && !cx->compartment()->wrap(cx, obj))
-        return false;
-
-    return true;
-}
-
 JSProtoKey
 JS::IdentifyStandardInstance(JSObject* obj)
 {
     // Note: The prototype shares its JSClass with instances.
     MOZ_ASSERT(!obj->is<CrossCompartmentWrapperObject>());
     JSProtoKey key = StandardProtoKeyOrNull(obj);
     if (key != JSProto_Null && !IsStandardPrototype(obj, key))
         return key;
@@ -3939,26 +3898,16 @@ js::SpeciesConstructor(JSContext* cx, Ha
     if (!Call(cx, func, UndefinedHandleValue, args, pctor))
         return false;
 
     pctor.set(args.rval());
     return true;
 }
 
 bool
-js::SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey,
-                       MutableHandleValue pctor)
-{
-    if (!GlobalObject::ensureConstructor(cx, cx->global(), ctorKey))
-        return false;
-    RootedValue defaultCtor(cx, cx->global()->getConstructor(ctorKey));
-    return SpeciesConstructor(cx, obj, defaultCtor, pctor);
-}
-
-bool
 js::Unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::boxedValue_unbox(cx, obj, vp);
 
     if (obj->is<BooleanObject>())
         vp.setBoolean(obj->as<BooleanObject>().unbox());
     else if (obj->is<NumberObject>())
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1370,17 +1370,11 @@ FreezeObject(JSContext* cx, HandleObject
  * Object.isFrozen.
  */
 extern bool
 TestIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level, bool* resultp);
 
 extern bool
 SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor, MutableHandleValue pctor);
 
-extern bool
-SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey, MutableHandleValue pctor);
-
-extern bool
-GetObjectFromIncumbentGlobal(JSContext* cx, MutableHandleObject obj);
-
 }  /* namespace js */
 
 #endif /* jsobj_h */
--- a/js/src/tests/ecma_6/Promise/get-wait-for-all-promise.js
+++ b/js/src/tests/ecma_6/Promise/get-wait-for-all-promise.js
@@ -1,65 +1,67 @@
 // |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue
 
 if (!this.Promise) {
     this.reportCompare && reportCompare(true,true);
     quit(0);
 }
 
+let GetWaitForAllPromise = getSelfHostedValue('GetWaitForAllPromise');
+
 function onResolved(val) {
     result = 'resolved with ' + val;
 }
 
 function onRejected(val) {
     result = 'rejected with ' + val;
 }
 
-// Replacing `Promise#then` shouldn't affect getWaitForAllPromise.
+// Replacing `Promise#then` shouldn't affect GetWaitForAllPromise.
 let originalThen = Promise.prototype.then;
 Promise.prototype.then = 1;
 
-// Replacing Promise[@@species] shouldn't affect getWaitForAllPromise.
+// Replacing Promise[@@species] shouldn't affect GetWaitForAllPromise.
 Promise[Symbol.species] = function(){};
 
-// Replacing `Promise` shouldn't affect getWaitForAllPromise.
+// Replacing `Promise` shouldn't affect GetWaitForAllPromise.
 let PromiseCtor = Promise;
 Promise = {};
 
-// Replacing Array[@@iterator] shouldn't affect getWaitForAllPromise.
+// Replacing Array[@@iterator] shouldn't affect GetWaitForAllPromise.
 Array.prototype[Symbol.iterator] = function(){};
 
 let resolveFunctions = [];
 let rejectFunctions = [];
 let promises = [];
 for (let i = 0; i < 3; i++) {
     let p = new PromiseCtor(function(res_, rej_) {
         resolveFunctions.push(res_);
         rejectFunctions.push(rej_);
     });
     promises.push(p);
 }
 
-let allPromise = getWaitForAllPromise(promises);
+let allPromise = GetWaitForAllPromise(promises);
 let then = originalThen.call(allPromise, onResolved, onRejected);
 
 resolveFunctions.forEach((fun, i)=>fun(i));
 drainJobQueue();
 
 assertEq(result, 'resolved with 0,1,2');
 
 // Empty lists result in a promise resolved with an empty array.
 result = undefined;
-originalThen.call(getWaitForAllPromise([]), v=>(result = v));
+originalThen.call(GetWaitForAllPromise([]), v=>(result = v));
 drainJobQueue();
 assertEq(result instanceof Array, true);
 assertEq(result.length, 0);
 
 //Empty lists result in a promise resolved with an empty array.
 result = undefined;
-originalThen.call(getWaitForAllPromise([]), v=>(result = v));
+originalThen.call(GetWaitForAllPromise([]), v=>(result = v));
 
 drainJobQueue();
 
 assertEq(result instanceof Array, true);
 assertEq(result.length, 0);
 
 this.reportCompare && reportCompare(true,true);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -116,16 +116,17 @@
     macro(float64, float64, "float64") \
     macro(Float64x2, Float64x2, "Float64x2") \
     macro(forceInterpreter, forceInterpreter, "forceInterpreter") \
     macro(forEach, forEach, "forEach") \
     macro(format, format, "format") \
     macro(frame, frame, "frame") \
     macro(from, from, "from") \
     macro(fulfilled, fulfilled, "fulfilled") \
+    macro(fulfillHandler, fulfillHandler, "fulfillHandler") \
     macro(futexNotEqual, futexNotEqual, "not-equal") \
     macro(futexOK, futexOK, "ok") \
     macro(futexTimedOut, futexTimedOut, "timed-out") \
     macro(gcCycleNumber, gcCycleNumber, "gcCycleNumber") \
     macro(Generator, Generator, "Generator") \
     macro(GeneratorFunction, GeneratorFunction, "GeneratorFunction") \
     macro(get, get, "get") \
     macro(getInternals, getInternals, "getInternals") \
@@ -248,16 +249,17 @@
     macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
     macro(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \
     macro(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \
     macro(RegExpTester, RegExpTester, "RegExpTester") \
     macro(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
     macro(Reify, Reify, "Reify") \
     macro(reject, reject, "reject") \
     macro(rejected, rejected, "rejected") \
+    macro(rejectHandler, rejectHandler, "rejectHandler") \
     macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \
     macro(resolve, resolve, "resolve") \
     macro(resumeGenerator, resumeGenerator, "resumeGenerator") \
     macro(return, return_, "return") \
     macro(revoke, revoke, "revoke") \
     macro(script, script, "script") \
     macro(scripts, scripts, "scripts") \
     macro(second, second, "second") \
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -728,18 +728,17 @@ JSRuntime::enqueuePromiseJob(JSContext* 
     RootedObject allocationSite(cx);
     if (promise) {
         RootedObject unwrappedPromise(cx, promise);
         // While the job object is guaranteed to be unwrapped, the promise
         // might be wrapped. See the comments in
         // intrinsic_EnqueuePromiseReactionJob for details.
         if (IsWrapper(promise))
             unwrappedPromise = UncheckedUnwrap(promise);
-        if (unwrappedPromise->is<PromiseObject>())
-            allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
+        allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
     }
     return cx->runtime()->enqueuePromiseJobCallback(cx, job, allocationSite, incumbentGlobal, data);
 }
 
 void
 JSRuntime::addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise)
 {
     MOZ_ASSERT(promise->is<PromiseObject>());
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -22,16 +22,17 @@
 #include "jsweakmap.h"
 #include "jswrapper.h"
 #include "selfhosted.out.h"
 
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/Object.h"
+#include "builtin/Promise.h"
 #include "builtin/Reflect.h"
 #include "builtin/SelfHostingDefines.h"
 #include "builtin/SIMD.h"
 #include "builtin/TypedObject.h"
 #include "builtin/WeakSetObject.h"
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "jit/AtomicOperations.h"
@@ -172,16 +173,69 @@ intrinsic_IsCallable(JSContext* cx, unsi
 static bool
 intrinsic_IsConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(IsConstructor(args[0]));
     return true;
 }
 
+/**
+ * Intrinsic for calling a wrapped self-hosted function without invoking the
+ * wrapper's security checks.
+ *
+ * Takes a wrapped function as the first and the receiver object as the
+ * second argument. Any additional arguments are passed on to the unwrapped
+ * function.
+ *
+ * Xray wrappers prevent lower-privileged code from passing objects to wrapped
+ * functions from higher-privileged realms. In some cases, this check is too
+ * strict, so this intrinsic allows getting around it.
+ *
+ * Note that it's not possible to replace all usages with dedicated intrinsics
+ * as the function in question might be an inner function that closes over
+ * state relevant to its execution.
+ *
+ * Right now, this is used for the Promise implementation to enable creating
+ * resolution functions for xrayed Promises in the privileged realm and then
+ * creating the Promise instance in the non-privileged one. The callbacks have
+ * to be called by non-privileged code in various places, in many cases
+ * passing objects as arguments.
+ */
+static bool
+intrinsic_UnsafeCallWrappedFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() >= 2);
+    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>().isSelfHostedOrIntrinsic());
+
+    InvokeArgs args2(cx);
+    if (!args2.init(cx, args.length() - 2))
+        return false;
+
+    args2.setThis(args[1]);
+
+    for (size_t i = 0; i < args2.length(); i++)
+        args2[i].set(args[i + 2]);
+
+    AutoWaivePolicy waivePolicy(cx, wrappedFun, JSID_VOIDHANDLE, BaseProxyHandler::CALL);
+    if (!CrossCompartmentWrapper::singleton.call(cx, wrappedFun, args2))
+        return false;
+    args.rval().set(args2.rval());
+    return true;
+}
+
 template<typename T>
 static bool
 intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     MOZ_ASSERT(args[0].isObject());
 
@@ -1826,16 +1880,64 @@ js::ReportIncompatibleSelfHostedMethod(J
         }
         ++iter;
     }
 
     MOZ_ASSERT_UNREACHABLE("How did we not find a useful self-hosted frame?");
     return false;
 }
 
+static bool
+intrinsic_EnqueuePromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 6);
+
+    RootedValue handler(cx, args[0]);
+    MOZ_ASSERT((handler.isNumber() &&
+                (handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
+                 handler.toNumber() == PROMISE_HANDLER_THROWER)) ||
+               handler.toObject().isCallable());
+
+    RootedValue handlerArg(cx, args[1]);
+
+    RootedObject resolve(cx, &args[2].toObject());
+    MOZ_ASSERT(IsCallable(resolve));
+
+    RootedObject reject(cx, &args[3].toObject());
+    MOZ_ASSERT(IsCallable(reject));
+
+    RootedObject promise(cx, args[4].toObjectOrNull());
+    RootedObject objectFromIncumbentGlobal(cx, args[5].toObjectOrNull());
+
+    if (!EnqueuePromiseReactionJob(cx, handler, handlerArg, resolve, reject, promise,
+                                   objectFromIncumbentGlobal))
+    {
+        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>());
+
+    Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
+    mozilla::DebugOnly<bool> isHandled = args[1].toBoolean();
+    MOZ_ASSERT(isHandled, "HostPromiseRejectionTracker intrinsic currently only marks as handled");
+    cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
+    args.rval().setUndefined();
+    return true;
+}
+
 /**
  * Returns the default locale as a well-formed, but not necessarily canonicalized,
  * BCP-47 language tag.
  */
 static bool
 intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -1952,16 +2054,71 @@ intrinsic_NameForTypedArray(JSContext* c
 
     JSProtoKey protoKey = StandardProtoKeyOrNull(object);
     MOZ_ASSERT(protoKey);
 
     args.rval().setString(ClassName(protoKey, cx));
     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.
+ *
+ * The reason for not just returning the global itself is that we'd need to
+ * wrap it into the current compartment, and later unwrap it. Unwrapping
+ * globals is tricky, though: we might accidentally unwrap through an inner
+ * to its outer window and end up with the wrong global. Plain objects don't
+ * have this problem, so we create one and return it. The code using it -
+ * e.g. EnqueuePromiseReactionJob - can then unwrap the object and get its
+ * global without fear of unwrapping too far.
+ */
+static bool
+intrinsic_GetObjectFromIncumbentGlobal(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    RootedObject obj(cx);
+    RootedObject global(cx, cx->runtime()->getIncumbentGlobal(cx));
+    if (global) {
+        MOZ_ASSERT(global->is<GlobalObject>());
+        AutoCompartment ac(cx, global);
+        obj = NewBuiltinClassInstance<PlainObject>(cx);
+        if (!obj)
+            return false;
+    }
+
+    RootedValue objVal(cx, ObjectOrNullValue(obj));
+
+    // The object might be from a different compartment, so wrap it.
+    if (obj && !cx->compartment()->wrap(cx, &objVal))
+        return false;
+
+    args.rval().set(objVal);
+    return true;
+}
+
+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");
+    args.rval().setBoolean(CheckedUnwrap(obj)->is<PromiseObject>());
+    return true;
+}
+
 static bool
 intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(args[0].toObject().is<ModuleObject>());
     MOZ_ASSERT(args[1].isString());
 
@@ -2246,16 +2403,17 @@ static const JSFunctionSpec intrinsic_fu
                     IntrinsicUnsafeGetObjectFromReservedSlot),
     JS_INLINABLE_FN("UnsafeGetInt32FromReservedSlot",   intrinsic_UnsafeGetInt32FromReservedSlot,  2,0,
                     IntrinsicUnsafeGetInt32FromReservedSlot),
     JS_INLINABLE_FN("UnsafeGetStringFromReservedSlot",  intrinsic_UnsafeGetStringFromReservedSlot, 2,0,
                     IntrinsicUnsafeGetStringFromReservedSlot),
     JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0,
                     IntrinsicUnsafeGetBooleanFromReservedSlot),
 
+    JS_FN("UnsafeCallWrappedFunction", intrinsic_UnsafeCallWrappedFunction,2,0),
     JS_FN("NewArrayInCompartment",   intrinsic_NewArrayInCompartment,   1,0),
 
     JS_FN("IsPackedArray",           intrinsic_IsPackedArray,           1,0),
 
     JS_FN("GetIteratorPrototype",    intrinsic_GetIteratorPrototype,    0,0),
 
     JS_FN("NewArrayIterator",        intrinsic_NewArrayIterator,        0,0),
     JS_FN("CallArrayIteratorMethodIfWrapped",
@@ -2358,16 +2516,24 @@ static const JSFunctionSpec intrinsic_fu
           CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0),
     JS_FN("CallStarGeneratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0),
 
     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("HostPromiseRejectionTracker",    intrinsic_HostPromiseRejectionTracker,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),
     JS_FN("AttachTypedObject",              js::AttachTypedObject, 3, 0),
     JS_FN("TypedObjectIsAttached",          js::TypedObjectIsAttached, 1, 0),
     JS_FN("TypedObjectTypeDescr",           js::TypedObjectTypeDescr, 1, 0),