зеркало из https://github.com/mozilla/gecko-dev.git
795 строки
23 KiB
JavaScript
795 строки
23 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
/* eslint-disable spaced-comment, brace-style, indent-legacy */
|
|
|
|
// This file defines the logic that runs in the record/replay devtools sandbox.
|
|
// This code is loaded into all recording/replaying processes, and responds to
|
|
// requests and other instructions from the middleman via the exported symbols
|
|
// defined at the end of this file.
|
|
//
|
|
// Like all other JavaScript in the recording/replaying process, this code's
|
|
// state is included in memory snapshots and reset when checkpoints are
|
|
// restored. In the process of handling the middleman's requests, however, its
|
|
// state may vary between recording and replaying, or between different
|
|
// replays. As a result, we have to be very careful about performing operations
|
|
// that might interact with the recording --- any time we enter the debuggee
|
|
// and evaluate code or perform other operations.
|
|
// The RecordReplayControl.maybeDivergeFromRecording function should be used at
|
|
// any point where such interactions might occur.
|
|
// eslint-disable spaced-comment
|
|
|
|
"use strict";
|
|
|
|
const CC = Components.Constructor;
|
|
|
|
// Create a sandbox with the resources we need. require() doesn't work here.
|
|
const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
|
|
Cu.evalInSandbox(
|
|
"Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
|
|
"Components.utils.import('resource://gre/modules/Services.jsm');" +
|
|
"addDebuggerToGlobal(this);",
|
|
sandbox
|
|
);
|
|
const Debugger = sandbox.Debugger;
|
|
const RecordReplayControl = sandbox.RecordReplayControl;
|
|
const Services = sandbox.Services;
|
|
|
|
const dbg = new Debugger();
|
|
|
|
// We are interested in debugging all globals in the process.
|
|
dbg.onNewGlobalObject = function(global) {
|
|
dbg.addDebuggee(global);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Utilities
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
function assert(v) {
|
|
if (!v) {
|
|
RecordReplayControl.dump("Assertion Failed: " + (new Error()).stack + "\n");
|
|
throw new Error("Assertion Failed!");
|
|
}
|
|
}
|
|
|
|
// Bidirectional map between objects and IDs.
|
|
function IdMap() {
|
|
this._idToObject = [ undefined ];
|
|
this._objectToId = new Map();
|
|
}
|
|
|
|
IdMap.prototype = {
|
|
add(object) {
|
|
assert(object && !this._objectToId.has(object));
|
|
const id = this._idToObject.length;
|
|
this._idToObject.push(object);
|
|
this._objectToId.set(object, id);
|
|
return id;
|
|
},
|
|
|
|
getId(object) {
|
|
const id = this._objectToId.get(object);
|
|
return (id === undefined) ? 0 : id;
|
|
},
|
|
|
|
getObject(id) {
|
|
return this._idToObject[id];
|
|
},
|
|
|
|
forEach(callback) {
|
|
for (let i = 1; i < this._idToObject.length; i++) {
|
|
callback(i, this._idToObject[i]);
|
|
}
|
|
},
|
|
|
|
lastId() {
|
|
return this._idToObject.length - 1;
|
|
},
|
|
};
|
|
|
|
function countScriptFrames() {
|
|
let count = 0;
|
|
let frame = dbg.getNewestFrame();
|
|
while (frame) {
|
|
if (considerScript(frame.script)) {
|
|
count++;
|
|
}
|
|
frame = frame.older;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function scriptFrameForIndex(index) {
|
|
let indexFromTop = countScriptFrames() - 1 - index;
|
|
let frame = dbg.getNewestFrame();
|
|
while (true) {
|
|
if (considerScript(frame.script)) {
|
|
if (indexFromTop-- == 0) {
|
|
break;
|
|
}
|
|
}
|
|
frame = frame.older;
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
function isNonNullObject(obj) {
|
|
return obj && (typeof obj == "object" || typeof obj == "function");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Persistent Script State
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Association between Debugger.Scripts and their IDs. The indices that this
|
|
// table assigns to scripts are stable across the entire recording, even though
|
|
// this table (like all JS state) is included in snapshots, rolled back when
|
|
// rewinding, and so forth. In debuggee time, this table only grows (there is
|
|
// no way to remove entries). Scripts created for debugger activity (e.g. eval)
|
|
// are ignored, and off thread compilation is disabled, so this table acquires
|
|
// the same scripts in the same order as we roll back and run forward in the
|
|
// recording.
|
|
const gScripts = new IdMap();
|
|
|
|
function addScript(script) {
|
|
gScripts.add(script);
|
|
script.getChildScripts().forEach(addScript);
|
|
}
|
|
|
|
// Association between Debugger.ScriptSources and their IDs. As for gScripts,
|
|
// the indices assigned to a script source are consistent across all replays
|
|
// and rewinding.
|
|
const gScriptSources = new IdMap();
|
|
|
|
function addScriptSource(source) {
|
|
// Tolerate redundant attempts to add the same source, as we might see
|
|
// onNewScript calls for different scripts with the same source.
|
|
if (!gScriptSources.getId(source)) {
|
|
gScriptSources.add(source);
|
|
}
|
|
}
|
|
|
|
function considerScript(script) {
|
|
// The set of scripts which is exposed to the debugger server is the same as
|
|
// the scripts for which the progress counter is updated.
|
|
return RecordReplayControl.shouldUpdateProgressCounter(script.url);
|
|
}
|
|
|
|
dbg.onNewScript = function(script) {
|
|
if (RecordReplayControl.areThreadEventsDisallowed()) {
|
|
// This script is part of an eval on behalf of the debugger.
|
|
return;
|
|
}
|
|
|
|
if (!considerScript(script)) {
|
|
return;
|
|
}
|
|
|
|
addScript(script);
|
|
addScriptSource(script.source);
|
|
|
|
// Each onNewScript call advances the progress counter, to preserve the
|
|
// ProgressCounter invariant when onNewScript is called multiple times
|
|
// without executing any scripts.
|
|
RecordReplayControl.advanceProgressCounter();
|
|
|
|
hitGlobalHandler("NewScript");
|
|
|
|
// Check in case any handlers we need to install are on the scripts just
|
|
// created.
|
|
installPendingHandlers();
|
|
};
|
|
|
|
const gConsoleObjectProperties = new Map();
|
|
|
|
function shouldSaveConsoleProperty({ desc }) {
|
|
// When logging an object to the console, only properties captured here will
|
|
// be shown. We limit this to non-object data properties, as more complex
|
|
// properties have two problems: A) to inspect them we will need to switch to
|
|
// a replaying child process, which is very slow when there are many console
|
|
// messages, and B) trying to access objects transitively referred to by
|
|
// logged console objects will fail when unpaused, and depends on the current
|
|
// state of the process otherwise.
|
|
return "value" in desc && !isNonNullObject(desc.value);
|
|
}
|
|
|
|
function saveConsoleObjectProperties(obj) {
|
|
if (obj instanceof Debugger.Object) {
|
|
const properties = getObjectProperties(obj).filter(shouldSaveConsoleProperty);
|
|
gConsoleObjectProperties.set(obj, properties);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Console Message State
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const gConsoleMessages = [];
|
|
|
|
function newConsoleMessage(messageType, executionPoint, contents) {
|
|
// Each new console message advances the progress counter, to make sure
|
|
// that different messages have different progress values.
|
|
RecordReplayControl.advanceProgressCounter();
|
|
|
|
if (!executionPoint) {
|
|
executionPoint =
|
|
RecordReplayControl.currentExecutionPoint({ kind: "ConsoleMessage" });
|
|
}
|
|
|
|
contents.messageType = messageType;
|
|
contents.executionPoint = executionPoint;
|
|
gConsoleMessages.push(contents);
|
|
|
|
hitGlobalHandler("ConsoleMessage");
|
|
}
|
|
|
|
function convertStack(stack) {
|
|
if (stack) {
|
|
const { source, line, column, functionDisplayName } = stack;
|
|
const parent = convertStack(stack.parent);
|
|
return { source, line, column, functionDisplayName, parent };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Listen to all console messages in the process.
|
|
Services.console.registerListener({
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
|
|
|
|
observe(message) {
|
|
if (message instanceof Ci.nsIScriptError) {
|
|
// If there is a warp target associated with the execution point, use
|
|
// that. This will take users to the point where the error was originally
|
|
// generated, rather than where it was reported to the console.
|
|
let executionPoint;
|
|
if (message.timeWarpTarget) {
|
|
executionPoint =
|
|
RecordReplayControl.timeWarpTargetExecutionPoint(message.timeWarpTarget);
|
|
}
|
|
|
|
const contents = JSON.parse(JSON.stringify(message));
|
|
contents.stack = convertStack(message.stack);
|
|
newConsoleMessage("PageError", executionPoint, contents);
|
|
}
|
|
},
|
|
});
|
|
|
|
// Listen to all console API messages in the process.
|
|
Services.obs.addObserver({
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
|
|
|
|
observe(message, topic, data) {
|
|
const apiMessage = message.wrappedJSObject;
|
|
|
|
const contents = {};
|
|
for (const id in apiMessage) {
|
|
if (id != "wrappedJSObject" && id != "arguments") {
|
|
contents[id] = JSON.parse(JSON.stringify(apiMessage[id]));
|
|
}
|
|
}
|
|
|
|
// Message arguments are preserved as debuggee values.
|
|
if (apiMessage.arguments) {
|
|
contents.arguments = apiMessage.arguments.map(makeDebuggeeValue);
|
|
contents.arguments.forEach(saveConsoleObjectProperties);
|
|
}
|
|
|
|
newConsoleMessage("ConsoleAPI", null, contents);
|
|
},
|
|
}, "console-api-log-event");
|
|
|
|
function convertConsoleMessage(contents) {
|
|
const result = {};
|
|
for (const id in contents) {
|
|
if (id == "arguments" && contents.messageType == "ConsoleAPI") {
|
|
// Copy arguments over as debuggee values.
|
|
result.arguments = contents.arguments.map(convertValue);
|
|
} else {
|
|
result[id] = contents[id];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Position Handler State
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Position kinds we are expected to hit.
|
|
let gPositionHandlerKinds = Object.create(null);
|
|
|
|
// Handlers we tried to install but couldn't due to a script not existing.
|
|
// Breakpoints requested by the middleman --- which are preserved when
|
|
// restoring earlier checkpoints --- identify target scripts by their stable ID
|
|
// in gScripts. This array holds the breakpoints for scripts whose IDs we know
|
|
// but which have not been created yet.
|
|
const gPendingPcHandlers = [];
|
|
|
|
// Script/offset pairs where we have installed a breakpoint handler. We have to
|
|
// avoid installing duplicate handlers here because they will both be called.
|
|
const gInstalledPcHandlers = [];
|
|
|
|
// Callbacks to test whether a frame should have an OnPop handler.
|
|
const gOnPopFilters = [];
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
function ClearPositionHandlers() {
|
|
dbg.clearAllBreakpoints();
|
|
dbg.onEnterFrame = undefined;
|
|
|
|
gPositionHandlerKinds = Object.create(null);
|
|
gPendingPcHandlers.length = 0;
|
|
gInstalledPcHandlers.length = 0;
|
|
gOnPopFilters.length = 0;
|
|
}
|
|
|
|
function installPendingHandlers() {
|
|
const pending = gPendingPcHandlers.map(position => position);
|
|
gPendingPcHandlers.length = 0;
|
|
|
|
pending.forEach(EnsurePositionHandler);
|
|
}
|
|
|
|
// Hit a position with the specified kind if we are expected to. This is for
|
|
// use with position kinds that have no script/offset/frameIndex information.
|
|
function hitGlobalHandler(kind) {
|
|
if (gPositionHandlerKinds[kind]) {
|
|
RecordReplayControl.positionHit({ kind });
|
|
}
|
|
}
|
|
|
|
// The completion state of any frame that is being popped.
|
|
let gPopFrameResult = null;
|
|
|
|
function onPopFrame(completion) {
|
|
gPopFrameResult = completion;
|
|
RecordReplayControl.positionHit({
|
|
kind: "OnPop",
|
|
script: gScripts.getId(this.script),
|
|
frameIndex: countScriptFrames() - 1,
|
|
});
|
|
gPopFrameResult = null;
|
|
}
|
|
|
|
function onEnterFrame(frame) {
|
|
hitGlobalHandler("EnterFrame");
|
|
|
|
if (considerScript(frame.script)) {
|
|
gOnPopFilters.forEach(filter => {
|
|
if (filter(frame)) {
|
|
frame.onPop = onPopFrame;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function addOnPopFilter(filter) {
|
|
let frame = dbg.getNewestFrame();
|
|
while (frame) {
|
|
if (considerScript(frame.script) && filter(frame)) {
|
|
frame.onPop = onPopFrame;
|
|
}
|
|
frame = frame.older;
|
|
}
|
|
|
|
gOnPopFilters.push(filter);
|
|
dbg.onEnterFrame = onEnterFrame;
|
|
}
|
|
|
|
function EnsurePositionHandler(position) {
|
|
gPositionHandlerKinds[position.kind] = true;
|
|
|
|
switch (position.kind) {
|
|
case "Break":
|
|
case "OnStep":
|
|
let debugScript;
|
|
if (position.script) {
|
|
debugScript = gScripts.getObject(position.script);
|
|
if (!debugScript) {
|
|
// The script referred to in this position does not exist yet, so we
|
|
// can't install a handler for it. Add a pending handler so that we
|
|
// can install the handler once the script is created.
|
|
gPendingPcHandlers.push(position);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const match = function({script, offset}) {
|
|
return script == position.script && offset == position.offset;
|
|
};
|
|
if (gInstalledPcHandlers.some(match)) {
|
|
return;
|
|
}
|
|
gInstalledPcHandlers.push({ script: position.script, offset: position.offset });
|
|
|
|
debugScript.setBreakpoint(position.offset, {
|
|
hit() {
|
|
RecordReplayControl.positionHit({
|
|
kind: "OnStep",
|
|
script: position.script,
|
|
offset: position.offset,
|
|
frameIndex: countScriptFrames() - 1,
|
|
});
|
|
},
|
|
});
|
|
break;
|
|
case "OnPop":
|
|
if (position.script) {
|
|
addOnPopFilter(frame => gScripts.getId(frame.script) == position.script);
|
|
} else {
|
|
addOnPopFilter(frame => true);
|
|
}
|
|
break;
|
|
case "EnterFrame":
|
|
dbg.onEnterFrame = onEnterFrame;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
function GetEntryPosition(position) {
|
|
if (position.kind == "Break" || position.kind == "OnStep") {
|
|
const script = gScripts.getObject(position.script);
|
|
if (script) {
|
|
return {
|
|
kind: "Break",
|
|
script: position.script,
|
|
offset: script.mainOffset,
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Paused State
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
let gPausedObjects = new IdMap();
|
|
|
|
function getObjectId(obj) {
|
|
const id = gPausedObjects.getId(obj);
|
|
if (!id && obj) {
|
|
assert((obj instanceof Debugger.Object) ||
|
|
(obj instanceof Debugger.Environment));
|
|
return gPausedObjects.add(obj);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
function convertValue(value) {
|
|
if (value instanceof Debugger.Object) {
|
|
return { object: getObjectId(value) };
|
|
}
|
|
if (value === undefined) {
|
|
return { special: "undefined" };
|
|
}
|
|
if (value !== value) { // eslint-disable-line no-self-compare
|
|
return { special: "NaN" };
|
|
}
|
|
if (value == Infinity) {
|
|
return { special: "Infinity" };
|
|
}
|
|
if (value == -Infinity) {
|
|
return { special: "-Infinity" };
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function convertCompletionValue(value) {
|
|
if ("return" in value) {
|
|
return { return: convertValue(value.return) };
|
|
}
|
|
if ("throw" in value) {
|
|
return { throw: convertValue(value.throw) };
|
|
}
|
|
throw new Error("Unexpected completion value");
|
|
}
|
|
|
|
function makeDebuggeeValue(value) {
|
|
if (isNonNullObject(value)) {
|
|
assert(!(value instanceof Debugger.Object));
|
|
const global = Cu.getGlobalForObject(value);
|
|
const dbgGlobal = dbg.makeGlobalObjectReference(global);
|
|
return dbgGlobal.makeDebuggeeValue(value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
function ClearPausedState() {
|
|
gPausedObjects = new IdMap();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Handler Helpers
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
function getScriptData(id) {
|
|
const script = gScripts.getObject(id);
|
|
return {
|
|
id,
|
|
sourceId: gScriptSources.getId(script.source),
|
|
startLine: script.startLine,
|
|
lineCount: script.lineCount,
|
|
sourceStart: script.sourceStart,
|
|
sourceLength: script.sourceLength,
|
|
displayName: script.displayName,
|
|
url: script.url,
|
|
};
|
|
}
|
|
|
|
function getSourceData(id) {
|
|
const source = gScriptSources.getObject(id);
|
|
const introductionScript = gScripts.getId(source.introductionScript);
|
|
return {
|
|
id: id,
|
|
text: source.text,
|
|
url: source.url,
|
|
displayURL: source.displayURL,
|
|
elementAttributeName: source.elementAttributeName,
|
|
introductionScript,
|
|
introductionOffset: introductionScript ? source.introductionOffset : undefined,
|
|
introductionType: source.introductionType,
|
|
sourceMapURL: source.sourceMapURL,
|
|
};
|
|
}
|
|
|
|
function forwardToScript(name) {
|
|
return request => gScripts.getObject(request.id)[name](request.value);
|
|
}
|
|
|
|
function getObjectProperties(object) {
|
|
const names = object.getOwnPropertyNames();
|
|
|
|
return names.map(name => {
|
|
const desc = object.getOwnPropertyDescriptor(name);
|
|
if ("value" in desc) {
|
|
desc.value = convertValue(desc.value);
|
|
}
|
|
if ("get" in desc) {
|
|
desc.get = getObjectId(desc.get);
|
|
}
|
|
if ("set" in desc) {
|
|
desc.set = getObjectId(desc.set);
|
|
}
|
|
return { name, desc };
|
|
});
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Handlers
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const gRequestHandlers = {
|
|
|
|
repaint() {
|
|
if (!RecordReplayControl.maybeDivergeFromRecording()) {
|
|
return {};
|
|
}
|
|
return RecordReplayControl.repaint();
|
|
},
|
|
|
|
findScripts(request) {
|
|
const query = Object.assign({}, request.query);
|
|
if ("global" in query) {
|
|
query.global = gPausedObjects.getObject(query.global);
|
|
}
|
|
if ("source" in query) {
|
|
query.source = gScriptSources.getObject(query.source);
|
|
if (!query.source) {
|
|
return [];
|
|
}
|
|
}
|
|
const scripts = dbg.findScripts(query);
|
|
const rv = [];
|
|
scripts.forEach(script => {
|
|
if (considerScript(script)) {
|
|
rv.push(getScriptData(gScripts.getId(script)));
|
|
}
|
|
});
|
|
return rv;
|
|
},
|
|
|
|
getScript(request) {
|
|
return getScriptData(request.id);
|
|
},
|
|
|
|
getNewScript(request) {
|
|
return getScriptData(gScripts.lastId());
|
|
},
|
|
|
|
getContent(request) {
|
|
return RecordReplayControl.getContent(request.url);
|
|
},
|
|
|
|
findSources(request) {
|
|
const sources = [];
|
|
gScriptSources.forEach((id) => {
|
|
sources.push(getSourceData(id));
|
|
});
|
|
return sources;
|
|
},
|
|
|
|
getSource(request) {
|
|
return getSourceData(request.id);
|
|
},
|
|
|
|
getObject(request) {
|
|
const object = gPausedObjects.getObject(request.id);
|
|
if (object instanceof Debugger.Object) {
|
|
return {
|
|
id: request.id,
|
|
kind: "Object",
|
|
callable: object.callable,
|
|
isBoundFunction: object.isBoundFunction,
|
|
isArrowFunction: object.isArrowFunction,
|
|
isGeneratorFunction: object.isGeneratorFunction,
|
|
isAsyncFunction: object.isAsyncFunction,
|
|
proto: getObjectId(object.proto),
|
|
class: object.class,
|
|
name: object.name,
|
|
displayName: object.displayName,
|
|
parameterNames: object.parameterNames,
|
|
script: gScripts.getId(object.script),
|
|
environment: getObjectId(object.environment),
|
|
global: getObjectId(object.global),
|
|
isProxy: object.isProxy,
|
|
isExtensible: object.isExtensible(),
|
|
isSealed: object.isSealed(),
|
|
isFrozen: object.isFrozen(),
|
|
};
|
|
}
|
|
if (object instanceof Debugger.Environment) {
|
|
return {
|
|
id: request.id,
|
|
kind: "Environment",
|
|
type: object.type,
|
|
parent: getObjectId(object.parent),
|
|
object: object.type == "declarative" ? 0 : getObjectId(object.object),
|
|
callee: getObjectId(object.callee),
|
|
optimizedOut: object.optimizedOut,
|
|
};
|
|
}
|
|
throw new Error("Unknown object kind");
|
|
},
|
|
|
|
getObjectProperties(request) {
|
|
if (!RecordReplayControl.maybeDivergeFromRecording()) {
|
|
return [{
|
|
name: "Unknown properties",
|
|
desc: {
|
|
value: "Recording divergence in getObjectProperties",
|
|
enumerable: true,
|
|
},
|
|
}];
|
|
}
|
|
|
|
const object = gPausedObjects.getObject(request.id);
|
|
return getObjectProperties(object);
|
|
},
|
|
|
|
getObjectPropertiesForConsole(request) {
|
|
const object = gPausedObjects.getObject(request.id);
|
|
const properties = gConsoleObjectProperties.get(object);
|
|
if (!properties) {
|
|
throw new Error("Console object properties not saved");
|
|
}
|
|
return properties;
|
|
},
|
|
|
|
getEnvironmentNames(request) {
|
|
if (!RecordReplayControl.maybeDivergeFromRecording()) {
|
|
return [{name: "Unknown names",
|
|
value: "Recording divergence in getEnvironmentNames" }];
|
|
}
|
|
|
|
const env = gPausedObjects.getObject(request.id);
|
|
const names = env.names();
|
|
|
|
return names.map(name => {
|
|
return { name, value: convertValue(env.getVariable(name)) };
|
|
});
|
|
},
|
|
|
|
getFrame(request) {
|
|
if (request.index == -1 /* NewestFrameIndex */) {
|
|
const numFrames = countScriptFrames();
|
|
if (!numFrames) {
|
|
// Return an empty object when there are no frames.
|
|
return {};
|
|
}
|
|
request.index = numFrames - 1;
|
|
}
|
|
|
|
const frame = scriptFrameForIndex(request.index);
|
|
|
|
let _arguments = null;
|
|
if (frame.arguments) {
|
|
_arguments = [];
|
|
for (let i = 0; i < frame.arguments.length; i++) {
|
|
_arguments.push(convertValue(frame.arguments[i]));
|
|
}
|
|
}
|
|
|
|
return {
|
|
index: request.index,
|
|
type: frame.type,
|
|
callee: getObjectId(frame.callee),
|
|
environment: getObjectId(frame.environment),
|
|
generator: frame.generator,
|
|
constructing: frame.constructing,
|
|
this: convertValue(frame.this),
|
|
script: gScripts.getId(frame.script),
|
|
offset: frame.offset,
|
|
arguments: _arguments,
|
|
};
|
|
},
|
|
|
|
getLineOffsets: forwardToScript("getLineOffsets"),
|
|
getOffsetLocation: forwardToScript("getOffsetLocation"),
|
|
getSuccessorOffsets: forwardToScript("getSuccessorOffsets"),
|
|
getPredecessorOffsets: forwardToScript("getPredecessorOffsets"),
|
|
|
|
frameEvaluate(request) {
|
|
if (!RecordReplayControl.maybeDivergeFromRecording()) {
|
|
return { throw: "Recording divergence in frameEvaluate" };
|
|
}
|
|
|
|
const frame = scriptFrameForIndex(request.index);
|
|
const rv = frame.eval(request.text, request.options);
|
|
return convertCompletionValue(rv);
|
|
},
|
|
|
|
popFrameResult(request) {
|
|
return gPopFrameResult ? convertCompletionValue(gPopFrameResult) : {};
|
|
},
|
|
|
|
findConsoleMessages(request) {
|
|
return gConsoleMessages.map(convertConsoleMessage);
|
|
},
|
|
|
|
getNewConsoleMessage(request) {
|
|
return convertConsoleMessage(gConsoleMessages[gConsoleMessages.length - 1]);
|
|
},
|
|
|
|
currentExecutionPoint(request) {
|
|
return RecordReplayControl.currentExecutionPoint();
|
|
},
|
|
|
|
recordingEndpoint(request) {
|
|
return RecordReplayControl.recordingEndpoint();
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
function ProcessRequest(request) {
|
|
try {
|
|
if (gRequestHandlers[request.type]) {
|
|
return gRequestHandlers[request.type](request);
|
|
}
|
|
return { exception: "No handler for " + request.type };
|
|
} catch (e) {
|
|
let msg;
|
|
try {
|
|
msg = "" + e;
|
|
} catch (ee) {
|
|
msg = "Unknown";
|
|
}
|
|
RecordReplayControl.dump("ReplayDebugger Record/Replay Error: " + msg + "\n");
|
|
return { exception: msg };
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
var EXPORTED_SYMBOLS = [
|
|
"EnsurePositionHandler",
|
|
"ClearPositionHandlers",
|
|
"ClearPausedState",
|
|
"ProcessRequest",
|
|
"GetEntryPosition",
|
|
];
|