зеркало из https://github.com/mozilla/gecko-dev.git
809 строки
25 KiB
JavaScript
809 строки
25 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
const { Cu } = require("chrome");
|
|
const { GeneratedLocation } = require("devtools/server/actors/common");
|
|
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
|
const { assert } = DevToolsUtils;
|
|
|
|
loader.lazyRequireGetter(this, "PropertyIteratorActor", "devtools/server/actors/object/property-iterator", true);
|
|
loader.lazyRequireGetter(this, "SymbolIteratorActor", "devtools/server/actors/object/symbol-iterator", true);
|
|
loader.lazyRequireGetter(this, "previewers", "devtools/server/actors/object/previewers");
|
|
loader.lazyRequireGetter(this, "stringify", "devtools/server/actors/object/stringifiers");
|
|
|
|
const {
|
|
getArrayLength,
|
|
getPromiseState,
|
|
isArray,
|
|
isTypedArray,
|
|
} = require("devtools/server/actors/object/utils");
|
|
/**
|
|
* Creates an actor for the specified object.
|
|
*
|
|
* @param obj Debugger.Object
|
|
* The debuggee object.
|
|
* @param hooks Object
|
|
* A collection of abstract methods that are implemented by the caller.
|
|
* ObjectActor requires the following functions to be implemented by
|
|
* the caller:
|
|
* - createValueGrip
|
|
* Creates a value grip for the given object
|
|
* - sources
|
|
* TabSources getter that manages the sources of a thread
|
|
* - createEnvironmentActor
|
|
* Creates and return an environment actor
|
|
* - getGripDepth
|
|
* An actor's grip depth getter
|
|
* - incrementGripDepth
|
|
* Increment the actor's grip depth
|
|
* - decrementGripDepth
|
|
* Decrement the actor's grip depth
|
|
* - globalDebugObject
|
|
* The Debuggee Global Object as given by the ThreadActor
|
|
*/
|
|
function ObjectActor(obj, {
|
|
createValueGrip: createValueGripHook,
|
|
sources,
|
|
createEnvironmentActor,
|
|
getGripDepth,
|
|
incrementGripDepth,
|
|
decrementGripDepth,
|
|
getGlobalDebugObject
|
|
}) {
|
|
assert(!obj.optimizedOut,
|
|
"Should not create object actors for optimized out values!");
|
|
this.obj = obj;
|
|
this.hooks = {
|
|
createValueGrip: createValueGripHook,
|
|
sources,
|
|
createEnvironmentActor,
|
|
getGripDepth,
|
|
incrementGripDepth,
|
|
decrementGripDepth,
|
|
getGlobalDebugObject
|
|
};
|
|
this.iterators = new Set();
|
|
}
|
|
|
|
ObjectActor.prototype = {
|
|
actorPrefix: "obj",
|
|
|
|
rawValue: function() {
|
|
return this.obj.unsafeDereference();
|
|
},
|
|
|
|
/**
|
|
* Returns a grip for this actor for returning in a protocol message.
|
|
*/
|
|
grip: function() {
|
|
let g = {
|
|
"type": "object",
|
|
"actor": this.actorID,
|
|
"class": this.obj.class,
|
|
};
|
|
|
|
let unwrapped = DevToolsUtils.unwrap(this.obj);
|
|
|
|
// Unsafe objects must be treated carefully.
|
|
if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
|
|
if (DevToolsUtils.isCPOW(this.obj)) {
|
|
// Cross-process object wrappers can't be accessed.
|
|
g.class = "CPOW: " + g.class;
|
|
} else if (unwrapped === undefined) {
|
|
// Objects belonging to an invisible-to-debugger compartment might be proxies,
|
|
// so just in case they shouldn't be accessed.
|
|
g.class = "InvisibleToDebugger: " + g.class;
|
|
} else if (unwrapped.isProxy) {
|
|
// Proxy objects can run traps when accessed, so just create a preview with
|
|
// the target and the handler.
|
|
g.class = "Proxy";
|
|
this.hooks.incrementGripDepth();
|
|
previewers.Proxy[0](this, g, null);
|
|
this.hooks.decrementGripDepth();
|
|
}
|
|
return g;
|
|
}
|
|
|
|
// If the debuggee does not subsume the object's compartment, most properties won't
|
|
// be accessible. Cross-orgin Window and Location objects might expose some, though.
|
|
// Change the displayed class, but when creating the preview use the original one.
|
|
if (unwrapped === null) {
|
|
g.class = "Restricted";
|
|
}
|
|
|
|
this.hooks.incrementGripDepth();
|
|
|
|
g.extensible = this.obj.isExtensible();
|
|
g.frozen = this.obj.isFrozen();
|
|
g.sealed = this.obj.isSealed();
|
|
|
|
if (g.class == "Promise") {
|
|
g.promiseState = this._createPromiseState();
|
|
}
|
|
|
|
// FF40+: Allow to know how many properties an object has to lazily display them
|
|
// when there is a bunch.
|
|
if (isTypedArray(g)) {
|
|
// Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
|
|
g.ownPropertyLength = getArrayLength(this.obj);
|
|
} else {
|
|
try {
|
|
g.ownPropertyLength = this.obj.getOwnPropertyNames().length;
|
|
} catch (err) {
|
|
// The above can throw when the debuggee does not subsume the object's
|
|
// compartment, or for some WrappedNatives like Cu.Sandbox.
|
|
}
|
|
}
|
|
|
|
let raw = this.obj.unsafeDereference();
|
|
|
|
// If Cu is not defined, we are running on a worker thread, where xrays
|
|
// don't exist.
|
|
if (Cu) {
|
|
raw = Cu.unwaiveXrays(raw);
|
|
}
|
|
|
|
if (!DevToolsUtils.isSafeJSObject(raw)) {
|
|
raw = null;
|
|
}
|
|
|
|
for (let fn of previewers[this.obj.class] || previewers.Object) {
|
|
try {
|
|
if (fn(this, g, raw)) {
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
let msg = "ObjectActor.prototype.grip previewer function";
|
|
DevToolsUtils.reportException(msg, e);
|
|
}
|
|
}
|
|
|
|
this.hooks.decrementGripDepth();
|
|
return g;
|
|
},
|
|
|
|
/**
|
|
* Returns an object exposing the internal Promise state.
|
|
*/
|
|
_createPromiseState: function() {
|
|
const { state, value, reason } = getPromiseState(this.obj);
|
|
let promiseState = { state };
|
|
|
|
if (state == "fulfilled") {
|
|
promiseState.value = this.hooks.createValueGrip(value);
|
|
} else if (state == "rejected") {
|
|
promiseState.reason = this.hooks.createValueGrip(reason);
|
|
}
|
|
|
|
promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
|
|
|
|
// Only add the timeToSettle property if the Promise isn't pending.
|
|
if (state !== "pending") {
|
|
promiseState.timeToSettle = this.obj.promiseTimeToResolution;
|
|
}
|
|
|
|
return promiseState;
|
|
},
|
|
|
|
/**
|
|
* Releases this actor from the pool.
|
|
*/
|
|
release: function() {
|
|
if (this.registeredPool.objectActors) {
|
|
this.registeredPool.objectActors.delete(this.obj);
|
|
}
|
|
this.iterators.forEach(actor => this.registeredPool.removeActor(actor));
|
|
this.iterators.clear();
|
|
this.registeredPool.removeActor(this);
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the definition site of this function
|
|
* object.
|
|
*/
|
|
onDefinitionSite: function() {
|
|
if (this.obj.class != "Function") {
|
|
return {
|
|
from: this.actorID,
|
|
error: "objectNotFunction",
|
|
message: this.actorID + " is not a function."
|
|
};
|
|
}
|
|
|
|
if (!this.obj.script) {
|
|
return {
|
|
from: this.actorID,
|
|
error: "noScript",
|
|
message: this.actorID + " has no Debugger.Script"
|
|
};
|
|
}
|
|
|
|
return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
|
|
this.hooks.sources().createNonSourceMappedActor(this.obj.script.source),
|
|
this.obj.script.startLine,
|
|
0 // TODO bug 901138: use Debugger.Script.prototype.startColumn
|
|
)).then((originalLocation) => {
|
|
return {
|
|
source: originalLocation.originalSourceActor.form(),
|
|
line: originalLocation.originalLine,
|
|
column: originalLocation.originalColumn
|
|
};
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the names of the properties defined on
|
|
* the object and not its prototype.
|
|
*/
|
|
onOwnPropertyNames: function() {
|
|
let props = [];
|
|
if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
|
|
try {
|
|
props = this.obj.getOwnPropertyNames();
|
|
} catch (err) {
|
|
// The above can throw when the debuggee does not subsume the object's
|
|
// compartment, or for some WrappedNatives like Cu.Sandbox.
|
|
}
|
|
}
|
|
return { from: this.actorID, ownPropertyNames: props };
|
|
},
|
|
|
|
/**
|
|
* Creates an actor to iterate over an object property names and values.
|
|
* See PropertyIteratorActor constructor for more info about options param.
|
|
*
|
|
* @param request object
|
|
* The protocol request object.
|
|
*/
|
|
onEnumProperties: function(request) {
|
|
let actor = new PropertyIteratorActor(this, request.options);
|
|
this.registeredPool.addActor(actor);
|
|
this.iterators.add(actor);
|
|
return { iterator: actor.form() };
|
|
},
|
|
|
|
/**
|
|
* Creates an actor to iterate over entries of a Map/Set-like object.
|
|
*/
|
|
onEnumEntries: function() {
|
|
let actor = new PropertyIteratorActor(this, { enumEntries: true });
|
|
this.registeredPool.addActor(actor);
|
|
this.iterators.add(actor);
|
|
return { iterator: actor.form() };
|
|
},
|
|
|
|
/**
|
|
* Creates an actor to iterate over an object symbols properties.
|
|
*/
|
|
onEnumSymbols: function() {
|
|
let actor = new SymbolIteratorActor(this);
|
|
this.registeredPool.addActor(actor);
|
|
this.iterators.add(actor);
|
|
return { iterator: actor.form() };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the prototype and own properties of
|
|
* the object.
|
|
*
|
|
* @returns {Object} An object containing the data of this.obj, of the following form:
|
|
* - {string} from: this.obj's actorID.
|
|
* - {Object} prototype: The descriptor of this.obj's prototype.
|
|
* - {Object} ownProperties: an object where the keys are the names of the
|
|
* this.obj's ownProperties, and the values the descriptors of
|
|
* the properties.
|
|
* - {Array} ownSymbols: An array containing all descriptors of this.obj's
|
|
* ownSymbols. Here we have an array, and not an object like for
|
|
* ownProperties, because we can have multiple symbols with the same
|
|
* name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
|
|
* - {Object} safeGetterValues: an object that maps this.obj's property names
|
|
* with safe getters descriptors.
|
|
*/
|
|
onPrototypeAndProperties: function() {
|
|
let proto = null;
|
|
let names = [];
|
|
let symbols = [];
|
|
if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
|
|
try {
|
|
proto = this.obj.proto;
|
|
names = this.obj.getOwnPropertyNames();
|
|
symbols = this.obj.getOwnPropertySymbols();
|
|
} catch (err) {
|
|
// The above can throw when the debuggee does not subsume the object's
|
|
// compartment, or for some WrappedNatives like Cu.Sandbox.
|
|
}
|
|
}
|
|
|
|
let ownProperties = Object.create(null);
|
|
let ownSymbols = [];
|
|
|
|
for (let name of names) {
|
|
ownProperties[name] = this._propertyDescriptor(name);
|
|
}
|
|
|
|
for (let sym of symbols) {
|
|
ownSymbols.push({
|
|
name: sym.toString(),
|
|
descriptor: this._propertyDescriptor(sym)
|
|
});
|
|
}
|
|
|
|
return { from: this.actorID,
|
|
prototype: this.hooks.createValueGrip(proto),
|
|
ownProperties,
|
|
ownSymbols,
|
|
safeGetterValues: this._findSafeGetterValues(names) };
|
|
},
|
|
|
|
/**
|
|
* Find the safe getter values for the current Debugger.Object, |this.obj|.
|
|
*
|
|
* @private
|
|
* @param array ownProperties
|
|
* The array that holds the list of known ownProperties names for
|
|
* |this.obj|.
|
|
* @param number [limit=0]
|
|
* Optional limit of getter values to find.
|
|
* @return object
|
|
* An object that maps property names to safe getter descriptors as
|
|
* defined by the remote debugging protocol.
|
|
*/
|
|
_findSafeGetterValues: function(ownProperties, limit = 0) {
|
|
let safeGetterValues = Object.create(null);
|
|
let obj = this.obj;
|
|
let level = 0, i = 0;
|
|
|
|
// Do not search safe getters in unsafe objects.
|
|
if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
|
|
return safeGetterValues;
|
|
}
|
|
|
|
// Most objects don't have any safe getters but inherit some from their
|
|
// prototype. Avoid calling getOwnPropertyNames on objects that may have
|
|
// many properties like Array, strings or js objects. That to avoid
|
|
// freezing firefox when doing so.
|
|
if (isArray(this.obj) || ["Object", "String"].includes(this.obj.class)) {
|
|
obj = obj.proto;
|
|
level++;
|
|
}
|
|
|
|
while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
|
|
let getters = this._findSafeGetters(obj);
|
|
for (let name of getters) {
|
|
// Avoid overwriting properties from prototypes closer to this.obj. Also
|
|
// avoid providing safeGetterValues from prototypes if property |name|
|
|
// is already defined as an own property.
|
|
if (name in safeGetterValues ||
|
|
(obj != this.obj && ownProperties.includes(name))) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore __proto__ on Object.prototye.
|
|
if (!obj.proto && name == "__proto__") {
|
|
continue;
|
|
}
|
|
|
|
let desc = null, getter = null;
|
|
try {
|
|
desc = obj.getOwnPropertyDescriptor(name);
|
|
getter = desc.get;
|
|
} catch (ex) {
|
|
// The above can throw if the cache becomes stale.
|
|
}
|
|
if (!getter) {
|
|
obj._safeGetters = null;
|
|
continue;
|
|
}
|
|
|
|
let result = getter.call(this.obj);
|
|
if (result && !("throw" in result)) {
|
|
let getterValue = undefined;
|
|
if ("return" in result) {
|
|
getterValue = result.return;
|
|
} else if ("yield" in result) {
|
|
getterValue = result.yield;
|
|
}
|
|
// WebIDL attributes specified with the LenientThis extended attribute
|
|
// return undefined and should be ignored.
|
|
if (getterValue !== undefined) {
|
|
safeGetterValues[name] = {
|
|
getterValue: this.hooks.createValueGrip(getterValue),
|
|
getterPrototypeLevel: level,
|
|
enumerable: desc.enumerable,
|
|
writable: level == 0 ? desc.writable : true,
|
|
};
|
|
if (limit && ++i == limit) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (limit && i == limit) {
|
|
break;
|
|
}
|
|
|
|
obj = obj.proto;
|
|
level++;
|
|
}
|
|
|
|
return safeGetterValues;
|
|
},
|
|
|
|
/**
|
|
* Find the safe getters for a given Debugger.Object. Safe getters are native
|
|
* getters which are safe to execute.
|
|
*
|
|
* @private
|
|
* @param Debugger.Object object
|
|
* The Debugger.Object where you want to find safe getters.
|
|
* @return Set
|
|
* A Set of names of safe getters. This result is cached for each
|
|
* Debugger.Object.
|
|
*/
|
|
_findSafeGetters: function(object) {
|
|
if (object._safeGetters) {
|
|
return object._safeGetters;
|
|
}
|
|
|
|
let getters = new Set();
|
|
|
|
if (!DevToolsUtils.isSafeDebuggerObject(object)) {
|
|
object._safeGetters = getters;
|
|
return getters;
|
|
}
|
|
|
|
let names = [];
|
|
try {
|
|
names = object.getOwnPropertyNames();
|
|
} catch (ex) {
|
|
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
|
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
|
}
|
|
|
|
for (let name of names) {
|
|
let desc = null;
|
|
try {
|
|
desc = object.getOwnPropertyDescriptor(name);
|
|
} catch (e) {
|
|
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
|
|
// allowed (bug 560072).
|
|
}
|
|
if (!desc || desc.value !== undefined || !("get" in desc)) {
|
|
continue;
|
|
}
|
|
|
|
if (DevToolsUtils.hasSafeGetter(desc)) {
|
|
getters.add(name);
|
|
}
|
|
}
|
|
|
|
object._safeGetters = getters;
|
|
return getters;
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the prototype of the object.
|
|
*/
|
|
onPrototype: function() {
|
|
let proto = null;
|
|
if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
|
|
proto = this.obj.proto;
|
|
}
|
|
return { from: this.actorID,
|
|
prototype: this.hooks.createValueGrip(proto) };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the property descriptor of the
|
|
* object's specified property.
|
|
*
|
|
* @param request object
|
|
* The protocol request object.
|
|
*/
|
|
onProperty: function(request) {
|
|
if (!request.name) {
|
|
return { error: "missingParameter",
|
|
message: "no property name was specified" };
|
|
}
|
|
|
|
return { from: this.actorID,
|
|
descriptor: this._propertyDescriptor(request.name) };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the display string for the object.
|
|
*/
|
|
onDisplayString: function() {
|
|
const string = stringify(this.obj);
|
|
return { from: this.actorID,
|
|
displayString: this.hooks.createValueGrip(string) };
|
|
},
|
|
|
|
/**
|
|
* A helper method that creates a property descriptor for the provided object,
|
|
* properly formatted for sending in a protocol response.
|
|
*
|
|
* @private
|
|
* @param string name
|
|
* The property that the descriptor is generated for.
|
|
* @param boolean [onlyEnumerable]
|
|
* Optional: true if you want a descriptor only for an enumerable
|
|
* property, false otherwise.
|
|
* @return object|undefined
|
|
* The property descriptor, or undefined if this is not an enumerable
|
|
* property and onlyEnumerable=true.
|
|
*/
|
|
_propertyDescriptor: function(name, onlyEnumerable) {
|
|
if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) {
|
|
return undefined;
|
|
}
|
|
|
|
let desc;
|
|
try {
|
|
desc = this.obj.getOwnPropertyDescriptor(name);
|
|
} catch (e) {
|
|
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
|
|
// allowed (bug 560072). Inform the user with a bogus, but hopefully
|
|
// explanatory, descriptor.
|
|
return {
|
|
configurable: false,
|
|
writable: false,
|
|
enumerable: false,
|
|
value: e.name
|
|
};
|
|
}
|
|
|
|
if (!desc || onlyEnumerable && !desc.enumerable) {
|
|
return undefined;
|
|
}
|
|
|
|
let retval = {
|
|
configurable: desc.configurable,
|
|
enumerable: desc.enumerable
|
|
};
|
|
|
|
if ("value" in desc) {
|
|
retval.writable = desc.writable;
|
|
retval.value = this.hooks.createValueGrip(desc.value);
|
|
} else {
|
|
if ("get" in desc) {
|
|
retval.get = this.hooks.createValueGrip(desc.get);
|
|
}
|
|
if ("set" in desc) {
|
|
retval.set = this.hooks.createValueGrip(desc.set);
|
|
}
|
|
}
|
|
return retval;
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the source code of a function.
|
|
*
|
|
* @param request object
|
|
* The protocol request object.
|
|
*/
|
|
onDecompile: function(request) {
|
|
if (this.obj.class !== "Function") {
|
|
return { error: "objectNotFunction",
|
|
message: "decompile request is only valid for object grips " +
|
|
"with a 'Function' class." };
|
|
}
|
|
|
|
return { from: this.actorID,
|
|
decompiledCode: this.obj.decompile(!!request.pretty) };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the parameters of a function.
|
|
*/
|
|
onParameterNames: function() {
|
|
if (this.obj.class !== "Function") {
|
|
return { error: "objectNotFunction",
|
|
message: "'parameterNames' request is only valid for object " +
|
|
"grips with a 'Function' class." };
|
|
}
|
|
|
|
return { parameterNames: this.obj.parameterNames };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to release a thread-lifetime grip.
|
|
*/
|
|
onRelease: function() {
|
|
this.release();
|
|
return {};
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to provide the lexical scope of a function.
|
|
*/
|
|
onScope: function() {
|
|
if (this.obj.class !== "Function") {
|
|
return { error: "objectNotFunction",
|
|
message: "scope request is only valid for object grips with a" +
|
|
" 'Function' class." };
|
|
}
|
|
|
|
let envActor = this.hooks.createEnvironmentActor(this.obj.environment,
|
|
this.registeredPool);
|
|
if (!envActor) {
|
|
return { error: "notDebuggee",
|
|
message: "cannot access the environment of this function." };
|
|
}
|
|
|
|
return { from: this.actorID, scope: envActor.form() };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to get the list of dependent promises of a
|
|
* promise.
|
|
*
|
|
* @return object
|
|
* Returns an object containing an array of object grips of the
|
|
* dependent promises
|
|
*/
|
|
onDependentPromises: function() {
|
|
if (this.obj.class != "Promise") {
|
|
return { error: "objectNotPromise",
|
|
message: "'dependentPromises' request is only valid for " +
|
|
"object grips with a 'Promise' class." };
|
|
}
|
|
|
|
let promises = this.obj.promiseDependentPromises
|
|
.map(p => this.hooks.createValueGrip(p));
|
|
|
|
return { promises };
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to get the allocation stack of a promise.
|
|
*/
|
|
onAllocationStack: function() {
|
|
if (this.obj.class != "Promise") {
|
|
return { error: "objectNotPromise",
|
|
message: "'allocationStack' request is only valid for " +
|
|
"object grips with a 'Promise' class." };
|
|
}
|
|
|
|
let stack = this.obj.promiseAllocationSite;
|
|
let allocationStacks = [];
|
|
|
|
while (stack) {
|
|
if (stack.source) {
|
|
let source = this._getSourceOriginalLocation(stack);
|
|
|
|
if (source) {
|
|
allocationStacks.push(source);
|
|
}
|
|
}
|
|
stack = stack.parent;
|
|
}
|
|
|
|
return Promise.all(allocationStacks).then(stacks => {
|
|
return { allocationStack: stacks };
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to get the fulfillment stack of a promise.
|
|
*/
|
|
onFulfillmentStack: function() {
|
|
if (this.obj.class != "Promise") {
|
|
return { error: "objectNotPromise",
|
|
message: "'fulfillmentStack' request is only valid for " +
|
|
"object grips with a 'Promise' class." };
|
|
}
|
|
|
|
let stack = this.obj.promiseResolutionSite;
|
|
let fulfillmentStacks = [];
|
|
|
|
while (stack) {
|
|
if (stack.source) {
|
|
let source = this._getSourceOriginalLocation(stack);
|
|
|
|
if (source) {
|
|
fulfillmentStacks.push(source);
|
|
}
|
|
}
|
|
stack = stack.parent;
|
|
}
|
|
|
|
return Promise.all(fulfillmentStacks).then(stacks => {
|
|
return { fulfillmentStack: stacks };
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handle a protocol request to get the rejection stack of a promise.
|
|
*/
|
|
onRejectionStack: function() {
|
|
if (this.obj.class != "Promise") {
|
|
return { error: "objectNotPromise",
|
|
message: "'rejectionStack' request is only valid for " +
|
|
"object grips with a 'Promise' class." };
|
|
}
|
|
|
|
let stack = this.obj.promiseResolutionSite;
|
|
let rejectionStacks = [];
|
|
|
|
while (stack) {
|
|
if (stack.source) {
|
|
let source = this._getSourceOriginalLocation(stack);
|
|
|
|
if (source) {
|
|
rejectionStacks.push(source);
|
|
}
|
|
}
|
|
stack = stack.parent;
|
|
}
|
|
|
|
return Promise.all(rejectionStacks).then(stacks => {
|
|
return { rejectionStack: stacks };
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Helper function for fetching the source location of a SavedFrame stack.
|
|
*
|
|
* @param SavedFrame stack
|
|
* The promise allocation stack frame
|
|
* @return object
|
|
* Returns an object containing the source location of the SavedFrame
|
|
* stack.
|
|
*/
|
|
_getSourceOriginalLocation: function(stack) {
|
|
let source;
|
|
|
|
// Catch any errors if the source actor cannot be found
|
|
try {
|
|
source = this.hooks.sources().getSourceActorByURL(stack.source);
|
|
} catch (e) {
|
|
// ignored
|
|
}
|
|
|
|
if (!source) {
|
|
return null;
|
|
}
|
|
|
|
return this.hooks.sources().getOriginalLocation(new GeneratedLocation(
|
|
source,
|
|
stack.line,
|
|
stack.column
|
|
)).then((originalLocation) => {
|
|
return {
|
|
source: originalLocation.originalSourceActor.form(),
|
|
line: originalLocation.originalLine,
|
|
column: originalLocation.originalColumn,
|
|
functionDisplayName: stack.functionDisplayName
|
|
};
|
|
});
|
|
}
|
|
};
|
|
|
|
ObjectActor.prototype.requestTypes = {
|
|
"definitionSite": ObjectActor.prototype.onDefinitionSite,
|
|
"parameterNames": ObjectActor.prototype.onParameterNames,
|
|
"prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
|
|
"enumProperties": ObjectActor.prototype.onEnumProperties,
|
|
"prototype": ObjectActor.prototype.onPrototype,
|
|
"property": ObjectActor.prototype.onProperty,
|
|
"displayString": ObjectActor.prototype.onDisplayString,
|
|
"ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
|
|
"decompile": ObjectActor.prototype.onDecompile,
|
|
"release": ObjectActor.prototype.onRelease,
|
|
"scope": ObjectActor.prototype.onScope,
|
|
"dependentPromises": ObjectActor.prototype.onDependentPromises,
|
|
"allocationStack": ObjectActor.prototype.onAllocationStack,
|
|
"fulfillmentStack": ObjectActor.prototype.onFulfillmentStack,
|
|
"rejectionStack": ObjectActor.prototype.onRejectionStack,
|
|
"enumEntries": ObjectActor.prototype.onEnumEntries,
|
|
"enumSymbols": ObjectActor.prototype.onEnumSymbols,
|
|
};
|
|
|
|
exports.ObjectActor = ObjectActor;
|