Bug 911216 - Part 11: Implement all Promise inspection functionality as Debugger getters. r=shu,fitzgen
authorTill Schneidereit <till@tillschneidereit.net>
Wed, 10 Feb 2016 23:10:08 +0100
changeset 289863 e947c9941fe17266770e9f56f283f0d7628b2b65
parent 289862 87c4e3921c4c419001c3ae554ab4249d3ee13c0a
child 289864 d860e70ee337ba7a648d7f8de234ea075c83f3f8
push id74020
push usertschneidereit@gmail.com
push dateTue, 22 Mar 2016 23:02:59 +0000
treeherdermozilla-inbound@e947c9941fe1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu, fitzgen
bugs911216
milestone48.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 911216 - Part 11: Implement all Promise inspection functionality as Debugger getters. r=shu,fitzgen
dom/promise/tests/chrome.ini
dom/promise/tests/test_dependentPromises.html
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/builtin/Promise.js
js/src/builtin/SelfHostingDefines.h
js/src/doc/Debugger/Debugger.Object.md
js/src/doc/Debugger/Debugger.md
js/src/doc/Debugger/config.sh
js/src/js.msg
js/src/jsapi.cpp
js/src/jsapi.h
js/src/tests/ecma_6/Promise/dependent-promises.js
js/src/tests/ecma_6/Promise/get-wait-for-all-promise.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Debugger.cpp
js/src/vm/SelfHosting.cpp
--- a/dom/promise/tests/chrome.ini
+++ b/dom/promise/tests/chrome.ini
@@ -1,8 +1,7 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 
-[test_dependentPromises.html]
 [test_on_new_promise.html]
 [test_on_promise_settled.html]
 [test_on_promise_settled_duplicates.html]
 [test_promise_xrays.html]
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2,19 +2,22 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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/. */
 
 
 #include "builtin/Promise.h"
 
+#include "mozilla/Atomics.h"
+
 #include "jscntxt.h"
 
 #include "gc/Heap.h"
+#include "js/Date.h"
 #include "js/Debug.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
@@ -84,16 +87,24 @@ PromiseObject::create(JSContext* cx, Han
             return nullptr;
         promise->setFixedSlot(PROMISE_FULFILL_REACTIONS_SLOT, ObjectValue(*reactions));
 
         // Step 7.
         reactions = NewDenseEmptyArray(cx);
         if (!reactions)
             return nullptr;
         promise->setFixedSlot(PROMISE_REJECT_REACTIONS_SLOT, ObjectValue(*reactions));
+
+        RootedObject stack(cx);
+        if (!JS::CaptureCurrentStack(cx, &stack, 0))
+            return nullptr;
+        promise->setFixedSlot(PROMISE_ALLOCATION_SITE_SLOT, ObjectValue(*stack));
+        Value now = JS::TimeValue(JS::TimeClip(static_cast<double>(PRMJ_Now()) /
+                                               PRMJ_USEC_PER_MSEC));
+        promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT, now);
     }
 
     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
@@ -167,16 +178,100 @@ PromiseObject::create(JSContext* cx, Han
     }
 
     JS::dbg::onNewPromise(cx, promise);
 
     // Step 11.
     return promise;
 }
 
