730 lines
24 KiB
JavaScript
730 lines
24 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
const Debugger = require("Debugger");
|
|
const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
|
|
|
|
const lazy = {};
|
|
if (!isWorker) {
|
|
ChromeUtils.defineESModuleGetters(
|
|
lazy,
|
|
{
|
|
Reflect: "resource://gre/modules/reflect.sys.mjs",
|
|
},
|
|
{ global: "contextual" }
|
|
);
|
|
}
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
["isCommand"],
|
|
"resource://devtools/server/actors/webconsole/commands/parser.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"WebConsoleCommandsManager",
|
|
"resource://devtools/server/actors/webconsole/commands/manager.js",
|
|
true
|
|
);
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"LongStringActor",
|
|
"resource://devtools/server/actors/string.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"eagerEcmaAllowlist",
|
|
"resource://devtools/server/actors/webconsole/eager-ecma-allowlist.js"
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"eagerFunctionAllowlist",
|
|
"resource://devtools/server/actors/webconsole/eager-function-allowlist.js"
|
|
);
|
|
|
|
function isObject(value) {
|
|
return Object(value) === value;
|
|
}
|
|
|
|
/**
|
|
* Evaluates a string using the debugger API.
|
|
*
|
|
* To allow the variables view to update properties from the Web Console we
|
|
* provide the "selectedObjectActor" mechanism: the Web Console tells the
|
|
* ObjectActor ID for which it desires to evaluate an expression. The
|
|
* Debugger.Object pointed at by the actor ID is bound such that it is
|
|
* available during expression evaluation (executeInGlobalWithBindings()).
|
|
*
|
|
* Example:
|
|
* _self['foobar'] = 'test'
|
|
* where |_self| refers to the desired object.
|
|
*
|
|
* The |frameActor| property allows the Web Console client to provide the
|
|
* frame actor ID, such that the expression can be evaluated in the
|
|
* user-selected stack frame.
|
|
*
|
|
* For the above to work we need the debugger and the Web Console to share
|
|
* a connection, otherwise the Web Console actor will not find the frame
|
|
* actor.
|
|
*
|
|
* The Debugger.Frame comes from the jsdebugger's Debugger instance, which
|
|
* is different from the Web Console's Debugger instance. This means that
|
|
* for evaluation to work, we need to create a new instance for the Web
|
|
* Console Commands helpers - they need to be Debugger.Objects coming from the
|
|
* jsdebugger's Debugger instance.
|
|
*
|
|
* When |selectedObjectActor| is used objects can come from different iframes,
|
|
* from different domains. To avoid permission-related errors when objects
|
|
* come from a different window, we also determine the object's own global,
|
|
* such that evaluation happens in the context of that global. This means that
|
|
* evaluation will happen in the object's iframe, rather than the top level
|
|
* window.
|
|
*
|
|
* @param string string
|
|
* String to evaluate.
|
|
* @param object [options]
|
|
* Options for evaluation:
|
|
* - selectedObjectActor: the ObjectActor ID to use for evaluation.
|
|
* |evalWithBindings()| will be called with one additional binding:
|
|
* |_self| which will point to the Debugger.Object of the given
|
|
* ObjectActor. Executes with the top level window as the global.
|
|
* - frameActor: the FrameActor ID to use for evaluation. The given
|
|
* debugger frame is used for evaluation, instead of the global window.
|
|
* - selectedNodeActor: the NodeActor ID of the currently selected node
|
|
* in the Inspector (or null, if there is no selection). This is used
|
|
* for helper functions that make reference to the currently selected
|
|
* node, like $0.
|
|
* - innerWindowID: An optional window id to use instead of webConsole.evalWindow.
|
|
* This is used by function that need to evaluate in a different window for which
|
|
* we don't have a dedicated target (for example a non-remote iframe).
|
|
* - eager: Set to true if you want the evaluation to bail if it may have side effects.
|
|
* - url: the url to evaluate the script as. Defaults to "debugger eval code",
|
|
* or "debugger eager eval code" if eager is true.
|
|
* - preferConsoleCommandsOverLocalSymbols: Set to true if console commands
|
|
* should override local symbols.
|
|
* @param object webConsole
|
|
*
|
|
* @return object
|
|
* An object that holds the following properties:
|
|
* - dbg: the debugger where the string was evaluated.
|
|
* - frame: (optional) the frame where the string was evaluated.
|
|
* - global: the Debugger.Object for the global where the string was evaluated in.
|
|
* - result: the result of the evaluation.
|
|
*/
|
|
function evalWithDebugger(string, options = {}, webConsole) {
|
|
const trimmedString = string.trim();
|
|
// The help function needs to be easy to guess, so accept "?" as a shortcut
|
|
if (trimmedString === "?") {
|
|
return evalWithDebugger(":help", options, webConsole);
|
|
}
|
|
|
|
const isCmd = isCommand(trimmedString);
|
|
|
|
if (isCmd && options.eager) {
|
|
return {
|
|
result: null,
|
|
};
|
|
}
|
|
|
|
const { frame, dbg } = getFrameDbg(options, webConsole);
|
|
|
|
const { dbgGlobal, bindSelf } = getDbgGlobal(options, dbg, webConsole);
|
|
|
|
// If the strings starts with a `:`, do not try to evaluate the strings
|
|
// and instead only call the related command function directly from
|
|
// the privileged codebase.
|
|
if (isCmd) {
|
|
try {
|
|
return WebConsoleCommandsManager.executeCommand(
|
|
webConsole,
|
|
dbgGlobal,
|
|
options.selectedNodeActor,
|
|
string
|
|
);
|
|
} catch (e) {
|
|
// Catch any exception and return a result similar to the output
|
|
// of executeCommand to notify the client about this unexpected error.
|
|
return {
|
|
helperResult: {
|
|
type: "exception",
|
|
message: e.message,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const helpers = WebConsoleCommandsManager.getWebConsoleCommands(
|
|
webConsole,
|
|
dbgGlobal,
|
|
frame,
|
|
string,
|
|
options.selectedNodeActor,
|
|
options.preferConsoleCommandsOverLocalSymbols
|
|
);
|
|
let { bindings } = helpers;
|
|
|
|
// Ease calling the help command by not requiring the "()".
|
|
// But wait for the bindings computation in order to know if "help" variable
|
|
// was overloaded by the page. If it is missing from bindings, it is overloaded and we should
|
|
// display its value by doing a regular evaluation.
|
|
if (trimmedString === "help" && bindings.help) {
|
|
return evalWithDebugger(":help", options, webConsole);
|
|
}
|
|
|
|
// '_self' refers to the JS object references via options.selectedObjectActor.
|
|
// This isn't exposed on typical console evaluation, but only when "Store As Global"
|
|
// runs an invisible script storing `_self` into `temp${i}`.
|
|
if (bindSelf) {
|
|
bindings._self = bindSelf;
|
|
}
|
|
|
|
// Log points calls this method from the server side and pass additional variables
|
|
// to be exposed to the evaluated JS string
|
|
if (options.bindings) {
|
|
bindings = { ...bindings, ...options.bindings };
|
|
}
|
|
|
|
const evalOptions = {};
|
|
|
|
const urlOption =
|
|
options.url || (options.eager ? "debugger eager eval code" : null);
|
|
if (typeof urlOption === "string") {
|
|
evalOptions.url = urlOption;
|
|
}
|
|
|
|
if (typeof options.lineNumber === "number") {
|
|
evalOptions.lineNumber = options.lineNumber;
|
|
}
|
|
|
|
if (options.disableBreaks || options.eager) {
|
|
// When we are disabling breakpoints for a given evaluation, or when we are doing an eager evaluation,
|
|
// also prevent spawning related Debugger.Source object to avoid showing it
|
|
// in the debugger UI
|
|
evalOptions.hideFromDebugger = true;
|
|
}
|
|
|
|
if (options.preferConsoleCommandsOverLocalSymbols) {
|
|
evalOptions.useInnerBindings = true;
|
|
}
|
|
|
|
updateConsoleInputEvaluation(dbg, webConsole);
|
|
|
|
const evalString = getEvalInput(string, bindings);
|
|
const result = getEvalResult(
|
|
dbg,
|
|
evalString,
|
|
evalOptions,
|
|
bindings,
|
|
frame,
|
|
dbgGlobal,
|
|
options.eager
|
|
);
|
|
|
|
// Attempt to initialize any declarations found in the evaluated string
|
|
// since they may now be stuck in an "initializing" state due to the
|
|
// error. Already-initialized bindings will be ignored.
|
|
if (!frame && result && "throw" in result) {
|
|
forceLexicalInitForVariableDeclarationsInThrowingExpression(
|
|
dbgGlobal,
|
|
string
|
|
);
|
|
}
|
|
|
|
return {
|
|
result,
|
|
// Retrieve the result of commands, if any ran
|
|
helperResult: helpers.getHelperResult(),
|
|
dbg,
|
|
frame,
|
|
dbgGlobal,
|
|
};
|
|
}
|
|
exports.evalWithDebugger = evalWithDebugger;
|
|
|
|
/**
|
|
* Sub-function to reduce the complexity of evalWithDebugger.
|
|
* This focuses on calling Debugger.Frame or Debugger.Object eval methods.
|
|
*
|
|
* @param {Debugger} dbg
|
|
* @param {String} string
|
|
* The string to evaluate.
|
|
* @param {Object} evalOptions
|
|
* Spidermonkey options to pass to eval methods.
|
|
* @param {Object} bindings
|
|
* Dictionary object with symbols to override in the evaluation.
|
|
* @param {Debugger.Frame} frame
|
|
* If paused, the paused frame.
|
|
* @param {Debugger.Object} dbgGlobal
|
|
* The target's global.
|
|
* @param {Boolean} eager
|
|
* Is this an eager evaluation?
|
|
* @return {Object}
|
|
* The evaluation result object.
|
|
* See `Debugger.Ojbect.executeInGlobalWithBindings` definition.
|
|
*/
|
|
function getEvalResult(
|
|
dbg,
|
|
string,
|
|
evalOptions,
|
|
bindings,
|
|
frame,
|
|
dbgGlobal,
|
|
eager
|
|
) {
|
|
// When we are doing an eager evaluation, we aren't using the target's Debugger object
|
|
// but a special one, dedicated to each evaluation.
|
|
let noSideEffectDebugger = null;
|
|
if (eager) {
|
|
noSideEffectDebugger = makeSideeffectFreeDebugger(dbg);
|
|
|
|
// When a sideeffect-free debugger has been created, we need to eval
|
|
// in the context of that debugger in order for the side-effect tracking
|
|
// to apply.
|
|
if (frame) {
|
|
frame = noSideEffectDebugger.adoptFrame(frame);
|
|
} else {
|
|
dbgGlobal = noSideEffectDebugger.adoptDebuggeeValue(dbgGlobal);
|
|
}
|
|
if (bindings) {
|
|
bindings = Object.keys(bindings).reduce((acc, key) => {
|
|
acc[key] = noSideEffectDebugger.adoptDebuggeeValue(bindings[key]);
|
|
return acc;
|
|
}, {});
|
|
}
|
|
}
|
|
|
|
try {
|
|
let result;
|
|
if (frame) {
|
|
result = frame.evalWithBindings(string, bindings, evalOptions);
|
|
} else {
|
|
result = dbgGlobal.executeInGlobalWithBindings(
|
|
string,
|
|
bindings,
|
|
evalOptions
|
|
);
|
|
}
|
|
if (noSideEffectDebugger && result) {
|
|
if ("return" in result) {
|
|
result.return = dbg.adoptDebuggeeValue(result.return);
|
|
}
|
|
if ("throw" in result) {
|
|
result.throw = dbg.adoptDebuggeeValue(result.throw);
|
|
}
|
|
}
|
|
return result;
|
|
} finally {
|
|
// We need to be absolutely sure that the sideeffect-free debugger's
|
|
// debuggees are removed because otherwise we risk them terminating
|
|
// execution of later code in the case of unexpected exceptions.
|
|
if (noSideEffectDebugger) {
|
|
noSideEffectDebugger.onNativeCall = undefined;
|
|
noSideEffectDebugger.shouldAvoidSideEffects = false;
|
|
// Ensure removing the debuggee only as the very last step as various
|
|
// cleanups within the Debugger API are done per still-registered debuggee.
|
|
noSideEffectDebugger.removeAllDebuggees();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Force lexical initialization for let/const variables declared in a throwing expression.
|
|
* By spec, a lexical declaration is added to the *page-visible* global lexical environment
|
|
* for those variables, meaning they can't be redeclared (See Bug 1246215).
|
|
*
|
|
* This function gets the AST of the throwing expression to collect all the let/const
|
|
* declarations and call `forceLexicalInitializationByName`, which will initialize them
|
|
* to undefined, making it possible for them to be redeclared.
|
|
*
|
|
* @param {DebuggerObject} dbgGlobal
|
|
* @param {String} string: The expression that was evaluated and threw
|
|
* @returns
|
|
*/
|
|
function forceLexicalInitForVariableDeclarationsInThrowingExpression(
|
|
dbgGlobal,
|
|
string
|
|
) {
|
|
// Reflect is not usable in workers, so return early to avoid logging an error
|
|
// to the console when loading it.
|
|
if (isWorker) {
|
|
return;
|
|
}
|
|
|
|
let ast;
|
|
// Parse errors will raise an exception. We can/should ignore the error
|
|
// since it's already being handled elsewhere and we are only interested
|
|
// in initializing bindings.
|
|
try {
|
|
ast = lazy.Reflect.parse(string);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
for (const line of ast.body) {
|
|
// Only let and const declarations put bindings into an
|
|
// "initializing" state.
|
|
if (!(line.kind == "let" || line.kind == "const")) {
|
|
continue;
|
|
}
|
|
|
|
const identifiers = [];
|
|
for (const decl of line.declarations) {
|
|
switch (decl.id.type) {
|
|
case "Identifier":
|
|
// let foo = bar;
|
|
identifiers.push(decl.id.name);
|
|
break;
|
|
case "ArrayPattern":
|
|
// let [foo, bar] = [1, 2];
|
|
// let [foo=99, bar] = [1, 2];
|
|
for (const e of decl.id.elements) {
|
|
if (e.type == "Identifier") {
|
|
identifiers.push(e.name);
|
|
} else if (e.type == "AssignmentExpression") {
|
|
identifiers.push(e.left.name);
|
|
}
|
|
}
|
|
break;
|
|
case "ObjectPattern":
|
|
// let {bilbo, my} = {bilbo: "baggins", my: "precious"};
|
|
// let {blah: foo} = {blah: yabba()}
|
|
// let {blah: foo=99} = {blah: yabba()}
|
|
for (const prop of decl.id.properties) {
|
|
// key
|
|
if (prop.key?.type == "Identifier") {
|
|
identifiers.push(prop.key.name);
|
|
}
|
|
// value
|
|
if (prop.value?.type == "Identifier") {
|
|
identifiers.push(prop.value.name);
|
|
} else if (prop.value?.type == "AssignmentExpression") {
|
|
identifiers.push(prop.value.left.name);
|
|
} else if (prop.type === "SpreadExpression") {
|
|
identifiers.push(prop.expression.name);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const name of identifiers) {
|
|
dbgGlobal.forceLexicalInitializationByName(name);
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
console.error(
|
|
"Error in forceLexicalInitForVariableDeclarationsInThrowingExpression:",
|
|
ex
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a side-effect-free Debugger instance.
|
|
*
|
|
* @param {Debugger} targetActorDbg
|
|
* The target actor's dbg object, crafted by make-debugger.js module.
|
|
* @return {Debugger}
|
|
* Side-effect-free Debugger instance.
|
|
*/
|
|
function makeSideeffectFreeDebugger(targetActorDbg) {
|
|
// Populate the cached Map once before the evaluation
|
|
ensureSideEffectFreeNatives();
|
|
|
|
// Note: It is critical for debuggee performance that we implement all of
|
|
// this debuggee tracking logic with a separate Debugger instance.
|
|
// Bug 1617666 arises otherwise if we set an onEnterFrame hook on the
|
|
// existing debugger object and then later clear it.
|
|
//
|
|
// Also note that we aren't registering any global to this debugger.
|
|
// We will only adopt values into it: the paused frame (if any) or the
|
|
// target's global (when not paused).
|
|
const dbg = new Debugger();
|
|
|
|
// Special flag in order to ensure that any evaluation or call being
|
|
// made via this debugger will be ignored by all debuggers except this one.
|
|
dbg.exclusiveDebuggerOnEval = true;
|
|
|
|
// We need to register all target actor's globals.
|
|
// In most cases, this will be only one global, except for the browser toolbox,
|
|
// where process target actors may interact with many.
|
|
// On the browser toolbox, we may have many debuggees and this is important to register
|
|
// them in order to detect native call made from/to these others globals.
|
|
for (const global of targetActorDbg.findDebuggees()) {
|
|
try {
|
|
dbg.addDebuggee(global);
|
|
} catch (e) {
|
|
// Ignore exceptions from the following cases:
|
|
// * A global from the same compartment (happens with parent process)
|
|
// * A dead wrapper (happens when the reference gets nuked after
|
|
// findAllGlobals call)
|
|
if (
|
|
!e.message.includes(
|
|
"debugger and debuggee must be in different compartments"
|
|
) &&
|
|
!e.message.includes("can't access dead object")
|
|
) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
const timeoutDuration = 100;
|
|
const endTime = Date.now() + timeoutDuration;
|
|
let count = 0;
|
|
function shouldCancel() {
|
|
// To keep the evaled code as quick as possible, we avoid querying the
|
|
// current time on ever single step and instead check every 100 steps
|
|
// as an arbitrary count that seemed to be "often enough".
|
|
return ++count % 100 === 0 && Date.now() > endTime;
|
|
}
|
|
|
|
const executedScripts = new Set();
|
|
const handler = {
|
|
hit: () => null,
|
|
};
|
|
dbg.onEnterFrame = frame => {
|
|
if (shouldCancel()) {
|
|
return null;
|
|
}
|
|
frame.onStep = () => {
|
|
if (shouldCancel()) {
|
|
return null;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const script = frame.script;
|
|
|
|
if (executedScripts.has(script)) {
|
|
return undefined;
|
|
}
|
|
executedScripts.add(script);
|
|
|
|
const offsets = script.getEffectfulOffsets();
|
|
for (const offset of offsets) {
|
|
script.setBreakpoint(offset, handler);
|
|
}
|
|
|
|
return undefined;
|
|
};
|
|
|
|
// The debugger only calls onNativeCall handlers on the debugger that is
|
|
// explicitly calling either eval, DebuggerObject.apply or DebuggerObject.call,
|
|
// so we need to add this hook on "dbg" even though the rest of our hooks work via "newDbg".
|
|
const { SIDE_EFFECT_FREE } = WebConsoleCommandsManager;
|
|
dbg.onNativeCall = (callee, reason) => {
|
|
try {
|
|
// Setters are always effectful. Natives called normally or called via
|
|
// getters are handled with an allowlist.
|
|
if (
|
|
(reason == "get" || reason == "call") &&
|
|
nativeIsEagerlyEvaluateable(callee)
|
|
) {
|
|
// Returning undefined causes execution to continue normally.
|
|
return undefined;
|
|
}
|
|
} catch (err) {
|
|
DevToolsUtils.reportException(
|
|
"evalWithDebugger onNativeCall",
|
|
new Error("Unable to validate native function against allowlist")
|
|
);
|
|
}
|
|
|
|
// The WebConsole Commands manager will use Cu.exportFunction which will force
|
|
// to call a native method which is hard to identify.
|
|
// getEvalResult will flag those getter methods with a magic attribute.
|
|
if (
|
|
reason == "call" &&
|
|
callee.unsafeDereference().isSideEffectFree === SIDE_EFFECT_FREE
|
|
) {
|
|
// Returning undefined causes execution to continue normally.
|
|
return undefined;
|
|
}
|
|
|
|
// Returning null terminates the current evaluation.
|
|
return null;
|
|
};
|
|
dbg.shouldAvoidSideEffects = true;
|
|
|
|
return dbg;
|
|
}
|
|
|
|
// Native functions which are considered to be side effect free.
|
|
let gSideEffectFreeNatives; // string => Array(Function)
|
|
|
|
/**
|
|
* Generate gSideEffectFreeNatives map.
|
|
*/
|
|
function ensureSideEffectFreeNatives() {
|
|
if (gSideEffectFreeNatives) {
|
|
return;
|
|
}
|
|
|
|
const { natives: domNatives } = eagerFunctionAllowlist;
|
|
|
|
const natives = [
|
|
...eagerEcmaAllowlist.functions,
|
|
...eagerEcmaAllowlist.getters,
|
|
|
|
// Pull in all of the non-ECMAScript native functions that we want to
|
|
// allow as well.
|
|
...domNatives,
|
|
];
|
|
|
|
const map = new Map();
|
|
for (const n of natives) {
|
|
if (!map.has(n.name)) {
|
|
map.set(n.name, []);
|
|
}
|
|
map.get(n.name).push(n);
|
|
}
|
|
|
|
gSideEffectFreeNatives = map;
|
|
}
|
|
|
|
function nativeIsEagerlyEvaluateable(fn) {
|
|
if (fn.isBoundFunction) {
|
|
fn = fn.boundTargetFunction;
|
|
}
|
|
|
|
// We assume all DOM getters have no major side effect, and they are
|
|
// eagerly-evaluateable.
|
|
//
|
|
// JitInfo is used only by methods/accessors in WebIDL, and being
|
|
// "a getter with JitInfo" can be used as a condition to check if given
|
|
// function is DOM getter.
|
|
//
|
|
// This includes privileged interfaces in addition to standard web APIs.
|
|
if (fn.isNativeGetterWithJitInfo()) {
|
|
return true;
|
|
}
|
|
|
|
// Natives with certain names are always considered side effect free.
|
|
switch (fn.name) {
|
|
case "toString":
|
|
case "toLocaleString":
|
|
case "valueOf":
|
|
return true;
|
|
}
|
|
|
|
// This needs to use isSameNativeWithJitInfo instead of isSameNative, given
|
|
// DOM methods share single native function with different JSJitInto,
|
|
// and isSameNative cannot distinguish between side-effect-free methods
|
|
// and others.
|
|
const natives = gSideEffectFreeNatives.get(fn.name);
|
|
return natives && natives.some(n => fn.isSameNativeWithJitInfo(n));
|
|
}
|
|
|
|
function updateConsoleInputEvaluation(dbg, webConsole) {
|
|
// Adopt webConsole._lastConsoleInputEvaluation value in the new debugger,
|
|
// to prevent "Debugger.Object belongs to a different Debugger" exceptions
|
|
// related to the $_ bindings if the debugger object is changed from the
|
|
// last evaluation.
|
|
if (webConsole._lastConsoleInputEvaluation) {
|
|
webConsole._lastConsoleInputEvaluation = dbg.adoptDebuggeeValue(
|
|
webConsole._lastConsoleInputEvaluation
|
|
);
|
|
}
|
|
}
|
|
|
|
function getEvalInput(string) {
|
|
const trimmedString = string.trim();
|
|
// Add easter egg for console.mihai().
|
|
if (
|
|
trimmedString == "console.mihai()" ||
|
|
trimmedString == "console.mihai();"
|
|
) {
|
|
return '"http://incompleteness.me/blog/2015/02/09/console-dot-mihai/"';
|
|
}
|
|
return string;
|
|
}
|
|
|
|
function getFrameDbg(options, webConsole) {
|
|
if (!options.frameActor) {
|
|
return { frame: null, dbg: webConsole.dbg };
|
|
}
|
|
// Find the Debugger.Frame of the given FrameActor.
|
|
const frameActor = webConsole.conn.getActor(options.frameActor);
|
|
if (frameActor) {
|
|
// If we've been given a frame actor in whose scope we should evaluate the
|
|
// expression, be sure to use that frame's Debugger (that is, the JavaScript
|
|
// debugger's Debugger) for the whole operation, not the console's Debugger.
|
|
// (One Debugger will treat a different Debugger's Debugger.Object instances
|
|
// as ordinary objects, not as references to be followed, so mixing
|
|
// debuggers causes strange behaviors.)
|
|
return { frame: frameActor.frame, dbg: frameActor.threadActor.dbg };
|
|
}
|
|
return DevToolsUtils.reportException(
|
|
"evalWithDebugger",
|
|
Error("The frame actor was not found: " + options.frameActor)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get debugger object for given debugger and Web Console.
|
|
*
|
|
* @param object options
|
|
* See the `options` parameter of evalWithDebugger
|
|
* @param {Debugger} dbg
|
|
* Debugger object
|
|
* @param {WebConsoleActor} webConsole
|
|
* A reference to a webconsole actor which is used to get the target
|
|
* eval global and optionally the target actor
|
|
* @return object
|
|
* An object that holds the following properties:
|
|
* - bindSelf: (optional) the self object for the evaluation
|
|
* - dbgGlobal: the global object reference in the debugger
|
|
*/
|
|
function getDbgGlobal(options, dbg, webConsole) {
|
|
let evalGlobal = webConsole.evalGlobal;
|
|
|
|
if (options.innerWindowID) {
|
|
const window = Services.wm.getCurrentInnerWindowWithId(
|
|
options.innerWindowID
|
|
);
|
|
|
|
if (window) {
|
|
evalGlobal = window;
|
|
}
|
|
}
|
|
|
|
const dbgGlobal = dbg.makeGlobalObjectReference(evalGlobal);
|
|
|
|
// If we have an object to bind to |_self|, create a Debugger.Object
|
|
// referring to that object, belonging to dbg.
|
|
if (!options.selectedObjectActor) {
|
|
return { bindSelf: null, dbgGlobal };
|
|
}
|
|
|
|
// All the Object Actors are collected in the Target Actor's "objectsPool",
|
|
// except for objects communicated by the thread actor on pause,
|
|
// or by the JS Tracer.
|
|
// But the "selected object actor" is generated via the console actor evaluation,
|
|
// which stores its objects actor in the target's shared pool.
|
|
const actor = webConsole.targetActor.objectsPool.getActorByID(
|
|
options.selectedObjectActor
|
|
);
|
|
|
|
if (!actor) {
|
|
return { bindSelf: null, dbgGlobal };
|
|
}
|
|
|
|
const jsVal = actor instanceof LongStringActor ? actor.str : actor.rawObj;
|
|
if (!isObject(jsVal)) {
|
|
return { bindSelf: jsVal, dbgGlobal };
|
|
}
|
|
|
|
// If we use the makeDebuggeeValue method of jsVal's own global, then
|
|
// we'll get a D.O that sees jsVal as viewed from its own compartment -
|
|
// that is, without wrappers. The evalWithBindings call will then wrap
|
|
// jsVal appropriately for the evaluation compartment.
|
|
const bindSelf = dbgGlobal.makeDebuggeeValue(jsVal);
|
|
return { bindSelf, dbgGlobal };
|
|
}
|