Bug 1313049 - Port Promise-related functions from self-hosted JS to C++. r=arai, f=bz
authorTill Schneidereit <till@tillschneidereit.net>
Mon, 10 Oct 2016 16:57:29 +0200
changeset 320184 309ecb16acfe18bcf53d42497d0c3a489b43bc9e
parent 320142 dc422956242bacfbf88d716f5b967d2c985b913b
child 320185 992fa87b5fbee066579a359fcecd7d4b0054ff9f
push id30889
push userphilringnalda@gmail.com
push dateSun, 30 Oct 2016 17:55:10 +0000
treeherdermozilla-central@e3279760cd97 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1313049
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1313049 - Port Promise-related functions from self-hosted JS to C++. r=arai, f=bz MozReview-Commit-ID: 1LIfYUXCtCy
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,151 +25,573 @@ using namespace js;
 static double
 MillisecondsSinceStartup()
 {
     auto now = mozilla::TimeStamp::Now();
     bool ignored;
     return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
 }
 
-enum ResolutionFunctionSlots {
-    ResolutionFunctionSlot_Promise = 0,
-    ResolutionFunctionSlot_OtherFunction,
+#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,
 };
 
-// ES2016, 25.4.1.8.
-static MOZ_MUST_USE bool
-TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state,
-                        HandleValue valueOrReason)
+class PromiseAllDataHolder : public NativeObject
 {
-    RootedObject reactions(cx, &reactionsVal.toObject());
-
-    AutoIdVector keys(cx);
-    if (!GetPropertyKeys(cx, reactions, JSITER_OWNONLY, &keys))
+  public:
+    static const Class class_;
+    JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); }
+    JSObject* resolveObj() {
+        return getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObjectOrNull();
+    }
+    Value valuesArray() { return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray); }
+    int32_t remainingCount() {
+        return getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
+    }
+    int32_t increaseRemainingCount() {
+        int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
+        remainingCount++;
+        setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount));
+        return remainingCount;
+    }
+    int32_t decreaseRemainingCount() {
+        int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
+        remainingCount--;
+        setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount));
+        return remainingCount;
+    }
+};
+
+const Class PromiseAllDataHolder::class_ = {
+    "PromiseAllDataHolder",
+    JSCLASS_HAS_RESERVED_SLOTS(PromiseAllDataHolderSlots)
+};
+
+static PromiseAllDataHolder*
+NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray,
+                        HandleObject resolve)
+{
+    Rooted<PromiseAllDataHolder*> dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
+    if (!dataHolder)
+        return nullptr;
+
+    assertSameCompartment(cx, resultPromise);
+    assertSameCompartment(cx, valuesArray);
+    assertSameCompartment(cx, resolve);
+
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise));
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1));
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray);
+    dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectOrNullValue(resolve));
+    return dataHolder;
+}
+
+static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun,
+                                               HandleValue result, ResolutionMode mode,
+                                               HandleObject promiseObj);
+
+// ES2016, 25.4.1.1.1, Steps 1.a-b.
+// Extracting all of this internal spec algorithm into a helper function would
+// be tedious, so the check in step 1 and the entirety of step 2 aren't
+// included.
+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;
-    MOZ_ASSERT(keys.length() > 0, "Reactions list should be created lazily");
-
-    RootedPropertyName handlerName(cx);
-    handlerName = state == JS::PromiseState::Fulfilled
-                  ? cx->names().fulfillHandler
-                  : cx->names().rejectHandler;
-
-    // Each reaction is an internally-created object with the structure:
-    // {
-    //   promise: [the promise this reaction resolves],
-    //   resolve: [the `resolve` callback content code provided],
-    //   reject:  [the `reject` callback content code provided],
-    //   fulfillHandler: [the internal handler that fulfills the promise]
-    //   rejectHandler: [the internal handler that rejects the promise]
-    //   incumbentGlobal: [an object from the global that was incumbent when
-    //                     the reaction was created]
-    // }
-    RootedValue val(cx);
-    RootedObject reaction(cx);
-    RootedValue handler(cx);
-    RootedObject promise(cx);
-    RootedObject resolve(cx);
-    RootedObject reject(cx);
-    RootedObject objectFromIncumbentGlobal(cx);
-
-    for (size_t i = 0; i < keys.length(); i++) {
-        if (!GetProperty(cx, reactions, reactions, keys[i], &val))
-            return false;
-        reaction = &val.toObject();
-
-        if (!GetProperty(cx, reaction, reaction, cx->names().promise, &val))
-            return false;
-
-        // The jsapi function AddPromiseReactions can add reaction records
-        // without a `promise` object. See comment for EnqueuePromiseReactions
-        // in Promise.js.
-        promise = val.toObjectOrNull();
-
-        if (!GetProperty(cx, reaction, reaction, handlerName, &handler))
-            return false;
-
-#ifdef DEBUG
-        if (handler.isNumber()) {
-            MOZ_ASSERT(handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
-                       handler.toNumber() == PROMISE_HANDLER_THROWER);
-        } else {
-            MOZ_ASSERT(handler.toObject().isCallable());
-        }
-#endif
-
-        if (!GetProperty(cx, reaction, reaction, cx->names().resolve, &val))
-            return false;
-        resolve = &val.toObject();
-        MOZ_ASSERT(IsCallable(resolve));
-
-        if (!GetProperty(cx, reaction, reaction, cx->names().reject, &val))
-            return false;
-        reject = &val.toObject();
-        MOZ_ASSERT(IsCallable(reject));
-
-        if (!GetProperty(cx, reaction, reaction, cx->names().incumbentGlobal, &val))
-            return false;
-        objectFromIncumbentGlobal = val.toObjectOrNull();
-
-        if (!EnqueuePromiseReactionJob(cx, handler, valueOrReason, resolve, reject, promise,
-                                       objectFromIncumbentGlobal))
-        {
-            return false;
-        }
+
+    // Step 1.b.
+    args.rval().setObject(*promiseObj);
+    return true;
+}
+
+enum ReactionRecordSlots {
+    ReactionRecordSlot_Promise = 0,
+    ReactionRecordSlot_OnFulfilled,
+    ReactionRecordSlot_OnRejected,
+    ReactionRecordSlot_Resolve,
+    ReactionRecordSlot_Reject,
+    ReactionRecordSlot_IncumbentGlobalObject,
+    ReactionRecordSlot_Flags,
+    ReactionRecordSlot_HandlerArg,
+    ReactionRecordSlots,
+};
+
+#define REACTION_FLAG_RESOLVED                  0x1
+#define REACTION_FLAG_FULFILLED                 0x2
+#define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4
+
+// ES2016, 25.4.1.2.
+class PromiseReactionRecord : public NativeObject
+{
+  public:
+    static const Class class_;
+
+    JSObject* promise() { return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull(); }
+    int32_t flags() { return getFixedSlot(ReactionRecordSlot_Flags).toInt32(); }
+    JS::PromiseState targetState() {
+        int32_t flags = this->flags();
+        if (!(flags & REACTION_FLAG_RESOLVED))
+            return JS::PromiseState::Pending;
+        return flags & REACTION_FLAG_FULFILLED
+               ? JS::PromiseState::Fulfilled
+               : JS::PromiseState::Rejected;
+    }
+    void setTargetState(JS::PromiseState state) {
+        int32_t flags = this->flags();
+        MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED));
+        MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending.");
+        flags |= REACTION_FLAG_RESOLVED;
+        if (state == JS::PromiseState::Fulfilled)
+            flags |= REACTION_FLAG_FULFILLED;
+        setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
+    }
+    Value handler() {
+        MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+        uint32_t slot = targetState() == JS::PromiseState::Fulfilled
+                        ? ReactionRecordSlot_OnFulfilled
+                        : ReactionRecordSlot_OnRejected;
+        return getFixedSlot(slot);
+    }
+    Value handlerArg() {
+        MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+        return getFixedSlot(ReactionRecordSlot_HandlerArg);
     }