+namespace {
+// Generator used by PromiseObject::getID.
+mozilla::Atomic<uint64_t> gIDGenerator(0);
+} // namespace
+
+double
+PromiseObject::getID()
+{
+    Value idVal(getReservedSlot(PROMISE_ID_SLOT));
+    if (idVal.isUndefined()) {
+        idVal.setDouble(++gIDGenerator);
+        setReservedSlot(PROMISE_ID_SLOT, idVal);
+    }
+    return 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`.
+ *
+ *
+ * For the then() case, we have both resolve and reject callbacks that know
+ * what the next promise is.
+ *
+ * For the race() case, likewise.
+ *
+ * For the all() case, our reject callback knows what the next promise is, but
+ * our resolve callback doesn't.
+ *
+ * So we walk over our _reject_ callbacks and ask each of them what promise
+ * its dependent promise is.
+ */
+bool
+PromiseObject::dependentPromises(JSContext* cx, AutoValueVector& values)
+{
+    RootedValue rejectReactionsVal(cx, getReservedSlot(PROMISE_REJECT_REACTIONS_SLOT));
+    RootedObject rejectReactions(cx, rejectReactionsVal.toObjectOrNull());
+    if (!rejectReactions)
+        return true;
+
+    AutoIdVector keys(cx);
+    if (!GetPropertyKeys(cx, rejectReactions, JSITER_OWNONLY, &keys))
+        return false;
+
+    if (keys.length() == 0)
+        return true;
+
+    if (!values.growBy(keys.length()))
+        return false;
+
+    RootedAtom capabilitiesAtom(cx, Atomize(cx, "capabilities", strlen("capabilities")));
+    if (!capabilitiesAtom)
+        return false;
+    RootedId capabilitiesId(cx, AtomToId(capabilitiesAtom));
+
+    // Each reaction is an internally-created object with the structure:
+    // {
+    //   capabilities: {
+    //     promise: [the promise this reaction resolves],
+    //     resolve: [the `resolve` callback content code provided],
+    //     reject:  [the `reject` callback content code provided],
+    //    },
+    //    handler: [the internal handler that fulfills/rejects the promise]
+    //  }
+    //
+    // In the following loop we collect the `capabilities.promise` values for
+    // each reaction.
+    for (size_t i = 0; i < keys.length(); i++) {
+        MutableHandleValue val = values[i];
+        if (!GetProperty(cx, rejectReactions, rejectReactions, keys[i], val))
+            return false;
+        RootedObject reaction(cx, &val.toObject());
+        if (!GetProperty(cx, reaction, reaction, capabilitiesId, val))
+            return false;
+        RootedObject capabilities(cx, &val.toObject());
+        if (!GetProperty(cx, capabilities, capabilities, cx->runtime()->commonNames->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);
 
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -3,36 +3,60 @@
  * 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/. */
 
 #ifndef builtin_Promise_h
 #define builtin_Promise_h
 
 #include "builtin/SelfHostingDefines.h"
+#include "js/Date.h"
 #include "vm/NativeObject.h"
 
 namespace js {
 
 class AutoSetNewObjectMetadata;
 
 class PromiseObject : public NativeObject
 {
   public:
-    static const unsigned RESERVED_SLOTS = 6;
+    static const unsigned RESERVED_SLOTS = 11;
     static const Class class_;
     static const Class protoClass_;
     static PromiseObject* create(JSContext* cx, HandleObject executor,
                                  HandleObject proto = nullptr);
 
     JS::PromiseState state() {
-        int32_t state = getReservedSlot(PROMISE_STATE_SLOT).toInt32();
+        int32_t state = getFixedSlot(PROMISE_STATE_SLOT).toInt32();
         MOZ_ASSERT(state >= 0 && state <= int32_t(JS::PromiseState::Rejected));
         return JS::PromiseState(state);
     }
+    Value value()  {
+        MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
+        return getFixedSlot(PROMISE_RESULT_SLOT);
+    }
+    Value reason() {
+        MOZ_ASSERT(state() == JS::PromiseState::Rejected);
+        return getFixedSlot(PROMISE_RESULT_SLOT);
+    }
 
     bool resolve(JSContext* cx, HandleValue resolutionValue);
     bool reject(JSContext* cx, HandleValue rejectionValue);
+
+    double allocationTime() { return getFixedSlot(PROMISE_ALLOCATION_TIME_SLOT).toNumber(); }
+    double resolutionTime() { return getFixedSlot(PROMISE_RESOLUTION_TIME_SLOT).toNumber(); }
+    JSObject* allocationSite() { return &getFixedSlot(PROMISE_ALLOCATION_SITE_SLOT).toObject(); }
+    JSObject* resolutionSite() { return &getFixedSlot(PROMISE_RESOLUTION_SITE_SLOT).toObject(); }
+    double lifetime() {
+        double now = JS::TimeClip(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC).toDouble();
+        return now - allocationTime();
+    }
+    double timeToResolution() {
+        MOZ_ASSERT(state() != JS::PromiseState::Pending);
+        return resolutionTime() - allocationTime();
+    }
+    bool dependentPromises(JSContext* cx, AutoValueVector& values);
+    double getID();
 };
 
 } // namespace js
 
 #endif /* builtin_Promise_h */
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -123,16 +123,18 @@ function FulfillUnwrappedPromise(value) 
     return ResolvePromise(this, value, PROMISE_FULFILL_REACTIONS_SLOT, PROMISE_STATE_FULFILLED);
 }
 
 // Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
 function ResolvePromise(promise, valueOrReason, reactionsSlot, state) {
     // Step 1.
     assert(GetPromiseState(promise) === PROMISE_STATE_PENDING,
            "Can't resolve non-pending promise");
+    assert(state >= PROMISE_STATE_PENDING && state <= PROMISE_STATE_REJECTED,
+           `Invalid Promise state <${state}>`);
 
     // Step 2.
     var reactions = UnsafeGetObjectFromReservedSlot(promise, reactionsSlot);
 
     // Step 3.
     UnsafeSetReservedSlot(promise, PROMISE_RESULT_SLOT, valueOrReason);
 
     // Step 4.
@@ -143,16 +145,20 @@ function ResolvePromise(promise, valueOr
 
     // Step 6.
     UnsafeSetReservedSlot(promise, PROMISE_STATE_SLOT, state);
 
     // Also null out the resolve/reject functions so they can be GC'd.
     UnsafeSetReservedSlot(promise, PROMISE_RESOLVE_FUNCTION_SLOT, null);
     UnsafeSetReservedSlot(promise, PROMISE_REJECT_FUNCTION_SLOT, null);
 
+    // Now that everything else is done, do the things the debugger needs.
+    let site = _dbg_captureCurrentStack(0);
+    UnsafeSetReservedSlot(promise, PROMISE_RESOLUTION_SITE_SLOT, site);
+    UnsafeSetReservedSlot(promise, PROMISE_RESOLUTION_TIME_SLOT, std_Date_now());
     _dbg_onPromiseSettled(promise);
 
     // Step 7.
     return TriggerPromiseReactions(reactions, valueOrReason);
 }
 
 // Used to verify that an object is a PromiseCapability record.
 var PromiseCapabilityRecordProto = {__proto__: null};
@@ -343,16 +349,17 @@ function PerformPromiseAll(iteratorRecor
 
     // 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.
@@ -372,17 +379,17 @@ function PerformPromiseAll(iteratorRecor
             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 resultCapability.promise;
+            return allPromise;
         }
         try {
             // Step 6.e.
             nextValue = next.value;
         } catch (e) {
             // Step 6.f.
             iteratorRecord.done = true;
 
@@ -400,18 +407,17 @@ function PerformPromiseAll(iteratorRecor
         let resolveElement = CreatePromiseAllResolveElementFunction(index, values,
                                                                     resultCapability,
                                                                     remainingElementsCount);
 
         // Step 6.q.
         remainingElementsCount.value++;
 
         // Steps 6.r-s.
-        let result = callContentFunction(nextPromise.then, nextPromise, resolveElement,
-                                         resultCapability.reject);
+        BlockOnPromise(nextPromise, allPromise, resolveElement, resultCapability.reject);
 
         // Step 6.t.
         index++;
     }
 }
 
 /**
  * Unforgeable version of Promise.all for internal use.
@@ -540,16 +546,17 @@ function Promise_static_race(iterable) {
 
 // ES7, 2016-01-21 draft, 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);
@@ -562,37 +569,142 @@ function PerformPromiseRace(iteratorReco
         }
 
         // Step 1.d.
         if (next.done) {
             // Step 1.d.i.
             iteratorRecord.done = true;
 
             // Step 1.d.ii.
-            return resultCapability.promise;
+            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.
-        callContentFunction(nextPromise.then, nextPromise,
-                            resultCapability.resolve, resultCapability.reject);
+        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);
     }
-    assert(false, "Shouldn't reach the end of PerformPromiseRace")
+    if (!addToDependent)
+        return;
+
+    // The promise 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 promise 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 (IsPromise(promise))
+        return callFunction(AddPromiseReaction, promise, PROMISE_REJECT_REACTIONS_SLOT,
+                            blockedPromise);
+
+    assert(IsWrappedPromise(promise), "Can only block on, maybe wrapped, Promise objects");
+    callFunction(CallPromiseMethodIfWrapped, promise, PROMISE_REJECT_REACTIONS_SLOT,
+                 blockedPromise, "AddPromiseReaction");
+}
+
+/**
+ * Invoked with a Promise as the receiver, AddPromiseReaction adds an entry to
+ * the reactions list in `slot`, using the other parameters as values for that
+ * reaction.
+ *
+ * If any of the callback functions aren't specified, they're set to
+ * NullFunction. Doing that here is useful in case the call is performed on an
+ * unwrapped Promise. Passing in NullFunctions would cause useless compartment
+ * switches.
+ *
+ * The reason for the target Promise to be passed as the receiver is so the
+ * same function can be used for wrapped and unwrapped Promise objects.
+ */
+function AddPromiseReaction(slot, dependentPromise, onResolve, onReject, handler) {
+    assert(IsPromise(this), "AddPromiseReaction expects an unwrapped Promise as the receiver");
+    assert(slot === PROMISE_FULFILL_REACTIONS_SLOT || slot === PROMISE_REJECT_REACTIONS_SLOT,
+           "Invalid slot");
+
+    if (!onResolve)
+        onResolve = NullFunction;
+    if (!onReject)
+        onReject = NullFunction;
+    if (!handler)
+        handler = NullFunction;
+
+    let reactions = UnsafeGetReservedSlot(this, slot);
+
+    // The reactions slot might've been reset because the Promise was resolved.
+    if (!reactions) {
+        assert(GetPromiseState(this) !== PROMISE_STATE_PENDING,
+               "Pending promises must have reactions lists.");
+        return;
+    }
+    _DefineDataProperty(reactions, reactions.length, {
+                            __proto__: PromiseReactionRecordProto,
+                            capabilities: {
+                                __proto__: PromiseCapabilityRecordProto,
+                                promise: dependentPromise,
+                                reject: onReject,
+                                resolve: onResolve
+                            },
+                            handler: handler
+                        });
 }
 
 // ES6, 25.4.4.4.
 function Promise_static_reject(r) {
     // Step 1.
     let C = this;
 
     // Step 2.
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -58,16 +58,21 @@
 #define ITEM_KIND_KEY_AND_VALUE 2
 
 #define PROMISE_STATE_SLOT             0
 #define PROMISE_RESULT_SLOT            1
 #define PROMISE_FULFILL_REACTIONS_SLOT 2
 #define PROMISE_REJECT_REACTIONS_SLOT  3
 #define PROMISE_RESOLVE_FUNCTION_SLOT  4
 #define PROMISE_REJECT_FUNCTION_SLOT   5
+#define PROMISE_ALLOCATION_SITE_SLOT   6
+#define PROMISE_RESOLUTION_SITE_SLOT   7
+#define PROMISE_ALLOCATION_TIME_SLOT   8
+#define PROMISE_RESOLUTION_TIME_SLOT   9
+#define PROMISE_ID_SLOT               10
 
 #define PROMISE_STATE_PENDING   0
 #define PROMISE_STATE_FULFILLED 1
 #define PROMISE_STATE_REJECTED  2
 
 #define PROMISE_HANDLER_IDENTITY 0
 #define PROMISE_HANDLER_THROWER  1
 
--- a/js/src/doc/Debugger/Debugger.Object.md
+++ b/js/src/doc/Debugger/Debugger.Object.md
@@ -154,16 +154,19 @@ from its prototype:
     is a function proxy or not debuggee code, this is `undefined`.
 
 `isBoundFunction`
 :   `true` if the referent is a bound function; `false` otherwise.
 
 `isArrowFunction`
 :   `true` if the referent is an arrow function; `false` otherwise.
 
+`isPromise`
+:   `true` if the referent is a Promise; `false` otherwise.
+
 `boundTargetFunction`
 :   If the referent is a bound function, this is its target function—the
     function that was bound to a particular `this` object. If the referent
     is not a bound function, this is `undefined`.
 
 `boundThis`
 :   If the referent is a bound function, this is the `this` value it was
     bound to. If the referent is not a bound function, this is `undefined`.
@@ -190,16 +193,99 @@ from its prototype:
 
 `proxyConstructTrap`
 :   If the referent is a function proxy whose handler object was allocated
     by debuggee code, its construction trap function—the function called
     when the function proxy is called via a `new` expression. If the
     referent is not a function proxy whose handler object was allocated by
     debuggee code, this is `null`.
 
+`promiseState`
+:   If the referent is a [`Promise`][promise], this is an object describing
+    the Promise's current state, with the following properties:
+
+    `state`
+    :   A string indicating whether the [`Promise`][promise] is pending or
+        has been fulfilled or rejected.
+        This accessor returns one of the following values:
+
+        * `"pending"`, if the [`Promise`][promise] hasn't been resolved.
+
+        * `"fulfilled"`, if the [`Promise`][promise] has been fulfilled.
+
+        * `"rejected"`, if the [`Promise`][promise] has been rejected.
+
+    `value`
+    :   If the [`Promise`][promise] has been *fulfilled*, this is a
+        `Debugger.Object` referring to the value it was fulfilled with,
+        `undefined` otherwise.
+
+    `reason`
+    :   If the [`Promise`][promise] has been *rejected*, this is a
+        `Debugger.Object` referring to the value it was rejected with,
+        `undefined` otherwise.
+
+    If the referent is not a [`Promise`][promise], throw a `TypeError`
+    exception.
+
+`promiseAllocationSite`
+:   If the referent is a [`Promise`][promise], this is the
+    [JavaScript execution stack][saved-frame] captured at the time of the
+    promise's allocation. This can return null if the promise was not
+    created from script. If the referent is not a [`Promise`][promise], throw
+    a `TypeError` exception.
+
+`promiseResolutionSite`
+:   If the referent is a [`Promise`][promise], this is the
+    [JavaScript execution stack][saved-frame] captured at the time of the
+    promise's resolution. This can return null if the promise was not
+    resolved by calling its `resolve` or `reject` resolving functions from
+    script. If the referent is not a [`Promise`][promise], throw a `TypeError`
+    exception.
+
+`promiseID`
+:   If the referent is a [`Promise`][promise], this is a process-unique
+    identifier for the [`Promise`][promise]. With e10s, the same id can
+    potentially be assigned to multiple [`Promise`][promise] instances, if
+    those instances were created in different processes. If the referent is
+    not a [`Promise`][promise], throw a `TypeError` exception.
+
+`promiseDependentPromises`
+:   If the referent is a [`Promise`][promise], this is an `Array` of
+    `Debugger.Objects` referring to the promises directly depending on the
+    referent [`Promise`][promise]. These are:
+
+   1) Return values of `then()` calls on the promise.
+   2) Return values of `Promise.all()` if the referent [`Promise`][promise]
+      was passed in as one of the arguments.
+   3) Return values of `Promise.race()` if the referent [`Promise`][promise]
+      was passed in as one of the arguments.
+
+   Once a [`Promise`][promise] is settled, it will generally notify its
+   dependent promises and forget about them, so this is most useful on
+   *pending* promises.
+
+   Note that the `Array` only contains the promises that directly depend on
+   the referent [`Promise`][promise]. It does not contain promises that depend
+   on promises that depend on the referent [`Promise`][promise].
+
+   If the referent is not a [`Promise`][promise], throw a `TypeError`
+   exception.
+
+`promiseLifetime`
+:   If the referent is a [`Promise`][promise], this is the number of
+    milliseconds elapsed since the [`Promise`][promise] was created. If the
+    referent is not a [`Promise`][promise], throw a `TypeError` exception.
+
+`promiseTimeToResolution`
+:   If the referent is a [`Promise`][promise], this is the number of
+    milliseconds elapsed between when the [`Promise`][promise] was created and
+    when it was resolved. If the referent hasn't been resolved or is not a
+    [`Promise`][promise], throw a `TypeError` exception.
+
 `global`
 :   A `Debugger.Object` instance referring to the global object in whose
     scope the referent was allocated. This does not unwrap cross-compartment
     wrappers: if the referent is a wrapper, the result refers to the
     wrapper's global, not the wrapped object's global. The result refers to
     the global directly, not via a wrapper.
 
 <code id="allocationsite">allocationSite</code>
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -112,29 +112,32 @@ compartment.
 <code>onNewScript(<i>script</i>, <i>global</i>)</code>
 :   New code, represented by the [`Debugger.Script`][script] instance
     <i>script</i>, has been loaded in the scope of the debuggees.
 
     This method's return value is ignored.
 
 <code>onNewPromise(<i>promise</i>)</code>
 :   A new Promise object, referenced by the [`Debugger.Object`][object] instance
-    *promise*, has been allocated in the scope of the debuggees.
+    *promise*, has been allocated in the scope of the debuggees. The Promise's
+    allocation stack can be obtained using the *promiseAllocationStack*
+    accessor property of the [`Debugger.Object`][object] instance *promise*.
 
     This handler method should return a [resumption value][rv] specifying how
     the debuggee's execution should proceed. However, note that a <code>{
     return: <i>value</i> }</code> resumption value is treated like `undefined`
     ("continue normally"); <i>value</i> is ignored.
 
 <code>onPromiseSettled(<i>promise</i>)</code>
 :   A Promise object, referenced by the [`Debugger.Object`][object] instance
     *promise* that was allocated within a debuggee scope, has settled (either
-    fulfilled or rejected). The Promise's state and fulfillment or rejection
-    value can be obtained via the
-    [PromiseDebugging webidl interface][promise-debugging].
+    fulfilled or rejected). The Promise's state, fulfillment or rejection
+    value, and the allocation and resolution stacks can be obtained using the
+    Promise-related accessor properties of the [`Debugger.Object`][object]
+    instance *promise*.
 
     This handler method should return a [resumption value][rv] specifying how
     the debuggee's execution should proceed. However, note that a <code>{
     return: <i>value</i> }</code> resumption value is treated like `undefined`
     ("continue normally"); <i>value</i> is ignored.
 
 <code>onDebuggerStatement(<i>frame</i>)</code>
 :   Debuggee code has executed a <i>debugger</i> statement in <i>frame</i>.
@@ -491,17 +494,16 @@ other kinds of objects.
     available to privileged code.
 
 <code>makeGlobalObjectReference(<i>global</i>)</code>
 :   Return the [`Debugger.Object`][object] whose referent is the global object
     designated by <i>global</i>, without adding the designated global as a
     debuggee. If <i>global</i> does not designate a global object, throw a
     `TypeError`. Determine which global is designated by <i>global</i>
     using the same rules as [`Debugger.prototype.addDebuggee`][add].
-
 ## Static methods of the Debugger Object
 
 The functions described below are not called with a `this` value.
 
 <code id="isCompilableUnit">isCompilableUnit(<i>source</i>)</code>
 :   Given a string of source code, designated by <i>source</i>, return false if
     the string might become a valid JavaScript statement with the addition of
     more lines. Otherwise return true. The intent is to support interactive
--- a/js/src/doc/Debugger/config.sh
+++ b/js/src/doc/Debugger/config.sh
@@ -59,9 +59,9 @@ resource 'img-chrome-pref'        enable
 resource 'img-scratchpad-browser' scratchpad-browser-environment.png $RBASE/7229/scratchpad-browser-environment.png
 resource 'img-example-alert'      debugger-alert.png                 $RBASE/7231/debugger-alert.png
 resource 'img-alloc-plot'         alloc-plot-console.png             $RBASE/8461/alloc-plot-console.png
 
 # External links:
 absolute-label 'protocol' https://wiki.mozilla.org/Remote_Debugging_Protocol "Remote Debugging Protocol"
 absolute-label 'saved-frame' https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/SavedFrame "SavedFrame"
 absolute-label 'bernoulli-trial' https://en.wikipedia.org/wiki/Bernoulli_trial "Bernoulli Trial"
-absolute-label 'promise-debugging' https://mxr.mozilla.org/mozilla-central/source/dom/webidl/PromiseDebugging.webidl?rev=331d71cabe1e "PromiseDebugging.webidl"
+absolute-label 'promise' https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -416,16 +416,17 @@ MSG_DEF(JSMSG_DEBUGGEE_WOULD_RUN,      2
 MSG_DEF(JSMSG_NOT_CALLABLE_OR_UNDEFINED, 0, JSEXN_TYPEERR, "value is not a function or undefined")
 MSG_DEF(JSMSG_NOT_TRACKING_ALLOCATIONS, 1, JSEXN_ERR, "Cannot call {0} without setting trackingAllocationSites to true")
 MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so")
 MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 0, JSEXN_TYPEERR, "findScripts query object with 'innermost' property must have 'line' and either 'displayURL', 'url', or 'source'")
 MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'displayURL', 'url', or 'source' property")
 MSG_DEF(JSMSG_DEBUG_CANT_SET_OPT_ENV, 1, JSEXN_REFERENCEERR, "can't set `{0}' in an optimized-out environment")
 MSG_DEF(JSMSG_DEBUG_INVISIBLE_COMPARTMENT, 0, JSEXN_TYPEERR, "object in compartment marked as invisible to Debugger")
 MSG_DEF(JSMSG_DEBUG_CENSUS_BREAKDOWN,  1, JSEXN_TYPEERR, "unrecognized 'by' value in takeCensus breakdown: {0}")
+MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_RESOLVED, 0, JSEXN_TYPEERR, "Promise hasn't been resolved")
 
 // Tracelogger
 MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1, JSEXN_ERR, "enabling tracelogger failed: {0}")
 
 // Intl
 MSG_DEF(JSMSG_DATE_NOT_FINITE,         0, JSEXN_RANGEERR, "date value is not finite in DateTimeFormat.format()")
 MSG_DEF(JSMSG_INTERNAL_INTL_ERROR,     0, JSEXN_ERR, "internal error while computing Intl data")
 MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED,  3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4682,16 +4682,42 @@ JS::GetPromisePrototype(JSContext* cx)
 
 JS_PUBLIC_API(JS::PromiseState)
 JS::GetPromiseState(JS::HandleObject obj)
 {
     JSObject* promise = CheckedUnwrap(obj);
     return promise->as<PromiseObject>().state();
 }
 
