summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/hudservice.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/hudservice.js')
-rw-r--r--devtools/client/webconsole/hudservice.js718
1 files changed, 718 insertions, 0 deletions
diff --git a/devtools/client/webconsole/hudservice.js b/devtools/client/webconsole/hudservice.js
new file mode 100644
index 000000000..46b4f2a13
--- /dev/null
+++ b/devtools/client/webconsole/hudservice.js
@@ -0,0 +1,718 @@
+/* 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 {Cc, Ci, Cu} = require("chrome");
+
+var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
+var { extend } = require("sdk/core/heritage");
+var {TargetFactory} = require("devtools/client/framework/target");
+var {Tools} = require("devtools/client/definitions");
+const { Task } = require("devtools/shared/task");
+var promise = require("promise");
+var Services = require("Services");
+
+loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
+loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole", true);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
+loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
+loader.lazyRequireGetter(this, "showDoorhanger", "devtools/client/shared/doorhanger", true);
+loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source");
+
+const STRINGS_URI = "devtools/client/locales/webconsole.properties";
+var l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+
+const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+// The preference prefix for all of the Browser Console filters.
+const BROWSER_CONSOLE_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter.";
+
+var gHudId = 0;
+
+// The HUD service
+
+function HUD_SERVICE()
+{
+ this.consoles = new Map();
+ this.lastFinishedRequest = { callback: null };
+}
+
+HUD_SERVICE.prototype =
+{
+ _browserConsoleID: null,
+ _browserConsoleDefer: null,
+
+ /**
+ * Keeps a reference for each Web Console / Browser Console that is created.
+ * @type Map
+ */
+ consoles: null,
+
+ /**
+ * Assign a function to this property 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 object
+ * Includes a property named |callback|. Assign the function to the
+ * |callback| property of this object.
+ */
+ lastFinishedRequest: null,
+
+ /**
+ * Get the current context, which is the main application window.
+ *
+ * @returns nsIDOMWindow
+ */
+ currentContext: function HS_currentContext() {
+ return Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ },
+
+ /**
+ * Open a Web Console for the given target.
+ *
+ * @see devtools/framework/target.js for details about targets.
+ *
+ * @param object aTarget
+ * The target that the web console will connect to.
+ * @param nsIDOMWindow aIframeWindow
+ * The window where the web console UI is already loaded.
+ * @param nsIDOMWindow aChromeWindow
+ * The window of the web console owner.
+ * @return object
+ * A promise object for the opening of the new WebConsole instance.
+ */
+ openWebConsole:
+ function HS_openWebConsole(aTarget, aIframeWindow, aChromeWindow)
+ {
+ let hud = new WebConsole(aTarget, aIframeWindow, aChromeWindow);
+ this.consoles.set(hud.hudId, hud);
+ return hud.init();
+ },
+
+ /**
+ * Open a Browser Console for the given target.
+ *
+ * @see devtools/framework/target.js for details about targets.
+ *
+ * @param object aTarget
+ * The target that the browser console will connect to.
+ * @param nsIDOMWindow aIframeWindow
+ * The window where the browser console UI is already loaded.
+ * @param nsIDOMWindow aChromeWindow
+ * The window of the browser console owner.
+ * @return object
+ * A promise object for the opening of the new BrowserConsole instance.
+ */
+ openBrowserConsole:
+ function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
+ {
+ let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
+ this._browserConsoleID = hud.hudId;
+ this.consoles.set(hud.hudId, hud);
+ return hud.init();
+ },
+
+ /**
+ * Returns the Web Console object associated to a content window.
+ *
+ * @param nsIDOMWindow aContentWindow
+ * @returns object
+ */
+ getHudByWindow: function HS_getHudByWindow(aContentWindow)
+ {
+ for (let [hudId, hud] of this.consoles) {
+ let target = hud.target;
+ if (target && target.tab && target.window === aContentWindow) {
+ return hud;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Returns the console instance for a given id.
+ *
+ * @param string aId
+ * @returns Object
+ */
+ getHudReferenceById: function HS_getHudReferenceById(aId)
+ {
+ return this.consoles.get(aId);
+ },
+
+ /**
+ * Find if there is a Web Console open for the current tab and return the
+ * instance.
+ * @return object|null
+ * The WebConsole object or null if the active tab has no open Web
+ * Console.
+ */
+ getOpenWebConsole: function HS_getOpenWebConsole()
+ {
+ let tab = this.currentContext().gBrowser.selectedTab;
+ if (!tab || !TargetFactory.isKnownTab(tab)) {
+ return null;
+ }
+ let target = TargetFactory.forTab(tab);
+ let toolbox = gDevTools.getToolbox(target);
+ let panel = toolbox ? toolbox.getPanel("webconsole") : null;
+ return panel ? panel.hud : null;
+ },
+
+ /**
+ * Toggle the Browser Console.
+ */
+ toggleBrowserConsole: function HS_toggleBrowserConsole()
+ {
+ if (this._browserConsoleID) {
+ let hud = this.getHudReferenceById(this._browserConsoleID);
+ return hud.destroy();
+ }
+
+ if (this._browserConsoleDefer) {
+ return this._browserConsoleDefer.promise;
+ }
+
+ this._browserConsoleDefer = promise.defer();
+
+ function connect()
+ {
+ let deferred = promise.defer();
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ return client.connect()
+ .then(() => client.getProcess())
+ .then(aResponse => {
+ // Set chrome:false in order to attach to the target
+ // (i.e. send an `attach` request to the chrome actor)
+ return { form: aResponse.form, client: client, chrome: false };
+ });
+ }
+
+ let target;
+ function getTarget(aConnection)
+ {
+ return TargetFactory.forRemoteTab(aConnection);
+ }
+
+ function openWindow(aTarget)
+ {
+ target = aTarget;
+
+ let deferred = promise.defer();
+
+ let win = Services.ww.openWindow(null, Tools.webConsole.url, "_blank",
+ BROWSER_CONSOLE_WINDOW_FEATURES, null);
+ win.addEventListener("DOMContentLoaded", function onLoad() {
+ win.removeEventListener("DOMContentLoaded", onLoad);
+
+ // Set the correct Browser Console title.
+ let root = win.document.documentElement;
+ root.setAttribute("title", root.getAttribute("browserConsoleTitle"));
+
+ deferred.resolve(win);
+ });
+
+ return deferred.promise;
+ }
+
+ connect().then(getTarget).then(openWindow).then((aWindow) => {
+ return this.openBrowserConsole(target, aWindow, aWindow)
+ .then((aBrowserConsole) => {
+ this._browserConsoleDefer.resolve(aBrowserConsole);
+ this._browserConsoleDefer = null;
+ });
+ }, console.error.bind(console));
+
+ return this._browserConsoleDefer.promise;
+ },
+
+ /**
+ * Opens or focuses the Browser Console.
+ */
+ openBrowserConsoleOrFocus: function HS_openBrowserConsoleOrFocus()
+ {
+ let hud = this.getBrowserConsole();
+ if (hud) {
+ hud.iframeWindow.focus();
+ return promise.resolve(hud);
+ }
+ else {
+ return this.toggleBrowserConsole();
+ }
+ },
+
+ /**
+ * Get the Browser Console instance, if open.
+ *
+ * @return object|null
+ * A BrowserConsole instance or null if the Browser Console is not
+ * open.
+ */
+ getBrowserConsole: function HS_getBrowserConsole()
+ {
+ return this.getHudReferenceById(this._browserConsoleID);
+ },
+};
+
+
+/**
+ * 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.
+ *
+ * @constructor
+ * @param object aTarget
+ * The target that the web console will connect to.
+ * @param nsIDOMWindow aIframeWindow
+ * The window where the web console UI is already loaded.
+ * @param nsIDOMWindow aChromeWindow
+ * The window of the web console owner.
+ */
+function WebConsole(aTarget, aIframeWindow, aChromeWindow)
+{
+ this.iframeWindow = aIframeWindow;
+ this.chromeWindow = aChromeWindow;
+ this.hudId = "hud_" + ++gHudId;
+ this.target = aTarget;
+
+ this.browserWindow = this.chromeWindow.top;
+
+ let element = this.browserWindow.document.documentElement;
+ if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
+ this.browserWindow = HUDService.currentContext();
+ }
+
+ this.ui = new WebConsoleFrame(this);
+}
+
+WebConsole.prototype = {
+ iframeWindow: null,
+ chromeWindow: null,
+ browserWindow: null,
+ hudId: null,
+ target: null,
+ ui: null,
+ _browserConsole: false,
+ _destroyer: null,
+
+ /**
+ * 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 HUDService.lastFinishedRequest.callback;
+ },
+
+ /**
+ * 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;
+ },
+
+ /**
+ * Getter for the xul:popupset that holds any popups we open.
+ * @type nsIDOMElement
+ */
+ get mainPopupSet()
+ {
+ return this.chromeUtilsWindow.document.getElementById("mainPopupSet");
+ },
+
+ /**
+ * Getter for the output element that holds messages we display.
+ * @type nsIDOMElement
+ */
+ get outputNode()
+ {
+ return this.ui ? this.ui.outputNode : null;
+ },
+
+ get gViewSourceUtils()
+ {
+ return this.chromeUtilsWindow.gViewSourceUtils;
+ },
+
+ /**
+ * Initialize the Web Console instance.
+ *
+ * @return object
+ * A promise for the initialization.
+ */
+ init: function WC_init()
+ {
+ return this.ui.init().then(() => this);
+ },
+
+ /**
+ * Retrieve the Web Console panel title.
+ *
+ * @return string
+ * The Web Console panel title.
+ */
+ getPanelTitle: function WC_getPanelTitle()
+ {
+ let url = this.ui ? this.ui.contentLocation : "";
+ return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]);
+ },
+
+ /**
+ * The JSTerm object that manages the console's input.
+ * @see webconsole.js::JSTerm
+ * @type object
+ */
+ get jsterm()
+ {
+ return this.ui ? this.ui.jsterm : null;
+ },
+
+ /**
+ * The clear output button handler.
+ * @private
+ */
+ _onClearButton: function WC__onClearButton()
+ {
+ if (this.target.isLocalTab) {
+ this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab);
+ }
+ },
+
+ /**
+ * Alias for the WebConsoleFrame.setFilterState() method.
+ * @see webconsole.js::WebConsoleFrame.setFilterState()
+ */
+ setFilterState: function WC_setFilterState()
+ {
+ this.ui && this.ui.setFilterState.apply(this.ui, arguments);
+ },
+
+ /**
+ * Open a link in a new tab.
+ *
+ * @param string aLink
+ * The URL you want to open in a new tab.
+ */
+ openLink: function WC_openLink(aLink)
+ {
+ this.chromeUtilsWindow.openUILinkIn(aLink, "tab");
+ },
+
+ /**
+ * Open a link in Firefox's view source.
+ *
+ * @param string aSourceURL
+ * The URL of the file.
+ * @param integer aSourceLine
+ * The line number which should be highlighted.
+ */
+ viewSource: function WC_viewSource(aSourceURL, aSourceLine) {
+ // Attempt to access view source via a browser first, which may display it in
+ // a tab, if enabled.
+ let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ if (browserWin && browserWin.BrowserViewSourceOfDocument) {
+ return browserWin.BrowserViewSourceOfDocument({
+ URL: aSourceURL,
+ lineNumber: aSourceLine
+ });
+ }
+ this.gViewSourceUtils.viewSource(aSourceURL, null, this.iframeWindow.document, aSourceLine || 0);
+ },
+
+ /**
+ * 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 aSourceURL
+ * The URL of the file.
+ * @param integer aSourceLine
+ * The line number which you want to place the caret.
+ */
+ viewSourceInStyleEditor: function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine) {
+ let toolbox = gDevTools.getToolbox(this.target);
+ if (!toolbox) {
+ this.viewSource(aSourceURL, aSourceLine);
+ return;
+ }
+ toolbox.viewSourceInStyleEditor(aSourceURL, aSourceLine);
+ },
+
+ /**
+ * 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 aSourceURL
+ * The URL of the file.
+ * @param integer aSourceLine
+ * The line number which you want to place the caret.
+ */
+ viewSourceInDebugger: function WC_viewSourceInDebugger(aSourceURL, aSourceLine) {
+ let toolbox = gDevTools.getToolbox(this.target);
+ if (!toolbox) {
+ this.viewSource(aSourceURL, aSourceLine);
+ return;
+ }
+ toolbox.viewSourceInDebugger(aSourceURL, aSourceLine).then(() => {
+ this.ui.emit("source-in-debugger-opened");
+ });
+ },
+
+ /**
+ * Tries to open a JavaScript file related to the web page for the web console
+ * instance in the corresponding Scratchpad.
+ *
+ * @param string aSourceURL
+ * The URL of the file which corresponds to a Scratchpad id.
+ */
+ viewSourceInScratchpad: function WC_viewSourceInScratchpad(aSourceURL, aSourceLine) {
+ viewSource.viewSourceInScratchpad(aSourceURL, aSourceLine);
+ },
+
+ /**
+ * 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: function WC_getDebuggerFrames()
+ {
+ let toolbox = gDevTools.getToolbox(this.target);
+ if (!toolbox) {
+ return null;
+ }
+ let panel = toolbox.getPanel("jsdebugger");
+
+ if (!panel) {
+ return null;
+ }
+
+ return panel.getFrames();
+ },
+
+ /**
+ * 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: function WC_getInspectorSelection()
+ {
+ let toolbox = gDevTools.getToolbox(this.target);
+ if (!toolbox) {
+ return null;
+ }
+ let panel = toolbox.getPanel("inspector");
+ if (!panel || !panel.selection) {
+ return null;
+ }
+ return panel.selection;
+ },
+
+ /**
+ * 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.
+ */
+ destroy: function WC_destroy()
+ {
+ if (this._destroyer) {
+ return this._destroyer.promise;
+ }
+
+ HUDService.consoles.delete(this.hudId);
+
+ this._destroyer = promise.defer();
+
+ // The document may already be removed
+ if (this.chromeUtilsWindow && this.mainPopupSet) {
+ let popupset = this.mainPopupSet;
+ let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
+ for (let panel of panels) {
+ panel.hidePopup();
+ }
+ }
+
+ let onDestroy = Task.async(function* () {
+ if (!this._browserConsole) {
+ try {
+ yield this.target.activeTab.focus();
+ }
+ catch (ex) {
+ // Tab focus can fail if the tab or target is closed.
+ }
+ }
+
+ let id = WebConsoleUtils.supportsString(this.hudId);
+ Services.obs.notifyObservers(id, "web-console-destroyed", null);
+ this._destroyer.resolve(null);
+ }.bind(this));
+
+ if (this.ui) {
+ this.ui.destroy().then(onDestroy);
+ }
+ else {
+ onDestroy();
+ }
+
+ return this._destroyer.promise;
+ },
+};
+
+/**
+ * A BrowserConsole 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 Browser Console UI. This is
+ * meant to be an integration point between the Firefox UI and the Browser Console
+ * UI and features.
+ *
+ * @constructor
+ * @param object aTarget
+ * The target that the browser console will connect to.
+ * @param nsIDOMWindow aIframeWindow
+ * The window where the browser console UI is already loaded.
+ * @param nsIDOMWindow aChromeWindow
+ * The window of the browser console owner.
+ */
+function BrowserConsole()
+{
+ WebConsole.apply(this, arguments);
+ this._telemetry = new Telemetry();
+}
+
+BrowserConsole.prototype = extend(WebConsole.prototype, {
+ _browserConsole: true,
+ _bc_init: null,
+ _bc_destroyer: null,
+
+ $init: WebConsole.prototype.init,
+
+ /**
+ * Initialize the Browser Console instance.
+ *
+ * @return object
+ * A promise for the initialization.
+ */
+ init: function BC_init()
+ {
+ if (this._bc_init) {
+ return this._bc_init;
+ }
+
+ this.ui._filterPrefsPrefix = BROWSER_CONSOLE_FILTER_PREFS_PREFIX;
+
+ let window = this.iframeWindow;
+
+ // Make sure that the closing of the Browser Console window destroys this
+ // instance.
+ let onClose = () => {
+ window.removeEventListener("unload", onClose);
+ window.removeEventListener("focus", onFocus);
+ this.destroy();
+ };
+ window.addEventListener("unload", onClose);
+
+ this._telemetry.toolOpened("browserconsole");
+
+ // Create an onFocus handler just to display the dev edition promo.
+ // This is to prevent race conditions in some environments.
+ // Hook to display promotional Developer Edition doorhanger. Only displayed once.
+ let onFocus = () => showDoorhanger({ window, type: "deveditionpromo" });
+ window.addEventListener("focus", onFocus);
+
+ this._bc_init = this.$init();
+ return this._bc_init;
+ },
+
+ $destroy: WebConsole.prototype.destroy,
+
+ /**
+ * Destroy the object.
+ *
+ * @return object
+ * A promise object that is resolved once the Browser Console is closed.
+ */
+ destroy: function BC_destroy()
+ {
+ if (this._bc_destroyer) {
+ return this._bc_destroyer.promise;
+ }
+
+ this._telemetry.toolClosed("browserconsole");
+
+ this._bc_destroyer = promise.defer();
+
+ let chromeWindow = this.chromeWindow;
+ this.$destroy().then(() =>
+ this.target.client.close().then(() => {
+ HUDService._browserConsoleID = null;
+ chromeWindow.close();
+ this._bc_destroyer.resolve(null);
+ }));
+
+ return this._bc_destroyer.promise;
+ },
+});
+
+const HUDService = new HUD_SERVICE();
+
+(() => {
+ let methods = ["openWebConsole", "openBrowserConsole",
+ "toggleBrowserConsole", "getOpenWebConsole",
+ "getBrowserConsole", "getHudByWindow",
+ "openBrowserConsoleOrFocus", "getHudReferenceById"];
+ for (let method of methods) {
+ exports[method] = HUDService[method].bind(HUDService);
+ }
+
+ exports.consoles = HUDService.consoles;
+ exports.lastFinishedRequest = HUDService.lastFinishedRequest;
+})();