|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "gDevToolsBrowser" ]; |
|
8 |
|
9 const { classes: Cc, interfaces: Ci, utils: Cu } = Components; |
|
10 |
|
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"); |
|
16 |
|
17 const FORBIDDEN_IDS = new Set(["toolbox", ""]); |
|
18 const MAX_ORDINAL = 99; |
|
19 |
|
20 |
|
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> |
|
28 |
|
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); |
|
32 |
|
33 this._testing = false; |
|
34 |
|
35 EventEmitter.decorate(this); |
|
36 |
|
37 Services.obs.addObserver(this._teardown, "devtools-unloaded", false); |
|
38 Services.obs.addObserver(this.destroy, "quit-application", false); |
|
39 } |
|
40 |
|
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 }, |
|
51 |
|
52 set testing(state) { |
|
53 this._testing = state; |
|
54 |
|
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 }, |
|
64 |
|
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; |
|
96 |
|
97 if (!toolId || FORBIDDEN_IDS.has(toolId)) { |
|
98 throw new Error("Invalid definition.id"); |
|
99 } |
|
100 |
|
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 } |
|
107 |
|
108 this._tools.set(toolId, toolDefinition); |
|
109 |
|
110 this.emit("tool-registered", toolId); |
|
111 }, |
|
112 |
|
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); |
|
134 |
|
135 if (!isQuitApplication) { |
|
136 this.emit("tool-unregistered", tool); |
|
137 } |
|
138 }, |
|
139 |
|
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 }, |
|
148 |
|
149 getDefaultTools: function DT_getDefaultTools() { |
|
150 return devtools.defaultTools.sort(this.ordinalSort); |
|
151 }, |
|
152 |
|
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 }, |
|
162 |
|
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 } |
|
179 |
|
180 let enabled; |
|
181 try { |
|
182 enabled = Services.prefs.getBoolPref(tool.visibilityswitch); |
|
183 } catch (e) { |
|
184 enabled = true; |
|
185 } |
|
186 |
|
187 return enabled ? tool : null; |
|
188 }, |
|
189 |
|
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(); |
|
199 |
|
200 for (let [id, definition] of this._tools) { |
|
201 if (this.getToolDefinition(id)) { |
|
202 tools.set(id, definition); |
|
203 } |
|
204 } |
|
205 |
|
206 return tools; |
|
207 }, |
|
208 |
|
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 = []; |
|
219 |
|
220 for (let [id, definition] of this._tools) { |
|
221 if (this.getToolDefinition(id)) { |
|
222 definitions.push(definition); |
|
223 } |
|
224 } |
|
225 |
|
226 return definitions.sort(this.ordinalSort); |
|
227 }, |
|
228 |
|
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(); |
|
251 |
|
252 let toolbox = this._toolboxes.get(target); |
|
253 if (toolbox) { |
|
254 |
|
255 let hostPromise = (hostType != null && toolbox.hostType != hostType) ? |
|
256 toolbox.switchHost(hostType) : |
|
257 promise.resolve(null); |
|
258 |
|
259 if (toolId != null && toolbox.currentToolId != toolId) { |
|
260 hostPromise = hostPromise.then(function() { |
|
261 return toolbox.selectTool(toolId); |
|
262 }); |
|
263 } |
|
264 |
|
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); |
|
273 |
|
274 this._toolboxes.set(target, toolbox); |
|
275 |
|
276 toolbox.once("destroyed", function() { |
|
277 this._toolboxes.delete(target); |
|
278 this.emit("toolbox-destroyed", target); |
|
279 }.bind(this)); |
|
280 |
|
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 } |
|
297 |
|
298 return deferred.promise; |
|
299 }, |
|
300 |
|
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 }, |
|
313 |
|
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 }, |
|
329 |
|
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 }, |
|
338 |
|
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"); |
|
345 |
|
346 for (let [key, tool] of this.getToolDefinitionMap()) { |
|
347 this.unregisterTool(key, true); |
|
348 } |
|
349 |
|
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 }, |
|
354 |
|
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 }; |
|
364 |
|
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; |
|
373 |
|
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(), |
|
384 |
|
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); |
|
393 |
|
394 toolbox ? toolbox.destroy() : gDevTools.showToolbox(target); |
|
395 }, |
|
396 |
|
397 toggleBrowserToolboxCommand: function(gBrowser) { |
|
398 let target = devtools.TargetFactory.forWindow(gBrowser.ownerDocument.defaultView); |
|
399 let toolbox = gDevTools.getToolbox(target); |
|
400 |
|
401 toolbox ? toolbox.destroy() |
|
402 : gDevTools.showToolbox(target, "inspector", Toolbox.HostType.WINDOW); |
|
403 }, |
|
404 |
|
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; |
|
412 |
|
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 }; |
|
423 |
|
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 } |
|
436 |
|
437 // Enable App Manager? |
|
438 let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled"); |
|
439 toggleCmd("Tools:DevAppMgr", appMgrEnabled); |
|
440 |
|
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); |
|
447 |
|
448 // Enable Error Console? |
|
449 let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled"); |
|
450 toggleCmd("Tools:ErrorConsole", consoleEnabled); |
|
451 |
|
452 // Enable DevTools connection screen, if the preference allows this. |
|
453 toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled); |
|
454 }, |
|
455 |
|
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 }, |
|
463 |
|
464 _prefObserverRegistered: false, |
|
465 |
|
466 ensurePrefObserver: function() { |
|
467 if (!this._prefObserverRegistered) { |
|
468 this._prefObserverRegistered = true; |
|
469 Services.prefs.addObserver("devtools.", this, false); |
|
470 } |
|
471 }, |
|
472 |
|
473 |
|
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); |
|
492 |
|
493 if (toolbox && |
|
494 (toolbox.currentToolId == toolId || |
|
495 (toolId == "webconsole" && toolbox.splitConsole))) |
|
496 { |
|
497 toolbox.fireCustomKey(toolId); |
|
498 |
|
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); |
|
508 |
|
509 toolbox.fireCustomKey(toolId); |
|
510 }); |
|
511 } |
|
512 }, |
|
513 |
|
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 }, |
|
520 |
|
521 /** |
|
522 * Open the App Manager |
|
523 */ |
|
524 openAppManager: function(gBrowser) { |
|
525 gBrowser.selectedTab = gBrowser.addTab("about:app-manager"); |
|
526 }, |
|
527 |
|
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); |
|
539 |
|
540 if (this._isFirebugInstalled()) { |
|
541 let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox"); |
|
542 broadcaster.removeAttribute("key"); |
|
543 } |
|
544 |
|
545 let tabContainer = win.document.getElementById("tabbrowser-tabs") |
|
546 tabContainer.addEventListener("TabSelect", |
|
547 gDevToolsBrowser._updateMenuCheckbox, false); |
|
548 }, |
|
549 |
|
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"); |
|
563 |
|
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 }, |
|
572 |
|
573 |
|
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 }, |
|
583 |
|
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 } |
|
595 |
|
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) {} |
|
603 |
|
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 } |
|
617 |
|
618 for (let win of gDevToolsBrowser._trackedBrowserWindows) { |
|
619 let doc = win.document; |
|
620 let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc); |
|
621 |
|
622 doc.getElementById("mainCommandSet").appendChild(elements.cmd); |
|
623 |
|
624 if (elements.key) { |
|
625 this.attachKeybindingsToBrowser(doc, elements.key); |
|
626 } |
|
627 |
|
628 doc.getElementById("mainBroadcasterSet").appendChild(elements.bc); |
|
629 |
|
630 let amp = doc.getElementById("appmenu_webDeveloper_popup"); |
|
631 if (amp) { |
|
632 let ref; |
|
633 |
|
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 } |
|
640 |
|
641 if (ref) { |
|
642 amp.insertBefore(elements.appmenuitem, ref); |
|
643 } |
|
644 } |
|
645 |
|
646 let mp = doc.getElementById("menuWebDeveloperPopup"); |
|
647 if (mp) { |
|
648 let ref; |
|
649 |
|
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 } |
|
656 |
|
657 if (ref) { |
|
658 mp.insertBefore(elements.menuitem, ref); |
|
659 } |
|
660 } |
|
661 } |
|
662 }, |
|
663 |
|
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(); |
|
676 |
|
677 for (let toolDefinition of gDevTools.getToolDefinitionArray()) { |
|
678 if (!toolDefinition.inMenu) { |
|
679 continue; |
|
680 } |
|
681 |
|
682 let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc); |
|
683 |
|
684 if (!elements) { |
|
685 return; |
|
686 } |
|
687 |
|
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 } |
|
696 |
|
697 let mcs = doc.getElementById("mainCommandSet"); |
|
698 mcs.appendChild(fragCommands); |
|
699 |
|
700 this.attachKeybindingsToBrowser(doc, fragKeys); |
|
701 |
|
702 let mbs = doc.getElementById("mainBroadcasterSet"); |
|
703 mbs.appendChild(fragBroadcasters); |
|
704 |
|
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 } |
|
710 |
|
711 let mp = doc.getElementById("menuWebDeveloperPopup"); |
|
712 let mps = doc.getElementById("menu_devtools_separator"); |
|
713 mp.insertBefore(fragMenuItems, mps); |
|
714 }, |
|
715 |
|
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; |
|
726 |
|
727 // Prevent multiple entries for the same tool. |
|
728 if (doc.getElementById("Tools:" + id)) { |
|
729 return; |
|
730 } |
|
731 |
|
732 let cmd = doc.createElement("command"); |
|
733 cmd.id = "Tools:" + id; |
|
734 cmd.setAttribute("oncommand", |
|
735 'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");'); |
|
736 |
|
737 let key = null; |
|
738 if (toolDefinition.key) { |
|
739 key = doc.createElement("key"); |
|
740 key.id = "key_" + id; |
|
741 |
|
742 if (toolDefinition.key.startsWith("VK_")) { |
|
743 key.setAttribute("keycode", toolDefinition.key); |
|
744 } else { |
|
745 key.setAttribute("key", toolDefinition.key); |
|
746 } |
|
747 |
|
748 key.setAttribute("command", cmd.id); |
|
749 key.setAttribute("modifiers", toolDefinition.modifiers); |
|
750 } |
|
751 |
|
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); |
|
756 |
|
757 if (key) { |
|
758 bc.setAttribute("key", "key_" + id); |
|
759 } |
|
760 |
|
761 let appmenuitem = doc.createElement("menuitem"); |
|
762 appmenuitem.id = "appmenuitem_" + id; |
|
763 appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id); |
|
764 |
|
765 let menuitem = doc.createElement("menuitem"); |
|
766 menuitem.id = "menuitem_" + id; |
|
767 menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id); |
|
768 |
|
769 if (toolDefinition.accesskey) { |
|
770 menuitem.setAttribute("accesskey", toolDefinition.accesskey); |
|
771 } |
|
772 |
|
773 return { |
|
774 cmd: cmd, |
|
775 key: key, |
|
776 bc: bc, |
|
777 appmenuitem: appmenuitem, |
|
778 menuitem: menuitem |
|
779 }; |
|
780 }, |
|
781 |
|
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) { |
|
788 |
|
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 } |
|
796 |
|
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 }, |
|
805 |
|
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"); |
|
811 |
|
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); |
|
820 |
|
821 return; |
|
822 } |
|
823 } |
|
824 } |
|
825 }, |
|
826 |
|
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 }, |
|
838 |
|
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 } |
|
852 |
|
853 let key = doc.getElementById("key_" + toolId); |
|
854 if (key) { |
|
855 key.parentNode.removeChild(key); |
|
856 } |
|
857 |
|
858 let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId); |
|
859 if (bc) { |
|
860 bc.parentNode.removeChild(bc); |
|
861 } |
|
862 |
|
863 let appmenuitem = doc.getElementById("appmenuitem_" + toolId); |
|
864 if (appmenuitem) { |
|
865 appmenuitem.parentNode.removeChild(appmenuitem); |
|
866 } |
|
867 |
|
868 let menuitem = doc.getElementById("menuitem_" + toolId); |
|
869 if (menuitem) { |
|
870 menuitem.parentNode.removeChild(menuitem); |
|
871 } |
|
872 }, |
|
873 |
|
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); |
|
883 |
|
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 } |
|
890 |
|
891 let tabContainer = win.document.getElementById("tabbrowser-tabs") |
|
892 tabContainer.removeEventListener("TabSelect", |
|
893 gDevToolsBrowser._updateMenuCheckbox, false); |
|
894 }, |
|
895 |
|
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 } |
|
905 |
|
906 this.gDevToolsBrowser = gDevToolsBrowser; |
|
907 |
|
908 gDevTools.on("tool-registered", function(ev, toolId) { |
|
909 let toolDefinition = gDevTools._tools.get(toolId); |
|
910 gDevToolsBrowser._addToolToWindows(toolDefinition); |
|
911 }); |
|
912 |
|
913 gDevTools.on("tool-unregistered", function(ev, toolId) { |
|
914 if (typeof toolId != "string") { |
|
915 toolId = toolId.id; |
|
916 } |
|
917 gDevToolsBrowser._removeToolFromWindows(toolId); |
|
918 }); |
|
919 |
|
920 gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox); |
|
921 gDevTools.on("toolbox-ready", gDevToolsBrowser._connectToProfiler); |
|
922 gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox); |
|
923 |
|
924 Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false); |
|
925 |
|
926 // Load the browser devtools main module as the loader's main module. |
|
927 devtools.main("main"); |