summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/script/script-command.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/commands/script/script-command.js')
-rw-r--r--devtools/shared/commands/script/script-command.js149
1 files changed, 149 insertions, 0 deletions
diff --git a/devtools/shared/commands/script/script-command.js b/devtools/shared/commands/script/script-command.js
new file mode 100644
index 0000000000..93917944d5
--- /dev/null
+++ b/devtools/shared/commands/script/script-command.js
@@ -0,0 +1,149 @@
+/* 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
+ *
+ * @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,
+ })
+ .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;