1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/framework/gDevTools.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,927 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "gDevToolsBrowser" ]; 1.11 + 1.12 +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; 1.13 + 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +Cu.import("resource://gre/modules/Services.jsm"); 1.16 +Cu.import("resource://gre/modules/devtools/event-emitter.js"); 1.17 +Cu.import("resource://gre/modules/devtools/Loader.jsm"); 1.18 +XPCOMUtils.defineLazyModuleGetter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise"); 1.19 + 1.20 +const FORBIDDEN_IDS = new Set(["toolbox", ""]); 1.21 +const MAX_ORDINAL = 99; 1.22 + 1.23 + 1.24 +/** 1.25 + * DevTools is a class that represents a set of developer tools, it holds a 1.26 + * set of tools and keeps track of open toolboxes in the browser. 1.27 + */ 1.28 +this.DevTools = function DevTools() { 1.29 + this._tools = new Map(); // Map<toolId, tool> 1.30 + this._toolboxes = new Map(); // Map<target, toolbox> 1.31 + 1.32 + // destroy() is an observer's handler so we need to preserve context. 1.33 + this.destroy = this.destroy.bind(this); 1.34 + this._teardown = this._teardown.bind(this); 1.35 + 1.36 + this._testing = false; 1.37 + 1.38 + EventEmitter.decorate(this); 1.39 + 1.40 + Services.obs.addObserver(this._teardown, "devtools-unloaded", false); 1.41 + Services.obs.addObserver(this.destroy, "quit-application", false); 1.42 +} 1.43 + 1.44 +DevTools.prototype = { 1.45 + /** 1.46 + * When the testing flag is set we take appropriate action to prevent race 1.47 + * conditions in our testing environment. This means setting 1.48 + * dom.send_after_paint_to_content to false to prevent infinite MozAfterPaint 1.49 + * loops and not autohiding the highlighter. 1.50 + */ 1.51 + get testing() { 1.52 + return this._testing; 1.53 + }, 1.54 + 1.55 + set testing(state) { 1.56 + this._testing = state; 1.57 + 1.58 + if (state) { 1.59 + // dom.send_after_paint_to_content is set to true (non-default) in 1.60 + // testing/profiles/prefs_general.js so lets set it to the same as it is 1.61 + // in a default browser profile for the duration of the test. 1.62 + Services.prefs.setBoolPref("dom.send_after_paint_to_content", false); 1.63 + } else { 1.64 + Services.prefs.setBoolPref("dom.send_after_paint_to_content", true); 1.65 + } 1.66 + }, 1.67 + 1.68 + /** 1.69 + * Register a new developer tool. 1.70 + * 1.71 + * A definition is a light object that holds different information about a 1.72 + * developer tool. This object is not supposed to have any operational code. 1.73 + * See it as a "manifest". 1.74 + * The only actual code lives in the build() function, which will be used to 1.75 + * start an instance of this tool. 1.76 + * 1.77 + * Each toolDefinition has the following properties: 1.78 + * - id: Unique identifier for this tool (string|required) 1.79 + * - visibilityswitch: Property name to allow us to hide this tool from the 1.80 + * DevTools Toolbox. 1.81 + * A falsy value indicates that it cannot be hidden. 1.82 + * - icon: URL pointing to a graphic which will be used as the src for an 1.83 + * 16x16 img tag (string|required) 1.84 + * - invertIconForLightTheme: The icon can automatically have an inversion 1.85 + * filter applied (default is false). All builtin tools are true, but 1.86 + * addons may omit this to prevent unwanted changes to the `icon` 1.87 + * image. See browser/themes/shared/devtools/filters.svg#invert for 1.88 + * the filter being applied to the images (boolean|optional) 1.89 + * - url: URL pointing to a XUL/XHTML document containing the user interface 1.90 + * (string|required) 1.91 + * - label: Localized name for the tool to be displayed to the user 1.92 + * (string|required) 1.93 + * - build: Function that takes an iframe, which has been populated with the 1.94 + * markup from |url|, and also the toolbox containing the panel. 1.95 + * And returns an instance of ToolPanel (function|required) 1.96 + */ 1.97 + registerTool: function DT_registerTool(toolDefinition) { 1.98 + let toolId = toolDefinition.id; 1.99 + 1.100 + if (!toolId || FORBIDDEN_IDS.has(toolId)) { 1.101 + throw new Error("Invalid definition.id"); 1.102 + } 1.103 + 1.104 + // Make sure that additional tools will always be able to be hidden. 1.105 + // When being called from main.js, defaultTools has not yet been exported. 1.106 + // But, we can assume that in this case, it is a default tool. 1.107 + if (devtools.defaultTools && devtools.defaultTools.indexOf(toolDefinition) == -1) { 1.108 + toolDefinition.visibilityswitch = "devtools." + toolId + ".enabled"; 1.109 + } 1.110 + 1.111 + this._tools.set(toolId, toolDefinition); 1.112 + 1.113 + this.emit("tool-registered", toolId); 1.114 + }, 1.115 + 1.116 + /** 1.117 + * Removes all tools that match the given |toolId| 1.118 + * Needed so that add-ons can remove themselves when they are deactivated 1.119 + * 1.120 + * @param {string|object} tool 1.121 + * Definition or the id of the tool to unregister. Passing the 1.122 + * tool id should be avoided as it is a temporary measure. 1.123 + * @param {boolean} isQuitApplication 1.124 + * true to indicate that the call is due to app quit, so we should not 1.125 + * cause a cascade of costly events 1.126 + */ 1.127 + unregisterTool: function DT_unregisterTool(tool, isQuitApplication) { 1.128 + let toolId = null; 1.129 + if (typeof tool == "string") { 1.130 + toolId = tool; 1.131 + tool = this._tools.get(tool); 1.132 + } 1.133 + else { 1.134 + toolId = tool.id; 1.135 + } 1.136 + this._tools.delete(toolId); 1.137 + 1.138 + if (!isQuitApplication) { 1.139 + this.emit("tool-unregistered", tool); 1.140 + } 1.141 + }, 1.142 + 1.143 + /** 1.144 + * Sorting function used for sorting tools based on their ordinals. 1.145 + */ 1.146 + ordinalSort: function DT_ordinalSort(d1, d2) { 1.147 + let o1 = (typeof d1.ordinal == "number") ? d1.ordinal : MAX_ORDINAL; 1.148 + let o2 = (typeof d2.ordinal == "number") ? d2.ordinal : MAX_ORDINAL; 1.149 + return o1 - o2; 1.150 + }, 1.151 + 1.152 + getDefaultTools: function DT_getDefaultTools() { 1.153 + return devtools.defaultTools.sort(this.ordinalSort); 1.154 + }, 1.155 + 1.156 + getAdditionalTools: function DT_getAdditionalTools() { 1.157 + let tools = []; 1.158 + for (let [key, value] of this._tools) { 1.159 + if (devtools.defaultTools.indexOf(value) == -1) { 1.160 + tools.push(value); 1.161 + } 1.162 + } 1.163 + return tools.sort(this.ordinalSort); 1.164 + }, 1.165 + 1.166 + /** 1.167 + * Get a tool definition if it exists and is enabled. 1.168 + * 1.169 + * @param {string} toolId 1.170 + * The id of the tool to show 1.171 + * 1.172 + * @return {ToolDefinition|null} tool 1.173 + * The ToolDefinition for the id or null. 1.174 + */ 1.175 + getToolDefinition: function DT_getToolDefinition(toolId) { 1.176 + let tool = this._tools.get(toolId); 1.177 + if (!tool) { 1.178 + return null; 1.179 + } else if (!tool.visibilityswitch) { 1.180 + return tool; 1.181 + } 1.182 + 1.183 + let enabled; 1.184 + try { 1.185 + enabled = Services.prefs.getBoolPref(tool.visibilityswitch); 1.186 + } catch (e) { 1.187 + enabled = true; 1.188 + } 1.189 + 1.190 + return enabled ? tool : null; 1.191 + }, 1.192 + 1.193 + /** 1.194 + * Allow ToolBoxes to get at the list of tools that they should populate 1.195 + * themselves with. 1.196 + * 1.197 + * @return {Map} tools 1.198 + * A map of the the tool definitions registered in this instance 1.199 + */ 1.200 + getToolDefinitionMap: function DT_getToolDefinitionMap() { 1.201 + let tools = new Map(); 1.202 + 1.203 + for (let [id, definition] of this._tools) { 1.204 + if (this.getToolDefinition(id)) { 1.205 + tools.set(id, definition); 1.206 + } 1.207 + } 1.208 + 1.209 + return tools; 1.210 + }, 1.211 + 1.212 + /** 1.213 + * Tools have an inherent ordering that can't be represented in a Map so 1.214 + * getToolDefinitionArray provides an alternative representation of the 1.215 + * definitions sorted by ordinal value. 1.216 + * 1.217 + * @return {Array} tools 1.218 + * A sorted array of the tool definitions registered in this instance 1.219 + */ 1.220 + getToolDefinitionArray: function DT_getToolDefinitionArray() { 1.221 + let definitions = []; 1.222 + 1.223 + for (let [id, definition] of this._tools) { 1.224 + if (this.getToolDefinition(id)) { 1.225 + definitions.push(definition); 1.226 + } 1.227 + } 1.228 + 1.229 + return definitions.sort(this.ordinalSort); 1.230 + }, 1.231 + 1.232 + /** 1.233 + * Show a Toolbox for a target (either by creating a new one, or if a toolbox 1.234 + * already exists for the target, by bring to the front the existing one) 1.235 + * If |toolId| is specified then the displayed toolbox will have the 1.236 + * specified tool selected. 1.237 + * If |hostType| is specified then the toolbox will be displayed using the 1.238 + * specified HostType. 1.239 + * 1.240 + * @param {Target} target 1.241 + * The target the toolbox will debug 1.242 + * @param {string} toolId 1.243 + * The id of the tool to show 1.244 + * @param {Toolbox.HostType} hostType 1.245 + * The type of host (bottom, window, side) 1.246 + * @param {object} hostOptions 1.247 + * Options for host specifically 1.248 + * 1.249 + * @return {Toolbox} toolbox 1.250 + * The toolbox that was opened 1.251 + */ 1.252 + showToolbox: function(target, toolId, hostType, hostOptions) { 1.253 + let deferred = promise.defer(); 1.254 + 1.255 + let toolbox = this._toolboxes.get(target); 1.256 + if (toolbox) { 1.257 + 1.258 + let hostPromise = (hostType != null && toolbox.hostType != hostType) ? 1.259 + toolbox.switchHost(hostType) : 1.260 + promise.resolve(null); 1.261 + 1.262 + if (toolId != null && toolbox.currentToolId != toolId) { 1.263 + hostPromise = hostPromise.then(function() { 1.264 + return toolbox.selectTool(toolId); 1.265 + }); 1.266 + } 1.267 + 1.268 + return hostPromise.then(function() { 1.269 + toolbox.raise(); 1.270 + return toolbox; 1.271 + }); 1.272 + } 1.273 + else { 1.274 + // No toolbox for target, create one 1.275 + toolbox = new devtools.Toolbox(target, toolId, hostType, hostOptions); 1.276 + 1.277 + this._toolboxes.set(target, toolbox); 1.278 + 1.279 + toolbox.once("destroyed", function() { 1.280 + this._toolboxes.delete(target); 1.281 + this.emit("toolbox-destroyed", target); 1.282 + }.bind(this)); 1.283 + 1.284 + // If we were asked for a specific tool then we need to wait for the 1.285 + // tool to be ready, otherwise we can just wait for toolbox open 1.286 + if (toolId != null) { 1.287 + toolbox.once(toolId + "-ready", function(event, panel) { 1.288 + this.emit("toolbox-ready", toolbox); 1.289 + deferred.resolve(toolbox); 1.290 + }.bind(this)); 1.291 + toolbox.open(); 1.292 + } 1.293 + else { 1.294 + toolbox.open().then(function() { 1.295 + deferred.resolve(toolbox); 1.296 + this.emit("toolbox-ready", toolbox); 1.297 + }.bind(this)); 1.298 + } 1.299 + } 1.300 + 1.301 + return deferred.promise; 1.302 + }, 1.303 + 1.304 + /** 1.305 + * Return the toolbox for a given target. 1.306 + * 1.307 + * @param {object} target 1.308 + * Target value e.g. the target that owns this toolbox 1.309 + * 1.310 + * @return {Toolbox} toolbox 1.311 + * The toobox that is debugging the given target 1.312 + */ 1.313 + getToolbox: function DT_getToolbox(target) { 1.314 + return this._toolboxes.get(target); 1.315 + }, 1.316 + 1.317 + /** 1.318 + * Close the toolbox for a given target 1.319 + * 1.320 + * @return promise 1.321 + * This promise will resolve to false if no toolbox was found 1.322 + * associated to the target. true, if the toolbox was successfuly 1.323 + * closed. 1.324 + */ 1.325 + closeToolbox: function DT_closeToolbox(target) { 1.326 + let toolbox = this._toolboxes.get(target); 1.327 + if (toolbox == null) { 1.328 + return promise.resolve(false); 1.329 + } 1.330 + return toolbox.destroy().then(() => true); 1.331 + }, 1.332 + 1.333 + /** 1.334 + * Called to tear down a tools provider. 1.335 + */ 1.336 + _teardown: function DT_teardown() { 1.337 + for (let [target, toolbox] of this._toolboxes) { 1.338 + toolbox.destroy(); 1.339 + } 1.340 + }, 1.341 + 1.342 + /** 1.343 + * All browser windows have been closed, tidy up remaining objects. 1.344 + */ 1.345 + destroy: function() { 1.346 + Services.obs.removeObserver(this.destroy, "quit-application"); 1.347 + Services.obs.removeObserver(this._teardown, "devtools-unloaded"); 1.348 + 1.349 + for (let [key, tool] of this.getToolDefinitionMap()) { 1.350 + this.unregisterTool(key, true); 1.351 + } 1.352 + 1.353 + // Cleaning down the toolboxes: i.e. 1.354 + // for (let [target, toolbox] of this._toolboxes) toolbox.destroy(); 1.355 + // Is taken care of by the gDevToolsBrowser.forgetBrowserWindow 1.356 + }, 1.357 + 1.358 + /** 1.359 + * Iterator that yields each of the toolboxes. 1.360 + */ 1.361 + '@@iterator': function*() { 1.362 + for (let toolbox of this._toolboxes) { 1.363 + yield toolbox; 1.364 + } 1.365 + } 1.366 +}; 1.367 + 1.368 +/** 1.369 + * gDevTools is a singleton that controls the Firefox Developer Tools. 1.370 + * 1.371 + * It is an instance of a DevTools class that holds a set of tools. It has the 1.372 + * same lifetime as the browser. 1.373 + */ 1.374 +let gDevTools = new DevTools(); 1.375 +this.gDevTools = gDevTools; 1.376 + 1.377 +/** 1.378 + * gDevToolsBrowser exposes functions to connect the gDevTools instance with a 1.379 + * Firefox instance. 1.380 + */ 1.381 +let gDevToolsBrowser = { 1.382 + /** 1.383 + * A record of the windows whose menus we altered, so we can undo the changes 1.384 + * as the window is closed 1.385 + */ 1.386 + _trackedBrowserWindows: new Set(), 1.387 + 1.388 + /** 1.389 + * This function is for the benefit of Tools:DevToolbox in 1.390 + * browser/base/content/browser-sets.inc and should not be used outside 1.391 + * of there 1.392 + */ 1.393 + toggleToolboxCommand: function(gBrowser) { 1.394 + let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); 1.395 + let toolbox = gDevTools.getToolbox(target); 1.396 + 1.397 + toolbox ? toolbox.destroy() : gDevTools.showToolbox(target); 1.398 + }, 1.399 + 1.400 + toggleBrowserToolboxCommand: function(gBrowser) { 1.401 + let target = devtools.TargetFactory.forWindow(gBrowser.ownerDocument.defaultView); 1.402 + let toolbox = gDevTools.getToolbox(target); 1.403 + 1.404 + toolbox ? toolbox.destroy() 1.405 + : gDevTools.showToolbox(target, "inspector", Toolbox.HostType.WINDOW); 1.406 + }, 1.407 + 1.408 + /** 1.409 + * This function ensures the right commands are enabled in a window, 1.410 + * depending on their relevant prefs. It gets run when a window is registered, 1.411 + * or when any of the devtools prefs change. 1.412 + */ 1.413 + updateCommandAvailability: function(win) { 1.414 + let doc = win.document; 1.415 + 1.416 + function toggleCmd(id, isEnabled) { 1.417 + let cmd = doc.getElementById(id); 1.418 + if (isEnabled) { 1.419 + cmd.removeAttribute("disabled"); 1.420 + cmd.removeAttribute("hidden"); 1.421 + } else { 1.422 + cmd.setAttribute("disabled", "true"); 1.423 + cmd.setAttribute("hidden", "true"); 1.424 + } 1.425 + }; 1.426 + 1.427 + // Enable developer toolbar? 1.428 + let devToolbarEnabled = Services.prefs.getBoolPref("devtools.toolbar.enabled"); 1.429 + toggleCmd("Tools:DevToolbar", devToolbarEnabled); 1.430 + let focusEl = doc.getElementById("Tools:DevToolbarFocus"); 1.431 + if (devToolbarEnabled) { 1.432 + focusEl.removeAttribute("disabled"); 1.433 + } else { 1.434 + focusEl.setAttribute("disabled", "true"); 1.435 + } 1.436 + if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) { 1.437 + win.DeveloperToolbar.show(false); 1.438 + } 1.439 + 1.440 + // Enable App Manager? 1.441 + let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled"); 1.442 + toggleCmd("Tools:DevAppMgr", appMgrEnabled); 1.443 + 1.444 + // Enable Browser Toolbox? 1.445 + let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled"); 1.446 + let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled"); 1.447 + let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled && 1.448 + Services.prefs.getBoolPref("devtools.debugger.chrome-enabled"); 1.449 + toggleCmd("Tools:BrowserToolbox", remoteEnabled); 1.450 + 1.451 + // Enable Error Console? 1.452 + let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled"); 1.453 + toggleCmd("Tools:ErrorConsole", consoleEnabled); 1.454 + 1.455 + // Enable DevTools connection screen, if the preference allows this. 1.456 + toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled); 1.457 + }, 1.458 + 1.459 + observe: function(subject, topic, prefName) { 1.460 + if (prefName.endsWith("enabled")) { 1.461 + for (let win of this._trackedBrowserWindows) { 1.462 + this.updateCommandAvailability(win); 1.463 + } 1.464 + } 1.465 + }, 1.466 + 1.467 + _prefObserverRegistered: false, 1.468 + 1.469 + ensurePrefObserver: function() { 1.470 + if (!this._prefObserverRegistered) { 1.471 + this._prefObserverRegistered = true; 1.472 + Services.prefs.addObserver("devtools.", this, false); 1.473 + } 1.474 + }, 1.475 + 1.476 + 1.477 + /** 1.478 + * This function is for the benefit of Tools:{toolId} commands, 1.479 + * triggered from the WebDeveloper menu and keyboard shortcuts. 1.480 + * 1.481 + * selectToolCommand's behavior: 1.482 + * - if the toolbox is closed, 1.483 + * we open the toolbox and select the tool 1.484 + * - if the toolbox is open, and the targetted tool is not selected, 1.485 + * we select it 1.486 + * - if the toolbox is open, and the targetted tool is selected, 1.487 + * and the host is NOT a window, we close the toolbox 1.488 + * - if the toolbox is open, and the targetted tool is selected, 1.489 + * and the host is a window, we raise the toolbox window 1.490 + */ 1.491 + selectToolCommand: function(gBrowser, toolId) { 1.492 + let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); 1.493 + let toolbox = gDevTools.getToolbox(target); 1.494 + let toolDefinition = gDevTools.getToolDefinition(toolId); 1.495 + 1.496 + if (toolbox && 1.497 + (toolbox.currentToolId == toolId || 1.498 + (toolId == "webconsole" && toolbox.splitConsole))) 1.499 + { 1.500 + toolbox.fireCustomKey(toolId); 1.501 + 1.502 + if (toolDefinition.preventClosingOnKey || toolbox.hostType == devtools.Toolbox.HostType.WINDOW) { 1.503 + toolbox.raise(); 1.504 + } else { 1.505 + toolbox.destroy(); 1.506 + } 1.507 + } else { 1.508 + gDevTools.showToolbox(target, toolId).then(() => { 1.509 + let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); 1.510 + let toolbox = gDevTools.getToolbox(target); 1.511 + 1.512 + toolbox.fireCustomKey(toolId); 1.513 + }); 1.514 + } 1.515 + }, 1.516 + 1.517 + /** 1.518 + * Open a tab to allow connects to a remote browser 1.519 + */ 1.520 + openConnectScreen: function(gBrowser) { 1.521 + gBrowser.selectedTab = gBrowser.addTab("chrome://browser/content/devtools/connect.xhtml"); 1.522 + }, 1.523 + 1.524 + /** 1.525 + * Open the App Manager 1.526 + */ 1.527 + openAppManager: function(gBrowser) { 1.528 + gBrowser.selectedTab = gBrowser.addTab("about:app-manager"); 1.529 + }, 1.530 + 1.531 + /** 1.532 + * Add this DevTools's presence to a browser window's document 1.533 + * 1.534 + * @param {XULDocument} doc 1.535 + * The document to which menuitems and handlers are to be added 1.536 + */ 1.537 + registerBrowserWindow: function DT_registerBrowserWindow(win) { 1.538 + this.updateCommandAvailability(win); 1.539 + this.ensurePrefObserver(); 1.540 + gDevToolsBrowser._trackedBrowserWindows.add(win); 1.541 + gDevToolsBrowser._addAllToolsToMenu(win.document); 1.542 + 1.543 + if (this._isFirebugInstalled()) { 1.544 + let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox"); 1.545 + broadcaster.removeAttribute("key"); 1.546 + } 1.547 + 1.548 + let tabContainer = win.document.getElementById("tabbrowser-tabs") 1.549 + tabContainer.addEventListener("TabSelect", 1.550 + gDevToolsBrowser._updateMenuCheckbox, false); 1.551 + }, 1.552 + 1.553 + /** 1.554 + * Add a <key> to <keyset id="devtoolsKeyset">. 1.555 + * Appending a <key> element is not always enough. The <keyset> needs 1.556 + * to be detached and reattached to make sure the <key> is taken into 1.557 + * account (see bug 832984). 1.558 + * 1.559 + * @param {XULDocument} doc 1.560 + * The document to which keys are to be added 1.561 + * @param {XULElement} or {DocumentFragment} keys 1.562 + * Keys to add 1.563 + */ 1.564 + attachKeybindingsToBrowser: function DT_attachKeybindingsToBrowser(doc, keys) { 1.565 + let devtoolsKeyset = doc.getElementById("devtoolsKeyset"); 1.566 + 1.567 + if (!devtoolsKeyset) { 1.568 + devtoolsKeyset = doc.createElement("keyset"); 1.569 + devtoolsKeyset.setAttribute("id", "devtoolsKeyset"); 1.570 + } 1.571 + devtoolsKeyset.appendChild(keys); 1.572 + let mainKeyset = doc.getElementById("mainKeyset"); 1.573 + mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset); 1.574 + }, 1.575 + 1.576 + 1.577 + /** 1.578 + * Detect the presence of a Firebug. 1.579 + * 1.580 + * @return promise 1.581 + */ 1.582 + _isFirebugInstalled: function DT_isFirebugInstalled() { 1.583 + let bootstrappedAddons = Services.prefs.getCharPref("extensions.bootstrappedAddons"); 1.584 + return bootstrappedAddons.indexOf("firebug@software.joehewitt.com") != -1; 1.585 + }, 1.586 + 1.587 + /** 1.588 + * Add the menuitem for a tool to all open browser windows. 1.589 + * 1.590 + * @param {object} toolDefinition 1.591 + * properties of the tool to add 1.592 + */ 1.593 + _addToolToWindows: function DT_addToolToWindows(toolDefinition) { 1.594 + // No menu item or global shortcut is required for options panel. 1.595 + if (!toolDefinition.inMenu) { 1.596 + return; 1.597 + } 1.598 + 1.599 + // Skip if the tool is disabled. 1.600 + try { 1.601 + if (toolDefinition.visibilityswitch && 1.602 + !Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) { 1.603 + return; 1.604 + } 1.605 + } catch(e) {} 1.606 + 1.607 + // We need to insert the new tool in the right place, which means knowing 1.608 + // the tool that comes before the tool that we're trying to add 1.609 + let allDefs = gDevTools.getToolDefinitionArray(); 1.610 + let prevDef; 1.611 + for (let def of allDefs) { 1.612 + if (!def.inMenu) { 1.613 + continue; 1.614 + } 1.615 + if (def === toolDefinition) { 1.616 + break; 1.617 + } 1.618 + prevDef = def; 1.619 + } 1.620 + 1.621 + for (let win of gDevToolsBrowser._trackedBrowserWindows) { 1.622 + let doc = win.document; 1.623 + let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc); 1.624 + 1.625 + doc.getElementById("mainCommandSet").appendChild(elements.cmd); 1.626 + 1.627 + if (elements.key) { 1.628 + this.attachKeybindingsToBrowser(doc, elements.key); 1.629 + } 1.630 + 1.631 + doc.getElementById("mainBroadcasterSet").appendChild(elements.bc); 1.632 + 1.633 + let amp = doc.getElementById("appmenu_webDeveloper_popup"); 1.634 + if (amp) { 1.635 + let ref; 1.636 + 1.637 + if (prevDef != null) { 1.638 + let menuitem = doc.getElementById("appmenuitem_" + prevDef.id); 1.639 + ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null; 1.640 + } else { 1.641 + ref = doc.getElementById("appmenu_devtools_separator"); 1.642 + } 1.643 + 1.644 + if (ref) { 1.645 + amp.insertBefore(elements.appmenuitem, ref); 1.646 + } 1.647 + } 1.648 + 1.649 + let mp = doc.getElementById("menuWebDeveloperPopup"); 1.650 + if (mp) { 1.651 + let ref; 1.652 + 1.653 + if (prevDef != null) { 1.654 + let menuitem = doc.getElementById("menuitem_" + prevDef.id); 1.655 + ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null; 1.656 + } else { 1.657 + ref = doc.getElementById("menu_devtools_separator"); 1.658 + } 1.659 + 1.660 + if (ref) { 1.661 + mp.insertBefore(elements.menuitem, ref); 1.662 + } 1.663 + } 1.664 + } 1.665 + }, 1.666 + 1.667 + /** 1.668 + * Add all tools to the developer tools menu of a window. 1.669 + * 1.670 + * @param {XULDocument} doc 1.671 + * The document to which the tool items are to be added. 1.672 + */ 1.673 + _addAllToolsToMenu: function DT_addAllToolsToMenu(doc) { 1.674 + let fragCommands = doc.createDocumentFragment(); 1.675 + let fragKeys = doc.createDocumentFragment(); 1.676 + let fragBroadcasters = doc.createDocumentFragment(); 1.677 + let fragAppMenuItems = doc.createDocumentFragment(); 1.678 + let fragMenuItems = doc.createDocumentFragment(); 1.679 + 1.680 + for (let toolDefinition of gDevTools.getToolDefinitionArray()) { 1.681 + if (!toolDefinition.inMenu) { 1.682 + continue; 1.683 + } 1.684 + 1.685 + let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc); 1.686 + 1.687 + if (!elements) { 1.688 + return; 1.689 + } 1.690 + 1.691 + fragCommands.appendChild(elements.cmd); 1.692 + if (elements.key) { 1.693 + fragKeys.appendChild(elements.key); 1.694 + } 1.695 + fragBroadcasters.appendChild(elements.bc); 1.696 + fragAppMenuItems.appendChild(elements.appmenuitem); 1.697 + fragMenuItems.appendChild(elements.menuitem); 1.698 + } 1.699 + 1.700 + let mcs = doc.getElementById("mainCommandSet"); 1.701 + mcs.appendChild(fragCommands); 1.702 + 1.703 + this.attachKeybindingsToBrowser(doc, fragKeys); 1.704 + 1.705 + let mbs = doc.getElementById("mainBroadcasterSet"); 1.706 + mbs.appendChild(fragBroadcasters); 1.707 + 1.708 + let amp = doc.getElementById("appmenu_webDeveloper_popup"); 1.709 + if (amp) { 1.710 + let amps = doc.getElementById("appmenu_devtools_separator"); 1.711 + amp.insertBefore(fragAppMenuItems, amps); 1.712 + } 1.713 + 1.714 + let mp = doc.getElementById("menuWebDeveloperPopup"); 1.715 + let mps = doc.getElementById("menu_devtools_separator"); 1.716 + mp.insertBefore(fragMenuItems, mps); 1.717 + }, 1.718 + 1.719 + /** 1.720 + * Add a menu entry for a tool definition 1.721 + * 1.722 + * @param {string} toolDefinition 1.723 + * Tool definition of the tool to add a menu entry. 1.724 + * @param {XULDocument} doc 1.725 + * The document to which the tool menu item is to be added. 1.726 + */ 1.727 + _createToolMenuElements: function DT_createToolMenuElements(toolDefinition, doc) { 1.728 + let id = toolDefinition.id; 1.729 + 1.730 + // Prevent multiple entries for the same tool. 1.731 + if (doc.getElementById("Tools:" + id)) { 1.732 + return; 1.733 + } 1.734 + 1.735 + let cmd = doc.createElement("command"); 1.736 + cmd.id = "Tools:" + id; 1.737 + cmd.setAttribute("oncommand", 1.738 + 'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");'); 1.739 + 1.740 + let key = null; 1.741 + if (toolDefinition.key) { 1.742 + key = doc.createElement("key"); 1.743 + key.id = "key_" + id; 1.744 + 1.745 + if (toolDefinition.key.startsWith("VK_")) { 1.746 + key.setAttribute("keycode", toolDefinition.key); 1.747 + } else { 1.748 + key.setAttribute("key", toolDefinition.key); 1.749 + } 1.750 + 1.751 + key.setAttribute("command", cmd.id); 1.752 + key.setAttribute("modifiers", toolDefinition.modifiers); 1.753 + } 1.754 + 1.755 + let bc = doc.createElement("broadcaster"); 1.756 + bc.id = "devtoolsMenuBroadcaster_" + id; 1.757 + bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label); 1.758 + bc.setAttribute("command", cmd.id); 1.759 + 1.760 + if (key) { 1.761 + bc.setAttribute("key", "key_" + id); 1.762 + } 1.763 + 1.764 + let appmenuitem = doc.createElement("menuitem"); 1.765 + appmenuitem.id = "appmenuitem_" + id; 1.766 + appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id); 1.767 + 1.768 + let menuitem = doc.createElement("menuitem"); 1.769 + menuitem.id = "menuitem_" + id; 1.770 + menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id); 1.771 + 1.772 + if (toolDefinition.accesskey) { 1.773 + menuitem.setAttribute("accesskey", toolDefinition.accesskey); 1.774 + } 1.775 + 1.776 + return { 1.777 + cmd: cmd, 1.778 + key: key, 1.779 + bc: bc, 1.780 + appmenuitem: appmenuitem, 1.781 + menuitem: menuitem 1.782 + }; 1.783 + }, 1.784 + 1.785 + /** 1.786 + * Update the "Toggle Tools" checkbox in the developer tools menu. This is 1.787 + * called when a toolbox is created or destroyed. 1.788 + */ 1.789 + _updateMenuCheckbox: function DT_updateMenuCheckbox() { 1.790 + for (let win of gDevToolsBrowser._trackedBrowserWindows) { 1.791 + 1.792 + let hasToolbox = false; 1.793 + if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) { 1.794 + let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab); 1.795 + if (gDevTools._toolboxes.has(target)) { 1.796 + hasToolbox = true; 1.797 + } 1.798 + } 1.799 + 1.800 + let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox"); 1.801 + if (hasToolbox) { 1.802 + broadcaster.setAttribute("checked", "true"); 1.803 + } else { 1.804 + broadcaster.removeAttribute("checked"); 1.805 + } 1.806 + } 1.807 + }, 1.808 + 1.809 + /** 1.810 + * Connects to the SPS profiler when the developer tools are open. 1.811 + */ 1.812 + _connectToProfiler: function DT_connectToProfiler() { 1.813 + let ProfilerController = devtools.require("devtools/profiler/controller"); 1.814 + 1.815 + for (let win of gDevToolsBrowser._trackedBrowserWindows) { 1.816 + if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) { 1.817 + let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab); 1.818 + if (gDevTools._toolboxes.has(target)) { 1.819 + target.makeRemote().then(() => { 1.820 + let profiler = new ProfilerController(target); 1.821 + profiler.connect(); 1.822 + }).then(null, Cu.reportError); 1.823 + 1.824 + return; 1.825 + } 1.826 + } 1.827 + } 1.828 + }, 1.829 + 1.830 + /** 1.831 + * Remove the menuitem for a tool to all open browser windows. 1.832 + * 1.833 + * @param {string} toolId 1.834 + * id of the tool to remove 1.835 + */ 1.836 + _removeToolFromWindows: function DT_removeToolFromWindows(toolId) { 1.837 + for (let win of gDevToolsBrowser._trackedBrowserWindows) { 1.838 + gDevToolsBrowser._removeToolFromMenu(toolId, win.document); 1.839 + } 1.840 + }, 1.841 + 1.842 + /** 1.843 + * Remove a tool's menuitem from a window 1.844 + * 1.845 + * @param {string} toolId 1.846 + * Id of the tool to add a menu entry for 1.847 + * @param {XULDocument} doc 1.848 + * The document to which the tool menu item is to be removed from 1.849 + */ 1.850 + _removeToolFromMenu: function DT_removeToolFromMenu(toolId, doc) { 1.851 + let command = doc.getElementById("Tools:" + toolId); 1.852 + if (command) { 1.853 + command.parentNode.removeChild(command); 1.854 + } 1.855 + 1.856 + let key = doc.getElementById("key_" + toolId); 1.857 + if (key) { 1.858 + key.parentNode.removeChild(key); 1.859 + } 1.860 + 1.861 + let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId); 1.862 + if (bc) { 1.863 + bc.parentNode.removeChild(bc); 1.864 + } 1.865 + 1.866 + let appmenuitem = doc.getElementById("appmenuitem_" + toolId); 1.867 + if (appmenuitem) { 1.868 + appmenuitem.parentNode.removeChild(appmenuitem); 1.869 + } 1.870 + 1.871 + let menuitem = doc.getElementById("menuitem_" + toolId); 1.872 + if (menuitem) { 1.873 + menuitem.parentNode.removeChild(menuitem); 1.874 + } 1.875 + }, 1.876 + 1.877 + /** 1.878 + * Called on browser unload to remove menu entries, toolboxes and event 1.879 + * listeners from the closed browser window. 1.880 + * 1.881 + * @param {XULWindow} win 1.882 + * The window containing the menu entry 1.883 + */ 1.884 + forgetBrowserWindow: function DT_forgetBrowserWindow(win) { 1.885 + gDevToolsBrowser._trackedBrowserWindows.delete(win); 1.886 + 1.887 + // Destroy toolboxes for closed window 1.888 + for (let [target, toolbox] of gDevTools._toolboxes) { 1.889 + if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) { 1.890 + toolbox.destroy(); 1.891 + } 1.892 + } 1.893 + 1.894 + let tabContainer = win.document.getElementById("tabbrowser-tabs") 1.895 + tabContainer.removeEventListener("TabSelect", 1.896 + gDevToolsBrowser._updateMenuCheckbox, false); 1.897 + }, 1.898 + 1.899 + /** 1.900 + * All browser windows have been closed, tidy up remaining objects. 1.901 + */ 1.902 + destroy: function() { 1.903 + gDevTools.off("toolbox-ready", gDevToolsBrowser._connectToProfiler); 1.904 + Services.prefs.removeObserver("devtools.", gDevToolsBrowser); 1.905 + Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application"); 1.906 + }, 1.907 +} 1.908 + 1.909 +this.gDevToolsBrowser = gDevToolsBrowser; 1.910 + 1.911 +gDevTools.on("tool-registered", function(ev, toolId) { 1.912 + let toolDefinition = gDevTools._tools.get(toolId); 1.913 + gDevToolsBrowser._addToolToWindows(toolDefinition); 1.914 +}); 1.915 + 1.916 +gDevTools.on("tool-unregistered", function(ev, toolId) { 1.917 + if (typeof toolId != "string") { 1.918 + toolId = toolId.id; 1.919 + } 1.920 + gDevToolsBrowser._removeToolFromWindows(toolId); 1.921 +}); 1.922 + 1.923 +gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox); 1.924 +gDevTools.on("toolbox-ready", gDevToolsBrowser._connectToProfiler); 1.925 +gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox); 1.926 + 1.927 +Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false); 1.928 + 1.929 +// Load the browser devtools main module as the loader's main module. 1.930 +devtools.main("main");