+JS_PUBLIC_API(double)
+JS::GetPromiseID(JS::HandleObject promise)
+{
+    return promise->as<PromiseObject>().getID();
+}
+
+JS_PUBLIC_API(JS::Value)
+JS::GetPromiseResult(JS::HandleObject promiseObj)
+{
+    PromiseObject* promise = &promiseObj->as<PromiseObject>();
+    MOZ_ASSERT(promise->state() != JS::PromiseState::Pending);
+    return promise->state() == JS::PromiseState::Fulfilled ? promise->value() : promise->reason();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::GetPromiseAllocationSite(JS::HandleObject promise)
+{
+    return promise->as<PromiseObject>().allocationSite();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::GetPromiseResolutionSite(JS::HandleObject promise)
+{
+    return promise->as<PromiseObject>().resolutionSite();
+}
+
 JS_PUBLIC_API(JSObject*)
 JS::CallOriginalPromiseResolve(JSContext* cx, JS::HandleValue resolutionValue)
 {
     InvokeArgs args(cx);
     if (!args.init(1))
         return nullptr;
     RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
     if (!promiseCtor)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4335,35 +4335,61 @@ GetPromisePrototype(JSContext* cx);
 
 // Keep this in sync with the PROMISE_STATE defines in SelfHostingDefines.h.
 enum class PromiseState {
     Pending,
     Fulfilled,
     Rejected
 };
 
+/**
+ * Returns the given Promise's state as a JS::PromiseState enum value.
+ */
 extern JS_PUBLIC_API(PromiseState)
 GetPromiseState(JS::HandleObject promise);
 
 /**
+ * Returns the given Promise's process-unique ID.
+ */
+JS_PUBLIC_API(double)
+GetPromiseID(JS::HandleObject promise);
+
+/**
+ * Returns the given Promise's result: either the resolution value for
+ * fulfilled promises, or the rejection reason for rejected ones.
+ */
+extern JS_PUBLIC_API(JS::Value)
+GetPromiseResult(JS::HandleObject promise);
+
+/**
+ * Returns a js::SavedFrame linked list of the stack that lead to the given
+ * Promise's allocation.
+ */
+extern JS_PUBLIC_API(JSObject*)
+GetPromiseAllocationSite(JS::HandleObject promise);
+
+extern JS_PUBLIC_API(JSObject*)
+GetPromiseResolutionSite(JS::HandleObject promise);
+
+/**
  * Calls the current compartment's original Promise.resolve on the original
  * Promise constructor, with `resolutionValue` passed as an argument.
  */
 extern JS_PUBLIC_API(JSObject*)
 CallOriginalPromiseResolve(JSContext* cx, JS::HandleValue resolutionValue);
 
 /**
  * Calls the current compartment's original Promise.reject on the original
  * Promise constructor, with `resolutionValue` passed as an argument.
  */
 extern JS_PUBLIC_API(JSObject*)
 CallOriginalPromiseReject(JSContext* cx, JS::HandleValue rejectionValue);
 
 /**
- * Resolves the given `promise` with the given `resolutionValue`.
+ * Resolves the given Promise with the given `resolutionValue`.
  *
  * Calls the `resolve` function that was passed to the executor function when
  * the Promise was created.
  */
 extern JS_PUBLIC_API(bool)
 ResolvePromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue resolutionValue);
 
 /**
rename from dom/promise/tests/test_dependentPromises.html
rename to js/src/tests/ecma_6/Promise/dependent-promises.js
--- a/dom/promise/tests/test_dependentPromises.html
+++ b/js/src/tests/ecma_6/Promise/dependent-promises.js
@@ -1,53 +1,41 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1083950
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 1083950</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
-  <script type="application/javascript">
+// |reftest| skip-if(!xulRuntime.shell) -- needs Debugger
 
-  /** Test for Bug 1083950 **/
-  var p = new Promise(() => {});
-  p.name = "p";
-  var q = p.then();
-  q.name = "q";
-  var r = p.then(null, () => {});
-  r.name = "r";
-  var s = Promise.all([p, q]);
-  s.name = "s";
-  var t = Promise.race([r, s]);
-  t.name = "t";
+if (!this.Promise) {
+    this.reportCompare && reportCompare(true,true);
+    quit(0);
+}
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
 
-  function getDependentNames(promise) {
-    return PromiseDebugging.getDependentPromises(promise).map((p) => p.name);
-  }
-
-  function arraysEqual(arr1, arr2, msg) {
-    is(arr1.length, arr2.length, msg + ": length");
-    for (var i = 0; i < arr1.length; ++i) {
-      is(arr1[i], arr2[i], msg + ": [" + i + "]");
-    }
-  }
+g.eval(`
+var p = new Promise(() => {});
+p.name = "p";
+var q = p.then();
+q.name = "q";
+var r = p.then(null, () => {});
+r.name = "r";
+var s = Promise.all([p, q]);
+s.name = "s";
+var t = Promise.race([r, s]);
+t.name = "t";
+`);
 
-  arraysEqual(getDependentNames(p), ["q", "r", "s"], "deps for p");
-  arraysEqual(getDependentNames(q), ["s"], "deps for q");
-  arraysEqual(getDependentNames(r), ["t"], "deps for r");
-  arraysEqual(getDependentNames(s), ["t"], "deps for s");
+function getDependentNames(promise) {
+    return gw.makeDebuggeeValue(promise).promiseDependentPromises.map((p) => p.getOwnPropertyDescriptor('name').value);
+}
 
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1083950">Mozilla Bug 1083950</a>
-<p id="display"></p>
-<div id="content" style="display: none">
+function arraysEqual(arr1, arr2, msg) {
+    assertEq(arr1.length, arr2.length, msg + ": length");
+    for (var i = 0; i < arr1.length; ++i) {
+        assertEq(arr1[i], arr2[i], msg + ": [" + i + "]");
+    }
+}
 
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
+arraysEqual(getDependentNames(g.p), ["q", "r", "s"], "deps for p");
+arraysEqual(getDependentNames(g.q), ["s"], "deps for q");
+arraysEqual(getDependentNames(g.r), ["t"], "deps for r");
+arraysEqual(getDependentNames(g.s), ["t"], "deps for s");
+
+this.reportCompare && reportCompare(true,true);
--- 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
@@ -43,16 +43,23 @@ for (let i = 0; i < 3; i++) {
 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));
+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));
 
 drainJobQueue();
 
 assertEq(result instanceof Array, true);
 assertEq(result.length, 0);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -197,16 +197,21 @@
     macro(optimizedOut, optimizedOut, "optimizedOut") \
     macro(other, other, "other") \
     macro(outOfMemory, outOfMemory, "out of memory") \
     macro(ownKeys, ownKeys, "ownKeys") \
     macro(parseFloat, parseFloat, "parseFloat") \
     macro(parseInt, parseInt, "parseInt") \
     macro(pattern, pattern, "pattern") \
     macro(preventExtensions, preventExtensions, "preventExtensions") \
