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/. */
7 loader
.lazyRequireGetter(
10 "resource://devtools/client/webconsole/utils.js",
13 loader
.lazyRequireGetter(
16 "resource://devtools/client/webconsole/webconsole-ui.js",
19 loader
.lazyRequireGetter(
22 "resource://devtools/client/framework/devtools.js",
25 loader
.lazyRequireGetter(
28 "resource://devtools/client/shared/link.js",
31 loader
.lazyRequireGetter(
34 "resource://devtools/shared/DevToolsUtils.js"
36 const EventEmitter
= require("resource://devtools/shared/event-emitter.js");
37 const Telemetry
= require("resource://devtools/client/shared/telemetry.js");
40 const isMacOS
= Services
.appinfo
.OS
=== "Darwin";
43 * A WebConsole instance is an interactive console initialized *per target*
44 * that displays console log data as well as provides an interactive terminal to
45 * manipulate the target's document content.
47 * This object only wraps the iframe that holds the Web Console UI. This is
48 * meant to be an integration point between the Firefox UI and the Web Console
54 * @param object toolbox
55 * The toolbox where the web console is displayed.
56 * @param object commands
57 * The commands object with all interfaces defined from devtools/shared/commands/
58 * @param nsIDOMWindow iframeWindow
59 * The window where the web console UI is already loaded.
60 * @param nsIDOMWindow chromeWindow
61 * The window of the web console owner.
62 * @param bool isBrowserConsole
69 isBrowserConsole
= false
71 this.toolbox
= toolbox
;
72 this.commands
= commands
;
73 this.iframeWindow
= iframeWindow
;
74 this.chromeWindow
= chromeWindow
;
75 this.hudId
= "hud_" + ++gHudId
;
76 this.browserWindow
= DevToolsUtils
.getTopWindow(this.chromeWindow
);
77 this.isBrowserConsole
= isBrowserConsole
;
79 // On the browser console, where we don't have a toolbox, we instantiate a dedicated Telemetry instance.
80 this.telemetry
= toolbox
?.telemetry
|| new Telemetry();
82 const element
= this.browserWindow
.document
.documentElement
;
83 if (element
.getAttribute("windowtype") != gDevTools
.chromeWindowType
) {
84 this.browserWindow
= Services
.wm
.getMostRecentWindow(
85 gDevTools
.chromeWindowType
88 this.ui
= new WebConsoleUI(this);
89 this._destroyer
= null;
91 EventEmitter
.decorate(this);
94 recordEvent(event
, extra
= {}) {
95 this.telemetry
.recordEvent(event
, "webconsole", null, extra
);
99 return this.commands
.targetCommand
.targetFront
;
102 get resourceCommand() {
103 return this.commands
.resourceCommand
;
107 * Getter for the window that can provide various utilities that the web
108 * console makes use of, like opening links, managing popups, etc. In
109 * most cases, this will be |this.browserWindow|, but in some uses (such as
110 * the Browser Toolbox), there is no browser window, so an alternative window
111 * hosts the utilities there.
114 get chromeUtilsWindow() {
115 if (this.browserWindow
) {
116 return this.browserWindow
;
118 return DevToolsUtils
.getTopWindow(this.chromeWindow
);
121 get gViewSourceUtils() {
122 return this.chromeUtilsWindow
.gViewSourceUtils
;
126 return this.commands
.client
.getFrontByID(id
);
130 * Initialize the Web Console instance.
132 * @param {Boolean} emitCreatedEvent: Defaults to true. If false is passed,
133 * We won't be sending the 'web-console-created' event.
136 * A promise for the initialization.
138 async
init(emitCreatedEvent
= true) {
139 await
this.ui
.init();
141 // This event needs to be fired later in the case of the BrowserConsole
142 if (emitCreatedEvent
) {
143 const id
= Utils
.supportsString(this.hudId
);
144 Services
.obs
.notifyObservers(id
, "web-console-created");
149 * The JSTerm object that manages the console's input.
150 * @see webconsole.js::JSTerm
154 return this.ui
? this.ui
.jsterm
: null;
158 * Get the value from the input field.
159 * @returns {String|null} returns null if there's no input.
166 return this.jsterm
._getValue();
169 inputHasSelection() {
170 const { editor
} = this.jsterm
|| {};
171 return editor
&& !!editor
.getSelection();
174 getInputSelection() {
175 if (!this.jsterm
|| !this.jsterm
.editor
) {
178 return this.jsterm
.editor
.getSelection();
182 * Sets the value of the input field (command line)
184 * @param {String} newValue: The new value to set.
186 setInputValue(newValue
) {
191 this.jsterm
._setValue(newValue
);
195 return this.jsterm
&& this.jsterm
.focus();
199 * Open a link in a new tab.
202 * The URL you want to open in a new tab.
204 openLink(link
, e
= {}) {
206 relatedToCurrent
: true,
207 inBackground
: isMacOS
? e
.metaKey
: e
.ctrlKey
,
209 if (e
&& typeof e
.stopPropagation
=== "function") {
215 * Open a link in Firefox's view source.
217 * @param string sourceURL
218 * The URL of the file.
219 * @param integer sourceLine
220 * The line number which should be highlighted.
222 viewSource(sourceURL
, sourceLine
) {
223 this.gViewSourceUtils
.viewSource({
225 lineNumber
: sourceLine
|| -1,
230 * Tries to open a JavaScript file related to the web page for the web console
231 * instance in the Script Debugger. If the file is not found, it is opened in
232 * source view instead.
234 * Manually handle the case where toolbox does not exist (Browser Console).
236 * @param string sourceURL
237 * The URL of the file.
238 * @param integer sourceLine
239 * The line number which you want to place the caret.
240 * @param integer sourceColumn
241 * The column number which you want to place the caret.
243 async
viewSourceInDebugger(sourceURL
, sourceLine
, sourceColumn
) {
244 const { toolbox
} = this;
246 this.viewSource(sourceURL
, sourceLine
, sourceColumn
);
250 await toolbox
.viewSourceInDebugger(sourceURL
, sourceLine
, sourceColumn
);
251 this.ui
.emitForTests("source-in-debugger-opened");
255 * Retrieve information about the JavaScript debugger's currently selected stackframe.
256 * is used to allow the Web Console to evaluate code in the selected stackframe.
259 * The Frame Actor ID.
260 * If the debugger is not open or if it's not paused, then |null| is
263 getSelectedFrameActorID() {
264 const { toolbox
} = this;
268 const panel
= toolbox
.getPanel("jsdebugger");
274 return panel
.getSelectedFrameActorID();
278 * Given an expression, returns an object containing a new expression, mapped by the
279 * parser worker to provide additional feature for the user (top-level await,
280 * original languages mapping, …).
282 * @param {String} expression: The input to maybe map.
283 * @returns {Object|null}
284 * Returns null if the input can't be mapped.
285 * If it can, returns an object containing the following:
286 * - {String} expression: The mapped expression
287 * - {Object} mapped: An object containing the different mapping that could
288 * be done and if they were applied on the input.
289 * At the moment, contains `await`, `bindings` and
290 * `originalExpression`.
292 getMappedExpression(expression
) {
293 const { toolbox
} = this;
295 // We need to check if the debugger is open, since it may perform a variable name
296 // substitution for sourcemapped script (i.e. evaluated `myVar.trim()` might need to
297 // be transformed into `a.trim()`).
298 const panel
= toolbox
&& toolbox
.getPanel("jsdebugger");
300 return panel
.getMappedExpression(expression
);
303 if (expression
.includes("await ")) {
304 const shouldMapBindings
= false;
305 const shouldMapAwait
= true;
306 const res
= this.parserWorker
.mapExpression(
319 getMappedVariables() {
320 const { toolbox
} = this;
321 return toolbox
?.getPanel("jsdebugger")?.getMappedVariables();
325 // If we have a toolbox, we could reuse the parser already instantiated for the debugger.
326 // Note that we won't have a toolbox when running the Browser Console...
328 return this.toolbox
.parserWorker
;
331 if (this._parserWorker
) {
332 return this._parserWorker
;
337 } = require("resource://devtools/client/debugger/src/workers/parser/index.js");
339 this._parserWorker
= new ParserDispatcher();
340 return this._parserWorker
;
344 * Retrieves the current selection from the Inspector, if such a selection
345 * exists. This is used to pass the ID of the selected actor to the Web
346 * Console server for the $0 helper.
348 * @return object|null
349 * A Selection referring to the currently selected node in the
351 * If the inspector was never opened, or no node was ever selected,
352 * then |null| is returned.
354 getInspectorSelection() {
355 const { toolbox
} = this;
359 const panel
= toolbox
.getPanel("inspector");
360 if (!panel
|| !panel
.selection
) {
363 return panel
.selection
;
366 async
onViewSourceInDebugger({ id
, url
, line
, column
}) {
368 await
this.toolbox
.viewSourceInDebugger(url
, line
, column
, id
);
370 this.recordEvent("jump_to_source");
371 this.emitForTests("source-in-debugger-opened");
375 async
onViewSourceInStyleEditor({ url
, line
, column
}) {
379 await
this.toolbox
.viewSourceInStyleEditorByURL(url
, line
, column
);
380 this.recordEvent("jump_to_source");
383 async
openNetworkPanel(requestId
) {
387 const netmonitor
= await
this.toolbox
.selectTool("netmonitor");
388 await netmonitor
.panelWin
.Netmonitor
.inspectRequest(requestId
);
396 if (this._highlighter
) {
397 return this._highlighter
;
400 this._highlighter
= this.toolbox
.getHighlighter();
401 return this._highlighter
;
404 async
resendNetworkRequest(requestId
) {
409 const api
= await
this.toolbox
.getNetMonitorAPI();
410 await api
.resendRequest(requestId
);
413 async
openNodeInInspector(grip
) {
418 const onSelectInspector
= this.toolbox
.selectTool(
423 const onNodeFront
= this.toolbox
.target
424 .getFront("inspector")
425 .then(inspectorFront
=> inspectorFront
.getNodeFrontFromNodeGrip(grip
));
427 const [nodeFront
, inspectorPanel
] = await Promise
.all([
432 const onInspectorUpdated
= inspectorPanel
.once("inspector-updated");
433 const onNodeFrontSet
= this.toolbox
.selection
.setNodeFront(nodeFront
, {
437 await Promise
.all([onNodeFrontSet
, onInspectorUpdated
]);
441 * Destroy the object. Call this method to avoid memory leaks when the Web
445 * A promise object that is resolved once the Web Console is closed.
456 if (this._parserWorker
) {
457 this._parserWorker
.stop();
458 this._parserWorker
= null;
461 const id
= Utils
.supportsString(this.hudId
);
462 Services
.obs
.notifyObservers(id
, "web-console-destroyed");
465 this.emit("destroyed");
469 module
.exports
= WebConsole
;