+    void setHandlerArg(Value& arg) {
+        MOZ_ASSERT(targetState() == JS::PromiseState::Pending);
+        setFixedSlot(ReactionRecordSlot_HandlerArg, arg);
+    }
+    JSObject* incumbentGlobalObject() {
+        return getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull();
+    }
+};
+
+const Class PromiseReactionRecord::class_ = {
+    "PromiseReactionRecord",
+    JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots)
+};
+
+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(PROMISE_REACTIONS_OR_RESULT_SLOT));
-
-    // Step 3-5.
+    RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
+
+    // Steps 3-5.
     // The same slot is used for the reactions list and the result, so setting
     // the result also removes the reactions list.
-    promise->setFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT, valueOrReason);
+    promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason);
 
     // Step 6.
-    int32_t flags = promise->getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
+    int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
     flags |= PROMISE_FLAG_RESOLVED;
     if (state == JS::PromiseState::Fulfilled)
         flags |= PROMISE_FLAG_FULFILLED;
-    promise->setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(flags));
+    promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags));
 
     // Also null out the resolve/reject functions so they can be GC'd.
-    promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, UndefinedValue());
+    promise->setFixedSlot(PromiseSlot_RejectFunction, 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)) {
@@ -182,19 +604,135 @@ 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)) {
@@ -225,182 +763,328 @@ 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)
+{
+    RootedObject reactions(cx, &reactionsVal.toObject());
+    RootedObject reaction(cx);
+
+    if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions))
+        return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state);
+
+    RootedNativeObject reactionsList(cx, &reactions->as<NativeObject>());
+    size_t reactionsCount = reactionsList->getDenseInitializedLength();
+    MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
+
+    for (size_t i = 0; i < reactionsCount; i++) {
+        reaction = &reactionsList->getDenseElement(i).toObject();
+        if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
+            return false;
+    }
+
+    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* otherFun = &resolutionFun->getExtendedSlot(ResolutionFunctionSlot_OtherFunction)
-                           .toObject().as<JSFunction>();
-    resolutionFun->setExtendedSlot(ResolutionFunctionSlot_Promise, UndefinedValue());
-    otherFun->setExtendedSlot(ResolutionFunctionSlot_Promise, UndefinedValue());
-
-    // Also reset the reference to the resolve function so it can be collected.
-    resolutionFun->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, UndefinedValue());
-    otherFun->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, UndefinedValue());
-}
-// ES2016, 25.4.1.3.1.
-static bool
-RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedFunction reject(cx, &args.callee().as<JSFunction>());
-    RootedValue reasonVal(cx, args.get(0));
-
-    // Steps 1-2.
-    RootedValue promiseVal(cx, reject->getExtendedSlot(ResolutionFunctionSlot_Promise));
-
-    // Steps 3-4.
-    // We use the existence of the Promise a a signal for whether it was
-    // already resolved. Upon resolution, it's reset to `undefined`.
-    if (promiseVal.isUndefined()) {
-        args.rval().setUndefined();
-        return true;
+    JSFunction* resolve;
+    JSFunction* reject;
+    if (resolutionFun->maybeNative() == ResolvePromiseFunction) {
+        resolve = resolutionFun;
+        reject = &resolutionFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
+                  .toObject().as<JSFunction>();
+    } else {
+        resolve = GetResolveFunctionFromReject(resolutionFun);
+        reject = resolutionFun;
     }
 
-    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;
-
-    // Step 13.
-    return true;
+    resolve->setExtendedSlot(ResolveFunctionSlot_Promise, UndefinedValue());
+    resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, UndefinedValue());
+
+    reject->setExtendedSlot(RejectFunctionSlot_Promise, UndefinedValue());
+    reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, UndefinedValue());
 }
 