+    macro(promise, promise, "promise") \
+    macro(state, state, "state") \
+    macro(pending, pending, "pending") \
+    macro(fulfilled, fulfilled, "fulfilled") \
+    macro(rejected, rejected, "rejected") \
     macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
     macro(proto, proto, "__proto__") \
     macro(prototype, prototype, "prototype") \
     macro(proxy, proxy, "proxy") \
     macro(reason, reason, "reason") \
     macro(Reify, Reify, "Reify") \
     macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \
     macro(resumeGenerator, resumeGenerator, "resumeGenerator") \
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -14,22 +14,24 @@
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jshashutil.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jswrapper.h"
 
+#include "builtin/Promise.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/Parser.h"
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineJIT.h"
+#include "js/Date.h"
 #include "js/GCAPI.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/Vector.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/DebuggerMemory.h"
 #include "vm/SPSProfiler.h"
 #include "vm/TraceLogging.h"
 #include "vm/WrapperObject.h"
@@ -7571,24 +7573,42 @@ DebuggerObject_checkThis(JSContext* cx, 
 #define THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj)            \
     CallArgs args = CallArgsFromVp(argc, vp);                                 \
     RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname));         \
     if (!obj)                                                                 \
         return false;                                                         \
     obj = (JSObject*) obj->as<NativeObject>().getPrivate();                   \
     MOZ_ASSERT(obj)
 
