2018-07-02 18:08:02 +03:00
|
|
|
/* 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";
|
|
|
|
|
|
|
|
var Services = require("Services");
|
|
|
|
loader.lazyRequireGetter(this, "Utils", "devtools/client/webconsole/utils", true);
|
2019-02-15 11:16:37 +03:00
|
|
|
loader.lazyRequireGetter(this, "WebConsoleUI", "devtools/client/webconsole/webconsole-ui", true);
|
2018-07-02 18:08:02 +03:00
|
|
|
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
|
|
|
|
loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source");
|
|
|
|
loader.lazyRequireGetter(this, "openDocLink", "devtools/client/shared/link", true);
|
|
|
|
|
|
|
|
var gHudId = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A WebConsole instance is an interactive console initialized *per target*
|
|
|
|
* that displays console log data as well as provides an interactive terminal to
|
|
|
|
* manipulate the target's document content.
|
|
|
|
*
|
|
|
|
* This object only wraps the iframe that holds the Web Console UI. This is
|
|
|
|
* meant to be an integration point between the Firefox UI and the Web Console
|
|
|
|
* UI and features.
|
|
|
|
*/
|
2019-02-27 13:05:51 +03:00
|
|
|
class WebConsole {
|
|
|
|
/*
|
|
|
|
* @constructor
|
|
|
|
* @param object target
|
|
|
|
* The target that the web console will connect to.
|
|
|
|
* @param nsIDOMWindow iframeWindow
|
|
|
|
* The window where the web console UI is already loaded.
|
|
|
|
* @param nsIDOMWindow chromeWindow
|
|
|
|
* The window of the web console owner.
|
|
|
|
* @param object hudService
|
|
|
|
* The parent HUD Service
|
|
|
|
* @param bool isBrowserConsole
|
|
|
|
*/
|
|
|
|
constructor(target, iframeWindow, chromeWindow, hudService, isBrowserConsole = false) {
|
|
|
|
this.iframeWindow = iframeWindow;
|
|
|
|
this.chromeWindow = chromeWindow;
|
|
|
|
this.hudId = "hud_" + ++gHudId;
|
|
|
|
this.target = target;
|
|
|
|
this.browserWindow = this.chromeWindow.top;
|
|
|
|
this.hudService = hudService;
|
|
|
|
this._browserConsole = isBrowserConsole;
|
|
|
|
|
|
|
|
const element = this.browserWindow.document.documentElement;
|
|
|
|
if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
|
|
|
|
this.browserWindow = this.hudService.currentContext();
|
|
|
|
}
|
|
|
|
this.ui = new WebConsoleUI(this);
|
|
|
|
this._destroyer = null;
|
2018-07-02 18:08:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter for a function to to listen for every request that completes. Used
|
|
|
|
* by unit tests. The callback takes one argument: the HTTP activity object as
|
|
|
|
* received from the remote Web Console.
|
|
|
|
*
|
|
|
|
* @type function
|
|
|
|
*/
|
|
|
|
get lastFinishedRequestCallback() {
|
|
|
|
return this.hudService.lastFinishedRequest.callback;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter for the window that can provide various utilities that the web
|
|
|
|
* console makes use of, like opening links, managing popups, etc. In
|
|
|
|
* most cases, this will be |this.browserWindow|, but in some uses (such as
|
|
|
|
* the Browser Toolbox), there is no browser window, so an alternative window
|
|
|
|
* hosts the utilities there.
|
|
|
|
* @type nsIDOMWindow
|
|
|
|
*/
|
|
|
|
get chromeUtilsWindow() {
|
|
|
|
if (this.browserWindow) {
|
|
|
|
return this.browserWindow;
|
|
|
|
}
|
|
|
|
return this.chromeWindow.top;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter for the output element that holds messages we display.
|
|
|
|
* @type Element
|
|
|
|
*/
|
|
|
|
get outputNode() {
|
|
|
|
return this.ui ? this.ui.outputNode : null;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
get gViewSourceUtils() {
|
|
|
|
return this.chromeUtilsWindow.gViewSourceUtils;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the Web Console instance.
|
|
|
|
*
|
|
|
|
* @return object
|
|
|
|
* A promise for the initialization.
|
|
|
|
*/
|
|
|
|
init() {
|
|
|
|
return this.ui.init().then(() => this);
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The JSTerm object that manages the console's input.
|
|
|
|
* @see webconsole.js::JSTerm
|
|
|
|
* @type object
|
|
|
|
*/
|
|
|
|
get jsterm() {
|
|
|
|
return this.ui ? this.ui.jsterm : null;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2019-02-26 17:42:13 +03:00
|
|
|
|
2018-07-02 18:08:02 +03:00
|
|
|
/**
|
2019-02-15 11:16:37 +03:00
|
|
|
* Alias for the WebConsoleUI.setFilterState() method.
|
|
|
|
* @see webconsole.js::WebConsoleUI.setFilterState()
|
2018-07-02 18:08:02 +03:00
|
|
|
*/
|
|
|
|
setFilterState() {
|
|
|
|
this.ui && this.ui.setFilterState.apply(this.ui, arguments);
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Open a link in a new tab.
|
|
|
|
*
|
|
|
|
* @param string link
|
|
|
|
* The URL you want to open in a new tab.
|
|
|
|
*/
|
|
|
|
openLink(link, e) {
|
|
|
|
openDocLink(link);
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Open a link in Firefox's view source.
|
|
|
|
*
|
|
|
|
* @param string sourceURL
|
|
|
|
* The URL of the file.
|
|
|
|
* @param integer sourceLine
|
|
|
|
* The line number which should be highlighted.
|
|
|
|
*/
|
|
|
|
viewSource(sourceURL, sourceLine) {
|
|
|
|
this.gViewSourceUtils.viewSource({ URL: sourceURL, lineNumber: sourceLine || 0 });
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to open a Stylesheet file related to the web page for the web console
|
|
|
|
* instance in the Style Editor. If the file is not found, it is opened in
|
|
|
|
* source view instead.
|
|
|
|
*
|
|
|
|
* Manually handle the case where toolbox does not exist (Browser Console).
|
|
|
|
*
|
|
|
|
* @param string sourceURL
|
|
|
|
* The URL of the file.
|
|
|
|
* @param integer sourceLine
|
|
|
|
* The line number which you want to place the caret.
|
|
|
|
*/
|
|
|
|
viewSourceInStyleEditor(sourceURL, sourceLine) {
|
|
|
|
const toolbox = gDevTools.getToolbox(this.target);
|
|
|
|
if (!toolbox) {
|
|
|
|
this.viewSource(sourceURL, sourceLine);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
toolbox.viewSourceInStyleEditor(sourceURL, sourceLine);
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to open a JavaScript file related to the web page for the web console
|
|
|
|
* instance in the Script Debugger. If the file is not found, it is opened in
|
|
|
|
* source view instead.
|
|
|
|
*
|
|
|
|
* Manually handle the case where toolbox does not exist (Browser Console).
|
|
|
|
*
|
|
|
|
* @param string sourceURL
|
|
|
|
* The URL of the file.
|
|
|
|
* @param integer sourceLine
|
|
|
|
* The line number which you want to place the caret.
|
|
|
|
*/
|
|
|
|
viewSourceInDebugger(sourceURL, sourceLine) {
|
|
|
|
const toolbox = gDevTools.getToolbox(this.target);
|
|
|
|
if (!toolbox) {
|
|
|
|
this.viewSource(sourceURL, sourceLine);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
toolbox.viewSourceInDebugger(sourceURL, sourceLine).then(() => {
|
|
|
|
this.ui.emit("source-in-debugger-opened");
|
|
|
|
});
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to open a JavaScript file related to the web page for the web console
|
|
|
|
* instance in the corresponding Scratchpad.
|
|
|
|
*
|
|
|
|
* @param string sourceURL
|
|
|
|
* The URL of the file which corresponds to a Scratchpad id.
|
|
|
|
*/
|
|
|
|
viewSourceInScratchpad(sourceURL, sourceLine) {
|
|
|
|
viewSource.viewSourceInScratchpad(sourceURL, sourceLine);
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve information about the JavaScript debugger's stackframes list. This
|
|
|
|
* is used to allow the Web Console to evaluate code in the selected
|
|
|
|
* stackframe.
|
|
|
|
*
|
|
|
|
* @return object|null
|
|
|
|
* An object which holds:
|
|
|
|
* - frames: the active ThreadClient.cachedFrames array.
|
|
|
|
* - selected: depth/index of the selected stackframe in the debugger
|
|
|
|
* UI.
|
|
|
|
* If the debugger is not open or if it's not paused, then |null| is
|
|
|
|
* returned.
|
|
|
|
*/
|
|
|
|
getDebuggerFrames() {
|
|
|
|
const toolbox = gDevTools.getToolbox(this.target);
|
|
|
|
if (!toolbox) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const panel = toolbox.getPanel("jsdebugger");
|
|
|
|
|
|
|
|
if (!panel) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return panel.getFrames();
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
Bug 1491354 - Extends top-level await mapping from debugger to toolbox; r=bgrins,jlast.
This patch makes the parser-worker available at the toolbox level.
This way, the console does not have to rely on the debugger being
open to map top-level await expression.
In order to make the worker works in the toolbox, some changes
are required (passing a window object, checking inToolbox differently).
We take this as an opportunity to *not* display the async iife result,
a promise, in the console. This is made by checking if the input was
mapped, and if so, ignoring the result we get from the server.
A couple tests are added to ensure the basic usage works as expected.
This patch should be considered as a v0 for top-level await evaluation
as there are things that are not perfect here. Since we rely on console.log
the result are treated differently from other evaluation results:
- the style is different
- the result gets added to the log cache (when restarting the console,
the results will still be displayed, but not the commands).
- the results can be filtered, although evaluation results should not
- `$_` after a top-level await evaluation returns the Promise created
by the async iife, not the result that was displayed in the console.
All those should be addressed in Bug 1410820.
Differential Revision: https://phabricator.services.mozilla.com/D6038
--HG--
extra : moz-landing-system : lando
2018-09-24 11:17:30 +03:00
|
|
|
/**
|
|
|
|
* Given an expression, returns an object containing a new expression, mapped by the
|
|
|
|
* parser worker to provide additional feature for the user (top-level await,
|
|
|
|
* original languages mapping, …).
|
|
|
|
*
|
|
|
|
* @param {String} expression: The input to maybe map.
|
|
|
|
* @returns {Object|null}
|
|
|
|
* Returns null if the input can't be mapped.
|
|
|
|
* If it can, returns an object containing the following:
|
|
|
|
* - {String} expression: The mapped expression
|
|
|
|
* - {Object} mapped: An object containing the different mapping that could
|
|
|
|
* be done and if they were applied on the input.
|
|
|
|
* At the moment, contains `await`, `bindings` and
|
|
|
|
* `originalExpression`.
|
|
|
|
*/
|
|
|
|
getMappedExpression(expression) {
|
2018-07-02 18:08:02 +03:00
|
|
|
const toolbox = gDevTools.getToolbox(this.target);
|
Bug 1491354 - Extends top-level await mapping from debugger to toolbox; r=bgrins,jlast.
This patch makes the parser-worker available at the toolbox level.
This way, the console does not have to rely on the debugger being
open to map top-level await expression.
In order to make the worker works in the toolbox, some changes
are required (passing a window object, checking inToolbox differently).
We take this as an opportunity to *not* display the async iife result,
a promise, in the console. This is made by checking if the input was
mapped, and if so, ignoring the result we get from the server.
A couple tests are added to ensure the basic usage works as expected.
This patch should be considered as a v0 for top-level await evaluation
as there are things that are not perfect here. Since we rely on console.log
the result are treated differently from other evaluation results:
- the style is different
- the result gets added to the log cache (when restarting the console,
the results will still be displayed, but not the commands).
- the results can be filtered, although evaluation results should not
- `$_` after a top-level await evaluation returns the Promise created
by the async iife, not the result that was displayed in the console.
All those should be addressed in Bug 1410820.
Differential Revision: https://phabricator.services.mozilla.com/D6038
--HG--
extra : moz-landing-system : lando
2018-09-24 11:17:30 +03:00
|
|
|
|
2018-10-16 16:57:32 +03:00
|
|
|
// We need to check if the debugger is open, since it may perform a variable name
|
|
|
|
// substitution for sourcemapped script (i.e. evaluated `myVar.trim()` might need to
|
|
|
|
// be transformed into `a.trim()`).
|
|
|
|
const panel = toolbox && toolbox.getPanel("jsdebugger");
|
Bug 1491354 - Extends top-level await mapping from debugger to toolbox; r=bgrins,jlast.
This patch makes the parser-worker available at the toolbox level.
This way, the console does not have to rely on the debugger being
open to map top-level await expression.
In order to make the worker works in the toolbox, some changes
are required (passing a window object, checking inToolbox differently).
We take this as an opportunity to *not* display the async iife result,
a promise, in the console. This is made by checking if the input was
mapped, and if so, ignoring the result we get from the server.
A couple tests are added to ensure the basic usage works as expected.
This patch should be considered as a v0 for top-level await evaluation
as there are things that are not perfect here. Since we rely on console.log
the result are treated differently from other evaluation results:
- the style is different
- the result gets added to the log cache (when restarting the console,
the results will still be displayed, but not the commands).
- the results can be filtered, although evaluation results should not
- `$_` after a top-level await evaluation returns the Promise created
by the async iife, not the result that was displayed in the console.
All those should be addressed in Bug 1410820.
Differential Revision: https://phabricator.services.mozilla.com/D6038
--HG--
extra : moz-landing-system : lando
2018-09-24 11:17:30 +03:00
|
|
|
if (panel) {
|
|
|
|
return panel.getMappedExpression(expression);
|
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
2018-10-16 16:57:32 +03:00
|
|
|
if (this.parserService && expression.includes("await ")) {
|
2019-02-01 18:19:47 +03:00
|
|
|
const shouldMapBindings = false;
|
|
|
|
const shouldMapAwait = true;
|
|
|
|
const res = this.parserService.mapExpression(
|
|
|
|
expression, null, null, shouldMapBindings, shouldMapAwait);
|
|
|
|
return res;
|
2018-07-02 18:08:02 +03:00
|
|
|
}
|
|
|
|
|
Bug 1491354 - Extends top-level await mapping from debugger to toolbox; r=bgrins,jlast.
This patch makes the parser-worker available at the toolbox level.
This way, the console does not have to rely on the debugger being
open to map top-level await expression.
In order to make the worker works in the toolbox, some changes
are required (passing a window object, checking inToolbox differently).
We take this as an opportunity to *not* display the async iife result,
a promise, in the console. This is made by checking if the input was
mapped, and if so, ignoring the result we get from the server.
A couple tests are added to ensure the basic usage works as expected.
This patch should be considered as a v0 for top-level await evaluation
as there are things that are not perfect here. Since we rely on console.log
the result are treated differently from other evaluation results:
- the style is different
- the result gets added to the log cache (when restarting the console,
the results will still be displayed, but not the commands).
- the results can be filtered, although evaluation results should not
- `$_` after a top-level await evaluation returns the Promise created
by the async iife, not the result that was displayed in the console.
All those should be addressed in Bug 1410820.
Differential Revision: https://phabricator.services.mozilla.com/D6038
--HG--
extra : moz-landing-system : lando
2018-09-24 11:17:30 +03:00
|
|
|
return null;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
2018-10-16 16:57:32 +03:00
|
|
|
/**
|
|
|
|
* A common access point for the client-side parser service that any panel can use.
|
|
|
|
*/
|
|
|
|
get parserService() {
|
|
|
|
if (this._parserService) {
|
|
|
|
return this._parserService;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._parserService =
|
|
|
|
require("devtools/client/debugger/new/src/workers/parser/index");
|
|
|
|
|
|
|
|
this._parserService.start(
|
|
|
|
"resource://devtools/client/debugger/new/dist/parser-worker.js",
|
2018-11-19 19:33:22 +03:00
|
|
|
this.chromeUtilsWindow);
|
2018-10-16 16:57:32 +03:00
|
|
|
return this._parserService;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-10-16 16:57:32 +03:00
|
|
|
|
2018-07-02 18:08:02 +03:00
|
|
|
/**
|
|
|
|
* Retrieves the current selection from the Inspector, if such a selection
|
|
|
|
* exists. This is used to pass the ID of the selected actor to the Web
|
|
|
|
* Console server for the $0 helper.
|
|
|
|
*
|
|
|
|
* @return object|null
|
|
|
|
* A Selection referring to the currently selected node in the
|
|
|
|
* Inspector.
|
|
|
|
* If the inspector was never opened, or no node was ever selected,
|
|
|
|
* then |null| is returned.
|
|
|
|
*/
|
|
|
|
getInspectorSelection() {
|
|
|
|
const toolbox = gDevTools.getToolbox(this.target);
|
|
|
|
if (!toolbox) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const panel = toolbox.getPanel("inspector");
|
|
|
|
if (!panel || !panel.selection) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return panel.selection;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy the object. Call this method to avoid memory leaks when the Web
|
|
|
|
* Console is closed.
|
|
|
|
*
|
|
|
|
* @return object
|
|
|
|
* A promise object that is resolved once the Web Console is closed.
|
|
|
|
*/
|
|
|
|
async destroy() {
|
|
|
|
if (this._destroyer) {
|
|
|
|
return this._destroyer;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._destroyer = (async () => {
|
|
|
|
this.hudService.consoles.delete(this.hudId);
|
|
|
|
|
|
|
|
if (this.ui) {
|
|
|
|
await this.ui.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._browserConsole) {
|
|
|
|
try {
|
2019-02-02 14:24:24 +03:00
|
|
|
await this.target.focus();
|
2018-07-02 18:08:02 +03:00
|
|
|
} catch (ex) {
|
|
|
|
// Tab focus can fail if the tab or target is closed.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-16 16:57:32 +03:00
|
|
|
if (this._parserService) {
|
|
|
|
this._parserService.stop();
|
|
|
|
this._parserService = null;
|
|
|
|
}
|
|
|
|
|
2018-07-02 18:08:02 +03:00
|
|
|
const id = Utils.supportsString(this.hudId);
|
|
|
|
Services.obs.notifyObservers(id, "web-console-destroyed");
|
|
|
|
})();
|
|
|
|
|
|
|
|
return this._destroyer;
|
2019-02-27 13:05:51 +03:00
|
|
|
}
|
|
|
|
}
|
2018-07-02 18:08:02 +03:00
|
|
|
|
|
|
|
module.exports = WebConsole;
|