Bug 1513118 Part 2 - Add support for virtual console logs to ReplayDebuggerScript, r=lsmyth.

--HG--
extra : rebase_source : e9b85495fdf2a5e695b6a1031eb1890061703a1d
extra : source : 5ce216ffcb1da139187cf8c62473d150df35dc5e
This commit is contained in:
Brian Hackett 2018-12-29 08:23:38 -10:00
Родитель 63524d8a48
Коммит 0af6ba1fa7
2 изменённых файлов: 150 добавлений и 14 удалений

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

@ -691,6 +691,9 @@ function flushRecording() {
}
}
// After flushing the recording there may be more search results.
maybeResumeSearch();
gLastRecordingCheckpoint = gActiveChild.lastCheckpoint();
// We now have a usable recording for replaying children.
@ -988,16 +991,92 @@ const gControl = {
timeWarp,
};
// eslint-disable-next-line no-unused-vars
function ConnectDebugger(dbg) {
gDebugger = dbg;
dbg._control = gControl;
////////////////////////////////////////////////////////////////////////////////
// Search Operations
////////////////////////////////////////////////////////////////////////////////
let gSearchChild;
let gSearchRestartNeeded;
function maybeRestartSearch() {
if (gSearchRestartNeeded && gSearchChild.paused) {
if (gSearchChild.lastPausePoint.checkpoint != FirstCheckpointId ||
gSearchChild.lastPausePoint.position) {
gSearchChild.sendRestoreCheckpoint(FirstCheckpointId);
gSearchChild.waitUntilPaused();
}
gSearchChild.sendClearBreakpoints();
gDebugger._forEachSearch(pos => gSearchChild.sendAddBreakpoint(pos));
gSearchRestartNeeded = false;
gSearchChild.sendResume({ forward: true });
return true;
}
return false;
}
function ChildRoleSearch() {}
ChildRoleSearch.prototype = {
name: "Search",
initialize(child, { startup }) {
this.child = child;
},
hitExecutionPoint({ point, recordingEndpoint }) {
if (maybeRestartSearch()) {
return;
}
if (point.position) {
gDebugger._onSearchPause(point);
}
if (!recordingEndpoint) {
this.poke();
}
},
poke() {
if (!gSearchRestartNeeded && !this.child.pauseNeeded) {
this.child.sendResume({ forward: true });
}
},
};
function ensureHasSearchChild() {
if (!gSearchChild) {
gSearchChild = spawnReplayingChild(new ChildRoleSearch());
}
}
function maybeResumeSearch() {
if (gSearchChild && gSearchChild.paused) {
gSearchChild.sendResume({ forward: true });
}
}
const gSearchControl = {
reset() {
ensureHasSearchChild();
gSearchRestartNeeded = true;
maybeRestartSearch();
},
sendRequest(request) { return gSearchChild.sendDebuggerRequest(request); },
};
///////////////////////////////////////////////////////////////////////////////
// Utilities
///////////////////////////////////////////////////////////////////////////////
// eslint-disable-next-line no-unused-vars
function ConnectDebugger(dbg) {
gDebugger = dbg;
dbg._control = gControl;
dbg._searchControl = gSearchControl;
}
function dumpv(str) {
//dump("[ReplayControl] " + str + "\n");
}

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

@ -42,6 +42,7 @@ function ReplayDebugger() {
// We should have been connected to control.js by the call above.
assert(this._control);
assert(this._searchControl);
// Preferred direction of travel when not explicitly resumed.
this._direction = Direction.NONE;
@ -75,6 +76,9 @@ function ReplayDebugger() {
// After we are done pausing, callback describing how to resume.
this._resumeCallback = null;
// Information about all searches that exist.
this._searches = [];
// Handler called when hitting the beginning/end of the recording, or when
// a time warp target has been reached.
this.replayingOnForcedPause = null;
@ -348,6 +352,47 @@ ReplayDebugger.prototype = {
this._objects.length = 0;
},
/////////////////////////////////////////////////////////
// Search management
/////////////////////////////////////////////////////////
_forEachSearch(callback) {
for (const { position } of this._searches) {
callback(position);
}
},
_virtualConsoleLog(position, text, callback) {
this._searches.push({ position, text, callback, results: [] });
this._searchControl.reset();
},
_onSearchPause(point) {
for (const { position, text, callback, results } of this._searches) {
if (RecordReplayControl.positionSubsumes(position, point.position)) {
if (!results.some(existing => point.progress == existing.progress)) {
let evaluateResult;
if (text) {
const frameData = this._searchControl.sendRequest({
type: "getFrame",
index: NewestFrameIndex,
});
if ("index" in frameData) {
const rv = this._searchControl.sendRequest({
type: "frameEvaluate",
index: frameData.index,
text,
});
evaluateResult = this._convertCompletionValue(rv, { forSearch: true });
}
}
results.push(point);
callback(point, evaluateResult);
}
}
}
},
/////////////////////////////////////////////////////////
// Breakpoint management
/////////////////////////////////////////////////////////
@ -485,14 +530,20 @@ ReplayDebugger.prototype = {
// Object methods
/////////////////////////////////////////////////////////
// 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) {
_getObject(id, options) {
if (options && options.forSearch) {
// Returning objects through searches is NYI.
return "<UnknownSearchObject>";
}
const forConsole = options && options.forConsole;
if (id && !this._objects[id]) {
const data = this._sendRequest({ type: "getObject", id });
switch (data.kind) {
case "Object":
// 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.
this._objects[id] = new ReplayDebuggerObject(this, data, forConsole);
break;
case "Environment":
@ -509,10 +560,10 @@ ReplayDebugger.prototype = {
return rv;
},
_convertValue(value, forConsole) {
_convertValue(value, options) {
if (isNonNullObject(value)) {
if (value.object) {
return this._getObject(value.object, forConsole);
return this._getObject(value.object, options);
} else if (value.special == "undefined") {
return undefined;
} else if (value.special == "NaN") {
@ -526,12 +577,12 @@ ReplayDebugger.prototype = {
return value;
},
_convertCompletionValue(value) {
_convertCompletionValue(value, options) {
if ("return" in value) {
return { return: this._convertValue(value.return) };
return { return: this._convertValue(value.return, options) };
}
if ("throw" in value) {
return { throw: this._convertValue(value.throw) };
return { throw: this._convertValue(value.throw, options) };
}
ThrowError("Unexpected completion value");
return null; // For eslint
@ -582,7 +633,7 @@ ReplayDebugger.prototype = {
if (message.messageType == "ConsoleAPI" && message.arguments) {
for (let i = 0; i < message.arguments.length; i++) {
message.arguments[i] = this._convertValue(message.arguments[i],
/* forConsole = */ true);
{ forConsole: true });
}
}
return message;
@ -662,6 +713,7 @@ ReplayDebuggerScript.prototype = {
get format() { return this._data.format; },
_forward(type, value) {
this._dbg._ensurePaused();
return this._dbg._sendRequest({ type, id: this._data.id, value });
},
@ -683,6 +735,11 @@ ReplayDebuggerScript.prototype = {
});
},
replayVirtualConsoleLog(offset, text, callback) {
this._dbg._virtualConsoleLog({ kind: "Break", script: this._data.id, offset },
text, callback);
},
get isGeneratorFunction() { NYI(); },
get isAsyncFunction() { NYI(); },
getChildScripts: NYI,