-#define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj) \
-   CallArgs args = CallArgsFromVp(argc, vp);                                  \
-   RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname));          \
-   if (!obj)                                                                  \
-       return false;                                                          \
-   Debugger* dbg = Debugger::fromChildJSObject(obj);                          \
-   obj = (JSObject*) obj->as<NativeObject>().getPrivate();                    \
-   MOZ_ASSERT(obj)
+#define THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj)  \
+    CallArgs args = CallArgsFromVp(argc, vp);                                  \
+    RootedObject obj(cx, DebuggerObject_checkThis(cx, args, fnname));          \
+    if (!obj)                                                                  \
+        return false;                                                          \
+    Debugger* dbg = Debugger::fromChildJSObject(obj);                          \
+    obj = (JSObject*) obj->as<NativeObject>().getPrivate();                    \
+    MOZ_ASSERT(obj)
+
+#define THIS_DEBUGOBJECT_PROMISE(cx, argc, vp, fnname, args, obj)                   \
+   THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, fnname, args, obj);                      \
+   if (!obj->is<PromiseObject>()) {                                                 \
+       JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,  \
+                            "Debugger", "Promise", obj->getClass()->name);          \
+       return false;                                                                \
+   }                                                                                \
+   Rooted<PromiseObject*> promise(cx, &obj->as<PromiseObject>());
+
+#define THIS_DEBUGOBJECT_OWNER_PROMISE(cx, argc, vp, fnname, args, dbg, obj)        \
+   THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, fnname, args, dbg, obj);           \
+   if (!obj->is<PromiseObject>()) {                                                 \
+       JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,  \
+                            "Debugger", "Promise", obj->getClass()->name);          \
+       return false;                                                                \
+   }                                                                                \
+   Rooted<PromiseObject*> promise(cx, &obj->as<PromiseObject>());
 
 static bool
 DebuggerObject_construct(JSContext* cx, unsigned argc, Value* vp)
 {
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
                          "Debugger.Object");
     return false;
 }
