зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a220a51576
Коммит
a5f01b639d
|
@ -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);
|
||||
getObjectPropertiesForConsole(request) {
|
||||
const object = gPausedObjects.getObject(request.id);
|
||||
const properties = gConsoleObjectProperties.get(object);
|
||||
if (!properties) {
|
||||
throw new Error("Console object properties not saved");
|
||||
}
|
||||
if ("get" in desc) {
|
||||
desc.get = getObjectId(desc.get);
|
||||
}
|
||||
if ("set" in desc) {
|
||||
desc.set = getObjectId(desc.set);
|
||||
}
|
||||
return { name, desc };
|
||||
});
|
||||
return properties;
|
||||
},
|
||||
|
||||
getEnvironmentNames(request) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче