script-command.js (6617B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 getAdHocFrontOrPrimitiveGrip, 9 // eslint-disable-next-line mozilla/reject-some-requires 10 } = require("resource://devtools/client/fronts/object.js"); 11 12 class ScriptCommand { 13 constructor({ commands }) { 14 this._commands = commands; 15 } 16 17 /** 18 * Execute a JavaScript expression. 19 * 20 * @param {string} expression: The code you want to evaluate. 21 * @param {object} options: Options for evaluation: 22 * @param {object} options.frameActor: a FrameActor ID. The actor holds a reference to 23 * a Debugger.Frame. This option allows you to evaluate the string in the frame 24 * of the given FrameActor. 25 * @param {string} options.url: the url to evaluate the script as. Defaults to "debugger eval code". 26 * @param {TargetFront} options.selectedTargetFront: When passed, the expression will be 27 * evaluated in the context of the target (as opposed to the default, top-level one). 28 * @param {string} options.selectedNodeActor: A NodeActor ID that may be used by helper 29 * functions that can reference the currently selected node in the Inspector, like $0. 30 * @param {string} options.selectedObjectActor: the actorID of a given objectActor. 31 * This is used by context menu entries to get a reference to an object, in order 32 * to perform some operation on it (copy it, store it as a global variable, …). 33 * @param {number} options.innerWindowID: An optional window id to be used for the evaluation, 34 * instead of the regular webConsoleActor.evalWindow. 35 * This is used by functions that may want to evaluate in a different window (for 36 * example a non-remote iframe), like getting the elements of a given document. 37 * @param {object} options.mapped: An optional object indicating if the original expression 38 * entered by the users have been modified 39 * @param {boolean} options.mapped.await: true if the expression was a top-level await 40 * expression that was wrapped in an async-iife 41 * @param {boolean} options.disableBreaks: Set to true to avoid triggering any 42 * type of breakpoint when evaluating the source. Also, the evaluated source won't be 43 * visible in the debugger UI. 44 * @param {boolean} options.preferConsoleCommandsOverLocalSymbols: Set to true to force 45 * overriding local symbols defined by the page with same-name console commands. 46 * @param {boolean} options.evalInTracer: Set to true to store the evaluation result object 47 * actors within the Tracer Object Actor Pool. 48 * So that if the evaluation result object has already been processed by the JS Tracer, 49 * we will receive the same Object Actor. 50 * This helps later match the object when doing a search per value. 51 * 52 * @return {Promise}: A promise that resolves with the response. 53 */ 54 async execute(expression, options = {}) { 55 const { 56 selectedObjectActor, 57 selectedNodeActor, 58 frameActor, 59 selectedTargetFront, 60 } = options; 61 62 // Retrieve the right WebConsole front that relates either to (by order of priority): 63 // - the currently selected target in the context selector 64 // (selectedTargetFront argument), 65 // - the object picked in the console (when using store as global) (selectedObjectActor), 66 // - the currently selected Node in the inspector (selectedNodeActor), 67 // - the currently selected frame in the debugger (when paused) (frameActor), 68 // - the currently selected target in the iframe dropdown 69 // (selectedTargetFront from the TargetCommand) 70 let targetFront = this._commands.targetCommand.selectedTargetFront; 71 72 const selectedActor = 73 selectedObjectActor || selectedNodeActor || frameActor; 74 75 if (selectedTargetFront) { 76 targetFront = selectedTargetFront; 77 } else if (selectedActor) { 78 const selectedFront = this._commands.client.getFrontByID(selectedActor); 79 if (selectedFront) { 80 targetFront = selectedFront.targetFront; 81 } 82 } 83 84 const consoleFront = await targetFront.getFront("console"); 85 86 // We call `evaluateJSAsync` RDP request, which immediately returns a simple `resultID`, 87 // for which we later receive a related `evaluationResult` RDP event, with the same resultID. 88 // The evaluation result will be contained in this RDP event. 89 let resultID; 90 const response = await new Promise(resolve => { 91 const offEvaluationResult = consoleFront.on( 92 "evaluationResult", 93 async packet => { 94 // In some cases, the evaluationResult event can be received before the call to 95 // evaluationJSAsync completes. So make sure to wait for the corresponding promise 96 // before handling the evaluationResult event. 97 await onEvaluateJSAsync; 98 99 if (packet.resultID === resultID) { 100 resolve(packet); 101 offEvaluationResult(); 102 } 103 } 104 ); 105 106 const onEvaluateJSAsync = consoleFront 107 .evaluateJSAsync({ 108 text: expression, 109 eager: options.eager, 110 frameActor, 111 innerWindowID: options.innerWindowID, 112 mapped: options.mapped, 113 selectedNodeActor, 114 selectedObjectActor, 115 url: options.url, 116 disableBreaks: options.disableBreaks, 117 preferConsoleCommandsOverLocalSymbols: 118 options.preferConsoleCommandsOverLocalSymbols, 119 evalInTracer: options.evalInTracer, 120 }) 121 .then(packet => { 122 resultID = packet.resultID; 123 }); 124 }); 125 126 // `response` is the packet sent via `evaluationResult` RDP event. 127 if (response.error) { 128 throw response; 129 } 130 131 if (response.result) { 132 response.result = getAdHocFrontOrPrimitiveGrip( 133 response.result, 134 consoleFront 135 ); 136 } 137 138 if (response.helperResult?.object) { 139 response.helperResult.object = getAdHocFrontOrPrimitiveGrip( 140 response.helperResult.object, 141 consoleFront 142 ); 143 } 144 145 if (response.exception) { 146 response.exception = getAdHocFrontOrPrimitiveGrip( 147 response.exception, 148 consoleFront 149 ); 150 } 151 152 if (response.exceptionMessage) { 153 response.exceptionMessage = getAdHocFrontOrPrimitiveGrip( 154 response.exceptionMessage, 155 consoleFront 156 ); 157 } 158 159 return response; 160 } 161 } 162 163 module.exports = ScriptCommand;