@@ -7861,34 +7881,176 @@ DebuggerObject_getBoundArguments(JSConte
     JSObject* aobj = NewDenseCopiedArray(cx, boundArgs.length(), boundArgs.begin());
     if (!aobj)
         return false;
     args.rval().setObject(*aobj);
     return true;
 }
 
 static bool
+null(CallArgs& args)
+{
+    args.rval().setNull();
+    return true;
+}
+
+#ifdef SPIDERMONKEY_PROMISE
+static bool
+DebuggerObject_getIsPromise(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get isPromise", args, refobj);
+
+    args.rval().setBoolean(refobj->is<PromiseObject>());
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseState(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_OWNER_PROMISE(cx, argc, vp, "get promiseState", args, dbg, refobj);
+
+    RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+    RootedValue result(cx, UndefinedValue());
+    RootedValue reason(cx, UndefinedValue());
+    if (!obj)
+        return false;
+    RootedValue state(cx);
+    switch (promise->state()) {
+      case JS::PromiseState::Pending:
+        state.setString(cx->names().pending);
+        break;
+      case JS::PromiseState::Fulfilled:
+        state.setString(cx->names().fulfilled);
+        result = promise->value();
+        break;
+      case JS::PromiseState::Rejected:
+        state.setString(cx->names().rejected);
+        reason = promise->reason();
+        break;
+    }
+
+    if (!dbg->wrapDebuggeeValue(cx, &result))
+        return false;
+    if (!dbg->wrapDebuggeeValue(cx, &reason))
+        return false;
+
+    if (!DefineProperty(cx, obj, cx->names().state.get(), state))
+        return false;
+    if (!DefineProperty(cx, obj, cx->names().value.get(), result))
+        return false;
+    if (!DefineProperty(cx, obj, cx->names().reason.get(), reason))
+        return false;
+    args.rval().setObject(*obj);
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseLifetime(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_PROMISE(cx, argc, vp, "get promiseLifetime", args, refobj);
+
+    args.rval().setNumber(promise->lifetime());
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseTimeToResolution(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_PROMISE(cx, argc, vp, "get promiseTimeToResolution", args, refobj);
+
+    if (promise->state() == JS::PromiseState::Pending) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROMISE_NOT_RESOLVED);
+        return false;
+    }
+
+    args.rval().setNumber(promise->timeToResolution());
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseAllocationSite(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_PROMISE(cx, argc, vp, "get promiseAllocationSite", args, refobj);
+
+    RootedObject allocSite(cx, promise->allocationSite());
+    if (!allocSite)
+        return null(args);
+    if (!cx->compartment()->wrap(cx, &allocSite))
+        return false;
+    args.rval().set(ObjectValue(*allocSite));
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseResolutionSite(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_PROMISE(cx, argc, vp, "get promiseResolutionSite", args, refobj);
+
+    if (promise->state() == JS::PromiseState::Pending) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROMISE_NOT_RESOLVED);
+        return false;
+    }
+
+    RootedObject resolutionSite(cx, promise->resolutionSite());
+    if (!resolutionSite)
+        return null(args);
+    if (!cx->compartment()->wrap(cx, &resolutionSite))
+        return false;
+    args.rval().set(ObjectValue(*resolutionSite));
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseID(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_PROMISE(cx, argc, vp, "get promiseID", args, refobj);
+
+    args.rval().setNumber(promise->getID());
+    return true;
+}
+
+static bool
+DebuggerObject_getPromiseDependentPromises(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT_OWNER_PROMISE(cx, argc, vp, "get promiseDependentPromises", args, dbg, refobj);
+
+    AutoValueVector values(cx);
+    {
+        JSAutoCompartment ac(cx, promise);
+        if (!promise->dependentPromises(cx, values))
+            return false;
+    }
+    for (size_t i = 0; i < values.length(); i++) {
+        if (!dbg->wrapDebuggeeValue(cx, values[i]))
+            return false;
+    }
+    RootedArrayObject promises(cx);
+    if (values.length() == 0)
+        promises = NewDenseEmptyArray(cx);
+    else
+        promises = NewDenseCopiedArray(cx, values.length(), values[0].address());
+    if (!promises)
+        return false;
+    args.rval().setObject(*promises);
+    return true;
+}
+#endif // SPIDERMONKEY_PROMISE
+
+static bool
 DebuggerObject_getGlobal(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get global", args, dbg, obj);
 
     RootedValue v(cx, ObjectValue(obj->global()));
     if (!dbg->wrapDebuggeeValue(cx, &v))
         return false;
     args.rval().set(v);
     return true;
 }
 
-static bool
-null(CallArgs& args)
-{
-    args.rval().setNull();
-    return true;
-}
-
 /* static */ SavedFrame*
 Debugger::getObjectAllocationSite(JSObject& obj)
 {
     JSObject* metadata = GetObjectMetadata(&obj);
     if (!metadata)
         return nullptr;
 
     MOZ_ASSERT(!metadata->is<WrapperObject>());
@@ -8534,16 +8696,30 @@ static const JSPropertySpec DebuggerObje
     JS_PSG("boundTargetFunction", DebuggerObject_getBoundTargetFunction, 0),
     JS_PSG("boundThis", DebuggerObject_getBoundThis, 0),
     JS_PSG("boundArguments", DebuggerObject_getBoundArguments, 0),
     JS_PSG("global", DebuggerObject_getGlobal, 0),
     JS_PSG("allocationSite", DebuggerObject_getAllocationSite, 0),
     JS_PS_END
 };
 
+#ifdef SPIDERMONKEY_PROMISE
+static const JSPropertySpec DebuggerObject_promiseProperties[] = {
+    JS_PSG("isPromise", DebuggerObject_getIsPromise, 0),
+    JS_PSG("promiseState", DebuggerObject_getPromiseState, 0),
+    JS_PSG("promiseLifetime", DebuggerObject_getPromiseLifetime, 0),
+    JS_PSG("promiseTimeToResolution", DebuggerObject_getPromiseTimeToResolution, 0),
+    JS_PSG("promiseAllocationSite", DebuggerObject_getPromiseAllocationSite, 0),
+    JS_PSG("promiseResolutionSite", DebuggerObject_getPromiseResolutionSite, 0),
+    JS_PSG("promiseID", DebuggerObject_getPromiseID, 0),
+    JS_PSG("promiseDependentPromises", DebuggerObject_getPromiseDependentPromises, 0),
+    JS_PS_END
+};
+#endif // SPIDERMONKEY_PROMISE
+
 static const JSFunctionSpec DebuggerObject_methods[] = {
     JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0),
     JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0),
     JS_FN("getOwnPropertySymbols", DebuggerObject_getOwnPropertySymbols, 0, 0),
     JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0),
     JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0),
     JS_FN("deleteProperty", DebuggerObject_deleteProperty, 1, 0),
     JS_FN("seal", DebuggerObject_seal, 0, 0),
