зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
fa2fdbb6ff
Коммит
518fed5491
|
@ -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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче