browser/devtools/framework/gDevTools.jsm

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 /* 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/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "gDevToolsBrowser" ];
     9 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 Cu.import("resource://gre/modules/Services.jsm");
    13 Cu.import("resource://gre/modules/devtools/event-emitter.js");
    14 Cu.import("resource://gre/modules/devtools/Loader.jsm");
    15 XPCOMUtils.defineLazyModuleGetter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
    17 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
    18 const MAX_ORDINAL = 99;
    21 /**
    22  * DevTools is a class that represents a set of developer tools, it holds a
    23  * set of tools and keeps track of open toolboxes in the browser.
    24  */
    25 this.DevTools = function DevTools() {
    26   this._tools = new Map();     // Map<toolId, tool>
    27   this._toolboxes = new Map(); // Map<target, toolbox>
    29   // destroy() is an observer's handler so we need to preserve context.
    30   this.destroy = this.destroy.bind(this);
    31   this._teardown = this._teardown.bind(this);
    33   this._testing = false;
    35   EventEmitter.decorate(this);
    37   Services.obs.addObserver(this._teardown, "devtools-unloaded", false);
    38   Services.obs.addObserver(this.destroy, "quit-application", false);
    39 }
    41 DevTools.prototype = {
    42   /**
    43    * When the testing flag is set we take appropriate action to prevent race
    44    * conditions in our testing environment. This means setting
    45    * dom.send_after_paint_to_content to false to prevent infinite MozAfterPaint
    46    * loops and not autohiding the highlighter.
    47    */
    48   get testing() {
    49     return this._testing;
    50   },
    52   set testing(state) {
    53     this._testing = state;
    55     if (state) {
    56       // dom.send_after_paint_to_content is set to true (non-default) in
    57       // testing/profiles/prefs_general.js so lets set it to the same as it is
    58       // in a default browser profile for the duration of the test.
    59       Services.prefs.setBoolPref("dom.send_after_paint_to_content", false);
    60     } else {
    61       Services.prefs.setBoolPref("dom.send_after_paint_to_content", true);
    62     }
    63   },
    65   /**
    66    * Register a new developer tool.
    67    *
    68    * A definition is a light object that holds different information about a
    69    * developer tool. This object is not supposed to have any operational code.
    70    * See it as a "manifest".
    71    * The only actual code lives in the build() function, which will be used to
    72    * start an instance of this tool.
    73    *
    74    * Each toolDefinition has the following properties:
    75    * - id: Unique identifier for this tool (string|required)
    76    * - visibilityswitch: Property name to allow us to hide this tool from the
    77    *                     DevTools Toolbox.
    78    *                     A falsy value indicates that it cannot be hidden.
    79    * - icon: URL pointing to a graphic which will be used as the src for an
    80    *         16x16 img tag (string|required)
    81    * - invertIconForLightTheme: The icon can automatically have an inversion
    82    *         filter applied (default is false).  All builtin tools are true, but
    83    *         addons may omit this to prevent unwanted changes to the `icon`
    84    *         image. See browser/themes/shared/devtools/filters.svg#invert for
    85    *         the filter being applied to the images (boolean|optional)
    86    * - url: URL pointing to a XUL/XHTML document containing the user interface
    87    *        (string|required)
    88    * - label: Localized name for the tool to be displayed to the user
    89    *          (string|required)
    90    * - build: Function that takes an iframe, which has been populated with the
    91    *          markup from |url|, and also the toolbox containing the panel.
    92    *          And returns an instance of ToolPanel (function|required)
    93    */
    94   registerTool: function DT_registerTool(toolDefinition) {
    95     let toolId = toolDefinition.id;
    97     if (!toolId || FORBIDDEN_IDS.has(toolId)) {
    98       throw new Error("Invalid definition.id");
    99     }
   101     // Make sure that additional tools will always be able to be hidden.
   102     // When being called from main.js, defaultTools has not yet been exported.
   103     // But, we can assume that in this case, it is a default tool.
   104     if (devtools.defaultTools && devtools.defaultTools.indexOf(toolDefinition) == -1) {
   105       toolDefinition.visibilityswitch = "devtools." + toolId + ".enabled";
   106     }
   108     this._tools.set(toolId, toolDefinition);
   110     this.emit("tool-registered", toolId);
   111   },
   113   /**
   114    * Removes all tools that match the given |toolId|
   115    * Needed so that add-ons can remove themselves when they are deactivated
   116    *
   117    * @param {string|object} tool
   118    *        Definition or the id of the tool to unregister. Passing the
   119    *        tool id should be avoided as it is a temporary measure.
   120    * @param {boolean} isQuitApplication
   121    *        true to indicate that the call is due to app quit, so we should not
   122    *        cause a cascade of costly events
   123    */
   124   unregisterTool: function DT_unregisterTool(tool, isQuitApplication) {
   125     let toolId = null;
   126     if (typeof tool == "string") {
   127       toolId = tool;
   128       tool = this._tools.get(tool);
   129     }
   130     else {
   131       toolId = tool.id;
   132     }
   133     this._tools.delete(toolId);
   135     if (!isQuitApplication) {
   136       this.emit("tool-unregistered", tool);
   137     }
   138   },
   140   /**
   141    * Sorting function used for sorting tools based on their ordinals.
   142    */
   143   ordinalSort: function DT_ordinalSort(d1, d2) {
   144     let o1 = (typeof d1.ordinal == "number") ? d1.ordinal : MAX_ORDINAL;
   145     let o2 = (typeof d2.ordinal == "number") ? d2.ordinal : MAX_ORDINAL;
   146     return o1 - o2;
   147   },
   149   getDefaultTools: function DT_getDefaultTools() {
   150     return devtools.defaultTools.sort(this.ordinalSort);
   151   },
   153   getAdditionalTools: function DT_getAdditionalTools() {
   154     let tools = [];
   155     for (let [key, value] of this._tools) {
   156       if (devtools.defaultTools.indexOf(value) == -1) {
   157         tools.push(value);
   158       }
   159     }
   160     return tools.sort(this.ordinalSort);
   161   },
   163   /**
   164    * Get a tool definition if it exists and is enabled.
   165    *
   166    * @param {string} toolId
   167    *        The id of the tool to show
   168    *
   169    * @return {ToolDefinition|null} tool
   170    *         The ToolDefinition for the id or null.
   171    */
   172   getToolDefinition: function DT_getToolDefinition(toolId) {
   173     let tool = this._tools.get(toolId);
   174     if (!tool) {
   175       return null;
   176     } else if (!tool.visibilityswitch) {
   177       return tool;
   178     }
   180     let enabled;
   181     try {
   182       enabled = Services.prefs.getBoolPref(tool.visibilityswitch);
   183     } catch (e) {
   184       enabled = true;
   185     }
   187     return enabled ? tool : null;
   188   },
   190   /**
   191    * Allow ToolBoxes to get at the list of tools that they should populate
   192    * themselves with.
   193    *
   194    * @return {Map} tools
   195    *         A map of the the tool definitions registered in this instance
   196    */
   197   getToolDefinitionMap: function DT_getToolDefinitionMap() {
   198     let tools = new Map();
   200     for (let [id, definition] of this._tools) {
   201       if (this.getToolDefinition(id)) {
   202         tools.set(id, definition);
   203       }
   204     }
   206     return tools;
   207   },
   209   /**
   210    * Tools have an inherent ordering that can't be represented in a Map so
   211    * getToolDefinitionArray provides an alternative representation of the
   212    * definitions sorted by ordinal value.
   213    *
   214    * @return {Array} tools
   215    *         A sorted array of the tool definitions registered in this instance
   216    */
   217   getToolDefinitionArray: function DT_getToolDefinitionArray() {
   218     let definitions = [];
   220     for (let [id, definition] of this._tools) {
   221       if (this.getToolDefinition(id)) {
   222         definitions.push(definition);
   223       }
   224     }
   226     return definitions.sort(this.ordinalSort);
   227   },
   229   /**
   230    * Show a Toolbox for a target (either by creating a new one, or if a toolbox
   231    * already exists for the target, by bring to the front the existing one)
   232    * If |toolId| is specified then the displayed toolbox will have the
   233    * specified tool selected.
   234    * If |hostType| is specified then the toolbox will be displayed using the
   235    * specified HostType.
   236    *
   237    * @param {Target} target
   238    *         The target the toolbox will debug
   239    * @param {string} toolId
   240    *        The id of the tool to show
   241    * @param {Toolbox.HostType} hostType
   242    *        The type of host (bottom, window, side)
   243    * @param {object} hostOptions
   244    *        Options for host specifically
   245    *
   246    * @return {Toolbox} toolbox
   247    *        The toolbox that was opened
   248    */
   249   showToolbox: function(target, toolId, hostType, hostOptions) {
   250     let deferred = promise.defer();
   252     let toolbox = this._toolboxes.get(target);
   253     if (toolbox) {
   255       let hostPromise = (hostType != null && toolbox.hostType != hostType) ?
   256           toolbox.switchHost(hostType) :
   257           promise.resolve(null);
   259       if (toolId != null && toolbox.currentToolId != toolId) {
   260         hostPromise = hostPromise.then(function() {
   261           return toolbox.selectTool(toolId);
   262         });
   263       }
   265       return hostPromise.then(function() {
   266         toolbox.raise();
   267         return toolbox;
   268       });
   269     }
   270     else {
   271       // No toolbox for target, create one
   272       toolbox = new devtools.Toolbox(target, toolId, hostType, hostOptions);
   274       this._toolboxes.set(target, toolbox);
   276       toolbox.once("destroyed", function() {
   277         this._toolboxes.delete(target);
   278         this.emit("toolbox-destroyed", target);
   279       }.bind(this));
   281       // If we were asked for a specific tool then we need to wait for the
   282       // tool to be ready, otherwise we can just wait for toolbox open
   283       if (toolId != null) {
   284         toolbox.once(toolId + "-ready", function(event, panel) {
   285           this.emit("toolbox-ready", toolbox);
   286           deferred.resolve(toolbox);
   287         }.bind(this));
   288         toolbox.open();
   289       }
   290       else {
   291         toolbox.open().then(function() {
   292           deferred.resolve(toolbox);
   293           this.emit("toolbox-ready", toolbox);
   294         }.bind(this));
   295       }
   296     }
   298     return deferred.promise;
   299   },
   301   /**
   302    * Return the toolbox for a given target.
   303    *
   304    * @param  {object} target
   305    *         Target value e.g. the target that owns this toolbox
   306    *
   307    * @return {Toolbox} toolbox
   308    *         The toobox that is debugging the given target
   309    */
   310   getToolbox: function DT_getToolbox(target) {
   311     return this._toolboxes.get(target);
   312   },
   314   /**
   315    * Close the toolbox for a given target
   316    *
   317    * @return promise
   318    *         This promise will resolve to false if no toolbox was found
   319    *         associated to the target. true, if the toolbox was successfuly
   320    *         closed.
   321    */
   322   closeToolbox: function DT_closeToolbox(target) {
   323     let toolbox = this._toolboxes.get(target);
   324     if (toolbox == null) {
   325       return promise.resolve(false);
   326     }
   327     return toolbox.destroy().then(() => true);
   328   },
   330   /**
   331    * Called to tear down a tools provider.
   332    */
   333   _teardown: function DT_teardown() {
   334     for (let [target, toolbox] of this._toolboxes) {
   335       toolbox.destroy();
   336     }
   337   },
   339   /**
   340    * All browser windows have been closed, tidy up remaining objects.
   341    */
   342   destroy: function() {
   343     Services.obs.removeObserver(this.destroy, "quit-application");
   344     Services.obs.removeObserver(this._teardown, "devtools-unloaded");
   346     for (let [key, tool] of this.getToolDefinitionMap()) {
   347       this.unregisterTool(key, true);
   348     }
   350     // Cleaning down the toolboxes: i.e.
   351     //   for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
   352     // Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
   353   },
   355   /**
   356    * Iterator that yields each of the toolboxes.
   357    */
   358   '@@iterator': function*() {
   359     for (let toolbox of this._toolboxes) {
   360       yield toolbox;
   361     }
   362   }
   363 };
   365 /**
   366  * gDevTools is a singleton that controls the Firefox Developer Tools.
   367  *
   368  * It is an instance of a DevTools class that holds a set of tools. It has the
   369  * same lifetime as the browser.
   370  */
   371 let gDevTools = new DevTools();
   372 this.gDevTools = gDevTools;
   374 /**
   375  * gDevToolsBrowser exposes functions to connect the gDevTools instance with a
   376  * Firefox instance.
   377  */
   378 let gDevToolsBrowser = {
   379   /**
   380    * A record of the windows whose menus we altered, so we can undo the changes
   381    * as the window is closed
   382    */
   383   _trackedBrowserWindows: new Set(),
   385   /**
   386    * This function is for the benefit of Tools:DevToolbox in
   387    * browser/base/content/browser-sets.inc and should not be used outside
   388    * of there
   389    */
   390   toggleToolboxCommand: function(gBrowser) {
   391     let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
   392     let toolbox = gDevTools.getToolbox(target);
   394     toolbox ? toolbox.destroy() : gDevTools.showToolbox(target);
   395   },
   397   toggleBrowserToolboxCommand: function(gBrowser) {
   398     let target = devtools.TargetFactory.forWindow(gBrowser.ownerDocument.defaultView);
   399     let toolbox = gDevTools.getToolbox(target);
   401     toolbox ? toolbox.destroy()
   402      : gDevTools.showToolbox(target, "inspector", Toolbox.HostType.WINDOW);
   403   },
   405   /**
   406    * This function ensures the right commands are enabled in a window,
   407    * depending on their relevant prefs. It gets run when a window is registered,
   408    * or when any of the devtools prefs change.
   409    */
   410   updateCommandAvailability: function(win) {
   411     let doc = win.document;
   413     function toggleCmd(id, isEnabled) {
   414       let cmd = doc.getElementById(id);
   415       if (isEnabled) {
   416         cmd.removeAttribute("disabled");
   417         cmd.removeAttribute("hidden");
   418       } else {
   419         cmd.setAttribute("disabled", "true");
   420         cmd.setAttribute("hidden", "true");
   421       }
   422     };
   424     // Enable developer toolbar?
   425     let devToolbarEnabled = Services.prefs.getBoolPref("devtools.toolbar.enabled");
   426     toggleCmd("Tools:DevToolbar", devToolbarEnabled);
   427     let focusEl = doc.getElementById("Tools:DevToolbarFocus");
   428     if (devToolbarEnabled) {
   429       focusEl.removeAttribute("disabled");
   430     } else {
   431       focusEl.setAttribute("disabled", "true");
   432     }
   433     if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) {
   434       win.DeveloperToolbar.show(false);
   435     }
   437     // Enable App Manager?
   438     let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled");
   439     toggleCmd("Tools:DevAppMgr", appMgrEnabled);
   441     // Enable Browser Toolbox?
   442     let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
   443     let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
   444     let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled &&
   445                         Services.prefs.getBoolPref("devtools.debugger.chrome-enabled");
   446     toggleCmd("Tools:BrowserToolbox", remoteEnabled);
   448     // Enable Error Console?
   449     let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
   450     toggleCmd("Tools:ErrorConsole", consoleEnabled);
   452     // Enable DevTools connection screen, if the preference allows this.
   453     toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled);
   454   },
   456   observe: function(subject, topic, prefName) {
   457     if (prefName.endsWith("enabled")) {
   458       for (let win of this._trackedBrowserWindows) {
   459         this.updateCommandAvailability(win);
   460       }
   461     }
   462   },
   464   _prefObserverRegistered: false,
   466   ensurePrefObserver: function() {
   467     if (!this._prefObserverRegistered) {
   468       this._prefObserverRegistered = true;
   469       Services.prefs.addObserver("devtools.", this, false);
   470     }
   471   },
   474   /**
   475    * This function is for the benefit of Tools:{toolId} commands,
   476    * triggered from the WebDeveloper menu and keyboard shortcuts.
   477    *
   478    * selectToolCommand's behavior:
   479    * - if the toolbox is closed,
   480    *   we open the toolbox and select the tool
   481    * - if the toolbox is open, and the targetted tool is not selected,
   482    *   we select it
   483    * - if the toolbox is open, and the targetted tool is selected,
   484    *   and the host is NOT a window, we close the toolbox
   485    * - if the toolbox is open, and the targetted tool is selected,
   486    *   and the host is a window, we raise the toolbox window
   487    */
   488   selectToolCommand: function(gBrowser, toolId) {
   489     let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
   490     let toolbox = gDevTools.getToolbox(target);
   491     let toolDefinition = gDevTools.getToolDefinition(toolId);
   493     if (toolbox &&
   494         (toolbox.currentToolId == toolId ||
   495           (toolId == "webconsole" && toolbox.splitConsole)))
   496     {
   497       toolbox.fireCustomKey(toolId);
   499       if (toolDefinition.preventClosingOnKey || toolbox.hostType == devtools.Toolbox.HostType.WINDOW) {
   500         toolbox.raise();
   501       } else {
   502         toolbox.destroy();
   503       }
   504     } else {
   505       gDevTools.showToolbox(target, toolId).then(() => {
   506         let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
   507         let toolbox = gDevTools.getToolbox(target);
   509         toolbox.fireCustomKey(toolId);
   510       });
   511     }
   512   },
   514   /**
   515    * Open a tab to allow connects to a remote browser
   516    */
   517   openConnectScreen: function(gBrowser) {
   518     gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/connect.xhtml");
   519   },
   521   /**
   522    * Open the App Manager
   523    */
   524   openAppManager: function(gBrowser) {
   525     gBrowser.selectedTab = gBrowser.addTab("about:app-manager");
   526   },
   528   /**
   529    * Add this DevTools's presence to a browser window's document
   530    *
   531    * @param {XULDocument} doc
   532    *        The document to which menuitems and handlers are to be added
   533    */
   534   registerBrowserWindow: function DT_registerBrowserWindow(win) {
   535     this.updateCommandAvailability(win);
   536     this.ensurePrefObserver();
   537     gDevToolsBrowser._trackedBrowserWindows.add(win);
   538     gDevToolsBrowser._addAllToolsToMenu(win.document);
   540     if (this._isFirebugInstalled()) {
   541       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
   542       broadcaster.removeAttribute("key");
   543     }
   545     let tabContainer = win.document.getElementById("tabbrowser-tabs")
   546     tabContainer.addEventListener("TabSelect",
   547                                   gDevToolsBrowser._updateMenuCheckbox, false);
   548   },
   550   /**
   551    * Add a <key> to <keyset id="devtoolsKeyset">.
   552    * Appending a <key> element is not always enough. The <keyset> needs
   553    * to be detached and reattached to make sure the <key> is taken into
   554    * account (see bug 832984).
   555    *
   556    * @param {XULDocument} doc
   557    *        The document to which keys are to be added
   558    * @param {XULElement} or {DocumentFragment} keys
   559    *        Keys to add
   560    */
   561   attachKeybindingsToBrowser: function DT_attachKeybindingsToBrowser(doc, keys) {
   562     let devtoolsKeyset = doc.getElementById("devtoolsKeyset");
   564     if (!devtoolsKeyset) {
   565       devtoolsKeyset = doc.createElement("keyset");
   566       devtoolsKeyset.setAttribute("id", "devtoolsKeyset");
   567     }
   568     devtoolsKeyset.appendChild(keys);
   569     let mainKeyset = doc.getElementById("mainKeyset");
   570     mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
   571   },
   574   /**
   575    * Detect the presence of a Firebug.
   576    *
   577    * @return promise
   578    */
   579   _isFirebugInstalled: function DT_isFirebugInstalled() {
   580     let bootstrappedAddons = Services.prefs.getCharPref("extensions.bootstrappedAddons");
   581     return bootstrappedAddons.indexOf("firebug@software.joehewitt.com") != -1;
   582   },
   584   /**
   585    * Add the menuitem for a tool to all open browser windows.
   586    *
   587    * @param {object} toolDefinition
   588    *        properties of the tool to add
   589    */
   590   _addToolToWindows: function DT_addToolToWindows(toolDefinition) {
   591     // No menu item or global shortcut is required for options panel.
   592     if (!toolDefinition.inMenu) {
   593       return;
   594     }
   596     // Skip if the tool is disabled.
   597     try {
   598       if (toolDefinition.visibilityswitch &&
   599          !Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) {
   600         return;
   601       }
   602     } catch(e) {}
   604     // We need to insert the new tool in the right place, which means knowing
   605     // the tool that comes before the tool that we're trying to add
   606     let allDefs = gDevTools.getToolDefinitionArray();
   607     let prevDef;
   608     for (let def of allDefs) {
   609       if (!def.inMenu) {
   610         continue;
   611       }
   612       if (def === toolDefinition) {
   613         break;
   614       }
   615       prevDef = def;
   616     }
   618     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
   619       let doc = win.document;
   620       let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
   622       doc.getElementById("mainCommandSet").appendChild(elements.cmd);
   624       if (elements.key) {
   625         this.attachKeybindingsToBrowser(doc, elements.key);
   626       }
   628       doc.getElementById("mainBroadcasterSet").appendChild(elements.bc);
   630       let amp = doc.getElementById("appmenu_webDeveloper_popup");
   631       if (amp) {
   632         let ref;
   634         if (prevDef != null) {
   635           let menuitem = doc.getElementById("appmenuitem_" + prevDef.id);
   636           ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
   637         } else {
   638           ref = doc.getElementById("appmenu_devtools_separator");
   639         }
   641         if (ref) {
   642           amp.insertBefore(elements.appmenuitem, ref);
   643         }
   644       }
   646       let mp = doc.getElementById("menuWebDeveloperPopup");
   647       if (mp) {
   648         let ref;
   650         if (prevDef != null) {
   651           let menuitem = doc.getElementById("menuitem_" + prevDef.id);
   652           ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
   653         } else {
   654           ref = doc.getElementById("menu_devtools_separator");
   655         }
   657         if (ref) {
   658           mp.insertBefore(elements.menuitem, ref);
   659         }
   660       }
   661     }
   662   },
   664   /**
   665    * Add all tools to the developer tools menu of a window.
   666    *
   667    * @param {XULDocument} doc
   668    *        The document to which the tool items are to be added.
   669    */
   670   _addAllToolsToMenu: function DT_addAllToolsToMenu(doc) {
   671     let fragCommands = doc.createDocumentFragment();
   672     let fragKeys = doc.createDocumentFragment();
   673     let fragBroadcasters = doc.createDocumentFragment();
   674     let fragAppMenuItems = doc.createDocumentFragment();
   675     let fragMenuItems = doc.createDocumentFragment();
   677     for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
   678       if (!toolDefinition.inMenu) {
   679         continue;
   680       }
   682       let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
   684       if (!elements) {
   685         return;
   686       }
   688       fragCommands.appendChild(elements.cmd);
   689       if (elements.key) {
   690         fragKeys.appendChild(elements.key);
   691       }
   692       fragBroadcasters.appendChild(elements.bc);
   693       fragAppMenuItems.appendChild(elements.appmenuitem);
   694       fragMenuItems.appendChild(elements.menuitem);
   695     }
   697     let mcs = doc.getElementById("mainCommandSet");
   698     mcs.appendChild(fragCommands);
   700     this.attachKeybindingsToBrowser(doc, fragKeys);
   702     let mbs = doc.getElementById("mainBroadcasterSet");
   703     mbs.appendChild(fragBroadcasters);
   705     let amp = doc.getElementById("appmenu_webDeveloper_popup");
   706     if (amp) {
   707       let amps = doc.getElementById("appmenu_devtools_separator");
   708       amp.insertBefore(fragAppMenuItems, amps);
   709     }
   711     let mp = doc.getElementById("menuWebDeveloperPopup");
   712     let mps = doc.getElementById("menu_devtools_separator");
   713     mp.insertBefore(fragMenuItems, mps);
   714   },
   716   /**
   717    * Add a menu entry for a tool definition
   718    *
   719    * @param {string} toolDefinition
   720    *        Tool definition of the tool to add a menu entry.
   721    * @param {XULDocument} doc
   722    *        The document to which the tool menu item is to be added.
   723    */
   724   _createToolMenuElements: function DT_createToolMenuElements(toolDefinition, doc) {
   725     let id = toolDefinition.id;
   727     // Prevent multiple entries for the same tool.
   728     if (doc.getElementById("Tools:" + id)) {
   729       return;
   730     }
   732     let cmd = doc.createElement("command");
   733     cmd.id = "Tools:" + id;
   734     cmd.setAttribute("oncommand",
   735         'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");');
   737     let key = null;
   738     if (toolDefinition.key) {
   739       key = doc.createElement("key");
   740       key.id = "key_" + id;
   742       if (toolDefinition.key.startsWith("VK_")) {
   743         key.setAttribute("keycode", toolDefinition.key);
   744       } else {
   745         key.setAttribute("key", toolDefinition.key);
   746       }
   748       key.setAttribute("command", cmd.id);
   749       key.setAttribute("modifiers", toolDefinition.modifiers);
   750     }
   752     let bc = doc.createElement("broadcaster");
   753     bc.id = "devtoolsMenuBroadcaster_" + id;
   754     bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label);
   755     bc.setAttribute("command", cmd.id);
   757     if (key) {
   758       bc.setAttribute("key", "key_" + id);
   759     }
   761     let appmenuitem = doc.createElement("menuitem");
   762     appmenuitem.id = "appmenuitem_" + id;
   763     appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
   765     let menuitem = doc.createElement("menuitem");
   766     menuitem.id = "menuitem_" + id;
   767     menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
   769     if (toolDefinition.accesskey) {
   770       menuitem.setAttribute("accesskey", toolDefinition.accesskey);
   771     }
   773     return {
   774       cmd: cmd,
   775       key: key,
   776       bc: bc,
   777       appmenuitem: appmenuitem,
   778       menuitem: menuitem
   779     };
   780   },
   782   /**
   783    * Update the "Toggle Tools" checkbox in the developer tools menu. This is
   784    * called when a toolbox is created or destroyed.
   785    */
   786   _updateMenuCheckbox: function DT_updateMenuCheckbox() {
   787     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
   789       let hasToolbox = false;
   790       if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
   791         let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab);
   792         if (gDevTools._toolboxes.has(target)) {
   793           hasToolbox = true;
   794         }
   795       }
   797       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
   798       if (hasToolbox) {
   799         broadcaster.setAttribute("checked", "true");
   800       } else {
   801         broadcaster.removeAttribute("checked");
   802       }
   803     }
   804   },
   806   /**
   807    * Connects to the SPS profiler when the developer tools are open.
   808    */
   809   _connectToProfiler: function DT_connectToProfiler() {
   810     let ProfilerController = devtools.require("devtools/profiler/controller");
   812     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
   813       if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
   814         let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab);
   815         if (gDevTools._toolboxes.has(target)) {
   816           target.makeRemote().then(() => {
   817             let profiler = new ProfilerController(target);
   818             profiler.connect();
   819           }).then(null, Cu.reportError);
   821           return;
   822         }
   823       }
   824     }
   825   },
   827   /**
   828    * Remove the menuitem for a tool to all open browser windows.
   829    *
   830    * @param {string} toolId
   831    *        id of the tool to remove
   832    */
   833   _removeToolFromWindows: function DT_removeToolFromWindows(toolId) {
   834     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
   835       gDevToolsBrowser._removeToolFromMenu(toolId, win.document);
   836     }
   837   },
   839   /**
   840    * Remove a tool's menuitem from a window
   841    *
   842    * @param {string} toolId
   843    *        Id of the tool to add a menu entry for
   844    * @param {XULDocument} doc
   845    *        The document to which the tool menu item is to be removed from
   846    */
   847   _removeToolFromMenu: function DT_removeToolFromMenu(toolId, doc) {
   848     let command = doc.getElementById("Tools:" + toolId);
   849     if (command) {
   850       command.parentNode.removeChild(command);
   851     }
   853     let key = doc.getElementById("key_" + toolId);
   854     if (key) {
   855       key.parentNode.removeChild(key);
   856     }
   858     let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId);
   859     if (bc) {
   860       bc.parentNode.removeChild(bc);
   861     }
   863     let appmenuitem = doc.getElementById("appmenuitem_" + toolId);
   864     if (appmenuitem) {
   865       appmenuitem.parentNode.removeChild(appmenuitem);
   866     }
   868     let menuitem = doc.getElementById("menuitem_" + toolId);
   869     if (menuitem) {
   870       menuitem.parentNode.removeChild(menuitem);
   871     }
   872   },
   874   /**
   875    * Called on browser unload to remove menu entries, toolboxes and event
   876    * listeners from the closed browser window.
   877    *
   878    * @param  {XULWindow} win
   879    *         The window containing the menu entry
   880    */
   881   forgetBrowserWindow: function DT_forgetBrowserWindow(win) {
   882     gDevToolsBrowser._trackedBrowserWindows.delete(win);
   884     // Destroy toolboxes for closed window
   885     for (let [target, toolbox] of gDevTools._toolboxes) {
   886       if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) {
   887         toolbox.destroy();
   888       }
   889     }
   891     let tabContainer = win.document.getElementById("tabbrowser-tabs")
   892     tabContainer.removeEventListener("TabSelect",
   893                                      gDevToolsBrowser._updateMenuCheckbox, false);
   894   },
   896   /**
   897    * All browser windows have been closed, tidy up remaining objects.
   898    */
   899   destroy: function() {
   900     gDevTools.off("toolbox-ready", gDevToolsBrowser._connectToProfiler);
   901     Services.prefs.removeObserver("devtools.", gDevToolsBrowser);
   902     Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
   903   },
   904 }
   906 this.gDevToolsBrowser = gDevToolsBrowser;
   908 gDevTools.on("tool-registered", function(ev, toolId) {
   909   let toolDefinition = gDevTools._tools.get(toolId);
   910   gDevToolsBrowser._addToolToWindows(toolDefinition);
   911 });
   913 gDevTools.on("tool-unregistered", function(ev, toolId) {
   914   if (typeof toolId != "string") {
   915     toolId = toolId.id;
   916   }
   917   gDevToolsBrowser._removeToolFromWindows(toolId);
   918 });
   920 gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
   921 gDevTools.on("toolbox-ready", gDevToolsBrowser._connectToProfiler);
   922 gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
   924 Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
   926 // Load the browser devtools main module as the loader's main module.
   927 devtools.main("main");

mercurial