зеркало из https://github.com/mozilla/gecko-dev.git
Bug 911216 - Part 11: Implement all Promise inspection functionality as Debugger getters. r=shu,fitzgen
--HG-- rename : dom/promise/tests/test_dependentPromises.html => js/src/tests/ecma_6/Promise/dependent-promises.js extra : rebase_source : af4f22cb26a42d3e6262e7fe582ea301c9055a70
This commit is contained in:
Родитель
cbced0986f
Коммит
069b390f89
|
@ -1,7 +1,6 @@
|
|||
[DEFAULT]
|
||||
skip-if = buildapp == 'b2g'
|
||||
|
||||
[test_dependentPromises.html]
|
||||
[test_on_new_promise.html]
|
||||
[test_on_promise_settled.html]
|
||||
[test_on_promise_settled_duplicates.html]
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<!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">
|
||||
|
||||
/** 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";
|
||||
|
||||
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 + "]");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
</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">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
#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"
|
||||
|
@ -89,6 +92,14 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
|
|||
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));
|
||||
|
@ -172,6 +183,90 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
|
|||
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.
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define builtin_Promise_h
|
||||
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "js/Date.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
namespace js {
|
||||
|
@ -17,20 +18,43 @@ 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
|
||||
|
|
|
@ -128,6 +128,8 @@ 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);
|
||||
|
@ -148,6 +150,10 @@ function ResolvePromise(promise, valueOrReason, reactionsSlot, state) {
|
|||
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.
|
||||
|
@ -348,6 +354,7 @@ function PerformPromiseAll(iteratorRecord, constructor, resultCapability) {
|
|||
let iterator = iteratorRecord.iterator;
|
||||
let next;
|
||||
let nextValue;
|
||||
let allPromise = resultCapability.promise;
|
||||
while (true) {
|
||||
try {
|
||||
// Step 6.a.
|
||||
|
@ -377,7 +384,7 @@ function PerformPromiseAll(iteratorRecord, constructor, resultCapability) {
|
|||
callContentFunction(resultCapability.resolve, undefined, values);
|
||||
|
||||
// Step 6.d.iv.
|
||||
return resultCapability.promise;
|
||||
return allPromise;
|
||||
}
|
||||
try {
|
||||
// Step 6.e.
|
||||
|
@ -405,8 +412,7 @@ function PerformPromiseAll(iteratorRecord, constructor, resultCapability) {
|
|||
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++;
|
||||
|
@ -545,6 +551,7 @@ function PerformPromiseRace(iteratorRecord, resultCapability, C) {
|
|||
|
||||
// Step 1.
|
||||
let iterator = iteratorRecord.iterator;
|
||||
let racePromise = resultCapability.promise;
|
||||
let next;
|
||||
let nextValue;
|
||||
while (true) {
|
||||
|
@ -567,7 +574,7 @@ function PerformPromiseRace(iteratorRecord, resultCapability, C) {
|
|||
iteratorRecord.done = true;
|
||||
|
||||
// Step 1.d.ii.
|
||||
return resultCapability.promise;
|
||||
return racePromise;
|
||||
}
|
||||
try {
|
||||
// Step 1.e.
|
||||
|
@ -584,10 +591,115 @@ function PerformPromiseRace(iteratorRecord, resultCapability, C) {
|
|||
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")
|
||||
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 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.
|
||||
|
|
|
@ -63,6 +63,11 @@
|
|||
#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
|
||||
|
|
|
@ -159,6 +159,9 @@ from its prototype:
|
|||
`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
|
||||
|
@ -195,6 +198,89 @@ from its prototype:
|
|||
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
|
||||
|
|
|
@ -117,7 +117,9 @@ compartment.
|
|||
|
||||
<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>{
|
||||
|
@ -127,9 +129,10 @@ compartment.
|
|||
<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>{
|
||||
|
@ -496,7 +499,6 @@ other kinds of objects.
|
|||
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.
|
||||
|
|
|
@ -64,4 +64,4 @@ resource 'img-alloc-plot' alloc-plot-console.png $RBASE/8461
|
|||
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"
|
||||
|
|
|
@ -421,6 +421,7 @@ MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 0, JSEXN_TYPEERR, "findScripts query objec
|
|||
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}")
|
||||
|
|
|
@ -4687,6 +4687,32 @@ JS::GetPromiseState(JS::HandleObject 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)
|
||||
{
|
||||
|
|
|
@ -4340,9 +4340,35 @@ enum class PromiseState {
|
|||
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.
|
||||
|
@ -4358,7 +4384,7 @@ 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.
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// |reftest| skip-if(!xulRuntime.shell) -- needs Debugger
|
||||
|
||||
if (!this.Promise) {
|
||||
this.reportCompare && reportCompare(true,true);
|
||||
quit(0);
|
||||
}
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger(g);
|
||||
var gw = dbg.addDebuggee(g);
|
||||
|
||||
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";
|
||||
`);
|
||||
|
||||
function getDependentNames(promise) {
|
||||
return gw.makeDebuggeeValue(promise).promiseDependentPromises.map((p) => p.getOwnPropertyDescriptor('name').value);
|
||||
}
|
||||
|
||||
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 + "]");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
|
@ -48,6 +48,13 @@ 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));
|
||||
|
|
|
@ -202,6 +202,11 @@
|
|||
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") \
|
||||
|
|
|
@ -19,12 +19,14 @@
|
|||
#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"
|
||||
|
@ -7576,14 +7578,32 @@ DebuggerObject_checkThis(JSContext* cx, const CallArgs& args, const char* fnname
|
|||
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)
|
||||
|
@ -7865,6 +7885,155 @@ DebuggerObject_getBoundArguments(JSContext* cx, unsigned argc, Value* vp)
|
|||
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)
|
||||
{
|
||||
|
@ -7877,13 +8046,6 @@ DebuggerObject_getGlobal(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
null(CallArgs& args)
|
||||
{
|
||||
args.rval().setNull();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ SavedFrame*
|
||||
Debugger::getObjectAllocationSite(JSObject& obj)
|
||||
{
|
||||
|
@ -8539,6 +8701,20 @@ static const JSPropertySpec DebuggerObject_properties[] = {
|
|||
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),
|
||||
|
@ -9104,6 +9280,11 @@ JS_DefineDebuggerObject(JSContext* cx, HandleObject obj)
|
|||
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,
|
||||
|
|
|
@ -1957,6 +1957,30 @@ intrinsic_onPromiseSettled(JSContext* cx, unsigned argc, Value* vp)
|
|||
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
|
||||
|
@ -2285,6 +2309,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
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
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче