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:
Till Schneidereit 2016-02-10 23:10:08 +01:00
Родитель cbced0986f
Коммит 069b390f89
17 изменённых файлов: 667 добавлений и 85 удалений

Просмотреть файл

@ -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
};