-// 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);
-
-        // 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;
-}
-
-// 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;
-}
-
-static PromiseObject*
-CreatePromiseObjectInternal(JSContext* cx, HandleObject proto, bool protoIsWrapped)
+// ES2016, 25.4.3.1. steps 3-7.
+static MOZ_MUST_USE PromiseObject*
+CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
+                            bool protoIsWrapped /* = false */, bool informDebugger /* = true */)
 {
     // Step 3.
     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.)
@@ -408,466 +1092,44 @@ CreatePromiseObjectInternal(JSContext* c
     if (protoIsWrapped)
         ac.emplace(cx, proto);
 
     promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
     if (!promise)
         return nullptr;
 
     // Step 4.
-    promise->setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(0));
+    promise->setFixedSlot(PromiseSlot_Flags, Int32Value(0));
 
     // Steps 5-6.
     // Omitted, we allocate our single list of reaction records lazily.
 
     // Step 7.
     // Implicit, the handled flag is unset by default.
 
     // Store an allocation stack so we can later figure out what the
     // control flow was for some unexpected results. Frightfully expensive,
     // but oh well.
     RootedObject stack(cx);
     if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
         if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
             return nullptr;
     }
-    promise->setFixedSlot(PROMISE_ALLOCATION_SITE_SLOT, ObjectOrNullValue(stack));
-    promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT,
-                          DoubleValue(MillisecondsSinceStartup()));
+    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);
 
     return promise;
 }
 
