browser/devtools/webconsole/hudservice.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     9 const {Cc, Ci, Cu} = require("chrome");
    11 let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
    12 let Heritage = require("sdk/core/heritage");
    14 loader.lazyGetter(this, "Telemetry", () => require("devtools/shared/telemetry"));
    15 loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/webconsole/webconsole").WebConsoleFrame);
    16 loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
    17 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
    18 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
    19 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
    20 loader.lazyImporter(this, "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
    21 loader.lazyImporter(this, "DebuggerClient", "resource://gre/modules/devtools/dbg-client.jsm");
    23 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
    24 let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
    26 const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
    28 // The preference prefix for all of the Browser Console filters.
    29 const BROWSER_CONSOLE_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter.";
    31 let gHudId = 0;
    33 ///////////////////////////////////////////////////////////////////////////
    34 //// The HUD service
    36 function HUD_SERVICE()
    37 {
    38   this.consoles = new Map();
    39   this.lastFinishedRequest = { callback: null };
    40 }
    42 HUD_SERVICE.prototype =
    43 {
    44   _browserConsoleID: null,
    45   _browserConsoleDefer: null,
    47   /**
    48    * Keeps a reference for each Web Console / Browser Console that is created.
    49    * @type Map
    50    */
    51   consoles: null,
    53   /**
    54    * Assign a function to this property to listen for every request that
    55    * completes. Used by unit tests. The callback takes one argument: the HTTP
    56    * activity object as received from the remote Web Console.
    57    *
    58    * @type object
    59    *       Includes a property named |callback|. Assign the function to the
    60    *       |callback| property of this object.
    61    */
    62   lastFinishedRequest: null,
    64   /**
    65    * Firefox-specific current tab getter
    66    *
    67    * @returns nsIDOMWindow
    68    */
    69   currentContext: function HS_currentContext() {
    70     return Services.wm.getMostRecentWindow("navigator:browser");
    71   },
    73   /**
    74    * Open a Web Console for the given target.
    75    *
    76    * @see devtools/framework/target.js for details about targets.
    77    *
    78    * @param object aTarget
    79    *        The target that the web console will connect to.
    80    * @param nsIDOMWindow aIframeWindow
    81    *        The window where the web console UI is already loaded.
    82    * @param nsIDOMWindow aChromeWindow
    83    *        The window of the web console owner.
    84    * @return object
    85    *         A promise object for the opening of the new WebConsole instance.
    86    */
    87   openWebConsole:
    88   function HS_openWebConsole(aTarget, aIframeWindow, aChromeWindow)
    89   {
    90     let hud = new WebConsole(aTarget, aIframeWindow, aChromeWindow);
    91     this.consoles.set(hud.hudId, hud);
    92     return hud.init();
    93   },
    95   /**
    96    * Open a Browser Console for the given target.
    97    *
    98    * @see devtools/framework/target.js for details about targets.
    99    *
   100    * @param object aTarget
   101    *        The target that the browser console will connect to.
   102    * @param nsIDOMWindow aIframeWindow
   103    *        The window where the browser console UI is already loaded.
   104    * @param nsIDOMWindow aChromeWindow
   105    *        The window of the browser console owner.
   106    * @return object
   107    *         A promise object for the opening of the new BrowserConsole instance.
   108    */
   109   openBrowserConsole:
   110   function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
   111   {
   112     let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
   113     this._browserConsoleID = hud.hudId;
   114     this.consoles.set(hud.hudId, hud);
   115     return hud.init();
   116   },
   118   /**
   119    * Returns the Web Console object associated to a content window.
   120    *
   121    * @param nsIDOMWindow aContentWindow
   122    * @returns object
   123    */
   124   getHudByWindow: function HS_getHudByWindow(aContentWindow)
   125   {
   126     for (let [hudId, hud] of this.consoles) {
   127       let target = hud.target;
   128       if (target && target.tab && target.window === aContentWindow) {
   129         return hud;
   130       }
   131     }
   132     return null;
   133   },
   135   /**
   136    * Returns the console instance for a given id.
   137    *
   138    * @param string aId
   139    * @returns Object
   140    */
   141   getHudReferenceById: function HS_getHudReferenceById(aId)
   142   {
   143     return this.consoles.get(aId);
   144   },
   146   /**
   147    * Find if there is a Web Console open for the current tab and return the
   148    * instance.
   149    * @return object|null
   150    *         The WebConsole object or null if the active tab has no open Web
   151    *         Console.
   152    */
   153   getOpenWebConsole: function HS_getOpenWebConsole()
   154   {
   155     let tab = this.currentContext().gBrowser.selectedTab;
   156     if (!tab || !devtools.TargetFactory.isKnownTab(tab)) {
   157       return null;
   158     }
   159     let target = devtools.TargetFactory.forTab(tab);
   160     let toolbox = gDevTools.getToolbox(target);
   161     let panel = toolbox ? toolbox.getPanel("webconsole") : null;
   162     return panel ? panel.hud : null;
   163   },
   165   /**
   166    * Toggle the Browser Console.
   167    */
   168   toggleBrowserConsole: function HS_toggleBrowserConsole()
   169   {
   170     if (this._browserConsoleID) {
   171       let hud = this.getHudReferenceById(this._browserConsoleID);
   172       return hud.destroy();
   173     }
   175     if (this._browserConsoleDefer) {
   176       return this._browserConsoleDefer.promise;
   177     }
   179     this._browserConsoleDefer = promise.defer();
   181     function connect()
   182     {
   183       let deferred = promise.defer();
   185       if (!DebuggerServer.initialized) {
   186         DebuggerServer.init();
   187         DebuggerServer.addBrowserActors();
   188       }
   190       let client = new DebuggerClient(DebuggerServer.connectPipe());
   191       client.connect(() =>
   192         client.listTabs((aResponse) => {
   193           // Add Global Process debugging...
   194           let globals = JSON.parse(JSON.stringify(aResponse));
   195           delete globals.tabs;
   196           delete globals.selected;
   197           // ...only if there are appropriate actors (a 'from' property will
   198           // always be there).
   199           if (Object.keys(globals).length > 1) {
   200             deferred.resolve({ form: globals, client: client, chrome: true });
   201           } else {
   202             deferred.reject("Global console not found!");
   203           }
   204         }));
   206       return deferred.promise;
   207     }
   209     let target;
   210     function getTarget(aConnection)
   211     {
   212       let options = {
   213         form: aConnection.form,
   214         client: aConnection.client,
   215         chrome: true,
   216       };
   218       return devtools.TargetFactory.forRemoteTab(options);
   219     }
   221     function openWindow(aTarget)
   222     {
   223       target = aTarget;
   225       let deferred = promise.defer();
   227       let win = Services.ww.openWindow(null, devtools.Tools.webConsole.url, "_blank",
   228                                        BROWSER_CONSOLE_WINDOW_FEATURES, null);
   229       win.addEventListener("DOMContentLoaded", function onLoad() {
   230         win.removeEventListener("DOMContentLoaded", onLoad);
   232         // Set the correct Browser Console title.
   233         let root = win.document.documentElement;
   234         root.setAttribute("title", root.getAttribute("browserConsoleTitle"));
   236         deferred.resolve(win);
   237       });
   239       return deferred.promise;
   240     }
   242     connect().then(getTarget).then(openWindow).then((aWindow) => {
   243       this.openBrowserConsole(target, aWindow, aWindow)
   244         .then((aBrowserConsole) => {
   245           this._browserConsoleDefer.resolve(aBrowserConsole);
   246           this._browserConsoleDefer = null;
   247         })
   248     }, console.error);
   250     return this._browserConsoleDefer.promise;
   251   },
   253   /**
   254    * Get the Browser Console instance, if open.
   255    *
   256    * @return object|null
   257    *         A BrowserConsole instance or null if the Browser Console is not
   258    *         open.
   259    */
   260   getBrowserConsole: function HS_getBrowserConsole()
   261   {
   262     return this.getHudReferenceById(this._browserConsoleID);
   263   },
   264 };
   267 /**
   268  * A WebConsole instance is an interactive console initialized *per target*
   269  * that displays console log data as well as provides an interactive terminal to
   270  * manipulate the target's document content.
   271  *
   272  * This object only wraps the iframe that holds the Web Console UI. This is
   273  * meant to be an integration point between the Firefox UI and the Web Console
   274  * UI and features.
   275  *
   276  * @constructor
   277  * @param object aTarget
   278  *        The target that the web console will connect to.
   279  * @param nsIDOMWindow aIframeWindow
   280  *        The window where the web console UI is already loaded.
   281  * @param nsIDOMWindow aChromeWindow
   282  *        The window of the web console owner.
   283  */
   284 function WebConsole(aTarget, aIframeWindow, aChromeWindow)
   285 {
   286   this.iframeWindow = aIframeWindow;
   287   this.chromeWindow = aChromeWindow;
   288   this.hudId = "hud_" + ++gHudId;
   289   this.target = aTarget;
   291   this.browserWindow = this.chromeWindow.top;
   293   let element = this.browserWindow.document.documentElement;
   294   if (element.getAttribute("windowtype") != "navigator:browser") {
   295     this.browserWindow = HUDService.currentContext();
   296   }
   298   this.ui = new WebConsoleFrame(this);
   299 }
   301 WebConsole.prototype = {
   302   iframeWindow: null,
   303   chromeWindow: null,
   304   browserWindow: null,
   305   hudId: null,
   306   target: null,
   307   ui: null,
   308   _browserConsole: false,
   309   _destroyer: null,
   311   /**
   312    * Getter for a function to to listen for every request that completes. Used
   313    * by unit tests. The callback takes one argument: the HTTP activity object as
   314    * received from the remote Web Console.
   315    *
   316    * @type function
   317    */
   318   get lastFinishedRequestCallback() HUDService.lastFinishedRequest.callback,
   320   /**
   321    * Getter for the window that can provide various utilities that the web
   322    * console makes use of, like opening links, managing popups, etc.  In
   323    * most cases, this will be |this.browserWindow|, but in some uses (such as
   324    * the Browser Toolbox), there is no browser window, so an alternative window
   325    * hosts the utilities there.
   326    * @type nsIDOMWindow
   327    */
   328   get chromeUtilsWindow()
   329   {
   330     if (this.browserWindow) {
   331       return this.browserWindow;
   332     }
   333     return this.chromeWindow.top;
   334   },
   336   /**
   337    * Getter for the xul:popupset that holds any popups we open.
   338    * @type nsIDOMElement
   339    */
   340   get mainPopupSet()
   341   {
   342     return this.chromeUtilsWindow.document.getElementById("mainPopupSet");
   343   },
   345   /**
   346    * Getter for the output element that holds messages we display.
   347    * @type nsIDOMElement
   348    */
   349   get outputNode()
   350   {
   351     return this.ui ? this.ui.outputNode : null;
   352   },
   354   get gViewSourceUtils()
   355   {
   356     return this.chromeUtilsWindow.gViewSourceUtils;
   357   },
   359   /**
   360    * Initialize the Web Console instance.
   361    *
   362    * @return object
   363    *         A promise for the initialization.
   364    */
   365   init: function WC_init()
   366   {
   367     return this.ui.init().then(() => this);
   368   },
   370   /**
   371    * Retrieve the Web Console panel title.
   372    *
   373    * @return string
   374    *         The Web Console panel title.
   375    */
   376   getPanelTitle: function WC_getPanelTitle()
   377   {
   378     let url = this.ui ? this.ui.contentLocation : "";
   379     return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]);
   380   },
   382   /**
   383    * The JSTerm object that manages the console's input.
   384    * @see webconsole.js::JSTerm
   385    * @type object
   386    */
   387   get jsterm()
   388   {
   389     return this.ui ? this.ui.jsterm : null;
   390   },
   392   /**
   393    * The clear output button handler.
   394    * @private
   395    */
   396   _onClearButton: function WC__onClearButton()
   397   {
   398     if (this.target.isLocalTab) {
   399       this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab);
   400     }
   401   },
   403   /**
   404    * Alias for the WebConsoleFrame.setFilterState() method.
   405    * @see webconsole.js::WebConsoleFrame.setFilterState()
   406    */
   407   setFilterState: function WC_setFilterState()
   408   {
   409     this.ui && this.ui.setFilterState.apply(this.ui, arguments);
   410   },
   412   /**
   413    * Open a link in a new tab.
   414    *
   415    * @param string aLink
   416    *        The URL you want to open in a new tab.
   417    */
   418   openLink: function WC_openLink(aLink)
   419   {
   420     this.chromeUtilsWindow.openUILinkIn(aLink, "tab");
   421   },
   423   /**
   424    * Open a link in Firefox's view source.
   425    *
   426    * @param string aSourceURL
   427    *        The URL of the file.
   428    * @param integer aSourceLine
   429    *        The line number which should be highlighted.
   430    */
   431   viewSource: function WC_viewSource(aSourceURL, aSourceLine)
   432   {
   433     this.gViewSourceUtils.viewSource(aSourceURL, null,
   434                                      this.iframeWindow.document, aSourceLine);
   435   },
   437   /**
   438    * Tries to open a Stylesheet file related to the web page for the web console
   439    * instance in the Style Editor. If the file is not found, it is opened in
   440    * source view instead.
   441    *
   442    * @param string aSourceURL
   443    *        The URL of the file.
   444    * @param integer aSourceLine
   445    *        The line number which you want to place the caret.
   446    * TODO: This function breaks the client-server boundaries.
   447    *       To be fixed in bug 793259.
   448    */
   449   viewSourceInStyleEditor:
   450   function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
   451   {
   452     let toolbox = gDevTools.getToolbox(this.target);
   453     if (!toolbox) {
   454       this.viewSource(aSourceURL, aSourceLine);
   455       return;
   456     }
   458     gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
   459       try {
   460         toolbox.getCurrentPanel().selectStyleSheet(aSourceURL, aSourceLine);
   461       } catch(e) {
   462         // Open view source if style editor fails.
   463         this.viewSource(aSourceURL, aSourceLine);
   464       }
   465     });
   466   },
   468   /**
   469    * Tries to open a JavaScript file related to the web page for the web console
   470    * instance in the Script Debugger. If the file is not found, it is opened in
   471    * source view instead.
   472    *
   473    * @param string aSourceURL
   474    *        The URL of the file.
   475    * @param integer aSourceLine
   476    *        The line number which you want to place the caret.
   477    */
   478   viewSourceInDebugger:
   479   function WC_viewSourceInDebugger(aSourceURL, aSourceLine)
   480   {
   481     let toolbox = gDevTools.getToolbox(this.target);
   482     if (!toolbox) {
   483       this.viewSource(aSourceURL, aSourceLine);
   484       return;
   485     }
   487     let showSource = ({ DebuggerView }) => {
   488       if (DebuggerView.Sources.containsValue(aSourceURL)) {
   489         DebuggerView.setEditorLocation(aSourceURL, aSourceLine,
   490                                        { noDebug: true }).then(() => {
   491           this.ui.emit("source-in-debugger-opened");
   492         });
   493         return;
   494       }
   495       toolbox.selectTool("webconsole");
   496       this.viewSource(aSourceURL, aSourceLine);
   497     }
   499     // If the Debugger was already open, switch to it and try to show the
   500     // source immediately. Otherwise, initialize it and wait for the sources
   501     // to be added first.
   502     let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
   503     toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
   504       if (debuggerAlreadyOpen) {
   505         showSource(dbg);
   506       } else {
   507         dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
   508       }
   509     });
   510   },
   513   /**
   514    * Tries to open a JavaScript file related to the web page for the web console
   515    * instance in the corresponding Scratchpad.
   516    *
   517    * @param string aSourceURL
   518    *        The URL of the file which corresponds to a Scratchpad id.
   519    */
   520   viewSourceInScratchpad: function WC_viewSourceInScratchpad(aSourceURL)
   521   {
   522     // Check for matching top level Scratchpad window.
   523     let wins = Services.wm.getEnumerator("devtools:scratchpad");
   525     while (wins.hasMoreElements()) {
   526       let win = wins.getNext();
   528       if (!win.closed && win.Scratchpad.uniqueName === aSourceURL) {
   529         win.focus();
   530         return;
   531       }
   532     }
   534     // Check for matching Scratchpad toolbox tab.
   535     for (let [, toolbox] of gDevTools) {
   536       let scratchpadPanel = toolbox.getPanel("scratchpad");
   537       if (scratchpadPanel) {
   538         let { scratchpad } = scratchpadPanel;
   539         if (scratchpad.uniqueName === aSourceURL) {
   540           toolbox.selectTool("scratchpad");
   541           toolbox.raise();
   542           scratchpad.editor.focus();
   543           return;
   544         }
   545       }
   546     }
   547   },
   549   /**
   550    * Retrieve information about the JavaScript debugger's stackframes list. This
   551    * is used to allow the Web Console to evaluate code in the selected
   552    * stackframe.
   553    *
   554    * @return object|null
   555    *         An object which holds:
   556    *         - frames: the active ThreadClient.cachedFrames array.
   557    *         - selected: depth/index of the selected stackframe in the debugger
   558    *         UI.
   559    *         If the debugger is not open or if it's not paused, then |null| is
   560    *         returned.
   561    */
   562   getDebuggerFrames: function WC_getDebuggerFrames()
   563   {
   564     let toolbox = gDevTools.getToolbox(this.target);
   565     if (!toolbox) {
   566       return null;
   567     }
   568     let panel = toolbox.getPanel("jsdebugger");
   569     if (!panel) {
   570       return null;
   571     }
   572     let framesController = panel.panelWin.DebuggerController.StackFrames;
   573     let thread = framesController.activeThread;
   574     if (thread && thread.paused) {
   575       return {
   576         frames: thread.cachedFrames,
   577         selected: framesController.currentFrameDepth,
   578       };
   579     }
   580     return null;
   581   },
   583   /**
   584    * Destroy the object. Call this method to avoid memory leaks when the Web
   585    * Console is closed.
   586    *
   587    * @return object
   588    *         A promise object that is resolved once the Web Console is closed.
   589    */
   590   destroy: function WC_destroy()
   591   {
   592     if (this._destroyer) {
   593       return this._destroyer.promise;
   594     }
   596     HUDService.consoles.delete(this.hudId);
   598     this._destroyer = promise.defer();
   600     let popupset = this.mainPopupSet;
   601     let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
   602     for (let panel of panels) {
   603       panel.hidePopup();
   604     }
   606     let onDestroy = function WC_onDestroyUI() {
   607       try {
   608         let tabWindow = this.target.isLocalTab ? this.target.window : null;
   609         tabWindow && tabWindow.focus();
   610       }
   611       catch (ex) {
   612         // Tab focus can fail if the tab or target is closed.
   613       }
   615       let id = WebConsoleUtils.supportsString(this.hudId);
   616       Services.obs.notifyObservers(id, "web-console-destroyed", null);
   617       this._destroyer.resolve(null);
   618     }.bind(this);
   620     if (this.ui) {
   621       this.ui.destroy().then(onDestroy);
   622     }
   623     else {
   624       onDestroy();
   625     }
   627     return this._destroyer.promise;
   628   },
   629 };
   632 /**
   633  * A BrowserConsole instance is an interactive console initialized *per target*
   634  * that displays console log data as well as provides an interactive terminal to
   635  * manipulate the target's document content.
   636  *
   637  * This object only wraps the iframe that holds the Browser Console UI. This is
   638  * meant to be an integration point between the Firefox UI and the Browser Console
   639  * UI and features.
   640  *
   641  * @constructor
   642  * @param object aTarget
   643  *        The target that the browser console will connect to.
   644  * @param nsIDOMWindow aIframeWindow
   645  *        The window where the browser console UI is already loaded.
   646  * @param nsIDOMWindow aChromeWindow
   647  *        The window of the browser console owner.
   648  */
   649 function BrowserConsole()
   650 {
   651   WebConsole.apply(this, arguments);
   652   this._telemetry = new Telemetry();
   653 }
   655 BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
   656 {
   657   _browserConsole: true,
   658   _bc_init: null,
   659   _bc_destroyer: null,
   661   $init: WebConsole.prototype.init,
   663   /**
   664    * Initialize the Browser Console instance.
   665    *
   666    * @return object
   667    *         A promise for the initialization.
   668    */
   669   init: function BC_init()
   670   {
   671     if (this._bc_init) {
   672       return this._bc_init;
   673     }
   675     this.ui._filterPrefsPrefix = BROWSER_CONSOLE_FILTER_PREFS_PREFIX;
   677     let window = this.iframeWindow;
   679     // Make sure that the closing of the Browser Console window destroys this
   680     // instance.
   681     let onClose = () => {
   682       window.removeEventListener("unload", onClose);
   683       this.destroy();
   684     };
   685     window.addEventListener("unload", onClose);
   687     // Make sure Ctrl-W closes the Browser Console window.
   688     window.document.getElementById("cmd_close").removeAttribute("disabled");
   690     this._telemetry.toolOpened("browserconsole");
   692     this._bc_init = this.$init();
   693     return this._bc_init;
   694   },
   696   $destroy: WebConsole.prototype.destroy,
   698   /**
   699    * Destroy the object.
   700    *
   701    * @return object
   702    *         A promise object that is resolved once the Browser Console is closed.
   703    */
   704   destroy: function BC_destroy()
   705   {
   706     if (this._bc_destroyer) {
   707       return this._bc_destroyer.promise;
   708     }
   710     this._telemetry.toolClosed("browserconsole");
   712     this._bc_destroyer = promise.defer();
   714     let chromeWindow = this.chromeWindow;
   715     this.$destroy().then(() =>
   716       this.target.client.close(() => {
   717         HUDService._browserConsoleID = null;
   718         chromeWindow.close();
   719         this._bc_destroyer.resolve(null);
   720       }));
   722     return this._bc_destroyer.promise;
   723   },
   724 });
   726 const HUDService = new HUD_SERVICE();
   728 (() => {
   729   let methods = ["openWebConsole", "openBrowserConsole",
   730                  "toggleBrowserConsole", "getOpenWebConsole",
   731                  "getBrowserConsole", "getHudByWindow", "getHudReferenceById"];
   732   for (let method of methods) {
   733     exports[method] = HUDService[method].bind(HUDService);
   734   }
   736   exports.consoles = HUDService.consoles;
   737   exports.lastFinishedRequest = HUDService.lastFinishedRequest;
   738 })();

mercurial