mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-28 19:30:02 +00:00
e39f9f88f7
- Bug 1152171 part 2 - Rename AnimationTimeline to DocumentTimeline; r=smaug (26c118319) - Bug 1152171 part 3 - Update web-platform-tests expectations; r=jgraham (b7b4032aa) - Bug 1153734 part 1 - Remove AnimationEffect; r=smaug (9cf67a02e) - Bug 1153734 part 2 - Rename Animation to KeyframeEffectReadonly; r=smaug (b69556ee6) - Bug 1153734 part 3 - Rename AnimationPlayer.source to AnimationPlayer.effect; r=smaug (50d3130ee) - Bug 1153734 part 4 - Rename other uses of 'source' and 'source content'; r=jwatt (b02c4ba36) - Bug 1153734 part 5 - Add AnimationEffectReadonly as a superinterface of KeyframeEffectReadonly; r=smaug (c3395d3f5) - Bug 1149990 - Support replaying of finished CSS transitions by supporting setting of currentTime/startTime. r=birtles (3fb2cb401) - Bug 1154615 part 1 - Rename AnimationPlayer to Animation in WebIDL; r=smaug (6c2125b49) - Bug 1154615 part 2 - Rename PendingPlayerTracker to PendingAnimationTracker; r=jwatt (8d6804def) - Bug 1154615 part 3 - Rename internal members of PendingAnimationTracker; r=jwatt (f348f6355) - Bug 1154615 part 4 - Rename references to players in dom/animation; r=jwatt (0250572e8) - Bug 1117603 part 1 - Don't assume style rules have been refreshed in GetAnimationRule; r=dbaron (a5d340d0f) - remove kungFuDeathGrip (49df758e6) - Bug 1117603 part 2 - Don't unregister from the refresh driver unless we are also queueing events; r=dbaron (715c9caa1) - Bug 1154615 part 5 - Rename AnimationPlayerCollection to AnimationCollection; r=jwatt (4c596f089) - Bug 1154615 part 6 - Rename references to players within layout/; r=jwatt (42405f3fc) - Bug 1154615 part 7 - Rename CSSAnimationPlayer and CSSTransitionPlayer; r=jwatt (49ab272ed) - Bug 1154615 part 8 - Rename references to players in animation observers; r=jwatt (c3fa26d7a) - Bug 1154615 part 9 - Rename test files; r=jwatt (9d9f03e7b) - Bug 1145439 (Part 1) - Throttle requestAnimationFrame for non-visible iframes. r=mstange,mchang (be7d183d6) - Bug 1145439 (Part 2) - Make test_scroll_event_ordering.html wait for rAF to unthrottle. r=roc (9ac8317c9) - Bug 1144324 - Try to register for, and handle, touch events when APZ is enabled. r=dvander,jimm (fb75d1665) - Bug 1144324 - Remove the codepaths that conditionally enable touch events based on touch the presence of touch listeners. r=smaug,jimm (710617e6b) - Bug 1003991 - Disable https:// only load for ServiceWorkers when Developer Tools are open. r=nsm, r=miker (9d6669814) - Bug 1153267 - part 1 - use smart-pointer .forget() instead of NS_ADDREF+assign; r=ehsan (e4555c90c) - Bug 1153267 - part 2 - use smart pointers instead of manual NS_ADDREF'ing outparams; r=ehsan (ae8b60d5a) - Bug 1153267 - fix typo that broke OS X builds on a CLOSED TREE; r=bustage (08fdb3c4f) - Bug 1146843 - Revert part of cset 33c30e283fa8 because the code is used in Fennec. r=snorp (407248257) - Bug 1151940 part 1. Make some readonly properties defined on Window by CSSOM-view replaceable. r=smaug (5cb9b91f0) - Bug 1151940 part 2. Add a convenience function in nsGlobalWindow for replacing a property on the window with a new value. r=smaug (2ba39331c) - Bug 1151940 part 3. Make some writable cssom-view attributes that we only allow setting from chrome act the way readonly replaceables would when called from content. r=smaug (b485e1b44) - Goanna -> Gecko (2c539d7be) - Goanna -> Gecko (25d34e213) - Bug 1148962 - Use TakeOwnershipOfErrorReporting in CPOW code (r=bholley) (96c997639) - pointer style (a07fbffaa) - Bug 1152577: Add 'aReason' argument to AutoEntryScript constructor, and provide plausible names for its instantiations. r=bholley (512fa27e2) - bug 1155691 - Expose WindowRoot to chrome from window in webidl. r=smaug (235281924) - Bug 404828 - No need to assert that the top window isn't reachable. r=smaug (d73154fa0) - Bug 404828 - Followup: remove assertion expectations on a CLOSED TREE. a=tomcat (a5dabe1b7) - Bug 1156102 - Mark nsGlobalWindowObserver::mWindow as MOZ_NON_OWNING_REF; r=baku (c0d4208b7) - Bug 1107801 - Improve gamepad support on MacOS. r=ted (c591bd5ac) - Goanna -> Gecko (d9b81bc9e) - Bug 852944 - Gamepad API IPC; r=ted, r=baku (521892538) - Bug 1143529 part 1. Stop manually calling WrapObject in DataStoreService::GetDataStoresResolve. r=baku (056ad6bfe) - Bug 1143529 part 2. Tighten up the assert in binding Wrap methods. r=peterv (765a13325) - Bug 1152169 - DataStoreService should check if the first revision exists, r=bent (ee371cc5d) - Bug 1152169 followup: Mark FirstRevisionIdCallback methods Run() and HandleEvent() as 'override'. rs=ehsan (8186c4168) - Bug 1143651 - don't use CallQueryInterface when the compiler can do the cast for us; r=ehsan (a50f0a54b) - Bug 1144322 - Handle tabindex in overridden IsInteractiveHTMLContent methods. r=smaug (fd4b9beed) - Bug 1086684 - Stash the full path for file inputs to avoid doing IPC at inopportune times. r=ehsan/bent/gps (b843b1efc) - Bug 1143934 - Disallow mozSetFileNameArray in content processes. r=ehsan (42e5c8c6d) - Bug 1143934 - Fix assorted forms mochitests for e10s-compatibility. r=smaug (7a3babfed) - Bug 1143934 - Work around SessionStore dependency on current brokenness. r=ttaubert (5b0fcb5ce) - Bug 956530 - Clear the delayed caret data when clicking on a selected part of a text control if the focus event handler selects the control; r=roc (2859f07b4) - Bug 956530 follow-up: Fix the test failure on Windows 8 caused by the text box having a glowing outline as a result of being clicked on (d34e8da1a) - Bug 1157898 part 1. Make code of the form "return rv.ErrorCode();" where rv is an ErrorResult use StealNSResult instead. r=peterv (800da50e2) - Bug 1157898 part 2. Make code of the form "NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());" use Failed and StealNSResult instead. r=peterv (472432a83) - Bug 1157898 part 3. Fix the remaining consumers of rv.ErrorCode() in NS_ENSURE_* expressions to not do that. r=peterv (d452807e7) - Bug 1122238 part 1. Switch to using the new stackframe APIs in JSStackFrame. r=bholley (9d87b261a) - Bug 1122238 part 2. Stop caching things in JSStackFrame when we're called over Xrays. r=bholley (83eda7275) - Bug 1122238 part 3. Drop all the DOMException-cloning and sanitization gunk we added in bug 1107592 and bug 1107953 and bug 1117242 . r=bholley (f237aa948) - add support for NetBSD/SPARC64 (065783b70) - Bug 1153484 - Fetch should ignore invalid headers, but still process later headers. r=nsm (8925ddd77) - Bug 1157754 part 2. Convert consumers of ErrorResult::ClearMessage() to the new better APIs we have for suppressing exceptions on ErrorResult. r=bkelly (6519fbd5e) - Bug 1157754 part 3. Make ClearMessage private on ErrorResult. r=peterv (3fb218692) - Bug 1157898 part 4. Add ErrorResult::ErrorCodeIs() and use it in various places to get rid of ErrorCode(). r=peterv (bed7bfb4c) - Bug 1130686 - Refactor PromiseHolder in the service worker clients code. r=nsm (b3dbdcbfe) - Bug 1130686 - Implement client.focus. r=baku (5dee6d850) - Bug 1149163 part 1 - Clean up nsHTMLEditRules::GetInnerContent; r=froydnj (cc8f65b54) - Bug 1149163 part 2 - Make nsDOMIterator infallible; r=froydnj (d975f6c62) - Bug 1149163 part 3 - Clean up nsHTMLEditRules::BustUpInlinesAtBRs; r=froydnj (58155adad) - Bug 1149163 part 4 - Allow use of temporary nsBoolDomIterFunctor; r=froydnj (dbafec00f) - Bug 1149163 part 5 - Clean up nsHTMLEditRules::GetNodesForOperation; r=froydnj (41179d810) - Bug 1149163 part 6 - Clean up nsHTMLEditRules::LookInsideDivBQandList; r=froydnj (0b757bf14) - Bug 1149163 part 7 - Clean up nsHTMLEditRules::PromoteRange; r=froydnj (c49c714b1) - Bug 1149163 part 8 - Clean up nsHTMLEditRules::GetPromotedRanges; r=froydnj (5163a0026) - Bug 1148228 - Stop checking ul twice (43a22088c) - Bug 1141017 - resurrect serif and monospace. r=ehsan (95a1b6fcf) - Bug 1147412 part 1 - Make methods take nsINode*, not just nsIContent*; r=ehsan (7f762cdbe) - Bug 1147412 part 2 - Clean up nsHTMLEditor::SetInlinePropertyOnTextNode; r=ehsan (faf805587) - Bug 1147412 part 3 - Fix completely broken nsHTMLCSSUtils::IsCSSEquivalentToHTMLInlineStyleSet implementation; r=ehsan (73fea67c1) - Bug 1147412 part 4 - Clean up nsHTMLEditor::GetInlinePropertyBase; r=ehsan (3265bfbce) - Bug 1147412 part 5 - Clean up nsHTMLEditor::RemoveInlinePropertyImpl; r=ehsan (0f402bd7e) - Bug 1147412 part 6 - Remove nsHTMLCSSUtils::IsCSSEditableProperty(nsIDOMNode*,...); r=ehsan (100e4038a) - Bug 1147412 part 7 - Remove nsHTMLCSSUtils::GetComputedStyle(nsIDOMElement*); r=ehsan (6c51103bc) - Bug 1147412 part 8 - Clean up nsHTMLCSSUtils::IsCSSInvertible; r=ehsan (01e60c446) - Bug 1147412 part 9 - Convert some nsHTMLEditor members to Element; r=ehsan (e7efb1ac4) - Bug 1147412 part 10 - Clean up nsHTMLCSSUtils::Get*Property, GetCSSInlinePropertyBase; r=ehsan (54154143d) - Bug 1149163 part 9 - Clean up nsHTMLEditRules::GetNodesFromSelection; r=froydnj (5186308b9) - Bug 1154701 part 1 - Clean up nsHTMLEditor::CreateListOfNodesToPaste; r=ehsan (ea95238d5) - Bug 1153629 part 1 - Clean up nsHTMLEditRules::GetListActionNodes; r=ehsan (51f3b3e95) - Bug 1153629 part 2 - Clean up nsHTMLEditRules::GetParagraphFormatNodes; r=ehsan (a27bd7751) - Bug 1153629 part 3 - Clean up nsHTMLEditRules::GetNodesFromPoint; r=ehsan (edc7e4561) - Bug 1153629 part 4 - Clean up nsHTMLEditRules::ListIsEmptyLine; r=ehsan (ce3289bc7) - Bug 1153629 part 5 - Clean up nsHTMLEditRules::GetChildNodesForOperation; r=ehsan (b3a509dbf) - Bug 1153629 part 6 - Clean up nsHTMLEditRules::MakeBlockquote; r=ehsan (cb3808182) - Bug 1153629 part 7 - Clean up nsHTMLEditRules::RemoveBlockStyle, RemovePartOfBlock; r=ehsan (660b9f76e) - Bug 1153629 part 8 - Clean up nsHTMLEditRules::ApplyBlockStyle; r=ehsan (f54f9538c) - Bug 1153629 part 9 - Clean up nsHTMLEditRules::MakeTransitionList; r=ehsan (fb63cf6d8) - Bug 1153629 part 10 - Clean up nsHTMLEditRules::AlignInnerBlocks; r=ehsan (752d2df7a) - Bug 1153629 part 11 - Clean up nsHTMLEditRules::AdjustSpecialBreaks; r=ehsan (16ef0416b) - Bug 1153629 part 12 - Clean up nsHTMLEditRules::RemoveEmptyNodes; r=ehsan (d528e70e6) - Bug 1154701 part 2 - Use more OwningNonNull in editor; r=ehsan (85b1929e6) - Bug 1154701 part 3 - Clean up nsHTMLEditor::GetListAndTableParents, DiscoverPartialListsAndTables, ScanForListAndTableStructure, ReplaceOrphanedStructure; r=ehsan (7fe31f058) - Bug 1154701 part 4 - Switch nsHTMLEditor::mContentFilters to nsTArray; r=ehsan (64e6dd160) - Bug 1154701 part 5 - Switch nsHTMLEditor::objectResizeEventListeners to nsTArray; r=ehsan (036bc65fe) - Bug 1154701 part 6 - Clean up nsHTMLEditor::SetInlinePropertyOnNodeImpl; r=ehsan (2d619ca16) - Bug 1154701 part 7 - Clean up nsHTMLEditor::SetInlineProperty; r=ehsan (7a367d31b) - Bug 1154701 part 8 - Clean up nsHTMLEditor::SetInlinePropertyOnNode; r=ehsan (707c07d93) - Bug 1154701 part 9 - Clean up nsHTMLEditor::RelativeFontChange; r=ehsan (273ae9c64) - Bug 1154701 part 10 - Switch nsEditor::mActionListeners to nsTArray; r=ehsan (d2b5732fe) - Bug 1154701 part 11 - Switch nsEditor::mEditorObservers to nsTArray; r=ehsan (25a5af12e) - Bug 1154701 part 12 - Switch nsEditor::mDocStateListeners to nsTArray; r=ehsan (665af0792) - Bug 1154701 part 13 - Clean up nsHTMLEditor::SetCSSBackgroundColor; r=ehsan (ba424ade8) - Bug 1154701 part 14 - Remove unused nsCOMArray cruft; r=ehsan (3a8679a67) - Bug 1101651 - Part 1: xpcomrt version of dom media library need for standalone webrtcs. r=jesup (ae37b5464) - Bug 1137447 - New app update telemetry for patch type (complete or partial), extended error codes, and general cleanup. r=bbondy (c736ae502)
1749 lines
54 KiB
JavaScript
1749 lines
54 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const MAX_ORDINAL = 99;
|
|
const ZOOM_PREF = "devtools.toolbox.zoomValue";
|
|
const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsoleEnabled";
|
|
const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
|
|
const MIN_ZOOM = 0.5;
|
|
const MAX_ZOOM = 2;
|
|
|
|
let {Cc, Ci, Cu} = require("chrome");
|
|
let {Promise: promise} = require("resource://gre/modules/Promise.jsm");
|
|
let EventEmitter = require("devtools/toolkit/event-emitter");
|
|
let {getHighlighterUtils} = require("devtools/framework/toolbox-highlighter-utils");
|
|
let {HUDService} = require("devtools/webconsole/hudservice");
|
|
let {showDoorhanger} = require("devtools/shared/doorhanger");
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/devtools/gDevTools.jsm");
|
|
Cu.import("resource://gre/modules/devtools/scratchpad-manager.jsm");
|
|
Cu.import("resource://gre/modules/devtools/DOMHelpers.jsm");
|
|
Cu.import("resource://gre/modules/Task.jsm");
|
|
|
|
loader.lazyGetter(this, "Hosts", () => require("devtools/framework/toolbox-hosts").Hosts);
|
|
|
|
loader.lazyImporter(this, "CommandUtils", "resource://gre/modules/devtools/DeveloperToolbar.jsm");
|
|
|
|
loader.lazyGetter(this, "toolboxStrings", () => {
|
|
let bundle = Services.strings.createBundle("chrome://global/locale/devtools/toolbox.properties");
|
|
return (name, ...args) => {
|
|
try {
|
|
if (!args.length) {
|
|
return bundle.GetStringFromName(name);
|
|
}
|
|
return bundle.formatStringFromName(name, args, args.length);
|
|
} catch (ex) {
|
|
Services.console.logStringMessage("Error reading '" + name + "'");
|
|
return null;
|
|
}
|
|
};
|
|
});
|
|
|
|
loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
|
|
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
|
|
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils");
|
|
|
|
// White-list buttons that can be toggled to prevent adding prefs for
|
|
// addons that have manually inserted toolbarbuttons into DOM.
|
|
// (By default, supported target is only local tab)
|
|
const ToolboxButtons = [
|
|
{ id: "command-button-pick",
|
|
isTargetSupported: target =>
|
|
target.getTrait("highlightable")
|
|
},
|
|
{ id: "command-button-frames",
|
|
isTargetSupported: target =>
|
|
( target.activeTab && target.activeTab.traits.frames )
|
|
},
|
|
{ id: "command-button-splitconsole",
|
|
isTargetSupported: target => !target.isAddon },
|
|
{ id: "command-button-responsive" },
|
|
{ id: "command-button-paintflashing" },
|
|
{ id: "command-button-tilt" },
|
|
{ id: "command-button-scratchpad" },
|
|
{ id: "command-button-eyedropper" },
|
|
{ id: "command-button-screenshot" }
|
|
];
|
|
|
|
/**
|
|
* A "Toolbox" is the component that holds all the tools for one specific
|
|
* target. Visually, it's a document that includes the tools tabs and all
|
|
* the iframes where the tool panels will be living in.
|
|
*
|
|
* @param {object} target
|
|
* The object the toolbox is debugging.
|
|
* @param {string} selectedTool
|
|
* Tool to select initially
|
|
* @param {Toolbox.HostType} hostType
|
|
* Type of host that will host the toolbox (e.g. sidebar, window)
|
|
* @param {object} hostOptions
|
|
* Options for host specifically
|
|
*/
|
|
function Toolbox(target, selectedTool, hostType, hostOptions) {
|
|
this._target = target;
|
|
this._toolPanels = new Map();
|
|
|
|
this._toolRegistered = this._toolRegistered.bind(this);
|
|
this._toolUnregistered = this._toolUnregistered.bind(this);
|
|
this._refreshHostTitle = this._refreshHostTitle.bind(this);
|
|
this.selectFrame = this.selectFrame.bind(this);
|
|
this._updateFrames = this._updateFrames.bind(this);
|
|
this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this);
|
|
this.destroy = this.destroy.bind(this);
|
|
this.highlighterUtils = getHighlighterUtils(this);
|
|
this._highlighterReady = this._highlighterReady.bind(this);
|
|
this._highlighterHidden = this._highlighterHidden.bind(this);
|
|
this._prefChanged = this._prefChanged.bind(this);
|
|
this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
|
|
this._onFocus = this._onFocus.bind(this);
|
|
this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
|
|
|
|
this._target.on("close", this.destroy);
|
|
|
|
if (!hostType) {
|
|
hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
|
|
}
|
|
if (!selectedTool) {
|
|
selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
|
|
}
|
|
if (!gDevTools.getToolDefinition(selectedTool)) {
|
|
selectedTool = "webconsole";
|
|
}
|
|
this._defaultToolId = selectedTool;
|
|
|
|
this._hostOptions = hostOptions;
|
|
this._host = this._createHost(hostType, hostOptions);
|
|
|
|
EventEmitter.decorate(this);
|
|
|
|
this._target.on("navigate", this._refreshHostTitle);
|
|
this._target.on("frame-update", this._updateFrames);
|
|
|
|
this.on("host-changed", this._refreshHostTitle);
|
|
this.on("select", this._refreshHostTitle);
|
|
|
|
this.on("ready", this._showDevEditionPromo);
|
|
|
|
gDevTools.on("tool-registered", this._toolRegistered);
|
|
gDevTools.on("tool-unregistered", this._toolUnregistered);
|
|
}
|
|
exports.Toolbox = Toolbox;
|
|
|
|
/**
|
|
* The toolbox can be 'hosted' either embedded in a browser window
|
|
* or in a separate window.
|
|
*/
|
|
Toolbox.HostType = {
|
|
BOTTOM: "bottom",
|
|
SIDE: "side",
|
|
WINDOW: "window",
|
|
CUSTOM: "custom"
|
|
};
|
|
|
|
Toolbox.prototype = {
|
|
_URL: "chrome://global/content/devtools/framework/toolbox.xul",
|
|
|
|
_prefs: {
|
|
LAST_HOST: "devtools.toolbox.host",
|
|
LAST_TOOL: "devtools.toolbox.selectedTool",
|
|
SIDE_ENABLED: "devtools.toolbox.sideEnabled"
|
|
},
|
|
|
|
currentToolId: null,
|
|
|
|
/**
|
|
* Returns a *copy* of the _toolPanels collection.
|
|
*
|
|
* @return {Map} panels
|
|
* All the running panels in the toolbox
|
|
*/
|
|
getToolPanels: function() {
|
|
return new Map(this._toolPanels);
|
|
},
|
|
|
|
/**
|
|
* Access the panel for a given tool
|
|
*/
|
|
getPanel: function(id) {
|
|
return this._toolPanels.get(id);
|
|
},
|
|
|
|
/**
|
|
* Get the panel instance for a given tool once it is ready.
|
|
* If the tool is already opened, the promise will resolve immediately,
|
|
* otherwise it will wait until the tool has been opened before resolving.
|
|
*
|
|
* Note that this does not open the tool, use selectTool if you'd
|
|
* like to select the tool right away.
|
|
*
|
|
* @param {String} id
|
|
* The id of the panel, for example "jsdebugger".
|
|
* @returns Promise
|
|
* A promise that resolves once the panel is ready.
|
|
*/
|
|
getPanelWhenReady: function(id) {
|
|
let deferred = promise.defer();
|
|
let panel = this.getPanel(id);
|
|
if (panel) {
|
|
deferred.resolve(panel);
|
|
} else {
|
|
this.on(id + "-ready", (e, panel) => {
|
|
deferred.resolve(panel);
|
|
});
|
|
}
|
|
|
|
return deferred.promise;
|
|
},
|
|
|
|
/**
|
|
* This is a shortcut for getPanel(currentToolId) because it is much more
|
|
* likely that we're going to want to get the panel that we've just made
|
|
* visible
|
|
*/
|
|
getCurrentPanel: function() {
|
|
return this._toolPanels.get(this.currentToolId);
|
|
},
|
|
|
|
/**
|
|
* Get/alter the target of a Toolbox so we're debugging something different.
|
|
* See Target.jsm for more details.
|
|
* TODO: Do we allow |toolbox.target = null;| ?
|
|
*/
|
|
get target() {
|
|
return this._target;
|
|
},
|
|
|
|
/**
|
|
* Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
|
|
* tab. See HostType for more details.
|
|
*/
|
|
get hostType() {
|
|
return this._host.type;
|
|
},
|
|
|
|
/**
|
|
* Get the iframe containing the toolbox UI.
|
|
*/
|
|
get frame() {
|
|
return this._host.frame;
|
|
},
|
|
|
|
/**
|
|
* Shortcut to the document containing the toolbox UI
|
|
*/
|
|
get doc() {
|
|
return this.frame.contentDocument;
|
|
},
|
|
|
|
/**
|
|
* Get current zoom level of toolbox
|
|
*/
|
|
get zoomValue() {
|
|
return parseFloat(Services.prefs.getCharPref(ZOOM_PREF));
|
|
},
|
|
|
|
/**
|
|
* Get the toolbox highlighter front. Note that it may not always have been
|
|
* initialized first. Use `initInspector()` if needed.
|
|
* Consider using highlighterUtils instead, it exposes the highlighter API in
|
|
* a useful way for the toolbox panels
|
|
*/
|
|
get highlighter() {
|
|
return this._highlighter;
|
|
},
|
|
|
|
/**
|
|
* Get the toolbox's inspector front. Note that it may not always have been
|
|
* initialized first. Use `initInspector()` if needed.
|
|
*/
|
|
get inspector() {
|
|
return this._inspector;
|
|
},
|
|
|
|
/**
|
|
* Get the toolbox's walker front. Note that it may not always have been
|
|
* initialized first. Use `initInspector()` if needed.
|
|
*/
|
|
get walker() {
|
|
return this._walker;
|
|
},
|
|
|
|
/**
|
|
* Get the toolbox's node selection. Note that it may not always have been
|
|
* initialized first. Use `initInspector()` if needed.
|
|
*/
|
|
get selection() {
|
|
return this._selection;
|
|
},
|
|
|
|
/**
|
|
* Get the toggled state of the split console
|
|
*/
|
|
get splitConsole() {
|
|
return this._splitConsole;
|
|
},
|
|
|
|
/**
|
|
* Open the toolbox
|
|
*/
|
|
open: function() {
|
|
let deferred = promise.defer();
|
|
|
|
return this._host.create().then(iframe => {
|
|
let deferred = promise.defer();
|
|
|
|
let domReady = () => {
|
|
this.isReady = true;
|
|
|
|
let framesPromise = this._listFrames();
|
|
|
|
this.closeButton = this.doc.getElementById("toolbox-close");
|
|
this.closeButton.addEventListener("command", this.destroy, true);
|
|
|
|
gDevTools.on("pref-changed", this._prefChanged);
|
|
|
|
let framesMenu = this.doc.getElementById("command-button-frames");
|
|
framesMenu.addEventListener("command", this.selectFrame, true);
|
|
|
|
this._buildDockButtons();
|
|
this._buildOptions();
|
|
this._buildTabs();
|
|
this._applyCacheSettings();
|
|
this._applyServiceWorkersTestingSettings();
|
|
this._addKeysToWindow();
|
|
this._addReloadKeys();
|
|
this._addHostListeners();
|
|
if (this._hostOptions && this._hostOptions.zoom === false) {
|
|
this._disableZoomKeys();
|
|
} else {
|
|
this._addZoomKeys();
|
|
this._loadInitialZoom();
|
|
}
|
|
|
|
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
|
|
this.webconsolePanel.height =
|
|
Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
|
|
this.webconsolePanel.addEventListener("resize",
|
|
this._saveSplitConsoleHeight);
|
|
|
|
let buttonsPromise = this._buildButtons();
|
|
|
|
this.selectTool(this._defaultToolId).then(panel => {
|
|
|
|
// Wait until the original tool is selected so that the split
|
|
// console input will receive focus.
|
|
let splitConsolePromise = promise.resolve();
|
|
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
|
|
splitConsolePromise = this.openSplitConsole();
|
|
}
|
|
|
|
promise.all([
|
|
splitConsolePromise,
|
|
buttonsPromise,
|
|
framesPromise
|
|
]).then(() => {
|
|
this.emit("ready");
|
|
deferred.resolve();
|
|
}, deferred.reject);
|
|
});
|
|
};
|
|
|
|
// Load the toolbox-level actor fronts and utilities now
|
|
this._target.makeRemote().then(() => {
|
|
iframe.setAttribute("src", this._URL);
|
|
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"))
|
|
let domHelper = new DOMHelpers(iframe.contentWindow);
|
|
domHelper.onceDOMReady(domReady);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}).then(null, console.error.bind(console));
|
|
},
|
|
|
|
/**
|
|
* Because our panels are lazy loaded this is a good place to watch for
|
|
* "pref-changed" events.
|
|
* @param {String} event
|
|
* The event type, "pref-changed".
|
|
* @param {Object} data
|
|
* {
|
|
* newValue: The new value
|
|
* oldValue: The old value
|
|
* pref: The name of the preference that has changed
|
|
* }
|
|
*/
|
|
_prefChanged: function(event, data) {
|
|
switch(data.pref) {
|
|
case "devtools.cache.disabled":
|
|
this._applyCacheSettings();
|
|
break;
|
|
case "devtools.serviceWorkers.testing.enabled":
|
|
this._applyServiceWorkersTestingSettings();
|
|
break;
|
|
}
|
|
},
|
|
|
|
_buildOptions: function() {
|
|
let key = this.doc.getElementById("toolbox-options-key");
|
|
key.addEventListener("command", () => {
|
|
this.selectTool("options");
|
|
}, true);
|
|
},
|
|
|
|
_splitConsoleOnKeypress: function(e) {
|
|
if (e.keyCode === e.DOM_VK_ESCAPE) {
|
|
this.toggleSplitConsole();
|
|
// If the debugger is paused, don't let the ESC key stop any pending
|
|
// navigation.
|
|
let jsdebugger = this.getPanel("jsdebugger");
|
|
if (jsdebugger && jsdebugger.panelWin.gThreadClient.state == "paused") {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
},
|
|
|
|
_addReloadKeys: function() {
|
|
[
|
|
["toolbox-reload-key", false],
|
|
["toolbox-reload-key2", false],
|
|
["toolbox-force-reload-key", true],
|
|
["toolbox-force-reload-key2", true]
|
|
].forEach(([id, force]) => {
|
|
this.doc.getElementById(id).addEventListener("command", (event) => {
|
|
this.reloadTarget(force);
|
|
}, true);
|
|
});
|
|
},
|
|
|
|
_addHostListeners: function() {
|
|
let nextKey = this.doc.getElementById("toolbox-next-tool-key");
|
|
nextKey.addEventListener("command", this.selectNextTool.bind(this), true);
|
|
let prevKey = this.doc.getElementById("toolbox-previous-tool-key");
|
|
prevKey.addEventListener("command", this.selectPreviousTool.bind(this), true);
|
|
|
|
// Split console uses keypress instead of command so the event can be
|
|
// cancelled with stopPropagation on the keypress, and not preventDefault.
|
|
this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
|
|
|
|
this.doc.addEventListener("focus", this._onFocus, true);
|
|
},
|
|
|
|
_saveSplitConsoleHeight: function() {
|
|
Services.prefs.setIntPref(SPLITCONSOLE_HEIGHT_PREF,
|
|
this.webconsolePanel.height);
|
|
},
|
|
|
|
/**
|
|
* Make sure that the console is showing up properly based on all the
|
|
* possible conditions.
|
|
* 1) If the console tab is selected, then regardless of split state
|
|
* it should take up the full height of the deck, and we should
|
|
* hide the deck and splitter.
|
|
* 2) If the console tab is not selected and it is split, then we should
|
|
* show the splitter, deck, and console.
|
|
* 3) If the console tab is not selected and it is *not* split,
|
|
* then we should hide the console and splitter, and show the deck
|
|
* at full height.
|
|
*/
|
|
_refreshConsoleDisplay: function() {
|
|
let deck = this.doc.getElementById("toolbox-deck");
|
|
let webconsolePanel = this.webconsolePanel;
|
|
let splitter = this.doc.getElementById("toolbox-console-splitter");
|
|
let openedConsolePanel = this.currentToolId === "webconsole";
|
|
|
|
if (openedConsolePanel) {
|
|
deck.setAttribute("collapsed", "true");
|
|
splitter.setAttribute("hidden", "true");
|
|
webconsolePanel.removeAttribute("collapsed");
|
|
} else {
|
|
deck.removeAttribute("collapsed");
|
|
if (this.splitConsole) {
|
|
webconsolePanel.removeAttribute("collapsed");
|
|
splitter.removeAttribute("hidden");
|
|
} else {
|
|
webconsolePanel.setAttribute("collapsed", "true");
|
|
splitter.setAttribute("hidden", "true");
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Wire up the listeners for the zoom keys.
|
|
*/
|
|
_addZoomKeys: function() {
|
|
let inKey = this.doc.getElementById("toolbox-zoom-in-key");
|
|
inKey.addEventListener("command", this.zoomIn.bind(this), true);
|
|
|
|
let inKey2 = this.doc.getElementById("toolbox-zoom-in-key2");
|
|
inKey2.addEventListener("command", this.zoomIn.bind(this), true);
|
|
|
|
let outKey = this.doc.getElementById("toolbox-zoom-out-key");
|
|
outKey.addEventListener("command", this.zoomOut.bind(this), true);
|
|
|
|
let resetKey = this.doc.getElementById("toolbox-zoom-reset-key");
|
|
resetKey.addEventListener("command", this.zoomReset.bind(this), true);
|
|
},
|
|
|
|
_disableZoomKeys: function() {
|
|
let inKey = this.doc.getElementById("toolbox-zoom-in-key");
|
|
inKey.setAttribute("disabled", "true");
|
|
|
|
let inKey2 = this.doc.getElementById("toolbox-zoom-in-key2");
|
|
inKey2.setAttribute("disabled", "true");
|
|
|
|
let outKey = this.doc.getElementById("toolbox-zoom-out-key");
|
|
outKey.setAttribute("disabled", "true");
|
|
|
|
let resetKey = this.doc.getElementById("toolbox-zoom-reset-key");
|
|
resetKey.setAttribute("disabled", "true");
|
|
},
|
|
|
|
/**
|
|
* Set zoom on toolbox to whatever the last setting was.
|
|
*/
|
|
_loadInitialZoom: function() {
|
|
this.setZoom(this.zoomValue);
|
|
},
|
|
|
|
/**
|
|
* Increase zoom level of toolbox window - make things bigger.
|
|
*/
|
|
zoomIn: function() {
|
|
this.setZoom(this.zoomValue + 0.1);
|
|
},
|
|
|
|
/**
|
|
* Decrease zoom level of toolbox window - make things smaller.
|
|
*/
|
|
zoomOut: function() {
|
|
this.setZoom(this.zoomValue - 0.1);
|
|
},
|
|
|
|
/**
|
|
* Reset zoom level of the toolbox window.
|
|
*/
|
|
zoomReset: function() {
|
|
this.setZoom(1);
|
|
},
|
|
|
|
/**
|
|
* Set zoom level of the toolbox window.
|
|
*
|
|
* @param {number} zoomValue
|
|
* Zoom level e.g. 1.2
|
|
*/
|
|
setZoom: function(zoomValue) {
|
|
// cap zoom value
|
|
zoomValue = Math.max(zoomValue, MIN_ZOOM);
|
|
zoomValue = Math.min(zoomValue, MAX_ZOOM);
|
|
|
|
let contViewer = this.frame.docShell.contentViewer;
|
|
|
|
contViewer.fullZoom = zoomValue;
|
|
|
|
Services.prefs.setCharPref(ZOOM_PREF, zoomValue);
|
|
},
|
|
|
|
/**
|
|
* Adds the keys and commands to the Toolbox Window in window mode.
|
|
*/
|
|
_addKeysToWindow: function() {
|
|
if (this.hostType != Toolbox.HostType.WINDOW) {
|
|
return;
|
|
}
|
|
|
|
let doc = this.doc.defaultView.parent.document;
|
|
|
|
for (let [id, toolDefinition] of gDevTools.getToolDefinitionMap()) {
|
|
// Prevent multiple entries for the same tool.
|
|
if (!toolDefinition.key || doc.getElementById("key_" + id)) {
|
|
continue;
|
|
}
|
|
|
|
let toolId = id;
|
|
let key = doc.createElement("key");
|
|
|
|
key.id = "key_" + toolId;
|
|
|
|
if (toolDefinition.key.startsWith("VK_")) {
|
|
key.setAttribute("keycode", toolDefinition.key);
|
|
} else {
|
|
key.setAttribute("key", toolDefinition.key);
|
|
}
|
|
|
|
key.setAttribute("modifiers", toolDefinition.modifiers);
|
|
key.setAttribute("oncommand", "void(0);"); // needed. See bug 371900
|
|
key.addEventListener("command", () => {
|
|
this.selectTool(toolId).then(() => this.fireCustomKey(toolId));
|
|
}, true);
|
|
doc.getElementById("toolbox-keyset").appendChild(key);
|
|
}
|
|
|
|
// Add key for toggling the browser console from the detached window
|
|
if (!doc.getElementById("key_browserconsole")) {
|
|
let key = doc.createElement("key");
|
|
key.id = "key_browserconsole";
|
|
|
|
key.setAttribute("key", toolboxStrings("browserConsoleCmd.commandkey"));
|
|
key.setAttribute("modifiers", "accel,shift");
|
|
key.setAttribute("oncommand", "void(0)"); // needed. See bug 371900
|
|
key.addEventListener("command", () => {
|
|
HUDService.toggleBrowserConsole();
|
|
}, true);
|
|
doc.getElementById("toolbox-keyset").appendChild(key);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle any custom key events. Returns true if there was a custom key binding run
|
|
* @param {string} toolId
|
|
* Which tool to run the command on (skip if not current)
|
|
*/
|
|
fireCustomKey: function(toolId) {
|
|
let toolDefinition = gDevTools.getToolDefinition(toolId);
|
|
|
|
if (toolDefinition.onkey &&
|
|
((this.currentToolId === toolId) ||
|
|
(toolId == "webconsole" && this.splitConsole))) {
|
|
toolDefinition.onkey(this.getCurrentPanel(), this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Build the buttons for changing hosts. Called every time
|
|
* the host changes.
|
|
*/
|
|
_buildDockButtons: function() {
|
|
let dockBox = this.doc.getElementById("toolbox-dock-buttons");
|
|
|
|
while (dockBox.firstChild) {
|
|
dockBox.removeChild(dockBox.firstChild);
|
|
}
|
|
|
|
if (!this._target.isLocalTab) {
|
|
return;
|
|
}
|
|
|
|
if (this.hostType == Toolbox.HostType.WINDOW) {
|
|
this.closeButton.setAttribute("hidden", "true");
|
|
} else {
|
|
this.closeButton.removeAttribute("hidden");
|
|
}
|
|
|
|
let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
|
|
|
|
for (let type in Toolbox.HostType) {
|
|
let position = Toolbox.HostType[type];
|
|
if (position == this.hostType ||
|
|
position == Toolbox.HostType.CUSTOM ||
|
|
(!sideEnabled && position == Toolbox.HostType.SIDE)) {
|
|
continue;
|
|
}
|
|
|
|
let button = this.doc.createElement("toolbarbutton");
|
|
button.id = "toolbox-dock-" + position;
|
|
button.className = "toolbox-dock-button";
|
|
button.setAttribute("tooltiptext", toolboxStrings("toolboxDockButtons." +
|
|
position + ".tooltip"));
|
|
button.addEventListener("command", () => {
|
|
this.switchHost(position);
|
|
});
|
|
|
|
dockBox.appendChild(button);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add tabs to the toolbox UI for registered tools
|
|
*/
|
|
_buildTabs: function() {
|
|
for (let definition of gDevTools.getToolDefinitionArray()) {
|
|
this._buildTabForTool(definition);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
|
|
*/
|
|
_buildButtons: function() {
|
|
if (!this.target.isAddon) {
|
|
this._buildPickerButton();
|
|
}
|
|
|
|
let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
|
|
let environment = CommandUtils.createEnvironment(this, '_target');
|
|
return CommandUtils.createRequisition(environment).then(requisition => {
|
|
this._requisition = requisition;
|
|
return CommandUtils.createButtons(spec, this.target, this.doc,
|
|
requisition).then(buttons => {
|
|
let container = this.doc.getElementById("toolbox-buttons");
|
|
buttons.forEach(button=> {
|
|
if (button) {
|
|
container.appendChild(button);
|
|
}
|
|
});
|
|
this.setToolboxButtonsVisibility();
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Adding the element picker button is done here unlike the other buttons
|
|
* since we want it to work for remote targets too
|
|
*/
|
|
_buildPickerButton: function() {
|
|
this._pickerButton = this.doc.createElement("toolbarbutton");
|
|
this._pickerButton.id = "command-button-pick";
|
|
this._pickerButton.className = "command-button command-button-invertable";
|
|
this._pickerButton.setAttribute("tooltiptext", toolboxStrings("pickButton.tooltip"));
|
|
this._pickerButton.setAttribute("hidden", "true");
|
|
|
|
let container = this.doc.querySelector("#toolbox-picker-container");
|
|
container.appendChild(this._pickerButton);
|
|
|
|
this._togglePicker = this.highlighterUtils.togglePicker.bind(this.highlighterUtils);
|
|
this._pickerButton.addEventListener("command", this._togglePicker, false);
|
|
},
|
|
|
|
/**
|
|
* Apply the current cache setting from devtools.cache.disabled to this
|
|
* toolbox's tab.
|
|
*/
|
|
_applyCacheSettings: function() {
|
|
let pref = "devtools.cache.disabled";
|
|
let cacheDisabled = Services.prefs.getBoolPref(pref);
|
|
|
|
if (this.target.activeTab) {
|
|
this.target.activeTab.reconfigure({"cacheDisabled": cacheDisabled});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Apply the current service workers testing setting from
|
|
* devtools.serviceWorkers.testing.enabled to this toolbox's tab.
|
|
*/
|
|
_applyServiceWorkersTestingSettings: function() {
|
|
let pref = "devtools.serviceWorkers.testing.enabled";
|
|
let serviceWorkersTestingEnabled =
|
|
Services.prefs.getBoolPref(pref) || false;
|
|
|
|
if (this.target.activeTab) {
|
|
this.target.activeTab.reconfigure({
|
|
"serviceWorkersTestingEnabled": serviceWorkersTestingEnabled
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Setter for the checked state of the picker button in the toolbar
|
|
* @param {Boolean} isChecked
|
|
*/
|
|
set pickerButtonChecked(isChecked) {
|
|
if (isChecked) {
|
|
this._pickerButton.setAttribute("checked", "true");
|
|
} else {
|
|
this._pickerButton.removeAttribute("checked");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Return all toolbox buttons (command buttons, plus any others that were
|
|
* added manually).
|
|
*/
|
|
get toolboxButtons() {
|
|
return ToolboxButtons.map(options => {
|
|
let button = this.doc.getElementById(options.id);
|
|
// Some buttons may not exist inside of Browser Toolbox
|
|
if (!button) {
|
|
return false;
|
|
}
|
|
|
|
// Disable tilt in E10S mode. Removing it from the list of toolbox buttons
|
|
// allows a bunch of tests to pass without modification.
|
|
if (this.target.isMultiProcess && options.id === "command-button-tilt") {
|
|
return false;
|
|
}
|
|
|
|
return {
|
|
id: options.id,
|
|
button: button,
|
|
label: button.getAttribute("tooltiptext"),
|
|
visibilityswitch: "devtools." + options.id + ".enabled",
|
|
isTargetSupported: options.isTargetSupported ? options.isTargetSupported
|
|
: target => target.isLocalTab
|
|
};
|
|
}).filter(button=>button);
|
|
},
|
|
|
|
/**
|
|
* Ensure the visibility of each toolbox button matches the
|
|
* preference value. Simply hide buttons that are preffed off.
|
|
*/
|
|
setToolboxButtonsVisibility: function() {
|
|
this.toolboxButtons.forEach(buttonSpec => {
|
|
let { visibilityswitch, id, button, isTargetSupported } = buttonSpec;
|
|
let on = true;
|
|
try {
|
|
on = Services.prefs.getBoolPref(visibilityswitch);
|
|
} catch (ex) { }
|
|
|
|
on = on && isTargetSupported(this.target);
|
|
|
|
if (button) {
|
|
if (on) {
|
|
button.removeAttribute("hidden");
|
|
} else {
|
|
button.setAttribute("hidden", "true");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Tilt is handled separately because it is disabled in E10S mode. Because
|
|
// we have removed tilt from toolboxButtons we have to deal with it here.
|
|
let tiltEnabled = !this.target.isMultiProcess &&
|
|
Services.prefs.getBoolPref("devtools.command-button-tilt.enabled");
|
|
let tiltButton = this.doc.getElementById("command-button-tilt");
|
|
// Remote toolboxes don't add the button to the DOM at all
|
|
if (!tiltButton) {
|
|
return;
|
|
}
|
|
|
|
if (tiltEnabled) {
|
|
tiltButton.removeAttribute("hidden");
|
|
} else {
|
|
tiltButton.setAttribute("hidden", "true");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Build a tab for one tool definition and add to the toolbox
|
|
*
|
|
* @param {string} toolDefinition
|
|
* Tool definition of the tool to build a tab for.
|
|
*/
|
|
_buildTabForTool: function(toolDefinition) {
|
|
if (!toolDefinition.isTargetSupported(this._target)) {
|
|
return;
|
|
}
|
|
|
|
let tabs = this.doc.getElementById("toolbox-tabs");
|
|
let deck = this.doc.getElementById("toolbox-deck");
|
|
|
|
let id = toolDefinition.id;
|
|
|
|
if (toolDefinition.ordinal == undefined || toolDefinition.ordinal < 0) {
|
|
toolDefinition.ordinal = MAX_ORDINAL;
|
|
}
|
|
|
|
let radio = this.doc.createElement("radio");
|
|
// The radio element is not being used in the conventional way, thus
|
|
// the devtools-tab class replaces the radio XBL binding with its base
|
|
// binding (the control-item binding).
|
|
radio.className = "devtools-tab";
|
|
radio.id = "toolbox-tab-" + id;
|
|
radio.setAttribute("toolid", id);
|
|
radio.setAttribute("ordinal", toolDefinition.ordinal);
|
|
radio.setAttribute("tooltiptext", toolDefinition.tooltip);
|
|
if (toolDefinition.invertIconForLightTheme) {
|
|
radio.setAttribute("icon-invertable", "true");
|
|
}
|
|
|
|
radio.addEventListener("command", () => {
|
|
this.selectTool(id);
|
|
});
|
|
|
|
// spacer lets us center the image and label, while allowing cropping
|
|
let spacer = this.doc.createElement("spacer");
|
|
spacer.setAttribute("flex", "1");
|
|
radio.appendChild(spacer);
|
|
|
|
if (toolDefinition.icon) {
|
|
let image = this.doc.createElement("image");
|
|
image.className = "default-icon";
|
|
image.setAttribute("src",
|
|
toolDefinition.icon || toolDefinition.highlightedicon);
|
|
radio.appendChild(image);
|
|
// Adding the highlighted icon image
|
|
image = this.doc.createElement("image");
|
|
image.className = "highlighted-icon";
|
|
image.setAttribute("src",
|
|
toolDefinition.highlightedicon || toolDefinition.icon);
|
|
radio.appendChild(image);
|
|
}
|
|
|
|
if (toolDefinition.label && !toolDefinition.iconOnly) {
|
|
let label = this.doc.createElement("label");
|
|
label.setAttribute("value", toolDefinition.label)
|
|
label.setAttribute("crop", "end");
|
|
label.setAttribute("flex", "1");
|
|
radio.appendChild(label);
|
|
radio.setAttribute("flex", "1");
|
|
}
|
|
|
|
if (!toolDefinition.bgTheme) {
|
|
toolDefinition.bgTheme = "theme-toolbar";
|
|
}
|
|
let vbox = this.doc.createElement("vbox");
|
|
vbox.className = "toolbox-panel " + toolDefinition.bgTheme;
|
|
|
|
// There is already a container for the webconsole frame.
|
|
if (!this.doc.getElementById("toolbox-panel-" + id)) {
|
|
vbox.id = "toolbox-panel-" + id;
|
|
}
|
|
|
|
if (id === "options") {
|
|
// Options panel is special. It doesn't belong in the same container as
|
|
// the other tabs.
|
|
radio.setAttribute("role", "button");
|
|
let optionTabContainer = this.doc.getElementById("toolbox-option-container");
|
|
optionTabContainer.appendChild(radio);
|
|
deck.appendChild(vbox);
|
|
} else {
|
|
radio.setAttribute("role", "tab");
|
|
|
|
// If there is no tab yet, or the ordinal to be added is the largest one.
|
|
if (tabs.childNodes.length == 0 ||
|
|
tabs.lastChild.getAttribute("ordinal") <= toolDefinition.ordinal) {
|
|
tabs.appendChild(radio);
|
|
deck.appendChild(vbox);
|
|
} else {
|
|
// else, iterate over all the tabs to get the correct location.
|
|
Array.some(tabs.childNodes, (node, i) => {
|
|
if (+node.getAttribute("ordinal") > toolDefinition.ordinal) {
|
|
tabs.insertBefore(radio, node);
|
|
deck.insertBefore(vbox, deck.childNodes[i]);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
this._addKeysToWindow();
|
|
},
|
|
|
|
/**
|
|
* Ensure the tool with the given id is loaded.
|
|
*
|
|
* @param {string} id
|
|
* The id of the tool to load.
|
|
*/
|
|
loadTool: function(id) {
|
|
if (id === "inspector" && !this._inspector) {
|
|
return this.initInspector().then(() => {
|
|
return this.loadTool(id);
|
|
});
|
|
}
|
|
|
|
let deferred = promise.defer();
|
|
let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
|
|
|
|
if (iframe) {
|
|
let panel = this._toolPanels.get(id);
|
|
if (panel) {
|
|
deferred.resolve(panel);
|
|
} else {
|
|
this.once(id + "-ready", panel => {
|
|
deferred.resolve(panel);
|
|
});
|
|
}
|
|
return deferred.promise;
|
|
}
|
|
|
|
let definition = gDevTools.getToolDefinition(id);
|
|
if (!definition) {
|
|
deferred.reject(new Error("no such tool id "+id));
|
|
return deferred.promise;
|
|
}
|
|
|
|
iframe = this.doc.createElement("iframe");
|
|
iframe.className = "toolbox-panel-iframe";
|
|
iframe.id = "toolbox-panel-iframe-" + id;
|
|
iframe.setAttribute("flex", 1);
|
|
iframe.setAttribute("forceOwnRefreshDriver", "");
|
|
iframe.tooltip = "aHTMLTooltip";
|
|
iframe.style.visibility = "hidden";
|
|
|
|
gDevTools.emit(id + "-init", this, iframe);
|
|
this.emit(id + "-init", iframe);
|
|
|
|
// If no parent yet, append the frame into default location.
|
|
if (!iframe.parentNode) {
|
|
let vbox = this.doc.getElementById("toolbox-panel-" + id);
|
|
vbox.appendChild(iframe);
|
|
}
|
|
|
|
let onLoad = () => {
|
|
// Prevent flicker while loading by waiting to make visible until now.
|
|
iframe.style.visibility = "visible";
|
|
|
|
// The build method should return a panel instance, so events can
|
|
// be fired with the panel as an argument. However, in order to keep
|
|
// backward compatibility with existing extensions do a check
|
|
// for a promise return value.
|
|
let built = definition.build(iframe.contentWindow, this);
|
|
if (!(built instanceof Promise)) {
|
|
let panel = built;
|
|
iframe.panel = panel;
|
|
|
|
// The panel instance is expected to fire (and listen to) various
|
|
// framework events, so make sure it's properly decorated with
|
|
// appropriate API (on, off, once, emit).
|
|
// In this case we decorate panel instances directly returned by
|
|
// the tool definition 'build' method.
|
|
if (typeof panel.emit == "undefined") {
|
|
EventEmitter.decorate(panel);
|
|
}
|
|
|
|
gDevTools.emit(id + "-build", this, panel);
|
|
this.emit(id + "-build", panel);
|
|
|
|
// The panel can implement an 'open' method for asynchronous
|
|
// initialization sequence.
|
|
if (typeof panel.open == "function") {
|
|
built = panel.open();
|
|
} else {
|
|
let deferred = promise.defer();
|
|
deferred.resolve(panel);
|
|
built = deferred.promise;
|
|
}
|
|
}
|
|
|
|
// Wait till the panel is fully ready and fire 'ready' events.
|
|
promise.resolve(built).then((panel) => {
|
|
this._toolPanels.set(id, panel);
|
|
|
|
// Make sure to decorate panel object with event API also in case
|
|
// where the tool definition 'build' method returns only a promise
|
|
// and the actual panel instance is available as soon as the
|
|
// promise is resolved.
|
|
if (typeof panel.emit == "undefined") {
|
|
EventEmitter.decorate(panel);
|
|
}
|
|
|
|
gDevTools.emit(id + "-ready", this, panel);
|
|
this.emit(id + "-ready", panel);
|
|
|
|
deferred.resolve(panel);
|
|
}, console.error);
|
|
};
|
|
|
|
iframe.setAttribute("src", definition.url);
|
|
if (definition.panelLabel) {
|
|
iframe.setAttribute("aria-label", definition.panelLabel);
|
|
}
|
|
|
|
// Depending on the host, iframe.contentWindow is not always
|
|
// defined at this moment. If it is not defined, we use an
|
|
// event listener on the iframe DOM node. If it's defined,
|
|
// we use the chromeEventHandler. We can't use a listener
|
|
// on the DOM node every time because this won't work
|
|
// if the (xul chrome) iframe is loaded in a content docshell.
|
|
if (iframe.contentWindow) {
|
|
let domHelper = new DOMHelpers(iframe.contentWindow);
|
|
domHelper.onceDOMReady(onLoad);
|
|
} else {
|
|
let callback = () => {
|
|
iframe.removeEventListener("DOMContentLoaded", callback);
|
|
onLoad();
|
|
}
|
|
iframe.addEventListener("DOMContentLoaded", callback);
|
|
}
|
|
|
|
return deferred.promise;
|
|
},
|
|
|
|
/**
|
|
* Switch to the tool with the given id
|
|
*
|
|
* @param {string} id
|
|
* The id of the tool to switch to
|
|
*/
|
|
selectTool: function(id) {
|
|
let selected = this.doc.querySelector(".devtools-tab[selected]");
|
|
if (selected) {
|
|
selected.removeAttribute("selected");
|
|
selected.setAttribute("aria-selected", "false");
|
|
}
|
|
|
|
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
|
tab.setAttribute("selected", "true");
|
|
tab.setAttribute("aria-selected", "true");
|
|
|
|
// If options is selected, the separator between it and the
|
|
// command buttons should be hidden.
|
|
let sep = this.doc.getElementById("toolbox-controls-separator");
|
|
if (id === "options") {
|
|
sep.setAttribute("invisible", "true");
|
|
} else {
|
|
sep.removeAttribute("invisible");
|
|
}
|
|
|
|
if (this.currentToolId == id) {
|
|
// re-focus tool to get key events again
|
|
this.focusTool(id);
|
|
|
|
// Return the existing panel in order to have a consistent return value.
|
|
return promise.resolve(this._toolPanels.get(id));
|
|
}
|
|
|
|
if (!this.isReady) {
|
|
throw new Error("Can't select tool, wait for toolbox 'ready' event");
|
|
}
|
|
|
|
tab = this.doc.getElementById("toolbox-tab-" + id);
|
|
|
|
if (!tab) {
|
|
throw new Error("No tool found");
|
|
}
|
|
|
|
let tabstrip = this.doc.getElementById("toolbox-tabs");
|
|
|
|
// select the right tab, making 0th index the default tab if right tab not
|
|
// found.
|
|
tabstrip.selectedItem = tab || tabstrip.childNodes[0];
|
|
|
|
// and select the right iframe
|
|
let deck = this.doc.getElementById("toolbox-deck");
|
|
let panel = this.doc.getElementById("toolbox-panel-" + id);
|
|
deck.selectedPanel = panel;
|
|
|
|
this.currentToolId = id;
|
|
this._refreshConsoleDisplay();
|
|
if (id != "options") {
|
|
Services.prefs.setCharPref(this._prefs.LAST_TOOL, id);
|
|
}
|
|
|
|
return this.loadTool(id).then(panel => {
|
|
// focus the tool's frame to start receiving key events
|
|
this.focusTool(id);
|
|
|
|
this.emit("select", id);
|
|
this.emit(id + "-selected", panel);
|
|
return panel;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Focus a tool's panel by id
|
|
* @param {string} id
|
|
* The id of tool to focus
|
|
*/
|
|
focusTool: function(id) {
|
|
let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
|
|
iframe.focus();
|
|
},
|
|
|
|
/**
|
|
* Focus split console's input line
|
|
*/
|
|
focusConsoleInput: function() {
|
|
let consolePanel = this.getPanel("webconsole");
|
|
if (consolePanel) {
|
|
consolePanel.focusInput();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* If the console is split and we are focusing an element outside
|
|
* of the console, then store the newly focused element, so that
|
|
* it can be restored once the split console closes.
|
|
*/
|
|
_onFocus: function({originalTarget}) {
|
|
// Ignore any non element nodes, or any elements contained
|
|
// within the webconsole frame.
|
|
let webconsoleURL = gDevTools.getToolDefinition("webconsole").url;
|
|
if (originalTarget.nodeType !== 1 ||
|
|
originalTarget.baseURI === webconsoleURL) {
|
|
return;
|
|
}
|
|
|
|
this._lastFocusedElement = originalTarget;
|
|
},
|
|
|
|
/**
|
|
* Opens the split console.
|
|
*
|
|
* @returns {Promise} a promise that resolves once the tool has been
|
|
* loaded and focused.
|
|
*/
|
|
openSplitConsole: function() {
|
|
this._splitConsole = true;
|
|
Services.prefs.setBoolPref(SPLITCONSOLE_ENABLED_PREF, true);
|
|
this._refreshConsoleDisplay();
|
|
this.emit("split-console");
|
|
|
|
return this.loadTool("webconsole").then(() => {
|
|
this.focusConsoleInput();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Closes the split console.
|
|
*
|
|
* @returns {Promise} a promise that resolves once the tool has been
|
|
* closed.
|
|
*/
|
|
closeSplitConsole: function() {
|
|
this._splitConsole = false;
|
|
Services.prefs.setBoolPref(SPLITCONSOLE_ENABLED_PREF, false);
|
|
this._refreshConsoleDisplay();
|
|
this.emit("split-console");
|
|
|
|
if (this._lastFocusedElement) {
|
|
this._lastFocusedElement.focus();
|
|
}
|
|
return promise.resolve();
|
|
},
|
|
|
|
/**
|
|
* Toggles the split state of the webconsole. If the webconsole panel
|
|
* is already selected then this command is ignored.
|
|
*
|
|
* @returns {Promise} a promise that resolves once the tool has been
|
|
* opened or closed.
|
|
*/
|
|
toggleSplitConsole: function() {
|
|
if (this.currentToolId !== "webconsole") {
|
|
return this.splitConsole ?
|
|
this.closeSplitConsole() :
|
|
this.openSplitConsole();
|
|
}
|
|
|
|
return promise.resolve();
|
|
},
|
|
|
|
/**
|
|
* Tells the target tab to reload.
|
|
*/
|
|
reloadTarget: function(force) {
|
|
this.target.activeTab.reload({ force: force });
|
|
},
|
|
|
|
/**
|
|
* Loads the tool next to the currently selected tool.
|
|
*/
|
|
selectNextTool: function() {
|
|
let tools = this.doc.querySelectorAll(".devtools-tab");
|
|
let selected = this.doc.querySelector(".devtools-tab[selected]");
|
|
let nextIndex = [...tools].indexOf(selected) + 1;
|
|
let next = tools[nextIndex] || tools[0];
|
|
let tool = next.getAttribute("toolid");
|
|
return this.selectTool(tool);
|
|
},
|
|
|
|
/**
|
|
* Loads the tool just left to the currently selected tool.
|
|
*/
|
|
selectPreviousTool: function() {
|
|
let tools = this.doc.querySelectorAll(".devtools-tab");
|
|
let selected = this.doc.querySelector(".devtools-tab[selected]");
|
|
let prevIndex = [...tools].indexOf(selected) - 1;
|
|
let prev = tools[prevIndex] || tools[tools.length - 1];
|
|
let tool = prev.getAttribute("toolid");
|
|
return this.selectTool(tool);
|
|
},
|
|
|
|
/**
|
|
* Highlights the tool's tab if it is not the currently selected tool.
|
|
*
|
|
* @param {string} id
|
|
* The id of the tool to highlight
|
|
*/
|
|
highlightTool: function(id) {
|
|
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
|
tab && tab.setAttribute("highlighted", "true");
|
|
},
|
|
|
|
/**
|
|
* De-highlights the tool's tab.
|
|
*
|
|
* @param {string} id
|
|
* The id of the tool to unhighlight
|
|
*/
|
|
unhighlightTool: function(id) {
|
|
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
|
tab && tab.removeAttribute("highlighted");
|
|
},
|
|
|
|
/**
|
|
* Raise the toolbox host.
|
|
*/
|
|
raise: function() {
|
|
this._host.raise();
|
|
},
|
|
|
|
/**
|
|
* Refresh the host's title.
|
|
*/
|
|
_refreshHostTitle: function() {
|
|
let toolName;
|
|
let toolDef = gDevTools.getToolDefinition(this.currentToolId);
|
|
if (toolDef) {
|
|
toolName = toolDef.label;
|
|
} else {
|
|
// no tool is selected
|
|
toolName = toolboxStrings("toolbox.defaultTitle");
|
|
}
|
|
let title = toolboxStrings("toolbox.titleTemplate",
|
|
toolName,
|
|
this.target.isAddon ?
|
|
this.target.name :
|
|
this.target.url || this.target.name);
|
|
this._host.setTitle(title);
|
|
},
|
|
|
|
_listFrames: function (event) {
|
|
if (!this._target.activeTab || !this._target.activeTab.traits.frames) {
|
|
// We are not targetting a regular TabActor
|
|
// it can be either an addon or browser toolbox actor
|
|
return promise.resolve();
|
|
}
|
|
let packet = {
|
|
to: this._target.form.actor,
|
|
type: "listFrames"
|
|
};
|
|
return this._target.client.request(packet, resp => {
|
|
this._updateFrames(null, { frames: resp.frames });
|
|
});
|
|
},
|
|
|
|
selectFrame: function (event) {
|
|
let windowId = event.target.getAttribute("data-window-id");
|
|
let packet = {
|
|
to: this._target.form.actor,
|
|
type: "switchToFrame",
|
|
windowId: windowId
|
|
};
|
|
this._target.client.request(packet);
|
|
// Wait for frameUpdate event to update the UI
|
|
},
|
|
|
|
_updateFrames: function (event, data) {
|
|
if (!Services.prefs.getBoolPref("devtools.command-button-frames.enabled")) {
|
|
return;
|
|
}
|
|
|
|
// We may receive this event before the toolbox is ready.
|
|
if (!this.isReady) {
|
|
return;
|
|
}
|
|
|
|
let menu = this.doc.getElementById("command-button-frames");
|
|
|
|
if (data.destroyAll) {
|
|
let menupopup = menu.firstChild;
|
|
while (menupopup.firstChild) {
|
|
menupopup.firstChild.remove();
|
|
}
|
|
return;
|
|
} else if (data.selected) {
|
|
let item = menu.querySelector("menuitem[data-window-id=\"" + data.selected + "\"]");
|
|
if (!item) {
|
|
return;
|
|
}
|
|
// Toggle the toolbarbutton if we selected a non top-level frame
|
|
if (item.hasAttribute("data-parent-id")) {
|
|
menu.setAttribute("checked", "true");
|
|
} else {
|
|
menu.removeAttribute("checked");
|
|
}
|
|
// Uncheck the previously selected frame
|
|
let selected = menu.querySelector("menuitem[checked=true]")
|
|
if (selected) {
|
|
selected.removeAttribute("checked");
|
|
}
|
|
// Check the new one
|
|
item.setAttribute("checked", "true");
|
|
} else if (data.frames) {
|
|
data.frames.forEach(win => {
|
|
let item = menu.querySelector("menuitem[data-window-id=\"" + win.id + "\"]");
|
|
if (win.destroy) {
|
|
if (item) {
|
|
item.remove();
|
|
}
|
|
return;
|
|
}
|
|
if (!item) {
|
|
item = this.doc.createElement("menuitem");
|
|
item.setAttribute("type", "radio");
|
|
item.setAttribute("data-window-id", win.id);
|
|
if (win.parentID) {
|
|
item.setAttribute("data-parent-id", win.parentID);
|
|
}
|
|
// If we register a root docshell and we don't have any selected,
|
|
// consider it as the currently targeted one.
|
|
if (!win.parentID && !menu.querySelector("menuitem[checked=true]")) {
|
|
item.setAttribute("checked", "true");
|
|
menu.removeAttribute("checked");
|
|
}
|
|
menu.firstChild.appendChild(item);
|
|
}
|
|
item.setAttribute("label", win.url);
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Create a host object based on the given host type.
|
|
*
|
|
* Warning: some hosts require that the toolbox target provides a reference to
|
|
* the attached tab. Not all Targets have a tab property - make sure you correctly
|
|
* mix and match hosts and targets.
|
|
*
|
|
* @param {string} hostType
|
|
* The host type of the new host object
|
|
*
|
|
* @return {Host} host
|
|
* The created host object
|
|
*/
|
|
_createHost: function(hostType, options) {
|
|
if (!Hosts[hostType]) {
|
|
throw new Error("Unknown hostType: " + hostType);
|
|
}
|
|
|
|
// clean up the toolbox if its window is closed
|
|
let newHost = new Hosts[hostType](this.target.tab, options);
|
|
newHost.on("window-closed", this.destroy);
|
|
return newHost;
|
|
},
|
|
|
|
/**
|
|
* Switch to a new host for the toolbox UI. E.g.
|
|
* bottom, sidebar, separate window.
|
|
*
|
|
* @param {string} hostType
|
|
* The host type of the new host object
|
|
*/
|
|
switchHost: function(hostType) {
|
|
if (hostType == this._host.type || !this._target.isLocalTab) {
|
|
return null;
|
|
}
|
|
|
|
let newHost = this._createHost(hostType);
|
|
return newHost.create().then(iframe => {
|
|
// change toolbox document's parent to the new host
|
|
iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
|
|
iframe.swapFrameLoaders(this.frame);
|
|
|
|
this._host.off("window-closed", this.destroy);
|
|
this.destroyHost();
|
|
|
|
this._host = newHost;
|
|
|
|
if (this.hostType != Toolbox.HostType.CUSTOM) {
|
|
Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
|
|
}
|
|
|
|
this._buildDockButtons();
|
|
this._addKeysToWindow();
|
|
|
|
this.emit("host-changed");
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handler for the tool-registered event.
|
|
* @param {string} event
|
|
* Name of the event ("tool-registered")
|
|
* @param {string} toolId
|
|
* Id of the tool that was registered
|
|
*/
|
|
_toolRegistered: function(event, toolId) {
|
|
let tool = gDevTools.getToolDefinition(toolId);
|
|
this._buildTabForTool(tool);
|
|
},
|
|
|
|
/**
|
|
* Handler for the tool-unregistered event.
|
|
* @param {string} event
|
|
* Name of the event ("tool-unregistered")
|
|
* @param {string|object} toolId
|
|
* Definition or id of the tool that was unregistered. Passing the
|
|
* tool id should be avoided as it is a temporary measure.
|
|
*/
|
|
_toolUnregistered: function(event, toolId) {
|
|
if (typeof toolId != "string") {
|
|
toolId = toolId.id;
|
|
}
|
|
|
|
if (this._toolPanels.has(toolId)) {
|
|
let instance = this._toolPanels.get(toolId);
|
|
instance.destroy();
|
|
this._toolPanels.delete(toolId);
|
|
}
|
|
|
|
let radio = this.doc.getElementById("toolbox-tab-" + toolId);
|
|
let panel = this.doc.getElementById("toolbox-panel-" + toolId);
|
|
|
|
if (radio) {
|
|
if (this.currentToolId == toolId) {
|
|
let nextToolName = null;
|
|
if (radio.nextSibling) {
|
|
nextToolName = radio.nextSibling.getAttribute("toolid");
|
|
}
|
|
if (radio.previousSibling) {
|
|
nextToolName = radio.previousSibling.getAttribute("toolid");
|
|
}
|
|
if (nextToolName) {
|
|
this.selectTool(nextToolName);
|
|
}
|
|
}
|
|
radio.parentNode.removeChild(radio);
|
|
}
|
|
|
|
if (panel) {
|
|
panel.parentNode.removeChild(panel);
|
|
}
|
|
|
|
if (this.hostType == Toolbox.HostType.WINDOW) {
|
|
let doc = this.doc.defaultView.parent.document;
|
|
let key = doc.getElementById("key_" + toolId);
|
|
if (key) {
|
|
key.parentNode.removeChild(key);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initialize the inspector/walker/selection/highlighter fronts.
|
|
* Returns a promise that resolves when the fronts are initialized
|
|
*/
|
|
initInspector: function() {
|
|
if (!this._initInspector) {
|
|
this._initInspector = Task.spawn(function*() {
|
|
this._inspector = InspectorFront(this._target.client, this._target.form);
|
|
this._walker = yield this._inspector.getWalker(
|
|
{showAllAnonymousContent: Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent")}
|
|
);
|
|
this._selection = new Selection(this._walker);
|
|
|
|
if (this.highlighterUtils.isRemoteHighlightable()) {
|
|
this.walker.on("highlighter-ready", this._highlighterReady);
|
|
this.walker.on("highlighter-hide", this._highlighterHidden);
|
|
|
|
let autohide = !gDevTools.testing;
|
|
this._highlighter = yield this._inspector.getHighlighter(autohide);
|
|
}
|
|
}.bind(this));
|
|
}
|
|
return this._initInspector;
|
|
},
|
|
|
|
/**
|
|
* Destroy the inspector/walker/selection fronts
|
|
* Returns a promise that resolves when the fronts are destroyed
|
|
*/
|
|
destroyInspector: function() {
|
|
if (this._destroying) {
|
|
return this._destroying;
|
|
}
|
|
|
|
if (!this._inspector) {
|
|
return promise.resolve();
|
|
}
|
|
|
|
let outstanding = () => {
|
|
return Task.spawn(function*() {
|
|
yield this.highlighterUtils.stopPicker();
|
|
yield this._inspector.destroy();
|
|
if (this._highlighter) {
|
|
yield this._highlighter.destroy();
|
|
}
|
|
if (this._selection) {
|
|
this._selection.destroy();
|
|
}
|
|
|
|
if (this.walker) {
|
|
this.walker.off("highlighter-ready", this._highlighterReady);
|
|
this.walker.off("highlighter-hide", this._highlighterHidden);
|
|
}
|
|
|
|
this._inspector = null;
|
|
this._highlighter = null;
|
|
this._selection = null;
|
|
this._walker = null;
|
|
}.bind(this));
|
|
};
|
|
|
|
// Releasing the walker (if it has been created)
|
|
// This can fail, but in any case, we want to continue destroying the
|
|
// inspector/highlighter/selection
|
|
let walker = (this._destroying = this._walker) ?
|
|
this._walker.release() :
|
|
promise.resolve();
|
|
return walker.then(outstanding, outstanding);
|
|
},
|
|
|
|
/**
|
|
* Get the toolbox's notification box
|
|
*
|
|
* @return The notification box element.
|
|
*/
|
|
getNotificationBox: function() {
|
|
return this.doc.getElementById("toolbox-notificationbox");
|
|
},
|
|
|
|
/**
|
|
* Destroy the current host, and remove event listeners from its frame.
|
|
*
|
|
* @return {promise} to be resolved when the host is destroyed.
|
|
*/
|
|
destroyHost: function() {
|
|
// The host iframe's contentDocument may already be gone.
|
|
if (this.doc) {
|
|
this.doc.removeEventListener("keypress",
|
|
this._splitConsoleOnKeypress, false);
|
|
this.doc.removeEventListener("focus", this._onFocus, true);
|
|
}
|
|
return this._host.destroy();
|
|
},
|
|
|
|
/**
|
|
* Remove all UI elements, detach from target and clear up
|
|
*/
|
|
destroy: function() {
|
|
// If several things call destroy then we give them all the same
|
|
// destruction promise so we're sure to destroy only once
|
|
if (this._destroyer) {
|
|
return this._destroyer;
|
|
}
|
|
|
|
this.emit("destroy");
|
|
|
|
this._target.off("navigate", this._refreshHostTitle);
|
|
this._target.off("frame-update", this._updateFrames);
|
|
this.off("select", this._refreshHostTitle);
|
|
this.off("host-changed", this._refreshHostTitle);
|
|
this.off("ready", this._showDevEditionPromo);
|
|
|
|
gDevTools.off("tool-registered", this._toolRegistered);
|
|
gDevTools.off("tool-unregistered", this._toolUnregistered);
|
|
|
|
gDevTools.off("pref-changed", this._prefChanged);
|
|
|
|
this._lastFocusedElement = null;
|
|
if (this.webconsolePanel) {
|
|
this._saveSplitConsoleHeight();
|
|
this.webconsolePanel.removeEventListener("resize",
|
|
this._saveSplitConsoleHeight);
|
|
}
|
|
this.closeButton.removeEventListener("command", this.destroy, true);
|
|
|
|
let outstanding = [];
|
|
for (let [id, panel] of this._toolPanels) {
|
|
try {
|
|
gDevTools.emit(id + "-destroy", this, panel);
|
|
this.emit(id + "-destroy", panel);
|
|
|
|
outstanding.push(panel.destroy());
|
|
} catch (e) {
|
|
// We don't want to stop here if any panel fail to close.
|
|
console.error("Panel " + id + ":", e);
|
|
}
|
|
}
|
|
|
|
// Now that we are closing the toolbox we can re-enable the cache settings
|
|
// and disable the service workers testing settings for the current tab.
|
|
if (this.target.activeTab) {
|
|
this.target.activeTab.reconfigure({
|
|
"cacheDisabled": false,
|
|
"serviceWorkersTestingEnabled": false
|
|
});
|
|
}
|
|
|
|
// Destroying the walker and inspector fronts
|
|
outstanding.push(this.destroyInspector().then(() => {
|
|
// Removing buttons
|
|
if (this._pickerButton) {
|
|
this._pickerButton.removeEventListener("command", this._togglePicker, false);
|
|
this._pickerButton = null;
|
|
}
|
|
}));
|
|
|
|
// We need to grab a reference to win before this._host is destroyed.
|
|
let win = this.frame.ownerGlobal;
|
|
|
|
if (this._requisition) {
|
|
this._requisition.destroy();
|
|
}
|
|
|
|
// Finish all outstanding tasks (which means finish destroying panels and
|
|
// then destroying the host, successfully or not) before destroying the
|
|
// target.
|
|
this._destroyer = DevToolsUtils.settleAll(outstanding)
|
|
.catch(console.error)
|
|
.then(() => this.destroyHost())
|
|
.catch(console.error)
|
|
.then(() => {
|
|
// Targets need to be notified that the toolbox is being torn down.
|
|
// This is done after other destruction tasks since it may tear down
|
|
// fronts and the debugger transport which earlier destroy methods may
|
|
// require to complete.
|
|
if (!this._target) {
|
|
return null;
|
|
}
|
|
let target = this._target;
|
|
this._target = null;
|
|
this.highlighterUtils.release();
|
|
target.off("close", this.destroy);
|
|
return target.destroy();
|
|
}, console.error).then(() => {
|
|
this.emit("destroyed");
|
|
|
|
// Free _host after the call to destroyed in order to let a chance
|
|
// to destroyed listeners to still query toolbox attributes
|
|
this._host = null;
|
|
this._toolPanels.clear();
|
|
|
|
// Force GC to prevent long GC pauses when running tests and to free up
|
|
// memory in general when the toolbox is closed.
|
|
if (gDevTools.testing) {
|
|
win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils)
|
|
.garbageCollect();
|
|
}
|
|
}).then(null, console.error);
|
|
|
|
let leakCheckObserver = ({wrappedJSObject: barrier}) => {
|
|
// Make the leak detector wait until this toolbox is properly destroyed.
|
|
barrier.client.addBlocker("DevTools: Wait until toolbox is destroyed",
|
|
this._destroyer);
|
|
};
|
|
|
|
let topic = "shutdown-leaks-before-check";
|
|
Services.obs.addObserver(leakCheckObserver, topic, false);
|
|
this._destroyer.then(() => {
|
|
Services.obs.removeObserver(leakCheckObserver, topic);
|
|
});
|
|
|
|
return this._destroyer;
|
|
},
|
|
|
|
_highlighterReady: function() {
|
|
this.emit("highlighter-ready");
|
|
},
|
|
|
|
_highlighterHidden: function() {
|
|
this.emit("highlighter-hide");
|
|
},
|
|
|
|
/**
|
|
* For displaying the promotional Doorhanger on first opening of
|
|
* the developer tools, promoting the Developer Edition.
|
|
*/
|
|
_showDevEditionPromo: function() {
|
|
// Do not display in browser toolbox
|
|
if (this.target.chrome) {
|
|
return;
|
|
}
|
|
let window = this.frame.contentWindow;
|
|
showDoorhanger({ window, type: "deveditionpromo" });
|
|
}
|
|
};
|