-/**
- * Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
- */
-/* static */ JSObject*
-PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
-{
-    // Steps 1-2 (omitted).
-
-    // Roughly step 3.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
-    if (!promise)
-        return nullptr;
-
-    // Let the Debugger know about this Promise.
-    JS::dbg::onNewPromise(cx, promise);
-
-    // Roughly step 4.
-    if (!ResolvePromise(cx, promise, value, JS::PromiseState::Rejected))
-        return nullptr;
-
-    // Step 5.
-    return promise;
-}
-
-/**
- * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
- */
-/* static */ JSObject*
-PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
-{
-    // Steps 1-2 (omitted).
-
-    // Step 3.
-    if (value.isObject()) {
-        JSObject* obj = &value.toObject();
-        if (IsWrapper(obj))
-            obj = CheckedUnwrap(obj);
-        // Instead of getting the `constructor` property, do an unforgeable
-        // check.
-        if (obj && obj->is<PromiseObject>())
-            return obj;
-    }
-
-    // Step 4.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
-    if (!promise)
-        return nullptr;
-
-    // Let the Debugger know about this Promise.
-    JS::dbg::onNewPromise(cx, promise);
-
-    // Steps 5.
-    if (!ResolvePromiseInternal(cx, promise, value))
-        return nullptr;
-
-    // Step 6.
-    return promise;
-}
-
-// ES6, 25.4.1.5.1.
-/* static */ bool
-GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    RootedFunction F(cx, &args.callee().as<JSFunction>());
-
-    // Steps 1-2 (implicit).
-
-    // Steps 3-4.
-    if (!F->getExtendedSlot(0).isUndefined() || !F->getExtendedSlot(1).isUndefined()) {
-        JS_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
-};
-
+// ES2016, 25.4.3.1.
 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;
 
@@ -938,49 +1200,1267 @@ 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)
+            return nullptr;
+        if (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.
+        RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
+        Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise,
+                                                                             valuesArrayVal,
+                                                                             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.
+    assertSameCompartment(cx, resolutionFun);
+    assertSameCompartment(cx, result);
+    assertSameCompartment(cx, promiseObj);
+    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,
+                                                                         valuesArrayVal, 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;
 
-    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>());
+    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;
 
     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 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>());
+    RootedValue funVal(cx, this->getFixedSlot(PromiseSlot_RejectFunction));
+    MOZ_ASSERT(IsCallable(funVal));
 
     FixedInvokeArgs<1> args(cx);
-
     args[0].set(rejectionValue);
 
     RootedValue dummy(cx);
     return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
 }
 
 void
 PromiseObject::onSettled(JSContext* cx)
@@ -988,382 +2468,33 @@ 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(PROMISE_RESOLUTION_SITE_SLOT, ObjectOrNullValue(stack));
-    promise->setFixedSlot(PROMISE_RESOLUTION_TIME_SLOT, DoubleValue(MillisecondsSinceStartup()));
+    promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack));
+    promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
 
     if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
         cx->runtime()->addUnhandledRejectedPromise(cx, promise);
 
     JS::dbg::onPromiseSettled(cx, promise);
 }
 
-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_MUST_USE bool
+js::EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
+                            HandleObject dependentPromise,
+                            HandleValue onFulfilled, HandleValue onRejected)
 {
-    // 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);
+    MOZ_ASSERT_IF(dependentPromise, dependentPromise->is<PromiseObject>());
+    return PerformPromiseThen(cx, promise, onFulfilled, onRejected, dependentPromise,
+                              nullptr, nullptr);
 }
 
 PromiseTask::PromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
   : runtime_(cx),
     promise_(cx, promise)
 {}
 
 PromiseTask::~PromiseTask()
@@ -1388,40 +2519,38 @@ 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_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
+    JS_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_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
-    JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
+    JS_FN("all", Promise_static_all, 1, 0),
+    JS_FN("race", Promise_static_race, 1, 0),
     JS_FN("reject", Promise_reject, 1, 0),
