Bug 1506469 - Allow inspecting objects logged to the console without switching to a replaying child, r=lsmyth.

--HG--
extra : rebase_source : 4e00da50edb5eeb8e5d9c490e893e6bddb47a440
This commit is contained in:
Brian Hackett 2018-11-15 14:08:22 -10:00
Родитель a220a51576
Коммит a5f01b639d
2 изменённых файлов: 86 добавлений и 29 удалений

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

@ -243,12 +243,15 @@ ReplayDebugger.prototype = {
// Object methods
/////////////////////////////////////////////////////////
_getObject(id) {
// Objects which |forConsole| is set are objects that were logged in console
// messages, and had their properties recorded so that they can be inspected
// without switching to a replaying child.
_getObject(id, forConsole) {
if (id && !this._objects[id]) {
const data = this._sendRequest({ type: "getObject", id });
switch (data.kind) {
case "Object":
this._objects[id] = new ReplayDebuggerObject(this, data);
this._objects[id] = new ReplayDebuggerObject(this, data, forConsole);
break;
case "Environment":
this._objects[id] = new ReplayDebuggerEnvironment(this, data);
@ -257,13 +260,17 @@ ReplayDebugger.prototype = {
ThrowError("Unknown object kind");
}
}
return this._objects[id];
const rv = this._objects[id];
if (forConsole) {
rv._forConsole = true;
}
return rv;
},
_convertValue(value) {
if (value && typeof value == "object") {
_convertValue(value, forConsole) {
if (isNonNullObject(value)) {
if (value.object) {
return this._getObject(value.object);
return this._getObject(value.object, forConsole);
} else if (value.special == "undefined") {
return undefined;
} else if (value.special == "NaN") {
@ -332,7 +339,8 @@ ReplayDebugger.prototype = {
// other contents of the message can be left alone.
if (message.messageType == "ConsoleAPI" && message.arguments) {
for (let i = 0; i < message.arguments.length; i++) {
message.arguments[i] = this._convertValue(message.arguments[i]);
message.arguments[i] = this._convertValue(message.arguments[i],
/* forConsole = */ true);
}
}
return message;
@ -497,7 +505,7 @@ function ReplayDebuggerFrame(dbg, data) {
this._data = data;
if (this._data.arguments) {
this._data.arguments =
this._data.arguments.map(this._dbg._convertValue.bind(this._dbg));
this._data.arguments.map(a => this._dbg._convertValue(a));
}
}
@ -606,9 +614,10 @@ ReplayDebuggerFrame.prototype = {
// ReplayDebuggerObject
///////////////////////////////////////////////////////////////////////////////
function ReplayDebuggerObject(dbg, data) {
function ReplayDebuggerObject(dbg, data, forConsole) {
this._dbg = dbg;
this._data = data;
this._forConsole = forConsole;
this._properties = null;
}
@ -623,7 +632,6 @@ ReplayDebuggerObject.prototype = {
get isArrowFunction() { return this._data.isArrowFunction; },
get isGeneratorFunction() { return this._data.isGeneratorFunction; },
get isAsyncFunction() { return this._data.isAsyncFunction; },
get proto() { return this._dbg._getObject(this._data.proto); },
get class() { return this._data.class; },
get name() { return this._data.name; },
get displayName() { return this._data.displayName; },
@ -641,6 +649,13 @@ ReplayDebuggerObject.prototype = {
isFrozen() { return this._data.isFrozen; },
unwrap() { return this.isProxy ? NYI() : this; },
get proto() {
// Don't allow inspection of the prototypes of objects logged to the
// console. This is a hack that prevents the object inspector from crawling
// the object's prototype chain.
return this._forConsole ? null : this._dbg._getObject(this._data.proto);
},
unsafeDereference() {
// Direct access to the referent is not currently available.
return null;
@ -664,10 +679,10 @@ ReplayDebuggerObject.prototype = {
_ensureProperties() {
if (!this._properties) {
const properties = this._dbg._sendRequestAllowDiverge({
type: "getObjectProperties",
id: this._data.id,
});
const id = this._data.id;
const properties = this._forConsole
? this._dbg._sendRequest({ type: "getObjectPropertiesForConsole", id })
: this._dbg._sendRequestAllowDiverge({ type: "getObjectProperties", id });
this._properties = {};
properties.forEach(({name, desc}) => { this._properties[name] = desc; });
}
@ -791,4 +806,8 @@ function assert(v) {
}
}
function isNonNullObject(obj) {
return obj && (typeof obj == "object" || typeof obj == "function");
}
module.exports = ReplayDebugger;

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

@ -116,6 +116,10 @@ function scriptFrameForIndex(index) {
return frame;
}
function isNonNullObject(obj) {
return obj && (typeof obj == "object" || typeof obj == "function");
}
///////////////////////////////////////////////////////////////////////////////
// Persistent Script State
///////////////////////////////////////////////////////////////////////////////
@ -179,6 +183,26 @@ dbg.onNewScript = function(script) {
installPendingHandlers();
};
const gConsoleObjectProperties = new Map();
function shouldSaveConsoleProperty({ desc }) {
// When logging an object to the console, only properties captured here will
// be shown. We limit this to non-object data properties, as more complex
// properties have two problems: A) to inspect them we will need to switch to
// a replaying child process, which is very slow when there are many console
// messages, and B) trying to access objects transitively referred to by
// logged console objects will fail when unpaused, and depends on the current
// state of the process otherwise.
return "value" in desc && !isNonNullObject(desc.value);
}
function saveConsoleObjectProperties(obj) {
if (obj instanceof Debugger.Object) {
const properties = getObjectProperties(obj).filter(shouldSaveConsoleProperty);
gConsoleObjectProperties.set(obj, properties);
}
}
///////////////////////////////////////////////////////////////////////////////
// Console Message State
///////////////////////////////////////////////////////////////////////////////
@ -250,6 +274,7 @@ Services.obs.addObserver({
// Message arguments are preserved as debuggee values.
if (apiMessage.arguments) {
contents.arguments = apiMessage.arguments.map(makeDebuggeeValue);
contents.arguments.forEach(saveConsoleObjectProperties);
}
newConsoleMessage("ConsoleAPI", null, contents);
@ -465,7 +490,7 @@ function convertCompletionValue(value) {
}
function makeDebuggeeValue(value) {
if (value && typeof value == "object") {
if (isNonNullObject(value)) {
assert(!(value instanceof Debugger.Object));
const global = Cu.getGlobalForObject(value);
const dbgGlobal = dbg.makeGlobalObjectReference(global);
@ -517,6 +542,24 @@ function forwardToScript(name) {
return request => gScripts.getObject(request.id)[name](request.value);
}
function getObjectProperties(object) {
const names = object.getOwnPropertyNames();
return names.map(name => {
const desc = object.getOwnPropertyDescriptor(name);
if ("value" in desc) {
desc.value = convertValue(desc.value);
}
if ("get" in desc) {
desc.get = getObjectId(desc.get);
}
if ("set" in desc) {
desc.set = getObjectId(desc.set);
}
return { name, desc };
});
}
///////////////////////////////////////////////////////////////////////////////
// Handlers
///////////////////////////////////////////////////////////////////////////////
@ -626,21 +669,16 @@ const gRequestHandlers = {
}
const object = gPausedObjects.getObject(request.id);
const names = object.getOwnPropertyNames();
return getObjectProperties(object);
},
return names.map(name => {
const desc = object.getOwnPropertyDescriptor(name);
if ("value" in desc) {
desc.value = convertValue(desc.value);
}
if ("get" in desc) {
desc.get = getObjectId(desc.get);
}
if ("set" in desc) {
desc.set = getObjectId(desc.set);
}
return { name, desc };
});
getObjectPropertiesForConsole(request) {
const object = gPausedObjects.getObject(request.id);
const properties = gConsoleObjectProperties.get(object);
if (!properties) {
throw new Error("Console object properties not saved");
}
return properties;
},
getEnvironmentNames(request) {