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/. */
8 Utils
: WebConsoleUtils
,
9 } = require("resource://devtools/client/webconsole/utils.js");
13 SET_TERMINAL_EAGER_RESULT
,
16 } = require("resource://devtools/client/webconsole/constants.js");
19 } = require("resource://devtools/client/webconsole/selectors/prefs.js");
20 const ResourceCommand
= require("resource://devtools/shared/commands/resource/resource-command.js");
21 const l10n
= require("resource://devtools/client/webconsole/utils/l10n.js");
23 loader
.lazyServiceGetter(
26 "@mozilla.org/widget/clipboardhelper;1",
29 loader
.lazyRequireGetter(
32 "resource://devtools/client/webconsole/actions/messages.js"
34 loader
.lazyRequireGetter(
37 "resource://devtools/client/webconsole/actions/history.js"
39 loader
.lazyRequireGetter(
42 "resource://devtools/client/webconsole/types.js",
45 loader
.lazyRequireGetter(
47 "netmonitorBlockingActions",
48 "resource://devtools/client/netmonitor/src/actions/request-blocking.js"
51 loader
.lazyRequireGetter(
53 ["saveScreenshot", "captureAndSaveScreenshot"],
54 "resource://devtools/client/shared/screenshot.js",
57 loader
.lazyRequireGetter(
59 "createSimpleTableMessage",
60 "resource://devtools/client/webconsole/utils/messages.js",
63 loader
.lazyRequireGetter(
66 "resource://devtools/shared/commands/target/selectors/targets.js",
70 async
function getMappedExpression(hud
, expression
) {
73 mapResult
= await hud
.getMappedExpression(expression
);
75 console
.warn("Error when calling getMappedExpression", e
);
80 ({ expression
, mapped
} = mapResult
);
82 return { expression
, mapped
};
85 function evaluateExpression(expression
, from = "input") {
86 return async ({ dispatch
, webConsoleUI
, hud
, commands
}) => {
88 expression
= hud
.getInputSelection() || hud
.getInputValue();
94 // We use the messages action as it's doing additional transformation on the message.
95 const { messages
} = dispatch(
96 messagesActions
.messagesAdd([
98 messageText
: expression
,
99 timeStamp
: Date
.now(),
103 const [consoleCommandMessage
] = messages
;
106 type
: EVALUATE_EXPRESSION
,
111 WebConsoleUtils
.usageCount
++;
114 ({ expression
, mapped
} = await
getMappedExpression(hud
, expression
));
116 // Even if the evaluation fails,
117 // we still need to pass the error response to onExpressionEvaluated.
118 const onSettled
= res
=> res
;
120 const response
= await commands
.scriptCommand
121 .execute(expression
, {
122 frameActor
: hud
.getSelectedFrameActorID(),
123 selectedNodeActor
: webConsoleUI
.getSelectedNodeActorID(),
124 selectedTargetFront
: getSelectedTarget(
125 webConsoleUI
.hud
.commands
.targetCommand
.store
.getState()
128 // Allow breakpoints to be triggerred and the evaluated source to be shown in debugger UI
129 disableBreaks
: false,
131 .then(onSettled
, onSettled
);
133 const serverConsoleCommandTimestamp
= response
.startTime
;
135 // In case of remote debugging, it might happen that the debuggee page does not have
136 // the exact same clock time as the client. This could cause some ordering issues
137 // where the result message is displayed *before* the expression that lead to it.
139 serverConsoleCommandTimestamp
&&
140 consoleCommandMessage
.timeStamp
> serverConsoleCommandTimestamp
142 // If we're in such case, we remove the original command message, and add it again,
143 // with the timestamp coming from the server.
144 dispatch(messagesActions
.messageRemove(consoleCommandMessage
.id
));
146 messagesActions
.messagesAdd([
148 messageText
: expression
,
149 timeStamp
: serverConsoleCommandTimestamp
,
155 return dispatch(onExpressionEvaluated(response
));
160 * The JavaScript evaluation response handler.
163 * @param {Object} response
164 * The message received from the server.
166 function onExpressionEvaluated(response
) {
167 return async ({ dispatch
, webConsoleUI
}) => {
168 if (response
.error
) {
169 console
.error(`Evaluation error`, response
.error
, ": ", response
.message
);
173 // If the evaluation was a top-level await expression that was rejected, there will
174 // be an uncaught exception reported, so we don't need to do anything.
175 if (response
.topLevelAwaitRejected
=== true) {
179 if (!response
.helperResult
) {
180 webConsoleUI
.wrapper
.dispatchMessageAdd(response
);
184 await
dispatch(handleHelperResult(response
));
188 function handleHelperResult(response
) {
189 // eslint-disable-next-line complexity
190 return async ({ dispatch
, hud
, toolbox
, webConsoleUI
, getState
}) => {
191 const { result
, helperResult
} = response
;
192 const helperHasRawOutput
= !!helperResult
?.rawOutput
;
194 if (helperResult
?.type
) {
195 switch (helperResult
.type
) {
198 messagesActions
.messagesAdd([
201 arguments
: [helperResult
.message
],
203 resourceType
: ResourceCommand
.TYPES
.CONSOLE_MESSAGE
,
209 dispatch(messagesActions
.messagesClear());
212 dispatch(historyActions
.clearHistory());
214 case "historyOutput":
215 const history
= getState().history
.entries
|| [];
216 const columns
= new Map([
217 ["_index", "(index)"],
218 ["expression", "Expressions"],
221 messagesActions
.messagesAdd([
223 ...createSimpleTableMessage(
225 history
.map((expression
, index
) => {
226 return { _index
: index
, expression
};
233 case "inspectObject": {
234 const objectActor
= helperResult
.object
;
235 if (hud
.toolbox
&& !helperResult
.forceExpandInConsole
) {
236 hud
.toolbox
.inspectObjectActor(objectActor
);
238 webConsoleUI
.inspectObjectActor(objectActor
);
243 hud
.openLink(HELP_URL
);
245 case "copyValueToClipboard":
246 clipboardHelper
.copyString(helperResult
.value
);
248 messagesActions
.messagesAdd([
250 resourceType
: ResourceCommand
.TYPES
.PLATFORM_MESSAGE
,
251 message
: l10n
.getStr(
252 "webconsole.message.commands.copyValueToClipboard"
258 case "screenshotOutput":
259 const { args
, value
} = helperResult
;
261 getSelectedTarget(hud
.commands
.targetCommand
.store
.getState()) ||
263 let screenshotMessages
;
265 // @backward-compat { version 87 } The screenshot-content actor isn't available
267 // With an old server, the console actor captures the screenshot when handling
268 // the command, and send it to the client which only needs to save it to a file.
269 // With a new server, the server simply acknowledges the command,
270 // and the client will drive the whole screenshot process (capture and save).
271 if (targetFront
.hasActor("screenshotContent")) {
272 screenshotMessages
= await
captureAndSaveScreenshot(
274 webConsoleUI
.getPanelWindow(),
278 screenshotMessages
= await
saveScreenshot(
279 webConsoleUI
.getPanelWindow(),
285 if (screenshotMessages
&& screenshotMessages
.length
) {
287 messagesActions
.messagesAdd(
288 screenshotMessages
.map(message
=> ({
289 level
: message
.level
|| "log",
290 arguments
: [message
.text
],
292 resourceType
: ResourceCommand
.TYPES
.CONSOLE_MESSAGE
,
299 const blockURL
= helperResult
.args
.url
;
300 // The console actor isn't able to block the request as the console actor runs in the content
301 // process, while the request has to be blocked from the parent process.
302 // Then, calling the Netmonitor action will only update the visual state of the Netmonitor,
303 // but we also have to block the request via the NetworkParentActor.
304 await hud
.commands
.networkCommand
.blockRequestForUrl(blockURL
);
306 .getPanel("netmonitor")
307 ?.panelWin
.store
.dispatch(
308 netmonitorBlockingActions
.addBlockedUrl(blockURL
)
312 messagesActions
.messagesAdd([
314 resourceType
: ResourceCommand
.TYPES
.PLATFORM_MESSAGE
,
315 message
: l10n
.getFormatStr(
316 "webconsole.message.commands.blockedURL",
324 const unblockURL
= helperResult
.args
.url
;
325 await hud
.commands
.networkCommand
.unblockRequestForUrl(unblockURL
);
327 .getPanel("netmonitor")
328 ?.panelWin
.store
.dispatch(
329 netmonitorBlockingActions
.removeBlockedUrl(unblockURL
)
333 messagesActions
.messagesAdd([
335 resourceType
: ResourceCommand
.TYPES
.PLATFORM_MESSAGE
,
336 message
: l10n
.getFormatStr(
337 "webconsole.message.commands.unblockedURL",
343 // early return as we already dispatched necessary messages.
346 // Sent when using ":command --help or :command --usage"
347 // to help discover command arguments.
349 // The remote runtime will tell us about the usage as it may
350 // be different from the client one.
353 messagesActions
.messagesAdd([
355 resourceType
: ResourceCommand
.TYPES
.PLATFORM_MESSAGE
,
356 message
: helperResult
.message
,
363 // Nothing in particular to do.
364 // The JSTRACER_STATE resource will report the start/stop of the profiler.
369 const hasErrorMessage
=
370 response
.exceptionMessage
||
371 (helperResult
&& helperResult
.type
=== "error");
373 // Hide undefined results coming from helper functions.
374 const hasUndefinedResult
=
375 result
&& typeof result
== "object" && result
.type
== "undefined";
377 if (hasErrorMessage
|| helperHasRawOutput
|| !hasUndefinedResult
) {
378 dispatch(messagesActions
.messagesAdd([response
]));
383 function focusInput() {
384 return ({ hud
}) => {
385 return hud
.focusInput();
389 function setInputValue(value
) {
390 return ({ hud
}) => {
391 return hud
.setInputValue(value
);
396 * Request an eager evaluation from the server.
398 * @param {String} expression: The expression to evaluate.
399 * @param {Boolean} force: When true, will request an eager evaluation again, even if
400 * the expression is the same one than the one that was used in
401 * the previous evaluation.
403 function terminalInputChanged(expression
, force
= false) {
404 return async ({ dispatch
, webConsoleUI
, hud
, commands
, getState
}) => {
405 const prefs
= getAllPrefs(getState());
406 if (!prefs
.eagerEvaluation
) {
410 const { terminalInput
= "" } = getState().history
;
412 // Only re-evaluate if the expression did change.
414 (!terminalInput
&& !expression
) ||
415 (typeof terminalInput
=== "string" &&
416 typeof expression
=== "string" &&
417 expression
.trim() === terminalInput
.trim() &&
424 type
: SET_TERMINAL_INPUT
,
425 expression
: expression
.trim(),
428 // There's no need to evaluate an empty string.
429 if (!expression
|| !expression
.trim()) {
431 type
: SET_TERMINAL_EAGER_RESULT
,
438 ({ expression
, mapped
} = await
getMappedExpression(hud
, expression
));
440 // We don't want to evaluate top-level await expressions (see Bug 1786805)
443 type
: SET_TERMINAL_EAGER_RESULT
,
449 const response
= await commands
.scriptCommand
.execute(expression
, {
450 frameActor
: hud
.getSelectedFrameActorID(),
451 selectedNodeActor
: webConsoleUI
.getSelectedNodeActorID(),
452 selectedTargetFront
: getSelectedTarget(
453 hud
.commands
.targetCommand
.store
.getState()
460 type
: SET_TERMINAL_EAGER_RESULT
,
461 result
: getEagerEvaluationResult(response
),
467 * Refresh the current eager evaluation by requesting a new eager evaluation.
469 function updateInstantEvaluationResultForCurrentExpression() {
470 return ({ getState
, dispatch
}) =>
471 dispatch(terminalInputChanged(getState().history
.terminalInput
, true));
474 function getEagerEvaluationResult(response
) {
475 const result
= response
.exception
|| response
.result
;
476 // Don't show syntax errors results to the user.
477 if (result
?.isSyntaxError
|| (result
&& result
.type
== "undefined")) {
484 function prettyPrintEditor() {
486 type
: EDITOR_PRETTY_PRINT
,
494 terminalInputChanged
,
495 updateInstantEvaluationResultForCurrentExpression
,