-    JS_FN("resolve", Promise_resolve, 1, 0),
+    JS_FN("resolve", Promise_static_resolve, 1, 0),
     JS_FS_END
 };
 
 static const JSPropertySpec promise_static_properties[] = {
     JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
     JS_PS_END
 };
 
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -7,110 +7,120 @@
 #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 = 8;
+    static const unsigned RESERVED_SLOTS = PromiseSlots;
     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(PROMISE_FLAGS_SLOT).toInt32();
+        int32_t flags = getFixedSlot(PromiseSlot_Flags).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(PROMISE_REACTIONS_OR_RESULT_SLOT);
+        return getFixedSlot(PromiseSlot_ReactionsOrResult);
     }
     Value reason() {
         MOZ_ASSERT(state() == JS::PromiseState::Rejected);
-        return getFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT);
+        return getFixedSlot(PromiseSlot_ReactionsOrResult);
     }
 
     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(PROMISE_ALLOCATION_TIME_SLOT).toNumber(); }
-    double resolutionTime() { return getFixedSlot(PROMISE_RESOLUTION_TIME_SLOT).toNumber(); }
+    double allocationTime() { return getFixedSlot(PromiseSlot_AllocationTime).toNumber(); }
+    double resolutionTime() { return getFixedSlot(PromiseSlot_ResolutionTime).toNumber(); }
     JSObject* allocationSite() {
-        return getFixedSlot(PROMISE_ALLOCATION_SITE_SLOT).toObjectOrNull();
+        return getFixedSlot(PromiseSlot_AllocationSite).toObjectOrNull();
     }
     JSObject* resolutionSite() {
-        return getFixedSlot(PROMISE_RESOLUTION_SITE_SLOT).toObjectOrNull();
+        return getFixedSlot(PromiseSlot_ResolutionSite).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(PROMISE_FLAGS_SLOT).toInt32() & PROMISE_FLAG_HANDLED);
+        return !(getFixedSlot(PromiseSlot_Flags).toInt32() & PROMISE_FLAG_HANDLED);
     }
     void markAsReported() {
         MOZ_ASSERT(isUnhandled());
-        int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
-        setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(flags | PROMISE_FLAG_REPORTED));
+        int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
+        setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
     }
 };
 
 /**
- * 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.
+ * 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.
  */
-bool EnqueuePromiseReactionJob(JSContext* cx, HandleValue handler, HandleValue handlerArg,
-                               HandleObject resolve, HandleObject reject,
-                               HandleObject promise, HandleObject objectFromIncumbentGlobal);
+MOZ_MUST_USE bool
+EnqueuePromiseReactions(JSContext* cx, Handle<PromiseObject*> promise,
+                        HandleObject dependentPromise,
+                        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);
+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);
 
 /**
  * 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,762 +1,16 @@
 /* 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,43 +73,16 @@
 #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,25 +1446,52 @@ 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(PROMISE_FLAGS_SLOT).toInt32();
-    promise->setFixedSlot(PROMISE_FLAGS_SLOT,
+    int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
+    promise->setFixedSlot(PromiseSlot_Flags,
                           Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
-    promise->setFixedSlot(PROMISE_REACTIONS_OR_RESULT_SLOT, UndefinedValue());
+    promise->setFixedSlot(PromiseSlot_ReactionsOrResult, 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)
@@ -4106,16 +4133,20 @@ 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,107 +4787,66 @@ 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 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::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_PUBLIC_API(JSObject*)
 JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
-    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;
+    return js::GetWaitForAllPromise(cx, promises);
 }
 
 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,16 +1969,57 @@ 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;
@@ -3898,16 +3939,26 @@ 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,11 +1370,17 @@ 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,67 +1,65 @@
 // |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,17 +116,16 @@
     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") \
@@ -249,17 +248,16 @@
     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,17 +728,18 @@ 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);
-        allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
+        if (unwrappedPromise->is<PromiseObject>())
+            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,17 +22,16 @@
 #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"
@@ -173,69 +172,16 @@ 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());
 
@@ -1880,64 +1826,16 @@ 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);
@@ -2054,71 +1952,16 @@ 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());
 
@@ -2403,17 +2246,16 @@ 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",
@@ -2516,24 +2358,16 @@ 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),