Bug 1579319 - Distinguish objects and frames from different locations, r=jlast.

Differential Revision: https://phabricator.services.mozilla.com/D44936

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Brian Hackett 2019-09-06 21:19:30 +00:00
Родитель fa2fdbb6ff
Коммит 518fed5491
7 изменённых файлов: 457 добавлений и 439 удалений

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

@ -45,6 +45,11 @@ async function checkMessageObjectContents(msg, expected, expandList = []) {
});
}
function checkJumpIcon(msg) {
const jumpIcon = msg.querySelector(".jump-definition");
ok(jumpIcon, "Found a jump icon");
}
// Test evaluating various expressions in the console after time warping.
add_task(async function() {
const dbg = await attachRecordingDebugger("doc_rr_objects.html", {
@ -54,13 +59,32 @@ add_task(async function() {
const { threadFront, toolbox } = dbg;
const console = await toolbox.selectTool("webconsole");
const hud = console.hud;
let msg;
await waitForMessage(hud, "Array(20) [ 0, 1, 2, 3, 4, 5,");
await waitForMessage(hud, "Uint8Array(20) [ 0, 1, 2, 3, 4, 5,");
await waitForMessage(hud, "Set(22) [ null, null, 0, 1, 2, 3, 4, 5,");
await waitForMessage(
hud,
"Map(21) { {…} → {…}, 0 → 1, 1 → 2, 2 → 3, 3 → 4, 4 → 5,"
);
await waitForMessage(hud, "WeakSet(10)");
await waitForMessage(hud, "WeakMap(10)");
await waitForMessage(
hud,
"Object { a: 0, a0: 0, a1: 1, a2: 2, a3: 3, a4: 4,"
);
await waitForMessage(hud, "/abc/gi");
msg = await waitForMessage(hud, "function bar()");
checkJumpIcon(msg);
await warpToMessage(hud, dbg, "Done");
const requests = await threadFront.debuggerRequests();
requests.forEach(({ request, stack }) => {
if (request.type != "pauseData") {
if (request.type != "pauseData" && request.type != "repaint") {
dump(`Unexpected debugger request stack:\n${stack}\n`);
ok(false, `Unexpected debugger request while paused: ${request.type}`);
}
@ -80,8 +104,6 @@ f();
);
await BrowserTest.checkMessageStack(hud, "Error: there", [3, 5]);
let msg;
BrowserTest.execute(hud, "Array(1, 2, 3)");
msg = await waitForMessage(hud, "Array(3) [ 1, 2, 3 ]");
await checkMessageObjectContents(msg, ["0: 1", "1: 2", "2: 3", "length: 3"]);
@ -97,9 +119,9 @@ f();
"byteOffset: 0",
]);
BrowserTest.execute(hud, `RegExp("abc", "g")`);
msg = await waitForMessage(hud, "/abc/g");
await checkMessageObjectContents(msg, ["global: true", `source: "abc"`]);
BrowserTest.execute(hud, `RegExp("abd", "g")`);
msg = await waitForMessage(hud, "/abd/g");
await checkMessageObjectContents(msg, ["global: true", `source: "abd"`]);
BrowserTest.execute(hud, "new Set([1, 2, 3])");
msg = await waitForMessage(hud, "Set(3) [ 1, 2, 3 ]");
@ -133,5 +155,9 @@ f();
["<entries>"]
);
BrowserTest.execute(hud, "baz");
msg = await waitForMessage(hud, "function baz()");
checkJumpIcon(msg);
await shutdownDebugger(dbg);
});

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

@ -31,8 +31,28 @@ var h = /abc/gi;
var i = new Date();
var j = RangeError();
var k = document.getElementById("foo");
var l = bar;
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(e);
console.log(f);
console.log(g);
console.log(h);
console.log(i);
console.log(j);
console.log(l);
console.log("Done");
window.setTimeout(recordingFinished);
}
foo();
function bar() {
console.log("bar");
}
function baz() {
console.log("baz");
}
</script>

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

@ -119,16 +119,18 @@ const previewers = {
grip.userDisplayName = hooks.createValueGrip(userDisplayName.value);
}
let script;
const dbgGlobal = hooks.getGlobalDebugObject();
if (dbgGlobal) {
const script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference())
.script;
if (script) {
grip.location = {
url: script.url,
line: script.startLine,
};
}
script = dbgGlobal.makeDebuggeeValue(obj.unsafeDereference()).script;
} else {
script = obj.script;
}
if (script) {
grip.location = {
url: script.url,
line: script.startLine,
};
}
return true;

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

@ -1434,10 +1434,10 @@ async function findLogpointHits(
contents() {
return { kind: "hitLogpoint", text, condition };
},
onFinished(child, { data, result }) {
onFinished(child, { pauseData, result, resultData }) {
if (result) {
addPauseData(point, data, /* trackCached */ true);
callback(point, gDebugger._convertCompletionValue(result));
addPauseData(point, pauseData, /* trackCached */ true);
callback(point, result, resultData);
}
child.divergedFromRecording = true;
},

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

@ -37,6 +37,142 @@ const Direction = {
NONE: "NONE",
};
// Pool of ReplayDebugger things that are grouped together and can refer to each
// other. Many things --- frames, objects, environments --- are specific to
// a pool and cannot be used in any other context. Normally a pool is associated
// with some point at which the debugger paused, but they may also be associated
// with the values in a console or logpoint message.
function ReplayPool(dbg, pauseData) {
this.dbg = dbg;
// All ReplayDebuggerFramees that have been created for this pool, indexed by
// their index (zero is the oldest frame, with the index increasing for newer
// frames).
this.frames = [];
// All ReplayDebuggerObjects and ReplayDebuggerEnvironments that are
// associated with this pool, indexed by their id.
this.objects = [];
if (pauseData) {
this.addPauseData(pauseData);
}
}
ReplayPool.prototype = {
getObject(id) {
if (id && !this.objects[id]) {
if (this != this.dbg._pool) {
return null;
}
const data = this.dbg._sendRequest({ type: "getObject", id });
this.addObject(data);
}
return this.objects[id];
},
addObject(data) {
switch (data.kind) {
case "Object":
this.objects[data.id] = new ReplayDebuggerObject(this, data);
break;
case "Environment":
this.objects[data.id] = new ReplayDebuggerEnvironment(this, data);
break;
default:
ThrowError("Unknown object kind");
}
},
getFrame(index) {
if (index == NewestFrameIndex) {
if (this.frames.length) {
return this.frames[this.frames.length - 1];
}
} else {
assert(index < this.frames.length);
if (this.frames[index]) {
return this.frames[index];
}
}
assert(this == this.dbg._pool);
const data = this.dbg._sendRequest({ type: "getFrame", index });
if (index == NewestFrameIndex) {
if ("index" in data) {
index = data.index;
} else {
// There are no frames on the stack.
return null;
}
}
this.frames[index] = new ReplayDebuggerFrame(this, data);
return this.frames[index];
},
addPauseData(pauseData) {
for (const { data, preview } of Object.values(pauseData.objects)) {
if (!this.objects[data.id]) {
this.addObject(data);
}
this.getObject(data.id)._preview = {
...preview,
enumerableOwnProperties: mapify(preview.enumerableOwnProperties),
};
}
for (const { data, names } of Object.values(pauseData.environments)) {
if (!this.objects[data.id]) {
this.addObject(data);
}
this.getObject(data.id)._setNames(names);
}
if (pauseData.frames) {
for (const frame of pauseData.frames) {
this.frames[frame.index] = new ReplayDebuggerFrame(this, frame);
}
}
},
convertValue(value) {
if (isNonNullObject(value)) {
if (value.object) {
return this.getObject(value.object);
}
switch (value.special) {
case "undefined":
return undefined;
case "Infinity":
return Infinity;
case "-Infinity":
return -Infinity;
case "NaN":
return NaN;
case "0":
return -0;
}
}
return value;
},
convertCompletionValue(value) {
if ("return" in value) {
return { return: this.convertValue(value.return) };
}
if ("throw" in value) {
return {
throw: this.convertValue(value.throw),
stack: value.stack,
};
}
ThrowError("Unexpected completion value");
return null; // For eslint
},
};
function ReplayDebugger() {
const existing = RecordReplayControl.registerReplayDebugger(this);
if (existing) {
@ -54,16 +190,8 @@ function ReplayDebugger() {
// All breakpoint positions and handlers installed by this debugger.
this._breakpoints = [];
// All ReplayDebuggerFramees that have been created while paused at the
// current position, indexed by their index (zero is the oldest frame, with
// the index increasing for newer frames). These are invalidated when
// unpausing.
this._frames = [];
// All ReplayDebuggerObjects and ReplayDebuggerEnvironments that have been
// created while paused at the current position, indexed by their id. These
// are invalidated when unpausing.
this._objects = [];
// The current pool of pause-local state.
this._pool = new ReplayPool(this);
// All ReplayDebuggerScripts and ReplayDebuggerScriptSources that have been
// created, indexed by their id. These stay valid even after unpausing.
@ -375,19 +503,15 @@ ReplayDebugger.prototype = {
}
},
// Clear out all data that becomes invalid when the child unpauses.
// Reset the per-pause pool when the child unpauses.
_invalidateAfterUnpause() {
this._frames.forEach(frame => frame._invalidate());
this._frames.length = 0;
this._objects.forEach(obj => obj._invalidate());
this._objects.length = 0;
this._pool = new ReplayPool(this);
},
// Fill in the debugger with (hopefully) all data the client/server need to
// pause at the current location.
_capturePauseData() {
if (this._frames.length) {
if (this._pool.frames.length) {
return;
}
@ -407,26 +531,7 @@ ReplayDebugger.prototype = {
}
}
for (const { data, preview } of Object.values(pauseData.objects)) {
if (!this._objects[data.id]) {
this._addObject(data);
}
this._getObject(data.id)._preview = {
...preview,
enumerableOwnProperties: mapify(preview.enumerableOwnProperties),
};
}
for (const { data, names } of Object.values(pauseData.environments)) {
if (!this._objects[data.id]) {
this._addObject(data);
}
this._getObject(data.id)._setNames(names);
}
for (const frame of pauseData.frames) {
this._frames[frame.index] = new ReplayDebuggerFrame(this, frame);
}
this._pool.addPauseData(pauseData);
},
_virtualConsoleLog(position, text, condition, callback) {
@ -581,70 +686,11 @@ ReplayDebugger.prototype = {
// Object methods
/////////////////////////////////////////////////////////
_getObject(id) {
if (id && !this._objects[id]) {
const data = this._sendRequest({ type: "getObject", id });
this._addObject(data);
}
return this._objects[id];
},
_addObject(data) {
switch (data.kind) {
case "Object":
this._objects[data.id] = new ReplayDebuggerObject(this, data);
break;
case "Environment":
this._objects[data.id] = new ReplayDebuggerEnvironment(this, data);
break;
default:
ThrowError("Unknown object kind");
}
},
// Convert a value we received from the child.
_convertValue(value) {
if (isNonNullObject(value)) {
if (value.object) {
return this._getObject(value.object);
}
if (value.snapshot) {
return new ReplayDebuggerObjectSnapshot(this, value.snapshot);
}
switch (value.special) {
case "undefined":
return undefined;
case "Infinity":
return Infinity;
case "-Infinity":
return -Infinity;
case "NaN":
return NaN;
case "0":
return -0;
}
}
return value;
},
_convertCompletionValue(value) {
if ("return" in value) {
return { return: this._convertValue(value.return) };
}
if ("throw" in value) {
return {
throw: this._convertValue(value.throw),
stack: value.stack,
};
}
ThrowError("Unexpected completion value");
return null; // For eslint
},
// Convert a value for sending to the child.
_convertValueForChild(value) {
if (isNonNullObject(value)) {
assert(value instanceof ReplayDebuggerObject);
assert(value._pool == this._pool);
return { object: value._data.id };
} else if (
value === undefined ||
@ -662,35 +708,8 @@ ReplayDebugger.prototype = {
// Frame methods
/////////////////////////////////////////////////////////
_getFrame(index) {
if (index == NewestFrameIndex) {
if (this._frames.length) {
return this._frames[this._frames.length - 1];
}
} else {
assert(index < this._frames.length);
if (this._frames[index]) {
return this._frames[index];
}
}
const data = this._sendRequest({ type: "getFrame", index });
if (index == NewestFrameIndex) {
if ("index" in data) {
index = data.index;
} else {
// There are no frames on the stack.
return null;
}
}
this._frames[index] = new ReplayDebuggerFrame(this, data);
return this._frames[index];
},
getNewestFrame() {
return this._getFrame(NewestFrameIndex);
return this._pool.getFrame(NewestFrameIndex);
},
/////////////////////////////////////////////////////////
@ -701,10 +720,13 @@ ReplayDebugger.prototype = {
// Console API message arguments need conversion to debuggee values, but
// other contents of the message can be left alone.
if (message.messageType == "ConsoleAPI" && message.arguments) {
// Each console message has its own pool of referenced objects.
const pool = new ReplayPool(this, message.argumentsData);
for (let i = 0; i < message.arguments.length; i++) {
message.arguments[i] = this._convertValue(message.arguments[i]);
message.arguments[i] = pool.convertValue(message.arguments[i]);
}
}
return message;
},
@ -827,7 +849,11 @@ ReplayDebuggerScript.prototype = {
{ kind: "Break", script: this._data.id, offset },
text,
condition,
callback
(point, result, resultData) => {
const pool = new ReplayPool(this._dbg, resultData);
const converted = pool.convertCompletionValue(result);
callback(point, converted);
}
);
},
@ -892,27 +918,24 @@ ReplayDebuggerScriptSource.prototype = {
// ReplayDebuggerFrame
///////////////////////////////////////////////////////////////////////////////
function ReplayDebuggerFrame(dbg, data) {
this._dbg = dbg;
function ReplayDebuggerFrame(pool, data) {
this._dbg = pool.dbg;
this._pool = pool;
this._data = data;
if (this._data.arguments) {
this._arguments = this._data.arguments.map(a => this._dbg._convertValue(a));
this._arguments = this._data.arguments.map(a => this._pool.convertValue(a));
}
}
ReplayDebuggerFrame.prototype = {
_invalidate() {
this._data = null;
},
get type() {
return this._data.type;
},
get callee() {
return this._dbg._getObject(this._data.callee);
return this._pool.getObject(this._data.callee);
},
get environment() {
return this._dbg._getObject(this._data.environment);
return this._pool.getObject(this._data.environment);
},
get generator() {
return this._data.generator;
@ -921,7 +944,7 @@ ReplayDebuggerFrame.prototype = {
return this._data.constructing;
},
get this() {
return this._dbg._convertValue(this._data.this);
return this._pool.convertValue(this._data.this);
},
get script() {
return this._dbg._getScript(this._data.script);
@ -938,6 +961,7 @@ ReplayDebuggerFrame.prototype = {
},
eval(text, options) {
assert(this._pool == this._dbg._pool);
const rv = this._dbg._sendRequestAllowDiverge(
{
type: "frameEvaluate",
@ -947,7 +971,7 @@ ReplayDebuggerFrame.prototype = {
},
{ throw: "Recording divergence in frameEvaluate" }
);
return this._dbg._convertCompletionValue(rv);
return this._pool.convertCompletionValue(rv);
},
_positionMatches(position, kind) {
@ -1001,7 +1025,7 @@ ReplayDebuggerFrame.prototype = {
const result = this._dbg._sendRequest({ type: "popFrameResult" });
handler.call(
this._dbg.getNewestFrame(),
this._dbg._convertCompletionValue(result)
this._pool.convertCompletionValue(result)
);
},
{
@ -1022,7 +1046,7 @@ ReplayDebuggerFrame.prototype = {
// This is the oldest frame.
return null;
}
return this._dbg._getFrame(this._data.index - 1);
return this._pool.getFrame(this._data.index - 1);
},
get implementation() {
@ -1035,8 +1059,9 @@ ReplayDebuggerFrame.prototype = {
// ReplayDebuggerObject
///////////////////////////////////////////////////////////////////////////////
function ReplayDebuggerObject(dbg, data) {
this._dbg = dbg;
function ReplayDebuggerObject(pool, data) {
this._dbg = pool.dbg;
this._pool = pool;
this._data = data;
this._preview = null;
this._properties = null;
@ -1044,13 +1069,6 @@ function ReplayDebuggerObject(dbg, data) {
}
ReplayDebuggerObject.prototype = {
_invalidate() {
this._data = null;
this._preview = null;
this._properties = null;
this._containerContents = null;
},
toString() {
const id = this._data ? this._data.id : "INVALID";
return `ReplayDebugger.Object #${id}`;
@ -1087,13 +1105,13 @@ ReplayDebuggerObject.prototype = {
return this._dbg._getScript(this._data.script);
},
get environment() {
return this._dbg._getObject(this._data.environment);
return this._pool.getObject(this._data.environment);
},
get isProxy() {
return this._data.isProxy;
},
get proto() {
return this._dbg._getObject(this._data.proto);
return this._pool.getObject(this._data.proto);
},
isExtensible() {
@ -1159,6 +1177,10 @@ ReplayDebuggerObject.prototype = {
_ensureProperties() {
if (!this._properties) {
if (this._pool != this._dbg._pool) {
this._properties = mapify([]);
return;
}
const id = this._data.id;
const properties = this._dbg._sendRequestAllowDiverge(
{ type: "getObjectProperties", id },
@ -1174,13 +1196,13 @@ ReplayDebuggerObject.prototype = {
}
const rv = Object.assign({}, desc);
if ("value" in desc) {
rv.value = this._dbg._convertValue(desc.value);
rv.value = this._pool.convertValue(desc.value);
}
if ("get" in desc) {
rv.get = this._dbg._getObject(desc.get);
rv.get = this._pool.getObject(desc.get);
}
if ("set" in desc) {
rv.set = this._dbg._getObject(desc.set);
rv.set = this._pool.getObject(desc.set);
}
return rv;
},
@ -1191,6 +1213,7 @@ ReplayDebuggerObject.prototype = {
contents = this._preview.containerContents;
} else {
if (!this._containerContents) {
assert(this._pool == this._dbg._pool);
const id = this._data.id;
this._containerContents = this._dbg._sendRequestAllowDiverge(
{ type: "getObjectContainerContents", id },
@ -1202,9 +1225,9 @@ ReplayDebuggerObject.prototype = {
return contents.map(value => {
// Watch for [key, value] pairs in maps.
if (value.length == 2) {
return value.map(v => this._dbg._convertValue(v));
return value.map(v => this._pool.convertValue(v));
}
return this._dbg._convertValue(value);
return this._pool.convertValue(value);
});
},
@ -1212,34 +1235,34 @@ ReplayDebuggerObject.prototype = {
if (!this.isProxy) {
return this;
}
return this._dbg._convertValue(this._data.proxyUnwrapped);
return this._pool.convertValue(this._data.proxyUnwrapped);
},
get proxyTarget() {
return this._dbg._convertValue(this._data.proxyTarget);
return this._pool.convertValue(this._data.proxyTarget);
},
get proxyHandler() {
return this._dbg._convertValue(this._data.proxyHandler);
return this._pool.convertValue(this._data.proxyHandler);
},
get boundTargetFunction() {
if (this.isBoundFunction) {
return this._dbg._getObject(this._data.boundTargetFunction);
return this._pool.getObject(this._data.boundTargetFunction);
}
return undefined;
},
get boundThis() {
if (this.isBoundFunction) {
return this._dbg._convertValue(this._data.boundThis);
return this._pool.convertValue(this._data.boundThis);
}
return undefined;
},
get boundArguments() {
if (this.isBoundFunction) {
return this._dbg._getObject(this._data.boundArguments);
return this._pool.getObject(this._data.boundArguments);
}
return undefined;
},
@ -1249,6 +1272,7 @@ ReplayDebuggerObject.prototype = {
},
apply(thisv, args) {
assert(this._pool == this._dbg._pool);
thisv = this._dbg._convertValueForChild(thisv);
args = (args || []).map(v => this._dbg._convertValueForChild(v));
@ -1261,7 +1285,7 @@ ReplayDebuggerObject.prototype = {
},
{ throw: "Recording divergence in objectApply" }
);
return this._dbg._convertCompletionValue(rv);
return this._pool.convertCompletionValue(rv);
},
get allocationSite() {
@ -1322,52 +1346,29 @@ ReplayDebuggerObject.prototype = {
ReplayDebugger.Object = ReplayDebuggerObject;
///////////////////////////////////////////////////////////////////////////////
// ReplayDebuggerObjectSnapshot
///////////////////////////////////////////////////////////////////////////////
// Create an object based on snapshot data which can be consulted without
// communicating with the child process. This uses data provided by the child
// process in the same format as for normal ReplayDebuggerObjects, except that
// it does not contain references to any other objects.
function ReplayDebuggerObjectSnapshot(dbg, data) {
this._dbg = dbg;
this._data = data;
this._properties = new Map();
data.properties.forEach(({ name, desc }) => {
this._properties.set(name, desc);
});
}
ReplayDebuggerObjectSnapshot.prototype = ReplayDebuggerObject.prototype;
///////////////////////////////////////////////////////////////////////////////
// ReplayDebuggerEnvironment
///////////////////////////////////////////////////////////////////////////////
function ReplayDebuggerEnvironment(dbg, data) {
this._dbg = dbg;
function ReplayDebuggerEnvironment(pool, data) {
this._dbg = pool.dbg;
this._pool = pool;
this._data = data;
this._names = null;
}
ReplayDebuggerEnvironment.prototype = {
_invalidate() {
this._data = null;
this._names = null;
},
get type() {
return this._data.type;
},
get parent() {
return this._dbg._getObject(this._data.parent);
return this._pool.getObject(this._data.parent);
},
get object() {
return this._dbg._getObject(this._data.object);
return this._pool.getObject(this._data.object);
},
get callee() {
return this._dbg._getObject(this._data.callee);
return this._pool.getObject(this._data.callee);
},
get optimizedOut() {
return this._data.optimizedOut;
@ -1376,12 +1377,13 @@ ReplayDebuggerEnvironment.prototype = {
_setNames(names) {
this._names = {};
names.forEach(({ name, value }) => {
this._names[name] = this._dbg._convertValue(value);
this._names[name] = this._pool.convertValue(value);
});
},
_ensureNames() {
if (!this._names) {
assert(this._pool == this._dbg._pool);
const names = this._dbg._sendRequestAllowDiverge(
{
type: "getEnvironmentNames",

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

@ -30,6 +30,10 @@ function dbg() {
return _dbg;
}
function dbgObject(id) {
return dbg()._pool.getObject(id);
}
///////////////////////////////////////////////////////////////////////////////
// Public Interface
///////////////////////////////////////////////////////////////////////////////
@ -96,7 +100,7 @@ const ReplayInspector = {
clientX: event.clientX,
clientY: event.clientY,
});
const obj = dbg()._getObject(rv.id);
const obj = dbgObject(rv.id);
return wrapValue(obj);
},
@ -139,7 +143,7 @@ function createSubstituteChrome(chrome) {
const data = dbg()._sendRequestAllowDiverge({
type: "newDeepTreeWalker",
});
const obj = dbg()._getObject(data.id);
const obj = dbgObject(data.id);
return wrapObject(obj);
},
},
@ -155,10 +159,10 @@ function createSubstituteChrome(chrome) {
// Objects are considered dead if we have unpaused since creating them
// and they are not one of the fixed proxies. This prevents the
// inspector from trying to continue using them.
if (!unwrapped._data) {
if (unwrapped._pool != dbg()._pool) {
updateFixedProxies();
unwrapped = proxyMap.get(node);
return !unwrapped._data;
return unwrapped._pool != dbg()._pool;
}
return false;
},
@ -266,7 +270,7 @@ function unwrapValue(value) {
if (value instanceof Object) {
const rv = dbg()._sendRequest({ type: "createObject" });
const newobj = dbg()._getObject(rv.id);
const newobj = dbgObject(rv.id);
Object.entries(value).forEach(([name, propvalue]) => {
const unwrapped = unwrapValue(propvalue);
@ -284,7 +288,7 @@ function getObjectProperty(obj, name) {
id: obj._data.id,
name,
});
return dbg()._convertCompletionValue(rv);
return dbg()._pool.convertCompletionValue(rv);
}
function setObjectProperty(obj, name, value) {
@ -294,7 +298,7 @@ function setObjectProperty(obj, name, value) {
name,
value: dbg()._convertValueForChild(value),
});
return dbg()._convertCompletionValue(rv);
return dbg()._pool.convertCompletionValue(rv);
}
function getTargetObject(target) {
@ -479,11 +483,7 @@ function updateFixedProxies() {
ReplayInspectorProxyHandler
);
}
initFixedProxy(
gFixedProxy[key],
gFixedProxyTargets[key],
dbg()._getObject(value)
);
initFixedProxy(gFixedProxy[key], gFixedProxyTargets[key], dbgObject(value));
}
}

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

@ -269,13 +269,10 @@ Services.obs.addObserver(
{
observe(subject, topic, data) {
assert(topic == "devtools-html-content");
const { uri, offset, contents } = JSON.parse(data);
const { uri, contents } = JSON.parse(data);
if (gHtmlContent.has(uri)) {
const existing = gHtmlContent.get(uri);
if (existing.content.length == offset) {
assert(!existing.complete);
existing.content = existing.content + contents;
}
existing.content = existing.content + contents;
} else {
gHtmlContent.set(uri, {
content: contents,
@ -287,52 +284,6 @@ Services.obs.addObserver(
"devtools-html-content"
);
///////////////////////////////////////////////////////////////////////////////
// Object Snapshots
///////////////////////////////////////////////////////////////////////////////
// Snapshots are generated for objects that might be inspected at times when we
// are not paused at the point where the snapshot was originally taken. The
// snapshot data is provided to the server, which can use it to provide limited
// answers to the client about the object's contents, without having to consult
// a child process.
function snapshotObjectProperty([name, desc]) {
// Only capture primitive properties in object snapshots.
if ("value" in desc && !convertedValueIsObject(desc.value)) {
return { name, desc };
}
return { name, desc: { value: "<unavailable>" } };
}
function makeObjectSnapshot(object) {
assert(object instanceof Debugger.Object);
// Include properties that would be included in a normal object's data packet,
// except do not allow inspection of any other referenced objects.
// In particular, don't set the prototype so that the object inspector will
// not attempt to crawl the object's prototype chain.
return {
kind: "Object",
callable: object.callable,
isBoundFunction: object.isBoundFunction,
isArrowFunction: object.isArrowFunction,
isGeneratorFunction: object.isGeneratorFunction,
isAsyncFunction: object.isAsyncFunction,
class: object.class,
name: object.name,
displayName: object.displayName,
parameterNames: object.parameterNames,
isProxy: object.isProxy,
isExtensible: object.isExtensible(),
isSealed: object.isSealed(),
isFrozen: object.isFrozen(),
properties: Object.entries(getObjectProperties(object)).map(
snapshotObjectProperty
),
};
}
///////////////////////////////////////////////////////////////////////////////
// Console Message State
///////////////////////////////////////////////////////////////////////////////
@ -343,13 +294,7 @@ const gConsoleMessages = [];
// Any new console messages since the last checkpoint.
const gNewConsoleMessages = [];
function newConsoleMessage(messageType, executionPoint, contents) {
if (!executionPoint) {
executionPoint = currentScriptedExecutionPoint();
}
contents.messageType = messageType;
contents.executionPoint = executionPoint;
function newConsoleMessage(contents) {
gConsoleMessages.push(contents);
if (gManifest.kind == "resume") {
@ -379,11 +324,16 @@ Services.console.registerListener({
// If there is a warp target associated with the error, use that. This
// will take users to the point where the error was originally generated,
// rather than where it was reported to the console.
const executionPoint = gWarpTargetPoints[message.timeWarpTarget];
let executionPoint = gWarpTargetPoints[message.timeWarpTarget];
if (!executionPoint) {
executionPoint = currentScriptedExecutionPoint();
}
const contents = JSON.parse(JSON.stringify(message));
contents.stack = convertStack(message.stack);
newConsoleMessage("PageError", executionPoint, contents);
contents.executionPoint = executionPoint;
contents.messageType = "PageError";
newConsoleMessage(contents);
}
},
});
@ -396,21 +346,30 @@ Services.obs.addObserver(
observe(message, topic, data) {
const apiMessage = message.wrappedJSObject;
const contents = {};
const contents = { messageType: "ConsoleAPI" };
for (const id in apiMessage) {
if (id != "wrappedJSObject" && id != "arguments") {
contents[id] = JSON.parse(JSON.stringify(apiMessage[id]));
}
}
contents.executionPoint = currentScriptedExecutionPoint();
// Message arguments are preserved as debuggee values.
if (apiMessage.arguments) {
contents.arguments = apiMessage.arguments.map(v => {
return convertValue(makeDebuggeeValue(v), { snapshot: true });
return convertValue(makeDebuggeeValue(v));
});
contents.argumentsData = new PreviewedObjects();
contents.arguments.forEach(v =>
contents.argumentsData.addValue(v, true)
);
ClearPausedState();
}
newConsoleMessage("ConsoleAPI", null, contents);
newConsoleMessage(contents);
},
},
"console-api-log-event"
@ -822,11 +781,8 @@ function getObjectId(obj) {
}
// Convert a value for sending to the parent.
function convertValue(value, options) {
function convertValue(value) {
if (value instanceof Debugger.Object) {
if (options && options.snapshot) {
return { snapshot: makeObjectSnapshot(value) };
}
return { object: getObjectId(value) };
}
if (
@ -841,17 +797,13 @@ function convertValue(value, options) {
return value;
}
function convertedValueIsObject(value) {
return isNonNullObject(value) && "object" in value;
}
function convertCompletionValue(value, options) {
function convertCompletionValue(value) {
if ("return" in value) {
return { return: convertValue(value.return, options) };
return { return: convertValue(value.return) };
}
if ("throw" in value) {
return {
throw: convertValue(value.throw, options),
throw: convertValue(value.throw),
stack: convertSavedFrameToPlainObject(value.stack),
};
}
@ -1030,12 +982,16 @@ const gManifestStartHandlers = {
const displayName = formatDisplayName(frame);
const rv = frame.evalWithBindings(text, { displayName });
const converted = convertCompletionValue(rv, { snapshot: true });
const data = getPauseData();
data.paintData = RecordReplayControl.repaint();
const pauseData = getPauseData();
pauseData.paintData = RecordReplayControl.repaint();
ClearPausedState();
RecordReplayControl.manifestFinished({ result: converted, data });
const result = convertCompletionValue(rv);
const resultData = new PreviewedObjects();
resultData.addCompletionValue(result, true);
RecordReplayControl.manifestFinished({ result, resultData, pauseData });
},
};
@ -1076,12 +1032,14 @@ function currentScriptedExecutionPoint() {
if (!numFrames) {
return null;
}
const frame = getFrameData(numFrames - 1);
const index = numFrames - 1;
const frame = scriptFrameForIndex(index);
return currentExecutionPoint({
kind: "OnStep",
script: frame.script,
script: gScripts.getId(frame.script),
offset: frame.offset,
frameIndex: frame.index,
frameIndex: index,
});
}
@ -1495,6 +1453,143 @@ function getWindow() {
// object.
const OBJECT_PREVIEW_MAX_ITEMS = 10;
// A collection of objects which we can send up to the server, along with
// property information so that the server can show a preview for the object.
function PreviewedObjects() {
this.objects = {};
this.environments = {};
}
PreviewedObjects.prototype = {
addValue(value, includeProperties) {
if (value && typeof value == "object" && value.object) {
this.addObject(value.object, includeProperties);
}
},
addObject(id, includeProperties) {
if (!id) {
return;
}
// If includeProperties is set then previewing the object requires knowledge
// of its enumerable properties.
const needObject = !this.objects[id];
const needProperties =
includeProperties &&
(needObject || !this.objects[id].preview.enumerableOwnProperties);
if (!needObject && !needProperties) {
return;
}
const object = gPausedObjects.getObject(id);
assert(object instanceof Debugger.Object);
const properties = getObjectProperties(object);
const propertyEntries = Object.entries(properties);
if (needObject) {
this.objects[id] = {
data: getObjectData(id),
preview: {
ownPropertyNamesCount: propertyEntries.length,
},
};
const preview = this.objects[id].preview;
// Add some properties (if present) which the server might ask for
// even when it isn't interested in the rest of the properties.
if (properties.length) {
preview.lengthProperty = properties.length;
}
if (properties.displayName) {
preview.displayNameProperty = properties.displayName;
}
}
if (needProperties) {
const preview = this.objects[id].preview;
// The server is only interested in enumerable properties, and at most
// OBJECT_PREVIEW_MAX_ITEMS of them. Limiting the properties we send to
// only those the server needs avoids having to send the contents of huge
// objects like Windows, most of which will not be used.
const enumerableOwnProperties = Object.create(null);
let enumerablePropertyCount = 0;
for (const [name, desc] of propertyEntries) {
if (desc.enumerable) {
enumerableOwnProperties[name] = desc;
this.addPropertyDescriptor(desc, false);
if (++enumerablePropertyCount == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
}
preview.enumerableOwnProperties = enumerableOwnProperties;
// The server is interested in at most OBJECT_PREVIEW_MAX_ITEMS items in
// set and map containers.
const containerContents = getObjectContainerContents(object);
if (containerContents) {
preview.containerContents = containerContents.slice(
0,
OBJECT_PREVIEW_MAX_ITEMS
);
preview.containerContents.forEach(v => this.addContainerValue(v));
}
}
},
addPropertyDescriptor(desc, includeProperties) {
if (desc.value) {
this.addValue(desc.value, includeProperties);
}
if (desc.get) {
this.addObject(desc.get, includeProperties);
}
if (desc.set) {
this.addObject(desc.set, includeProperties);
}
},
addContainerValue(value) {
// Watch for [key, value] pairs in maps.
if (value.length == 2) {
value.forEach(v => this.addValue(v));
} else {
this.addValue(value);
}
},
addCompletionValue(value, includeProperties) {
if ("return" in value) {
this.addValue(value.return, includeProperties);
} else if ("throw" in value) {
this.addValue(value.throw, includeProperties);
}
},
addEnvironment(id) {
if (!id || this.environments[id]) {
return;
}
const env = gPausedObjects.getObject(id);
assert(env instanceof Debugger.Environment);
const data = getObjectData(id);
const names = getEnvironmentNames(env);
this.environments[id] = { data, names };
names.forEach(({ value }) => this.addValue(value, true));
this.addObject(data.callee);
this.addEnvironment(data.parent);
},
};
// When the replaying process pauses, the server needs to inspect a lot of state
// around frames, objects, etc. in order to fill in all the information the
// client needs to update the UI for the pause location. Done naively, this
@ -1514,133 +1609,11 @@ function getPauseData() {
return {};
}
const rv = {
frames: [],
scripts: {},
offsetMetadata: [],
objects: {},
environments: {},
};
const rv = new PreviewedObjects();
function addValue(value, includeProperties) {
if (value && typeof value == "object" && value.object) {
addObject(value.object, includeProperties);
}
}
function addObject(id, includeProperties) {
if (!id) {
return;
}
// If includeProperties is set then previewing the object requires knowledge
// of its enumerable properties.
const needObject = !rv.objects[id];
const needProperties =
includeProperties &&
(needObject || !rv.objects[id].preview.enumerableOwnProperties);
if (!needObject && !needProperties) {
return;
}
const object = gPausedObjects.getObject(id);
assert(object instanceof Debugger.Object);
const properties = getObjectProperties(object);
const propertyEntries = Object.entries(properties);
if (needObject) {
rv.objects[id] = {
data: getObjectData(id),
preview: {
ownPropertyNamesCount: propertyEntries.length,
},
};
const preview = rv.objects[id].preview;
// Add some properties (if present) which the server might ask for
// even when it isn't interested in the rest of the properties.
if (properties.length) {
preview.lengthProperty = properties.length;
}
if (properties.displayName) {
preview.displayNameProperty = properties.displayName;
}
}
if (needProperties) {
const preview = rv.objects[id].preview;
// The server is only interested in enumerable properties, and at most
// OBJECT_PREVIEW_MAX_ITEMS of them. Limiting the properties we send to
// only those the server needs avoids having to send the contents of huge
// objects like Windows, most of which will not be used.
const enumerableOwnProperties = Object.create(null);
let enumerablePropertyCount = 0;
for (const [name, desc] of propertyEntries) {
if (desc.enumerable) {
enumerableOwnProperties[name] = desc;
addPropertyDescriptor(desc, false);
if (++enumerablePropertyCount == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
}
preview.enumerableOwnProperties = enumerableOwnProperties;
// The server is interested in at most OBJECT_PREVIEW_MAX_ITEMS items in
// set and map containers.
const containerContents = getObjectContainerContents(object);
if (containerContents) {
preview.containerContents = containerContents.slice(
0,
OBJECT_PREVIEW_MAX_ITEMS
);
preview.containerContents.forEach(v => addContainerValue(v));
}
}
}
function addPropertyDescriptor(desc, includeProperties) {
if (desc.value) {
addValue(desc.value, includeProperties);
}
if (desc.get) {
addObject(desc.get, includeProperties);
}
if (desc.set) {
addObject(desc.set, includeProperties);
}
}
function addContainerValue(value) {
// Watch for [key, value] pairs in maps.
if (value.length == 2) {
value.forEach(v => addValue(v));
} else {
addValue(value);
}
}
function addEnvironment(id) {
if (!id || rv.environments[id]) {
return;
}
const env = gPausedObjects.getObject(id);
assert(env instanceof Debugger.Environment);
const data = getObjectData(id);
const names = getEnvironmentNames(env);
rv.environments[id] = { data, names };
names.forEach(({ value }) => addValue(value, true));
addObject(data.callee);
addEnvironment(data.parent);
}
rv.frames = [];
rv.scripts = {};
rv.offsetMetadata = [];
// eslint-disable-next-line no-shadow
function addScript(id) {
@ -1660,14 +1633,14 @@ function getPauseData() {
metadata: script.getOffsetMetadata(dbgFrame.offset),
});
addScript(frame.script);
addValue(frame.this, true);
rv.addValue(frame.this, true);
if (frame.arguments) {
for (const arg of frame.arguments) {
addValue(arg, true);
rv.addValue(arg, true);
}
}
addObject(frame.callee, false);
addEnvironment(frame.environment, true);
rv.addObject(frame.callee, false);
rv.addEnvironment(frame.environment, true);
}
return rv;
@ -1687,11 +1660,6 @@ function divergeFromRecording() {
}
const gRequestHandlers = {
repaint() {
divergeFromRecording();
return RecordReplayControl.repaint();
},
/////////////////////////////////////////////////////////
// Debugger Requests
/////////////////////////////////////////////////////////
@ -1809,7 +1777,7 @@ const gRequestHandlers = {
divergeFromRecording();
const frame = scriptFrameForIndex(request.index);
const rv = frame.eval(request.text, request.options);
return convertCompletionValue(rv, request.convertOptions);
return convertCompletionValue(rv);
},
popFrameResult(request) {