author | Till Schneidereit <till@tillschneidereit.net> |
Wed, 10 Feb 2016 23:10:08 +0100 | |
changeset 289863 | e947c9941fe17266770e9f56f283f0d7628b2b65 |
parent 289862 | 87c4e3921c4c419001c3ae554ab4249d3ee13c0a |
child 289864 | d860e70ee337ba7a648d7f8de234ea075c83f3f8 |
push id | 74020 |
push user | tschneidereit@gmail.com |
push date | Tue, 22 Mar 2016 23:02:59 +0000 |
treeherder | mozilla-inbound@e947c9941fe1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | shu, fitzgen |
bugs | 911216 |
milestone | 48.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
|
--- 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) { /*