Files
tubestation/devtools/shared/commands/script/script-command.js
Alexandre Poirot 8de5c5849e Bug 1921020 - [devtools] Allow evaluating console results reusing the Tracer actor Object Actor Pool. r=devtools-reviewers,devtools-backward-compat-reviewers,nchevobbe
This helps ensure that the object actors returned by the console actor evaluation RDP request
are the same for the same JS values being processed and notified via the tracer ressources.
This allows identifying same objects when doing a search per value.

Differential Revision: https://phabricator.services.mozilla.com/D225237
2024-11-04 14:42:51 +00:00

164 lines
6.5 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 {
getAdHocFrontOrPrimitiveGrip,
// eslint-disable-next-line mozilla/reject-some-requires
} = require("resource://devtools/client/fronts/object.js");
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.
* @param {boolean} options.evalInTracer: Set to true to store the evaluation result object
* actors within the Tracer Object Actor Pool.
* So that if the evaluation result object has already been processed by the JS Tracer,
* we will receive the same Object Actor.
* This helps later match the object when doing a search per value.
*
* @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,
evalInTracer: options.evalInTracer,
})
.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;