Source code

Revision control

Copy as Markdown

Other Tools

/* 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 {
getAdHocFrontOrPrimitiveGrip,
// eslint-disable-next-line mozilla/reject-some-requires
class ScriptCommand {
constructor({ commands }) {
this._commands = commands;
}
/**
* Execute a JavaScript expression.
*
* @param {String} expression: The code you want to evaluate.
* @param {Object} options: Options for evaluation:
* @param {Object} options.frameActor: a FrameActor ID. The actor holds a reference to
* a Debugger.Frame. This option allows you to evaluate the string in the frame
* of the given FrameActor.
* @param {String} options.url: the url to evaluate the script as. Defaults to "debugger eval code".
* @param {TargetFront} options.selectedTargetFront: When passed, the expression will be
* evaluated in the context of the target (as opposed to the default, top-level one).
* @param {String} options.selectedNodeActor: A NodeActor ID that may be used by helper
* functions that can reference the currently selected node in the Inspector, like $0.
* @param {String} options.selectedObjectActor: the actorID of a given objectActor.
* This is used by context menu entries to get a reference to an object, in order
* to perform some operation on it (copy it, store it as a global variable, …).
* @param {Number} options.innerWindowID: An optional window id to be used for the evaluation,
* instead of the regular webConsoleActor.evalWindow.
* This is used by functions that may want to evaluate in a different window (for
* example a non-remote iframe), like getting the elements of a given document.
* @param {object} options.mapped: An optional object indicating if the original expression
* entered by the users have been modified
* @param {boolean} options.mapped.await: true if the expression was a top-level await
* expression that was wrapped in an async-iife
* @param {boolean} options.disableBreaks: Set to true to avoid triggering any
* type of breakpoint when evaluating the source. Also, the evaluated source won't be
* visible in the debugger UI.
* @param {boolean} options.preferConsoleCommandsOverLocalSymbols: Set to true to force
* overriding local symbols defined by the page with same-name console commands.
*
* @return {Promise}: A promise that resolves with the response.
*/
async execute(expression, options = {}) {
const {
selectedObjectActor,
selectedNodeActor,
frameActor,
selectedTargetFront,
} = options;
// Retrieve the right WebConsole front that relates either to (by order of priority):
// - the currently selected target in the context selector
// (selectedTargetFront argument),
// - the object picked in the console (when using store as global) (selectedObjectActor),
// - the currently selected Node in the inspector (selectedNodeActor),
// - the currently selected frame in the debugger (when paused) (frameActor),
// - the currently selected target in the iframe dropdown
// (selectedTargetFront from the TargetCommand)
let targetFront = this._commands.targetCommand.selectedTargetFront;
const selectedActor =
selectedObjectActor || selectedNodeActor || frameActor;
if (selectedTargetFront) {
targetFront = selectedTargetFront;
} else if (selectedActor) {
const selectedFront = this._commands.client.getFrontByID(selectedActor);
if (selectedFront) {
targetFront = selectedFront.targetFront;
}
}
const consoleFront = await targetFront.getFront("console");
// We call `evaluateJSAsync` RDP request, which immediately returns a simple `resultID`,
// for which we later receive a related `evaluationResult` RDP event, with the same resultID.
// The evaluation result will be contained in this RDP event.
let resultID;
const response = await new Promise(resolve => {
const offEvaluationResult = consoleFront.on(
"evaluationResult",
async packet => {
// In some cases, the evaluationResult event can be received before the call to
// evaluationJSAsync completes. So make sure to wait for the corresponding promise
// before handling the evaluationResult event.
await onEvaluateJSAsync;
if (packet.resultID === resultID) {
resolve(packet);
offEvaluationResult();
}
}
);
const onEvaluateJSAsync = consoleFront
.evaluateJSAsync({
text: expression,
eager: options.eager,
frameActor,
innerWindowID: options.innerWindowID,
mapped: options.mapped,
selectedNodeActor,
selectedObjectActor,
url: options.url,
disableBreaks: options.disableBreaks,
preferConsoleCommandsOverLocalSymbols:
options.preferConsoleCommandsOverLocalSymbols,
})
.then(packet => {
resultID = packet.resultID;
});
});
// `response` is the packet sent via `evaluationResult` RDP event.
if (response.error) {
throw response;
}
if (response.result) {
response.result = getAdHocFrontOrPrimitiveGrip(
response.result,
consoleFront
);
}
if (response.helperResult?.object) {
response.helperResult.object = getAdHocFrontOrPrimitiveGrip(
response.helperResult.object,
consoleFront
);
}
if (response.exception) {
response.exception = getAdHocFrontOrPrimitiveGrip(
response.exception,
consoleFront
);
}
if (response.exceptionMessage) {
response.exceptionMessage = getAdHocFrontOrPrimitiveGrip(
response.exceptionMessage,
consoleFront
);
}
return response;
}
}
module.exports = ScriptCommand;