@@ -9099,16 +9275,21 @@ JS_DefineDebuggerObject(JSContext* cx, H
 
     objectProto = InitClass(cx, debugCtor, objProto, &DebuggerObject_class,
                             DebuggerObject_construct, 0,
                             DebuggerObject_properties, DebuggerObject_methods,
                             nullptr, nullptr);
     if (!objectProto)
         return false;
 
+#ifdef SPIDERMONKEY_PROMISE
+    if (!DefinePropertiesAndFunctions(cx, objectProto, DebuggerObject_promiseProperties, nullptr))
+        return false;
+#endif // SPIDERMONKEY_PROMISE
+
     envProto = InitClass(cx, debugCtor, objProto, &DebuggerEnv_class,
                          DebuggerEnv_construct, 0,
                          DebuggerEnv_properties, DebuggerEnv_methods,
                          nullptr, nullptr);
     if (!envProto)
         return false;
 
     memoryProto = InitClass(cx, debugCtor, objProto, &DebuggerMemory::class_,
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1952,16 +1952,40 @@ intrinsic_onPromiseSettled(JSContext* cx
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     RootedObject promise(cx, &args[0].toObject());
     JS::dbg::onPromiseSettled(cx, promise);
     args.rval().setUndefined();
     return true;
 }
 
+/**
+ * Intrinsic used to tell the debugger about settled promises.
+ *
+ * This is invoked both when resolving and rejecting promises, after the
+ * resulting state has been set on the promise, and it's up to the debugger
+ * to act on this signal in whichever way it wants.
+ */
+static bool
+intrinsic_captureCurrentStack(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() < 2);
+    unsigned maxFrameCount = 0;
+    if (args.length() == 1)
+        maxFrameCount = args[0].toInt32();
+
+    RootedObject stack(cx);
+    if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount))
+        return false;
+
+    args.rval().setObject(*stack);
+    return true;
+}
+
 // The self-hosting global isn't initialized with the normal set of builtins.
 // Instead, individual C++-implemented functions that're required by
 // self-hosted code are defined as global functions. Accessing these
 // functions via a content compartment's builtins would be unsafe, because
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
@@ -2280,16 +2304,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0),
     JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
     JS_FN("IsModuleNamespace", intrinsic_IsInstanceOfBuiltin<ModuleNamespaceObject>, 1, 0),
     JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0),
     JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
     JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
 
     JS_FN("_dbg_onPromiseSettled", intrinsic_onPromiseSettled, 1, 0),
+    JS_FN("_dbg_captureCurrentStack", intrinsic_captureCurrentStack, 1, 0),
 
     JS_FS_END
 };
 
 void
 js::FillSelfHostingCompileOptions(CompileOptions& options)
 {
     /*