summaryrefslogtreecommitdiffstats
path: root/browser/components/customizableui
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-06-04 13:17:38 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-06-04 13:17:38 +0200
commita1be17c1cea81ebb1e8b131a662c698d78f3f7f2 (patch)
treea92f7de513be600cc07bac458183e9af40e00c06 /browser/components/customizableui
parentbf11fdd304898ac675e39b01b280d39550e419d0 (diff)
downloadUXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.gz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.lz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.xz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.zip
Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
Diffstat (limited to 'browser/components/customizableui')
-rw-r--r--browser/components/customizableui/CustomizableUI.jsm4401
-rw-r--r--browser/components/customizableui/CustomizableWidgets.jsm1219
-rw-r--r--browser/components/customizableui/CustomizeMode.jsm2297
-rw-r--r--browser/components/customizableui/DragPositionManager.jsm420
-rw-r--r--browser/components/customizableui/PanelWideWidgetTracker.jsm172
-rw-r--r--browser/components/customizableui/ScrollbarSampler.jsm65
-rw-r--r--browser/components/customizableui/content/customizeMode.inc.xul82
-rw-r--r--browser/components/customizableui/content/jar.mn10
-rw-r--r--browser/components/customizableui/content/moz.build7
-rw-r--r--browser/components/customizableui/content/panelUI.css31
-rw-r--r--browser/components/customizableui/content/panelUI.inc.xul386
-rw-r--r--browser/components/customizableui/content/panelUI.js558
-rw-r--r--browser/components/customizableui/content/panelUI.xml509
-rw-r--r--browser/components/customizableui/content/toolbar.xml618
-rw-r--r--browser/components/customizableui/moz.build21
15 files changed, 0 insertions, 10796 deletions
diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm
deleted file mode 100644
index cb0f519b2..000000000
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ /dev/null
@@ -1,4401 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["CustomizableUI"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PanelWideWidgetTracker",
- "resource:///modules/PanelWideWidgetTracker.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
- "resource:///modules/CustomizableWidgets.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
- "resource://gre/modules/DeferredTask.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
- const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
- return Services.strings.createBundle(kUrl);
-});
-XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
- "resource://gre/modules/ShortcutUtils.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "gELS",
- "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
-XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
- "resource://gre/modules/LightweightThemeManager.jsm");
-
-const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-const kSpecialWidgetPfx = "customizableui-special-";
-
-const kPrefCustomizationState = "browser.uiCustomization.state";
-const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
-const kPrefCustomizationDebug = "browser.uiCustomization.debug";
-const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
-const kPrefWebIDEInNavbar = "devtools.webide.widget.inNavbarByDefault";
-
-const kExpectedWindowURL = "chrome://browser/content/browser.xul";
-
-/**
- * The keys are the handlers that are fired when the event type (the value)
- * is fired on the subview. A widget that provides a subview has the option
- * of providing onViewShowing and onViewHiding event handlers.
- */
-const kSubviewEvents = [
- "ViewShowing",
- "ViewHiding"
-];
-
-/**
- * The current version. We can use this to auto-add new default widgets as necessary.
- * (would be const but isn't because of testing purposes)
- */
-var kVersion = 6;
-
-/**
- * Buttons removed from built-ins by version they were removed. kVersion must be
- * bumped any time a new id is added to this. Use the button id as key, and
- * version the button is removed in as the value. e.g. "pocket-button": 5
- */
-var ObsoleteBuiltinButtons = {
- "pocket-button": 6
-};
-
-/**
- * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed
- * on their IDs.
- */
-var gPalette = new Map();
-
-/**
- * gAreas maps area IDs to Sets of properties about those areas. An area is a
- * place where a widget can be put.
- */
-var gAreas = new Map();
-
-/**
- * gPlacements maps area IDs to Arrays of widget IDs, indicating that the widgets
- * are placed within that area (either directly in the area node, or in the
- * customizationTarget of the node).
- */
-var gPlacements = new Map();
-
-/**
- * gFuturePlacements represent placements that will happen for areas that have
- * not yet loaded (due to lazy-loading). This can occur when add-ons register
- * widgets.
- */
-var gFuturePlacements = new Map();
-
-// XXXunf Temporary. Need a nice way to abstract functions to build widgets
-// of these types.
-var gSupportedWidgetTypes = new Set(["button", "view", "custom"]);
-
-/**
- * gPanelsForWindow is a list of known panels in a window which we may need to close
- * should command events fire which target them.
- */
-var gPanelsForWindow = new WeakMap();
-
-/**
- * gSeenWidgets remembers which widgets the user has seen for the first time
- * before. This way, if a new widget is created, and the user has not seen it
- * before, it can be put in its default location. Otherwise, it remains in the
- * palette.
- */
-var gSeenWidgets = new Set();
-
-/**
- * gDirtyAreaCache is a set of area IDs for areas where items have been added,
- * moved or removed at least once. This set is persisted, and is used to
- * optimize building of toolbars in the default case where no toolbars should
- * be "dirty".
- */
-var gDirtyAreaCache = new Set();
-
-/**
- * gPendingBuildAreas is a map from area IDs to map from build nodes to their
- * existing children at the time of node registration, that are waiting
- * for the area to be registered
- */
-var gPendingBuildAreas = new Map();
-
-var gSavedState = null;
-var gRestoring = false;
-var gDirty = false;
-var gInBatchStack = 0;
-var gResetting = false;
-var gUndoResetting = false;
-
-/**
- * gBuildAreas maps area IDs to actual area nodes within browser windows.
- */
-var gBuildAreas = new Map();
-
-/**
- * gBuildWindows is a map of windows that have registered build areas, mapped
- * to a Set of known toolboxes in that window.
- */
-var gBuildWindows = new Map();
-
-var gNewElementCount = 0;
-var gGroupWrapperCache = new Map();
-var gSingleWrapperCache = new WeakMap();
-var gListeners = new Set();
-
-var gUIStateBeforeReset = {
- uiCustomizationState: null,
- drawInTitlebar: null,
- currentTheme: null,
-};
-
-XPCOMUtils.defineLazyGetter(this, "log", () => {
- let scope = {};
- Cu.import("resource://gre/modules/Console.jsm", scope);
- let debug;
- try {
- debug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
- } catch (ex) {}
- let consoleOptions = {
- maxLogLevel: debug ? "all" : "log",
- prefix: "CustomizableUI",
- };
- return new scope.ConsoleAPI(consoleOptions);
-});
-
-var CustomizableUIInternal = {
- initialize: function() {
- log.debug("Initializing");
-
- this.addListener(this);
- this._defineBuiltInWidgets();
- this.loadSavedState();
- this._introduceNewBuiltinWidgets();
- this._markObsoleteBuiltinButtonsSeen();
-
- /**
- * Please be advised that adding items to the panel by default could
- * cause CART talos test regressions. This might happen when the
- * number of items in the panel causes the area to become "scrollable"
- * during the last phases of the transition. See bug 1230671 for an
- * example of this. Be sure that what you're adding really needs to go
- * into the panel by default, and if it does, consider swapping
- * something out for it.
- */
- let panelPlacements = [
- "edit-controls",
- "zoom-controls",
- "new-window-button",
- "privatebrowsing-button",
- "save-page-button",
- "print-button",
- "history-panelmenu",
- "fullscreen-button",
- "find-button",
- "preferences-button",
- "add-ons-button",
- "sync-button",
- ];
-
- if (!AppConstants.MOZ_DEV_EDITION) {
- panelPlacements.splice(-1, 0, "developer-button");
- }
-
- let showCharacterEncoding = Services.prefs.getComplexValue(
- "browser.menu.showCharacterEncoding",
- Ci.nsIPrefLocalizedString
- ).data;
- if (showCharacterEncoding == "true") {
- panelPlacements.push("characterencoding-button");
- }
-
- this.registerArea(CustomizableUI.AREA_PANEL, {
- anchor: "PanelUI-menu-button",
- type: CustomizableUI.TYPE_MENU_PANEL,
- defaultPlacements: panelPlacements
- }, true);
- PanelWideWidgetTracker.init();
-
- let navbarPlacements = [
- "urlbar-container",
- "search-container",
- "bookmarks-menu-button",
- "downloads-button",
- "home-button",
- ];
-
- if (AppConstants.MOZ_DEV_EDITION) {
- navbarPlacements.splice(2, 0, "developer-button");
- }
-
- if (Services.prefs.getBoolPref(kPrefWebIDEInNavbar)) {
- navbarPlacements.push("webide-button");
- }
-
- // Place this last, when createWidget is called for pocket, it will
- // append to the toolbar.
- if (Services.prefs.getPrefType("extensions.pocket.enabled") != Services.prefs.PREF_INVALID &&
- Services.prefs.getBoolPref("extensions.pocket.enabled")) {
- navbarPlacements.push("pocket-button");
- }
-
- this.registerArea(CustomizableUI.AREA_NAVBAR, {
- legacy: true,
- type: CustomizableUI.TYPE_TOOLBAR,
- overflowable: true,
- defaultPlacements: navbarPlacements,
- defaultCollapsed: false,
- }, true);
-
- if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
- this.registerArea(CustomizableUI.AREA_MENUBAR, {
- legacy: true,
- type: CustomizableUI.TYPE_TOOLBAR,
- defaultPlacements: [
- "menubar-items",
- ],
- defaultCollapsed: true,
- }, true);
- }
-
- this.registerArea(CustomizableUI.AREA_TABSTRIP, {
- legacy: true,
- type: CustomizableUI.TYPE_TOOLBAR,
- defaultPlacements: [
- "tabbrowser-tabs",
- "new-tab-button",
- "alltabs-button",
- ],
- defaultCollapsed: null,
- }, true);
- this.registerArea(CustomizableUI.AREA_BOOKMARKS, {
- legacy: true,
- type: CustomizableUI.TYPE_TOOLBAR,
- defaultPlacements: [
- "personal-bookmarks",
- ],
- defaultCollapsed: true,
- }, true);
-
- this.registerArea(CustomizableUI.AREA_ADDONBAR, {
- type: CustomizableUI.TYPE_TOOLBAR,
- legacy: true,
- defaultPlacements: ["addonbar-closebutton", "status-bar"],
- defaultCollapsed: false,
- }, true);
- },
-
- get _builtinToolbars() {
- let toolbars = new Set([
- CustomizableUI.AREA_NAVBAR,
- CustomizableUI.AREA_BOOKMARKS,
- CustomizableUI.AREA_TABSTRIP,
- CustomizableUI.AREA_ADDONBAR,
- ]);
- if (AppConstants.platform != "macosx") {
- toolbars.add(CustomizableUI.AREA_MENUBAR);
- }
- return toolbars;
- },
-
- _defineBuiltInWidgets: function() {
- for (let widgetDefinition of CustomizableWidgets) {
- this.createBuiltinWidget(widgetDefinition);
- }
- },
-
- _introduceNewBuiltinWidgets: function() {
- // We should still enter even if gSavedState.currentVersion >= kVersion
- // because the per-widget pref facility is independent of versioning.
- if (!gSavedState) {
- // Flip all the prefs so we don't try to re-introduce later:
- for (let [, widget] of gPalette) {
- if (widget.defaultArea && widget._introducedInVersion === "pref") {
- let prefId = "browser.toolbarbuttons.introduced." + widget.id;
- Services.prefs.setBoolPref(prefId, true);
- }
- }
- return;
- }
-
- let currentVersion = gSavedState.currentVersion;
- for (let [id, widget] of gPalette) {
- if (widget.defaultArea) {
- let shouldAdd = false;
- let shouldSetPref = false;
- let prefId = "browser.toolbarbuttons.introduced." + widget.id;
- if (widget._introducedInVersion === "pref") {
- try {
- shouldAdd = !Services.prefs.getBoolPref(prefId);
- } catch (ex) {
- // Pref doesn't exist:
- shouldAdd = true;
- }
- shouldSetPref = shouldAdd;
- } else if (widget._introducedInVersion > currentVersion) {
- shouldAdd = true;
- }
-
- if (shouldAdd) {
- let futurePlacements = gFuturePlacements.get(widget.defaultArea);
- if (futurePlacements) {
- futurePlacements.add(id);
- } else {
- gFuturePlacements.set(widget.defaultArea, new Set([id]));
- }
- if (shouldSetPref) {
- Services.prefs.setBoolPref(prefId, true);
- }
- }
- }
- }
-
- if (currentVersion < 2) {
- // Nuke the old 'loop-call-button' out of orbit.
- CustomizableUI.removeWidgetFromArea("loop-call-button");
- }
-
- if (currentVersion < 4) {
- CustomizableUI.removeWidgetFromArea("loop-button-throttled");
- }
- },
-
- /**
- * _markObsoleteBuiltinButtonsSeen
- * when upgrading, ensure obsoleted buttons are in seen state.
- */
- _markObsoleteBuiltinButtonsSeen: function() {
- if (!gSavedState)
- return;
- let currentVersion = gSavedState.currentVersion;
- if (currentVersion >= kVersion)
- return;
- // we're upgrading, update state if necessary
- for (let id in ObsoleteBuiltinButtons) {
- let version = ObsoleteBuiltinButtons[id]
- if (version == kVersion) {
- gSeenWidgets.add(id);
- gDirty = true;
- }
- }
- },
-
- _placeNewDefaultWidgetsInArea: function(aArea) {
- let futurePlacedWidgets = gFuturePlacements.get(aArea);
- let savedPlacements = gSavedState && gSavedState.placements && gSavedState.placements[aArea];
- let defaultPlacements = gAreas.get(aArea).get("defaultPlacements");
- if (!savedPlacements || !savedPlacements.length || !futurePlacedWidgets || !defaultPlacements ||
- !defaultPlacements.length) {
- return;
- }
- let defaultWidgetIndex = -1;
-
- for (let widgetId of futurePlacedWidgets) {
- let widget = gPalette.get(widgetId);
- if (!widget || widget.source !== CustomizableUI.SOURCE_BUILTIN ||
- !widget.defaultArea || !widget._introducedInVersion ||
- savedPlacements.indexOf(widget.id) !== -1) {
- continue;
- }
- defaultWidgetIndex = defaultPlacements.indexOf(widget.id);
- if (defaultWidgetIndex === -1) {
- continue;
- }
- // Now we know that this widget should be here by default, was newly introduced,
- // and we have a saved state to insert into, and a default state to work off of.
- // Try introducing after widgets that come before it in the default placements:
- for (let i = defaultWidgetIndex; i >= 0; i--) {
- // Special case: if the defaults list this widget as coming first, insert at the beginning:
- if (i === 0 && i === defaultWidgetIndex) {
- savedPlacements.splice(0, 0, widget.id);
- // Before you ask, yes, deleting things inside a let x of y loop where y is a Set is
- // safe, and we won't skip any items.
- futurePlacedWidgets.delete(widget.id);
- gDirty = true;
- break;
- }
- // Otherwise, if we're somewhere other than the beginning, check if the previous
- // widget is in the saved placements.
- if (i) {
- let previousWidget = defaultPlacements[i - 1];
- let previousWidgetIndex = savedPlacements.indexOf(previousWidget);
- if (previousWidgetIndex != -1) {
- savedPlacements.splice(previousWidgetIndex + 1, 0, widget.id);
- futurePlacedWidgets.delete(widget.id);
- gDirty = true;
- break;
- }
- }
- }
- // The loop above either inserts the item or doesn't - either way, we can get away
- // with doing nothing else now; if the item remains in gFuturePlacements, we'll
- // add it at the end in restoreStateForArea.
- }
- this.saveState();
- },
-
- wrapWidget: function(aWidgetId) {
- if (gGroupWrapperCache.has(aWidgetId)) {
- return gGroupWrapperCache.get(aWidgetId);
- }
-
- let provider = this.getWidgetProvider(aWidgetId);
- if (!provider) {
- return null;
- }
-
- if (provider == CustomizableUI.PROVIDER_API) {
- let widget = gPalette.get(aWidgetId);
- if (!widget.wrapper) {
- widget.wrapper = new WidgetGroupWrapper(widget);
- gGroupWrapperCache.set(aWidgetId, widget.wrapper);
- }
- return widget.wrapper;
- }
-
- // PROVIDER_SPECIAL gets treated the same as PROVIDER_XUL.
- let wrapper = new XULWidgetGroupWrapper(aWidgetId);
- gGroupWrapperCache.set(aWidgetId, wrapper);
- return wrapper;
- },
-
- registerArea: function(aName, aProperties, aInternalCaller) {
- if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
- throw new Error("Invalid area name");
- }
-
- let areaIsKnown = gAreas.has(aName);
- let props = areaIsKnown ? gAreas.get(aName) : new Map();
- const kImmutableProperties = new Set(["type", "legacy", "overflowable"]);
- for (let key in aProperties) {
- if (areaIsKnown && kImmutableProperties.has(key) &&
- props.get(key) != aProperties[key]) {
- throw new Error("An area cannot change the property for '" + key + "'");
- }
- // XXXgijs for special items, we need to make sure they have an appropriate ID
- // so we aren't perpetually in a non-default state:
- if (key == "defaultPlacements" && Array.isArray(aProperties[key])) {
- props.set(key, aProperties[key].map(x => this.isSpecialWidget(x) ? this.ensureSpecialWidgetId(x) : x ));
- } else {
- props.set(key, aProperties[key]);
- }
- }
- // Default to a toolbar:
- if (!props.has("type")) {
- props.set("type", CustomizableUI.TYPE_TOOLBAR);
- }
- if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
- // Check aProperties instead of props because this check is only interested
- // in the passed arguments, not the state of a potentially pre-existing area.
- if (!aInternalCaller && aProperties["defaultCollapsed"]) {
- throw new Error("defaultCollapsed is only allowed for default toolbars.")
- }
- if (!props.has("defaultCollapsed")) {
- props.set("defaultCollapsed", true);
- }
- } else if (props.has("defaultCollapsed")) {
- throw new Error("defaultCollapsed only applies for TYPE_TOOLBAR areas.");
- }
- // Sanity check type:
- let allTypes = [CustomizableUI.TYPE_TOOLBAR, CustomizableUI.TYPE_MENU_PANEL];
- if (allTypes.indexOf(props.get("type")) == -1) {
- throw new Error("Invalid area type " + props.get("type"));
- }
-
- // And to no placements:
- if (!props.has("defaultPlacements")) {
- props.set("defaultPlacements", []);
- }
- // Sanity check default placements array:
- if (!Array.isArray(props.get("defaultPlacements"))) {
- throw new Error("Should provide an array of default placements");
- }
-
- if (!areaIsKnown) {
- gAreas.set(aName, props);
-
- // Reconcile new default widgets. Have to do this before we start restoring things.
- this._placeNewDefaultWidgetsInArea(aName);
-
- if (props.get("legacy") && !gPlacements.has(aName)) {
- // Guarantee this area exists in gFuturePlacements, to avoid checking it in
- // various places elsewhere.
- if (!gFuturePlacements.has(aName)) {
- gFuturePlacements.set(aName, new Set());
- }
- } else {
- this.restoreStateForArea(aName);
- }
-
- // If we have pending build area nodes, register all of them
- if (gPendingBuildAreas.has(aName)) {
- let pendingNodes = gPendingBuildAreas.get(aName);
- for (let [pendingNode, existingChildren] of pendingNodes) {
- this.registerToolbarNode(pendingNode, existingChildren);
- }
- gPendingBuildAreas.delete(aName);
- }
- }
- },
-
- unregisterArea: function(aName, aDestroyPlacements) {
- if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
- throw new Error("Invalid area name");
- }
- if (!gAreas.has(aName) && !gPlacements.has(aName)) {
- throw new Error("Area not registered");
- }
-
- // Move all the widgets out
- this.beginBatchUpdate();
- try {
- let placements = gPlacements.get(aName);
- if (placements) {
- // Need to clone this array so removeWidgetFromArea doesn't modify it
- placements = [...placements];
- placements.forEach(this.removeWidgetFromArea, this);
- }
-
- // Delete all remaining traces.
- gAreas.delete(aName);
- // Only destroy placements when necessary:
- if (aDestroyPlacements) {
- gPlacements.delete(aName);
- } else {
- // Otherwise we need to re-set them, as removeFromArea will have emptied
- // them out:
- gPlacements.set(aName, placements);
- }
- gFuturePlacements.delete(aName);
- let existingAreaNodes = gBuildAreas.get(aName);
- if (existingAreaNodes) {
- for (let areaNode of existingAreaNodes) {
- this.notifyListeners("onAreaNodeUnregistered", aName, areaNode.customizationTarget,
- CustomizableUI.REASON_AREA_UNREGISTERED);
- }
- }
- gBuildAreas.delete(aName);
- } finally {
- this.endBatchUpdate(true);
- }
- },
-
- registerToolbarNode: function(aToolbar, aExistingChildren) {
- let area = aToolbar.id;
- if (gBuildAreas.has(area) && gBuildAreas.get(area).has(aToolbar)) {
- return;
- }
- let areaProperties = gAreas.get(area);
-
- // If this area is not registered, try to do it automatically:
- if (!areaProperties) {
- // If there's no defaultset attribute and this isn't a legacy extra toolbar,
- // we assume that we should wait for registerArea to be called:
- if (!aToolbar.hasAttribute("defaultset") &&
- !aToolbar.hasAttribute("customindex")) {
- if (!gPendingBuildAreas.has(area)) {
- gPendingBuildAreas.set(area, new Map());
- }
- let pendingNodes = gPendingBuildAreas.get(area);
- pendingNodes.set(aToolbar, aExistingChildren);
- return;
- }
- let props = {type: CustomizableUI.TYPE_TOOLBAR, legacy: true};
- let defaultsetAttribute = aToolbar.getAttribute("defaultset") || "";
- props.defaultPlacements = defaultsetAttribute.split(',').filter(s => s);
- this.registerArea(area, props);
- areaProperties = gAreas.get(area);
- }
-
- this.beginBatchUpdate();
- try {
- let placements = gPlacements.get(area);
- if (!placements && areaProperties.has("legacy")) {
- let legacyState = aToolbar.getAttribute("currentset");
- if (legacyState) {
- legacyState = legacyState.split(",").filter(s => s);
- }
-
- // Manually restore the state here, so the legacy state can be converted.
- this.restoreStateForArea(area, legacyState);
- placements = gPlacements.get(area);
- }
-
- // Check that the current children and the current placements match. If
- // not, mark it as dirty:
- if (aExistingChildren.length != placements.length ||
- aExistingChildren.every((id, i) => id == placements[i])) {
- gDirtyAreaCache.add(area);
- }
-
- if (areaProperties.has("overflowable")) {
- aToolbar.overflowable = new OverflowableToolbar(aToolbar);
- }
-
- this.registerBuildArea(area, aToolbar);
-
- // We only build the toolbar if it's been marked as "dirty". Dirty means
- // one of the following things:
- // 1) Items have been added, moved or removed from this toolbar before.
- // 2) The number of children of the toolbar does not match the length of
- // the placements array for that area.
- //
- // This notion of being "dirty" is stored in a cache which is persisted
- // in the saved state.
- if (gDirtyAreaCache.has(area)) {
- this.buildArea(area, placements, aToolbar);
- }
- this.notifyListeners("onAreaNodeRegistered", area, aToolbar.customizationTarget);
- aToolbar.setAttribute("currentset", placements.join(","));
- } finally {
- this.endBatchUpdate();
- }
- },
-
- buildArea: function(aArea, aPlacements, aAreaNode) {
- let document = aAreaNode.ownerDocument;
- let window = document.defaultView;
- let inPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(window);
- let container = aAreaNode.customizationTarget;
- let areaIsPanel = gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL;
-
- if (!container) {
- throw new Error("Expected area " + aArea
- + " to have a customizationTarget attribute.");
- }
-
- // Restore nav-bar visibility since it may have been hidden
- // through a migration path (bug 938980) or an add-on.
- if (aArea == CustomizableUI.AREA_NAVBAR) {
- aAreaNode.collapsed = false;
- }
-
- this.beginBatchUpdate();
-
- try {
- let currentNode = container.firstChild;
- let placementsToRemove = new Set();
- for (let id of aPlacements) {
- while (currentNode && currentNode.getAttribute("skipintoolbarset") == "true") {
- currentNode = currentNode.nextSibling;
- }
-
- if (currentNode && currentNode.id == id) {
- currentNode = currentNode.nextSibling;
- continue;
- }
-
- if (this.isSpecialWidget(id) && areaIsPanel) {
- placementsToRemove.add(id);
- continue;
- }
-
- let [provider, node] = this.getWidgetNode(id, window);
- if (!node) {
- log.debug("Unknown widget: " + id);
- continue;
- }
-
- let widget = null;
- // If the placements have items in them which are (now) no longer removable,
- // we shouldn't be moving them:
- if (provider == CustomizableUI.PROVIDER_API) {
- widget = gPalette.get(id);
- if (!widget.removable && aArea != widget.defaultArea) {
- placementsToRemove.add(id);
- continue;
- }
- } else if (provider == CustomizableUI.PROVIDER_XUL &&
- node.parentNode != container && !this.isWidgetRemovable(node)) {
- placementsToRemove.add(id);
- continue;
- } // Special widgets are always removable, so no need to check them
-
- if (inPrivateWindow && widget && !widget.showInPrivateBrowsing) {
- continue;
- }
-
- this.ensureButtonContextMenu(node, aAreaNode);
- if (node.localName == "toolbarbutton") {
- if (areaIsPanel) {
- node.setAttribute("wrap", "true");
- } else {
- node.removeAttribute("wrap");
- }
- }
-
- // This needs updating in case we're resetting / undoing a reset.
- if (widget) {
- widget.currentArea = aArea;
- }
- this.insertWidgetBefore(node, currentNode, container, aArea);
- if (gResetting) {
- this.notifyListeners("onWidgetReset", node, container);
- } else if (gUndoResetting) {
- this.notifyListeners("onWidgetUndoMove", node, container);
- }
- }
-
- if (currentNode) {
- let palette = aAreaNode.toolbox ? aAreaNode.toolbox.palette : null;
- let limit = currentNode.previousSibling;
- let node = container.lastChild;
- while (node && node != limit) {
- let previousSibling = node.previousSibling;
- // Nodes opt-in to removability. If they're removable, and we haven't
- // seen them in the placements array, then we toss them into the palette
- // if one exists. If no palette exists, we just remove the node. If the
- // node is not removable, we leave it where it is. However, we can only
- // safely touch elements that have an ID - both because we depend on
- // IDs, and because such elements are not intended to be widgets
- // (eg, titlebar-placeholder elements).
- if (node.id && node.getAttribute("skipintoolbarset") != "true") {
- if (this.isWidgetRemovable(node)) {
- if (palette && !this.isSpecialWidget(node.id)) {
- palette.appendChild(node);
- this.removeLocationAttributes(node);
- } else {
- container.removeChild(node);
- }
- } else {
- node.setAttribute("removable", false);
- log.debug("Adding non-removable widget to placements of " + aArea + ": " +
- node.id);
- gPlacements.get(aArea).push(node.id);
- gDirty = true;
- }
- }
- node = previousSibling;
- }
- }
-
- // If there are placements in here which aren't removable from their original area,
- // we remove them from this area's placement array. They will (have) be(en) added
- // to their original area's placements array in the block above this one.
- if (placementsToRemove.size) {
- let placementAry = gPlacements.get(aArea);
- for (let id of placementsToRemove) {
- let index = placementAry.indexOf(id);
- placementAry.splice(index, 1);
- }
- }
-
- if (gResetting) {
- this.notifyListeners("onAreaReset", aArea, container);
- }
- } finally {
- this.endBatchUpdate();
- }
- },
-
- addPanelCloseListeners: function(aPanel) {
- gELS.addSystemEventListener(aPanel, "click", this, false);
- gELS.addSystemEventListener(aPanel, "keypress", this, false);
- let win = aPanel.ownerGlobal;
- if (!gPanelsForWindow.has(win)) {
- gPanelsForWindow.set(win, new Set());
- }
- gPanelsForWindow.get(win).add(this._getPanelForNode(aPanel));
- },
-
- removePanelCloseListeners: function(aPanel) {
- gELS.removeSystemEventListener(aPanel, "click", this, false);
- gELS.removeSystemEventListener(aPanel, "keypress", this, false);
- let win = aPanel.ownerGlobal;
- let panels = gPanelsForWindow.get(win);
- if (panels) {
- panels.delete(this._getPanelForNode(aPanel));
- }
- },
-
- ensureButtonContextMenu: function(aNode, aAreaNode) {
- const kPanelItemContextMenu = "customizationPanelItemContextMenu";
-
- let currentContextMenu = aNode.getAttribute("context") ||
- aNode.getAttribute("contextmenu");
- let place = CustomizableUI.getPlaceForItem(aAreaNode);
- let contextMenuForPlace = place == "panel" ?
- kPanelItemContextMenu :
- null;
- if (contextMenuForPlace && !currentContextMenu) {
- aNode.setAttribute("context", contextMenuForPlace);
- } else if (currentContextMenu == kPanelItemContextMenu &&
- contextMenuForPlace != kPanelItemContextMenu) {
- aNode.removeAttribute("context");
- aNode.removeAttribute("contextmenu");
- }
- },
-
- getWidgetProvider: function(aWidgetId) {
- if (this.isSpecialWidget(aWidgetId)) {
- return CustomizableUI.PROVIDER_SPECIAL;
- }
- if (gPalette.has(aWidgetId)) {
- return CustomizableUI.PROVIDER_API;
- }
- // If this was an API widget that was destroyed, return null:
- if (gSeenWidgets.has(aWidgetId)) {
- return null;
- }
-
- // We fall back to the XUL provider, but we don't know for sure (at this
- // point) whether it exists there either. So the API is technically lying.
- // Ideally, it would be able to return an error value (or throw an
- // exception) if it really didn't exist. Our code calling this function
- // handles that fine, but this is a public API.
- return CustomizableUI.PROVIDER_XUL;
- },
-
- getWidgetNode: function(aWidgetId, aWindow) {
- let document = aWindow.document;
-
- if (this.isSpecialWidget(aWidgetId)) {
- let widgetNode = document.getElementById(aWidgetId) ||
- this.createSpecialWidget(aWidgetId, document);
- return [ CustomizableUI.PROVIDER_SPECIAL, widgetNode];
- }
-
- let widget = gPalette.get(aWidgetId);
- if (widget) {
- // If we have an instance of this widget already, just use that.
- if (widget.instances.has(document)) {
- log.debug("An instance of widget " + aWidgetId + " already exists in this "
- + "document. Reusing.");
- return [ CustomizableUI.PROVIDER_API,
- widget.instances.get(document) ];
- }
-
- return [ CustomizableUI.PROVIDER_API,
- this.buildWidget(document, widget) ];
- }
-
- log.debug("Searching for " + aWidgetId + " in toolbox.");
- let node = this.findWidgetInWindow(aWidgetId, aWindow);
- if (node) {
- return [ CustomizableUI.PROVIDER_XUL, node ];
- }
-
- log.debug("No node for " + aWidgetId + " found.");
- return [null, null];
- },
-
- registerMenuPanel: function(aPanelContents) {
- if (gBuildAreas.has(CustomizableUI.AREA_PANEL) &&
- gBuildAreas.get(CustomizableUI.AREA_PANEL).has(aPanelContents)) {
- return;
- }
-
- let document = aPanelContents.ownerDocument;
-
- aPanelContents.toolbox = document.getElementById("navigator-toolbox");
- aPanelContents.customizationTarget = aPanelContents;
-
- this.addPanelCloseListeners(this._getPanelForNode(aPanelContents));
-
- let placements = gPlacements.get(CustomizableUI.AREA_PANEL);
- this.buildArea(CustomizableUI.AREA_PANEL, placements, aPanelContents);
- this.notifyListeners("onAreaNodeRegistered", CustomizableUI.AREA_PANEL, aPanelContents);
-
- for (let child of aPanelContents.children) {
- if (child.localName != "toolbarbutton") {
- if (child.localName == "toolbaritem") {
- this.ensureButtonContextMenu(child, aPanelContents);
- }
- continue;
- }
- this.ensureButtonContextMenu(child, aPanelContents);
- child.setAttribute("wrap", "true");
- }
-
- this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanelContents);
- },
-
- onWidgetAdded: function(aWidgetId, aArea, aPosition) {
- this.insertNode(aWidgetId, aArea, aPosition, true);
-
- if (!gResetting) {
- this._clearPreviousUIState();
- }
- },
-
- onWidgetRemoved: function(aWidgetId, aArea) {
- let areaNodes = gBuildAreas.get(aArea);
- if (!areaNodes) {
- return;
- }
-
- let area = gAreas.get(aArea);
- let isToolbar = area.get("type") == CustomizableUI.TYPE_TOOLBAR;
- let isOverflowable = isToolbar && area.get("overflowable");
- let showInPrivateBrowsing = gPalette.has(aWidgetId)
- ? gPalette.get(aWidgetId).showInPrivateBrowsing
- : true;
-
- for (let areaNode of areaNodes) {
- let window = areaNode.ownerGlobal;
- if (!showInPrivateBrowsing &&
- PrivateBrowsingUtils.isWindowPrivate(window)) {
- continue;
- }
-
- let container = areaNode.customizationTarget;
- let widgetNode = window.document.getElementById(aWidgetId);
- if (widgetNode && isOverflowable) {
- container = areaNode.overflowable.getContainerFor(widgetNode);
- }
-
- if (!widgetNode || !container.contains(widgetNode)) {
- log.info("Widget " + aWidgetId + " not found, unable to remove from " + aArea);
- continue;
- }
-
- this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
-
- // We remove location attributes here to make sure they're gone too when a
- // widget is removed from a toolbar to the palette. See bug 930950.
- this.removeLocationAttributes(widgetNode);
- // We also need to remove the panel context menu if it's there:
- this.ensureButtonContextMenu(widgetNode);
- widgetNode.removeAttribute("wrap");
- if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
- container.removeChild(widgetNode);
- } else {
- areaNode.toolbox.palette.appendChild(widgetNode);
- }
- this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
-
- if (isToolbar) {
- areaNode.setAttribute("currentset", gPlacements.get(aArea).join(','));
- }
-
- let windowCache = gSingleWrapperCache.get(window);
- if (windowCache) {
- windowCache.delete(aWidgetId);
- }
- }
- if (!gResetting) {
- this._clearPreviousUIState();
- }
- },
-
- onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
- this.insertNode(aWidgetId, aArea, aNewPosition);
- if (!gResetting) {
- this._clearPreviousUIState();
- }
- },
-
- onCustomizeEnd: function(aWindow) {
- this._clearPreviousUIState();
- },
-
- registerBuildArea: function(aArea, aNode) {
- // We ensure that the window is registered to have its customization data
- // cleaned up when unloading.
- let window = aNode.ownerGlobal;
- if (window.closed) {
- return;
- }
- this.registerBuildWindow(window);
-
- // Also register this build area's toolbox.
- if (aNode.toolbox) {
- gBuildWindows.get(window).add(aNode.toolbox);
- }
-
- if (!gBuildAreas.has(aArea)) {
- gBuildAreas.set(aArea, new Set());
- }
-
- gBuildAreas.get(aArea).add(aNode);
-
- // Give a class to all customize targets to be used for styling in Customize Mode
- let customizableNode = this.getCustomizeTargetForArea(aArea, window);
- customizableNode.classList.add("customization-target");
- },
-
- registerBuildWindow: function(aWindow) {
- if (!gBuildWindows.has(aWindow)) {
- gBuildWindows.set(aWindow, new Set());
-
- aWindow.addEventListener("unload", this);
- aWindow.addEventListener("command", this, true);
-
- this.notifyListeners("onWindowOpened", aWindow);
- }
- },
-
- unregisterBuildWindow: function(aWindow) {
- aWindow.removeEventListener("unload", this);
- aWindow.removeEventListener("command", this, true);
- gPanelsForWindow.delete(aWindow);
- gBuildWindows.delete(aWindow);
- gSingleWrapperCache.delete(aWindow);
- let document = aWindow.document;
-
- for (let [areaId, areaNodes] of gBuildAreas) {
- let areaProperties = gAreas.get(areaId);
- for (let node of areaNodes) {
- if (node.ownerDocument == document) {
- this.notifyListeners("onAreaNodeUnregistered", areaId, node.customizationTarget,
- CustomizableUI.REASON_WINDOW_CLOSED);
- if (areaProperties.has("overflowable")) {
- node.overflowable.uninit();
- node.overflowable = null;
- }
- areaNodes.delete(node);
- }
- }
- }
-
- for (let [, widget] of gPalette) {
- widget.instances.delete(document);
- this.notifyListeners("onWidgetInstanceRemoved", widget.id, document);
- }
-
- for (let [, areaMap] of gPendingBuildAreas) {
- let toDelete = [];
- for (let [areaNode, ] of areaMap) {
- if (areaNode.ownerDocument == document) {
- toDelete.push(areaNode);
- }
- }
- for (let areaNode of toDelete) {
- areaMap.delete(areaNode);
- }
- }
-
- this.notifyListeners("onWindowClosed", aWindow);
- },
-
- setLocationAttributes: function(aNode, aArea) {
- let props = gAreas.get(aArea);
- if (!props) {
- throw new Error("Expected area " + aArea + " to have a properties Map " +
- "associated with it.");
- }
-
- aNode.setAttribute("cui-areatype", props.get("type") || "");
- let anchor = props.get("anchor");
- if (anchor) {
- aNode.setAttribute("cui-anchorid", anchor);
- } else {
- aNode.removeAttribute("cui-anchorid");
- }
- },
-
- removeLocationAttributes: function(aNode) {
- aNode.removeAttribute("cui-areatype");
- aNode.removeAttribute("cui-anchorid");
- },
-
- insertNode: function(aWidgetId, aArea, aPosition, isNew) {
- let areaNodes = gBuildAreas.get(aArea);
- if (!areaNodes) {
- return;
- }
-
- let placements = gPlacements.get(aArea);
- if (!placements) {
- log.error("Could not find any placements for " + aArea +
- " when moving a widget.");
- return;
- }
-
- // Go through each of the nodes associated with this area and move the
- // widget to the requested location.
- for (let areaNode of areaNodes) {
- this.insertNodeInWindow(aWidgetId, areaNode, isNew);
- }
- },
-
- insertNodeInWindow: function(aWidgetId, aAreaNode, isNew) {
- let window = aAreaNode.ownerGlobal;
- let showInPrivateBrowsing = gPalette.has(aWidgetId)
- ? gPalette.get(aWidgetId).showInPrivateBrowsing
- : true;
-
- if (!showInPrivateBrowsing && PrivateBrowsingUtils.isWindowPrivate(window)) {
- return;
- }
-
- let [, widgetNode] = this.getWidgetNode(aWidgetId, window);
- if (!widgetNode) {
- log.error("Widget '" + aWidgetId + "' not found, unable to move");
- return;
- }
-
- let areaId = aAreaNode.id;
- if (isNew) {
- this.ensureButtonContextMenu(widgetNode, aAreaNode);
- if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
- widgetNode.setAttribute("wrap", "true");
- }
- }
-
- let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aAreaNode);
- this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId);
-
- if (gAreas.get(areaId).get("type") == CustomizableUI.TYPE_TOOLBAR) {
- aAreaNode.setAttribute("currentset", gPlacements.get(areaId).join(','));
- }
- },
-
- findInsertionPoints: function(aNode, aAreaNode) {
- let areaId = aAreaNode.id;
- let props = gAreas.get(areaId);
-
- // For overflowable toolbars, rely on them (because the work is more complicated):
- if (props.get("type") == CustomizableUI.TYPE_TOOLBAR && props.get("overflowable")) {
- return aAreaNode.overflowable.findOverflowedInsertionPoints(aNode);
- }
-
- let container = aAreaNode.customizationTarget;
- let placements = gPlacements.get(areaId);
- let nodeIndex = placements.indexOf(aNode.id);
-
- while (++nodeIndex < placements.length) {
- let nextNodeId = placements[nodeIndex];
- let nextNode = container.getElementsByAttribute("id", nextNodeId).item(0);
-
- if (nextNode) {
- return [container, nextNode];
- }
- }
-
- return [container, null];
- },
-
- insertWidgetBefore: function(aNode, aNextNode, aContainer, aArea) {
- this.notifyListeners("onWidgetBeforeDOMChange", aNode, aNextNode, aContainer);
- this.setLocationAttributes(aNode, aArea);
- aContainer.insertBefore(aNode, aNextNode);
- this.notifyListeners("onWidgetAfterDOMChange", aNode, aNextNode, aContainer);
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "command":
- if (!this._originalEventInPanel(aEvent)) {
- break;
- }
- aEvent = aEvent.sourceEvent;
- // Fall through
- case "click":
- case "keypress":
- this.maybeAutoHidePanel(aEvent);
- break;
- case "unload":
- this.unregisterBuildWindow(aEvent.currentTarget);
- break;
- }
- },
-
- _originalEventInPanel: function(aEvent) {
- let e = aEvent.sourceEvent;
- if (!e) {
- return false;
- }
- let node = this._getPanelForNode(e.target);
- if (!node) {
- return false;
- }
- let win = e.view;
- let panels = gPanelsForWindow.get(win);
- return !!panels && panels.has(node);
- },
-
- isSpecialWidget: function(aId) {
- return (aId.startsWith(kSpecialWidgetPfx) ||
- aId.startsWith("separator") ||
- aId.startsWith("spring") ||
- aId.startsWith("spacer"));
- },
-
- ensureSpecialWidgetId: function(aId) {
- let nodeType = aId.match(/spring|spacer|separator/)[0];
- // If the ID we were passed isn't a generated one, generate one now:
- if (nodeType == aId) {
- // Ids are differentiated through a unique count suffix.
- return kSpecialWidgetPfx + aId + (++gNewElementCount);
- }
- return aId;
- },
-
- createSpecialWidget: function(aId, aDocument) {
- let nodeName = "toolbar" + aId.match(/spring|spacer|separator/)[0];
- let node = aDocument.createElementNS(kNSXUL, nodeName);
- node.id = this.ensureSpecialWidgetId(aId);
- if (nodeName == "toolbarspring") {
- node.flex = 1;
- }
- return node;
- },
-
- /* Find a XUL-provided widget in a window. Don't try to use this
- * for an API-provided widget or a special widget.
- */
- findWidgetInWindow: function(aId, aWindow) {
- if (!gBuildWindows.has(aWindow)) {
- throw new Error("Build window not registered");
- }
-
- if (!aId) {
- log.error("findWidgetInWindow was passed an empty string.");
- return null;
- }
-
- let document = aWindow.document;
-
- // look for a node with the same id, as the node may be
- // in a different toolbar.
- let node = document.getElementById(aId);
- if (node) {
- let parent = node.parentNode;
- while (parent && !(parent.customizationTarget ||
- parent == aWindow.gNavToolbox.palette)) {
- parent = parent.parentNode;
- }
-
- if (parent) {
- let nodeInArea = node.parentNode.localName == "toolbarpaletteitem" ?
- node.parentNode : node;
- // Check if we're in a customization target, or in the palette:
- if ((parent.customizationTarget == nodeInArea.parentNode &&
- gBuildWindows.get(aWindow).has(parent.toolbox)) ||
- aWindow.gNavToolbox.palette == nodeInArea.parentNode) {
- // Normalize the removable attribute. For backwards compat, if
- // the widget is not located in a toolbox palette then absence
- // of the "removable" attribute means it is not removable.
- if (!node.hasAttribute("removable")) {
- // If we first see this in customization mode, it may be in the
- // customization palette instead of the toolbox palette.
- node.setAttribute("removable", !parent.customizationTarget);
- }
- return node;
- }
- }
- }
-
- let toolboxes = gBuildWindows.get(aWindow);
- for (let toolbox of toolboxes) {
- if (toolbox.palette) {
- // Attempt to locate a node with a matching ID within
- // the palette.
- let node = toolbox.palette.getElementsByAttribute("id", aId)[0];
- if (node) {
- // Normalize the removable attribute. For backwards compat, this
- // is optional if the widget is located in the toolbox palette,
- // and defaults to *true*, unlike if it was located elsewhere.
- if (!node.hasAttribute("removable")) {
- node.setAttribute("removable", true);
- }
- return node;
- }
- }
- }
- return null;
- },
-
- buildWidget: function(aDocument, aWidget) {
- if (aDocument.documentURI != kExpectedWindowURL) {
- throw new Error("buildWidget was called for a non-browser window!");
- }
- if (typeof aWidget == "string") {
- aWidget = gPalette.get(aWidget);
- }
- if (!aWidget) {
- throw new Error("buildWidget was passed a non-widget to build.");
- }
-
- log.debug("Building " + aWidget.id + " of type " + aWidget.type);
-
- let node;
- if (aWidget.type == "custom") {
- if (aWidget.onBuild) {
- node = aWidget.onBuild(aDocument);
- }
- if (!node || !(node instanceof aDocument.defaultView.XULElement))
- log.error("Custom widget with id " + aWidget.id + " does not return a valid node");
- }
- else {
- if (aWidget.onBeforeCreated) {
- aWidget.onBeforeCreated(aDocument);
- }
- node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
-
- node.setAttribute("id", aWidget.id);
- node.setAttribute("widget-id", aWidget.id);
- node.setAttribute("widget-type", aWidget.type);
- if (aWidget.disabled) {
- node.setAttribute("disabled", true);
- }
- node.setAttribute("removable", aWidget.removable);
- node.setAttribute("overflows", aWidget.overflows);
- if (aWidget.tabSpecific) {
- node.setAttribute("tabspecific", aWidget.tabSpecific);
- }
- node.setAttribute("label", this.getLocalizedProperty(aWidget, "label"));
- let additionalTooltipArguments = [];
- if (aWidget.shortcutId) {
- let keyEl = aDocument.getElementById(aWidget.shortcutId);
- if (keyEl) {
- additionalTooltipArguments.push(ShortcutUtils.prettifyShortcut(keyEl));
- } else {
- log.error("Key element with id '" + aWidget.shortcutId + "' for widget '" + aWidget.id +
- "' not found!");
- }
- }
-
- let tooltip = this.getLocalizedProperty(aWidget, "tooltiptext", additionalTooltipArguments);
- if (tooltip) {
- node.setAttribute("tooltiptext", tooltip);
- }
- node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
-
- let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node);
- node.addEventListener("command", commandHandler, false);
- let clickHandler = this.handleWidgetClick.bind(this, aWidget, node);
- node.addEventListener("click", clickHandler, false);
-
- // If the widget has a view, and has view showing / hiding listeners,
- // hook those up to this widget.
- if (aWidget.type == "view") {
- log.debug("Widget " + aWidget.id + " has a view. Auto-registering event handlers.");
- let viewNode = aDocument.getElementById(aWidget.viewId);
-
- if (viewNode) {
- // PanelUI relies on the .PanelUI-subView class to be able to show only
- // one sub-view at a time.
- viewNode.classList.add("PanelUI-subView");
-
- for (let eventName of kSubviewEvents) {
- let handler = "on" + eventName;
- if (typeof aWidget[handler] == "function") {
- viewNode.addEventListener(eventName, aWidget[handler], false);
- }
- }
-
- log.debug("Widget " + aWidget.id + " showing and hiding event handlers set.");
- } else {
- log.error("Could not find the view node with id: " + aWidget.viewId +
- ", for widget: " + aWidget.id + ".");
- }
- }
-
- if (aWidget.onCreated) {
- aWidget.onCreated(node);
- }
- }
-
- aWidget.instances.set(aDocument, node);
- return node;
- },
-
- getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
- const kReqStringProps = ["label"];
-
- if (typeof aWidget == "string") {
- aWidget = gPalette.get(aWidget);
- }
- if (!aWidget) {
- throw new Error("getLocalizedProperty was passed a non-widget to work with.");
- }
- let def, name;
- // Let widgets pass their own string identifiers or strings, so that
- // we can use strings which aren't the default (in case string ids change)
- // and so that non-builtin-widgets can also provide labels, tooltips, etc.
- if (aWidget[aProp] != null) {
- name = aWidget[aProp];
- // By using this as the default, if a widget provides a full string rather
- // than a string ID for localization, we will fall back to that string
- // and return that.
- def = aDef || name;
- } else {
- name = aWidget.id + "." + aProp;
- def = aDef || "";
- }
- try {
- if (Array.isArray(aFormatArgs) && aFormatArgs.length) {
- return gWidgetsBundle.formatStringFromName(name, aFormatArgs,
- aFormatArgs.length) || def;
- }
- return gWidgetsBundle.GetStringFromName(name) || def;
- } catch (ex) {
- // If an empty string was explicitly passed, treat it as an actual
- // value rather than a missing property.
- if (!def && (name != "" || kReqStringProps.includes(aProp))) {
- log.error("Could not localize property '" + name + "'.");
- }
- }
- return def;
- },
-
- addShortcut: function(aShortcutNode, aTargetNode) {
- if (!aTargetNode)
- aTargetNode = aShortcutNode;
- let document = aShortcutNode.ownerDocument;
-
- // Detect if we've already been here before.
- if (!aTargetNode || aTargetNode.hasAttribute("shortcut"))
- return;
-
- let shortcutId = aShortcutNode.getAttribute("key");
- let shortcut;
- if (shortcutId) {
- shortcut = document.getElementById(shortcutId);
- } else {
- let commandId = aShortcutNode.getAttribute("command");
- if (commandId)
- shortcut = ShortcutUtils.findShortcut(document.getElementById(commandId));
- }
- if (!shortcut) {
- return;
- }
-
- aTargetNode.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(shortcut));
- },
-
- handleWidgetCommand: function(aWidget, aNode, aEvent) {
- log.debug("handleWidgetCommand");
-
- if (aWidget.type == "button") {
- if (aWidget.onCommand) {
- try {
- aWidget.onCommand.call(null, aEvent);
- } catch (e) {
- log.error(e);
- }
- } else {
- // XXXunf Need to think this through more, and formalize.
- Services.obs.notifyObservers(aNode,
- "customizedui-widget-command",
- aWidget.id);
- }
- } else if (aWidget.type == "view") {
- let ownerWindow = aNode.ownerGlobal;
- let area = this.getPlacementOfWidget(aNode.id).area;
- let anchor = aNode;
- if (area != CustomizableUI.AREA_PANEL) {
- let wrapper = this.wrapWidget(aWidget.id).forWindow(ownerWindow);
- if (wrapper && wrapper.anchor) {
- this.hidePanelForNode(aNode);
- anchor = wrapper.anchor;
- }
- }
- ownerWindow.PanelUI.showSubView(aWidget.viewId, anchor, area);
- }
- },
-
- handleWidgetClick: function(aWidget, aNode, aEvent) {
- log.debug("handleWidgetClick");
- if (aWidget.onClick) {
- try {
- aWidget.onClick.call(null, aEvent);
- } catch (e) {
- Cu.reportError(e);
- }
- } else {
- // XXXunf Need to think this through more, and formalize.
- Services.obs.notifyObservers(aNode, "customizedui-widget-click", aWidget.id);
- }
- },
-
- _getPanelForNode: function(aNode) {
- let panel = aNode;
- while (panel && panel.localName != "panel")
- panel = panel.parentNode;
- return panel;
- },
-
- /*
- * If people put things in the panel which need more than single-click interaction,
- * we don't want to close it. Right now we check for text inputs and menu buttons.
- * We also check for being outside of any toolbaritem/toolbarbutton, ie on a blank
- * part of the menu.
- */
- _isOnInteractiveElement: function(aEvent) {
- function getMenuPopupForDescendant(aNode) {
- let lastPopup = null;
- while (aNode && aNode.parentNode &&
- aNode.parentNode.localName.startsWith("menu")) {
- lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
- aNode = aNode.parentNode;
- }
- return lastPopup;
- }
-
- let target = aEvent.originalTarget;
- let panel = this._getPanelForNode(aEvent.currentTarget);
- // This can happen in e.g. customize mode. If there's no panel,
- // there's clearly nothing for us to close; pretend we're interactive.
- if (!panel) {
- return true;
- }
- // We keep track of:
- // whether we're in an input container (text field)
- let inInput = false;
- // whether we're in a popup/context menu
- let inMenu = false;
- // whether we're in a toolbarbutton/toolbaritem
- let inItem = false;
- // whether the current menuitem has a valid closemenu attribute
- let menuitemCloseMenu = "auto";
- // whether the toolbarbutton/item has a valid closemenu attribute.
- let closemenu = "auto";
-
- // While keeping track of that, we go from the original target back up,
- // to the panel if we have to. We bail as soon as we find an input,
- // a toolbarbutton/item, or the panel:
- while (true && target) {
- // Skip out of iframes etc:
- if (target.nodeType == target.DOCUMENT_NODE) {
- if (!target.defaultView) {
- // Err, we're done.
- break;
- }
- // Cue some voodoo
- target = target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .chromeEventHandler;
- if (!target) {
- break;
- }
- }
- let tagName = target.localName;
- inInput = tagName == "input" || tagName == "textbox";
- inItem = tagName == "toolbaritem" || tagName == "toolbarbutton";
- let isMenuItem = tagName == "menuitem";
- inMenu = inMenu || isMenuItem;
- if (inItem && target.hasAttribute("closemenu")) {
- let closemenuVal = target.getAttribute("closemenu");
- closemenu = (closemenuVal == "single" || closemenuVal == "none") ?
- closemenuVal : "auto";
- }
-
- if (isMenuItem && target.hasAttribute("closemenu")) {
- let closemenuVal = target.getAttribute("closemenu");
- menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
- closemenuVal : "auto";
- }
- // Break out of the loop immediately for disabled items, as we need to
- // keep the menu open in that case.
- if (target.getAttribute("disabled") == "true") {
- return true;
- }
-
- // This isn't in the loop condition because we want to break before
- // changing |target| if any of these conditions are true
- if (inInput || inItem || target == panel) {
- break;
- }
- // We need specific code for popups: the item on which they were invoked
- // isn't necessarily in their parentNode chain:
- if (isMenuItem) {
- let topmostMenuPopup = getMenuPopupForDescendant(target);
- target = (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
- target.parentNode;
- } else {
- target = target.parentNode;
- }
- }
-
- // If the user clicked a menu item...
- if (inMenu) {
- // We care if we're in an input also,
- // or if the user specified closemenu!="auto":
- if (inInput || menuitemCloseMenu != "auto") {
- return true;
- }
- // Otherwise, we're probably fine to close the panel
- return false;
- }
- // If we're not in a menu, and we *are* in a type="menu" toolbarbutton,
- // we'll now interact with the menu
- if (inItem && target.getAttribute("type") == "menu") {
- return true;
- }
- // If we're not in a menu, and we *are* in a type="menu-button" toolbarbutton,
- // it depends whether we're in the dropmarker or the 'real' button:
- if (inItem && target.getAttribute("type") == "menu-button") {
- // 'real' button (which has a single action):
- if (target.getAttribute("anonid") == "button") {
- return closemenu != "none";
- }
- // otherwise, this is the outer button, and the user will now
- // interact with the menu:
- return true;
- }
- return inInput || !inItem;
- },
-
- hidePanelForNode: function(aNode) {
- let panel = this._getPanelForNode(aNode);
- if (panel) {
- panel.hidePopup();
- }
- },
-
- maybeAutoHidePanel: function(aEvent) {
- if (aEvent.type == "keypress") {
- if (aEvent.keyCode != aEvent.DOM_VK_RETURN) {
- return;
- }
- // If the user hit enter/return, we don't check preventDefault - it makes sense
- // that this was prevented, but we probably still want to close the panel.
- // If consumers don't want this to happen, they should specify the closemenu
- // attribute.
-
- } else if (aEvent.type != "command") { // mouse events:
- if (aEvent.defaultPrevented || aEvent.button != 0) {
- return;
- }
- let isInteractive = this._isOnInteractiveElement(aEvent);
- log.debug("maybeAutoHidePanel: interactive ? " + isInteractive);
- if (isInteractive) {
- return;
- }
- }
-
- // We can't use event.target because we might have passed a panelview
- // anonymous content boundary as well, and so target points to the
- // panelmultiview in that case. Unfortunately, this means we get
- // anonymous child nodes instead of the real ones, so looking for the
- // 'stoooop, don't close me' attributes is more involved.
- let target = aEvent.originalTarget;
- let closemenu = "auto";
- let widgetType = "button";
- while (target.parentNode && target.localName != "panel") {
- closemenu = target.getAttribute("closemenu");
- widgetType = target.getAttribute("widget-type");
- if (closemenu == "none" || closemenu == "single" ||
- widgetType == "view") {
- break;
- }
- target = target.parentNode;
- }
- if (closemenu == "none" || widgetType == "view") {
- return;
- }
-
- if (closemenu == "single") {
- let panel = this._getPanelForNode(target);
- let multiview = panel.querySelector("panelmultiview");
- if (multiview.showingSubView) {
- multiview.showMainView();
- return;
- }
- }
-
- // If we get here, we can actually hide the popup:
- this.hidePanelForNode(aEvent.target);
- },
-
- getUnusedWidgets: function(aWindowPalette) {
- let window = aWindowPalette.ownerGlobal;
- let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
- // We use a Set because there can be overlap between the widgets in
- // gPalette and the items in the palette, especially after the first
- // customization, since programmatically generated widgets will remain
- // in the toolbox palette.
- let widgets = new Set();
-
- // It's possible that some widgets have been defined programmatically and
- // have not been overlayed into the palette. We can find those inside
- // gPalette.
- for (let [id, widget] of gPalette) {
- if (!widget.currentArea) {
- if (widget.showInPrivateBrowsing || !isWindowPrivate) {
- widgets.add(id);
- }
- }
- }
-
- log.debug("Iterating the actual nodes of the window palette");
- for (let node of aWindowPalette.children) {
- log.debug("In palette children: " + node.id);
- if (node.id && !this.getPlacementOfWidget(node.id)) {
- widgets.add(node.id);
- }
- }
-
- return [...widgets];
- },
-
- getPlacementOfWidget: function(aWidgetId, aOnlyRegistered, aDeadAreas) {
- if (aOnlyRegistered && !this.widgetExists(aWidgetId)) {
- return null;
- }
-
- for (let [area, placements] of gPlacements) {
- if (!gAreas.has(area) && !aDeadAreas) {
- continue;
- }
- let index = placements.indexOf(aWidgetId);
- if (index != -1) {
- return { area: area, position: index };
- }
- }
-
- return null;
- },
-
- widgetExists: function(aWidgetId) {
- if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
- return true;
- }
-
- // Destroyed API widgets are in gSeenWidgets, but not in gPalette:
- if (gSeenWidgets.has(aWidgetId)) {
- return false;
- }
-
- // We're assuming XUL widgets always exist, as it's much harder to check,
- // and checking would be much more error prone.
- return true;
- },
-
- addWidgetToArea: function(aWidgetId, aArea, aPosition, aInitialAdd) {
- if (!gAreas.has(aArea)) {
- throw new Error("Unknown customization area: " + aArea);
- }
-
- // Hack: don't want special widgets in the panel (need to check here as well
- // as in canWidgetMoveToArea because the menu panel is lazy):
- if (gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL &&
- this.isSpecialWidget(aWidgetId)) {
- return;
- }
-
- // If this is a lazy area that hasn't been restored yet, we can't yet modify
- // it - would would at least like to add to it. So we keep track of it in
- // gFuturePlacements, and use that to add it when restoring the area. We
- // throw away aPosition though, as that can only be bogus if the area hasn't
- // yet been restorted (caller can't possibly know where its putting the
- // widget in relation to other widgets).
- if (this.isAreaLazy(aArea)) {
- gFuturePlacements.get(aArea).add(aWidgetId);
- return;
- }
-
- if (this.isSpecialWidget(aWidgetId)) {
- aWidgetId = this.ensureSpecialWidgetId(aWidgetId);
- }
-
- let oldPlacement = this.getPlacementOfWidget(aWidgetId, false, true);
- if (oldPlacement && oldPlacement.area == aArea) {
- this.moveWidgetWithinArea(aWidgetId, aPosition);
- return;
- }
-
- // Do nothing if the widget is not allowed to move to the target area.
- if (!this.canWidgetMoveToArea(aWidgetId, aArea)) {
- return;
- }
-
- if (oldPlacement) {
- this.removeWidgetFromArea(aWidgetId);
- }
-
- if (!gPlacements.has(aArea)) {
- gPlacements.set(aArea, [aWidgetId]);
- aPosition = 0;
- } else {
- let placements = gPlacements.get(aArea);
- if (typeof aPosition != "number") {
- aPosition = placements.length;
- }
- if (aPosition < 0) {
- aPosition = 0;
- }
- placements.splice(aPosition, 0, aWidgetId);
- }
-
- let widget = gPalette.get(aWidgetId);
- if (widget) {
- widget.currentArea = aArea;
- widget.currentPosition = aPosition;
- }
-
- // We initially set placements with addWidgetToArea, so in that case
- // we don't consider the area "dirtied".
- if (!aInitialAdd) {
- gDirtyAreaCache.add(aArea);
- }
-
- gDirty = true;
- this.saveState();
-
- this.notifyListeners("onWidgetAdded", aWidgetId, aArea, aPosition);
- },
-
- removeWidgetFromArea: function(aWidgetId) {
- let oldPlacement = this.getPlacementOfWidget(aWidgetId, false, true);
- if (!oldPlacement) {
- return;
- }
-
- if (!this.isWidgetRemovable(aWidgetId)) {
- return;
- }
-
- let placements = gPlacements.get(oldPlacement.area);
- let position = placements.indexOf(aWidgetId);
- if (position != -1) {
- placements.splice(position, 1);
- }
-
- let widget = gPalette.get(aWidgetId);
- if (widget) {
- widget.currentArea = null;
- widget.currentPosition = null;
- }
-
- gDirty = true;
- this.saveState();
- gDirtyAreaCache.add(oldPlacement.area);
-
- this.notifyListeners("onWidgetRemoved", aWidgetId, oldPlacement.area);
- },
-
- moveWidgetWithinArea: function(aWidgetId, aPosition) {
- let oldPlacement = this.getPlacementOfWidget(aWidgetId);
- if (!oldPlacement) {
- return;
- }
-
- let placements = gPlacements.get(oldPlacement.area);
- if (typeof aPosition != "number") {
- aPosition = placements.length;
- } else if (aPosition < 0) {
- aPosition = 0;
- } else if (aPosition > placements.length) {
- aPosition = placements.length;
- }
-
- let widget = gPalette.get(aWidgetId);
- if (widget) {
- widget.currentPosition = aPosition;
- widget.currentArea = oldPlacement.area;
- }
-
- if (aPosition == oldPlacement.position) {
- return;
- }
-
- placements.splice(oldPlacement.position, 1);
- // If we just removed the item from *before* where it is now added,
- // we need to compensate the position offset for that:
- if (oldPlacement.position < aPosition) {
- aPosition--;
- }
- placements.splice(aPosition, 0, aWidgetId);
-
- gDirty = true;
- gDirtyAreaCache.add(oldPlacement.area);
-
- this.saveState();
-
- this.notifyListeners("onWidgetMoved", aWidgetId, oldPlacement.area,
- oldPlacement.position, aPosition);
- },
-
- // Note that this does not populate gPlacements, which is done lazily so that
- // the legacy state can be migrated, which is only available once a browser
- // window is openned.
- // The panel area is an exception here, since it has no legacy state and is
- // built lazily - and therefore wouldn't otherwise result in restoring its
- // state immediately when a browser window opens, which is important for
- // other consumers of this API.
- loadSavedState: function() {
- let state = null;
- try {
- state = Services.prefs.getCharPref(kPrefCustomizationState);
- } catch (e) {
- log.debug("No saved state found");
- // This will fail if nothing has been customized, so silently fall back to
- // the defaults.
- }
-
- if (!state) {
- return;
- }
- try {
- gSavedState = JSON.parse(state);
- if (typeof gSavedState != "object" || gSavedState === null) {
- throw "Invalid saved state";
- }
- } catch (e) {
- Services.prefs.clearUserPref(kPrefCustomizationState);
- gSavedState = {};
- log.debug("Error loading saved UI customization state, falling back to defaults.");
- }
-
- if (!("placements" in gSavedState)) {
- gSavedState.placements = {};
- }
-
- if (!("currentVersion" in gSavedState)) {
- gSavedState.currentVersion = 0;
- }
-
- gSeenWidgets = new Set(gSavedState.seen || []);
- gDirtyAreaCache = new Set(gSavedState.dirtyAreaCache || []);
- gNewElementCount = gSavedState.newElementCount || 0;
- },
-
- restoreStateForArea: function(aArea, aLegacyState) {
- let placementsPreexisted = gPlacements.has(aArea);
-
- this.beginBatchUpdate();
- try {
- gRestoring = true;
-
- let restored = false;
- if (placementsPreexisted) {
- log.debug("Restoring " + aArea + " from pre-existing placements");
- for (let [position, id] of gPlacements.get(aArea).entries()) {
- this.moveWidgetWithinArea(id, position);
- }
- gDirty = false;
- restored = true;
- } else {
- gPlacements.set(aArea, []);
- }
-
- if (!restored && gSavedState && aArea in gSavedState.placements) {
- log.debug("Restoring " + aArea + " from saved state");
- let placements = gSavedState.placements[aArea];
- for (let id of placements)
- this.addWidgetToArea(id, aArea);
- gDirty = false;
- restored = true;
- }
-
- if (!restored && aLegacyState) {
- log.debug("Restoring " + aArea + " from legacy state");
- for (let id of aLegacyState)
- this.addWidgetToArea(id, aArea);
- // Don't override dirty state, to ensure legacy state is saved here and
- // therefore only used once.
- restored = true;
- }
-
- if (!restored) {
- log.debug("Restoring " + aArea + " from default state");
- let defaults = gAreas.get(aArea).get("defaultPlacements");
- if (defaults) {
- for (let id of defaults)
- this.addWidgetToArea(id, aArea, null, true);
- }
- gDirty = false;
- }
-
- // Finally, add widgets to the area that were added before the it was able
- // to be restored. This can occur when add-ons register widgets for a
- // lazily-restored area before it's been restored.
- if (gFuturePlacements.has(aArea)) {
- for (let id of gFuturePlacements.get(aArea))
- this.addWidgetToArea(id, aArea);
- gFuturePlacements.delete(aArea);
- }
-
- log.debug("Placements for " + aArea + ":\n\t" + gPlacements.get(aArea).join("\n\t"));
-
- gRestoring = false;
- } finally {
- this.endBatchUpdate();
- }
- },
-
- saveState: function() {
- if (gInBatchStack || !gDirty) {
- return;
- }
- // Clone because we want to modify this map:
- let state = { placements: new Map(gPlacements),
- seen: gSeenWidgets,
- dirtyAreaCache: gDirtyAreaCache,
- currentVersion: kVersion,
- newElementCount: gNewElementCount };
-
- // Merge in previously saved areas if not present in gPlacements.
- // This way, state is still persisted for e.g. temporarily disabled
- // add-ons - see bug 989338.
- if (gSavedState && gSavedState.placements) {
- for (let area of Object.keys(gSavedState.placements)) {
- if (!state.placements.has(area)) {
- let placements = gSavedState.placements[area];
- state.placements.set(area, placements);
- }
- }
- }
-
- log.debug("Saving state.");
- let serialized = JSON.stringify(state, this.serializerHelper);
- log.debug("State saved as: " + serialized);
- Services.prefs.setCharPref(kPrefCustomizationState, serialized);
- gDirty = false;
- },
-
- serializerHelper: function(aKey, aValue) {
- if (typeof aValue == "object" && aValue.constructor.name == "Map") {
- let result = {};
- for (let [mapKey, mapValue] of aValue)
- result[mapKey] = mapValue;
- return result;
- }
-
- if (typeof aValue == "object" && aValue.constructor.name == "Set") {
- return [...aValue];
- }
-
- return aValue;
- },
-
- beginBatchUpdate: function() {
- gInBatchStack++;
- },
-
- endBatchUpdate: function(aForceDirty) {
- gInBatchStack--;
- if (aForceDirty === true) {
- gDirty = true;
- }
- if (gInBatchStack == 0) {
- this.saveState();
- } else if (gInBatchStack < 0) {
- throw new Error("The batch editing stack should never reach a negative number.");
- }
- },
-
- addListener: function(aListener) {
- gListeners.add(aListener);
- },
-
- removeListener: function(aListener) {
- if (aListener == this) {
- return;
- }
-
- gListeners.delete(aListener);
- },
-
- notifyListeners: function(aEvent, ...aArgs) {
- if (gRestoring) {
- return;
- }
-
- for (let listener of gListeners) {
- try {
- if (typeof listener[aEvent] == "function") {
- listener[aEvent].apply(listener, aArgs);
- }
- } catch (e) {
- log.error(e + " -- " + e.fileName + ":" + e.lineNumber);
- }
- }
- },
-
- _dispatchToolboxEventToWindow: function(aEventType, aDetails, aWindow) {
- let evt = new aWindow.CustomEvent(aEventType, {
- bubbles: true,
- cancelable: true,
- detail: aDetails
- });
- aWindow.gNavToolbox.dispatchEvent(evt);
- },
-
- dispatchToolboxEvent: function(aEventType, aDetails={}, aWindow=null) {
- if (aWindow) {
- this._dispatchToolboxEventToWindow(aEventType, aDetails, aWindow);
- return;
- }
- for (let [win, ] of gBuildWindows) {
- this._dispatchToolboxEventToWindow(aEventType, aDetails, win);
- }
- },
-
- createWidget: function(aProperties) {
- let widget = this.normalizeWidget(aProperties, CustomizableUI.SOURCE_EXTERNAL);
- // XXXunf This should probably throw.
- if (!widget) {
- log.error("unable to normalize widget");
- return undefined;
- }
-
- gPalette.set(widget.id, widget);
-
- // Clear our caches:
- gGroupWrapperCache.delete(widget.id);
- for (let [win, ] of gBuildWindows) {
- let cache = gSingleWrapperCache.get(win);
- if (cache) {
- cache.delete(widget.id);
- }
- }
-
- this.notifyListeners("onWidgetCreated", widget.id);
-
- if (widget.defaultArea) {
- let addToDefaultPlacements = false;
- let area = gAreas.get(widget.defaultArea);
- if (!CustomizableUI.isBuiltinToolbar(widget.defaultArea) &&
- widget.defaultArea != CustomizableUI.AREA_PANEL) {
- addToDefaultPlacements = true;
- }
-
- if (addToDefaultPlacements) {
- if (area.has("defaultPlacements")) {
- area.get("defaultPlacements").push(widget.id);
- } else {
- area.set("defaultPlacements", [widget.id]);
- }
- }
- }
-
- // Look through previously saved state to see if we're restoring a widget.
- let seenAreas = new Set();
- let widgetMightNeedAutoAdding = true;
- for (let [area, ] of gPlacements) {
- seenAreas.add(area);
- let areaIsRegistered = gAreas.has(area);
- let index = gPlacements.get(area).indexOf(widget.id);
- if (index != -1) {
- widgetMightNeedAutoAdding = false;
- if (areaIsRegistered) {
- widget.currentArea = area;
- widget.currentPosition = index;
- }
- break;
- }
- }
-
- // Also look at saved state data directly in areas that haven't yet been
- // restored. Can't rely on this for restored areas, as they may have
- // changed.
- if (widgetMightNeedAutoAdding && gSavedState) {
- for (let area of Object.keys(gSavedState.placements)) {
- if (seenAreas.has(area)) {
- continue;
- }
-
- let areaIsRegistered = gAreas.has(area);
- let index = gSavedState.placements[area].indexOf(widget.id);
- if (index != -1) {
- widgetMightNeedAutoAdding = false;
- if (areaIsRegistered) {
- widget.currentArea = area;
- widget.currentPosition = index;
- }
- break;
- }
- }
- }
-
- // If we're restoring the widget to it's old placement, fire off the
- // onWidgetAdded event - our own handler will take care of adding it to
- // any build areas.
- this.beginBatchUpdate();
- try {
- if (widget.currentArea) {
- this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
- widget.currentPosition);
- } else if (widgetMightNeedAutoAdding) {
- let autoAdd = true;
- try {
- autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
- } catch (e) {}
-
- // If the widget doesn't have an existing placement, and it hasn't been
- // seen before, then add it to its default area so it can be used.
- // If the widget is not removable, we *have* to add it to its default
- // area here.
- let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
- if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
- if (widget.defaultArea) {
- if (this.isAreaLazy(widget.defaultArea)) {
- gFuturePlacements.get(widget.defaultArea).add(widget.id);
- } else {
- this.addWidgetToArea(widget.id, widget.defaultArea);
- }
- }
- }
- }
- } finally {
- // Ensure we always have this widget in gSeenWidgets, and save
- // state in case this needs to be done here.
- gSeenWidgets.add(widget.id);
- this.endBatchUpdate(true);
- }
-
- this.notifyListeners("onWidgetAfterCreation", widget.id, widget.currentArea);
- return widget.id;
- },
-
- createBuiltinWidget: function(aData) {
- // This should only ever be called on startup, before any windows are
- // opened - so we know there's no build areas to handle. Also, builtin
- // widgets are expected to be (mostly) static, so shouldn't affect the
- // current placement settings.
-
- // This allows a widget to be both built-in by default but also able to be
- // destroyed and removed from the area based on criteria that may not be
- // available when the widget is created -- for example, because some other
- // feature in the browser supersedes the widget.
- let conditionalDestroyPromise = aData.conditionalDestroyPromise || null;
- delete aData.conditionalDestroyPromise;
-
- let widget = this.normalizeWidget(aData, CustomizableUI.SOURCE_BUILTIN);
- if (!widget) {
- log.error("Error creating builtin widget: " + aData.id);
- return;
- }
-
- log.debug("Creating built-in widget with id: " + widget.id);
- gPalette.set(widget.id, widget);
-
- if (conditionalDestroyPromise) {
- conditionalDestroyPromise.then(shouldDestroy => {
- if (shouldDestroy) {
- this.destroyWidget(widget.id);
- this.removeWidgetFromArea(widget.id);
- }
- }, err => {
- Cu.reportError(err);
- });
- }
- },
-
- // Returns true if the area will eventually lazily restore (but hasn't yet).
- isAreaLazy: function(aArea) {
- if (gPlacements.has(aArea)) {
- return false;
- }
- return gAreas.get(aArea).has("legacy");
- },
-
- // XXXunf Log some warnings here, when the data provided isn't up to scratch.
- normalizeWidget: function(aData, aSource) {
- let widget = {
- implementation: aData,
- source: aSource || CustomizableUI.SOURCE_EXTERNAL,
- instances: new Map(),
- currentArea: null,
- removable: true,
- overflows: true,
- defaultArea: null,
- shortcutId: null,
- tabSpecific: false,
- tooltiptext: null,
- showInPrivateBrowsing: true,
- _introducedInVersion: -1,
- };
-
- if (typeof aData.id != "string" || !/^[a-z0-9-_]{1,}$/i.test(aData.id)) {
- log.error("Given an illegal id in normalizeWidget: " + aData.id);
- return null;
- }
-
- delete widget.implementation.currentArea;
- widget.implementation.__defineGetter__("currentArea", () => widget.currentArea);
-
- const kReqStringProps = ["id"];
- for (let prop of kReqStringProps) {
- if (typeof aData[prop] != "string") {
- log.error("Missing required property '" + prop + "' in normalizeWidget: "
- + aData.id);
- return null;
- }
- widget[prop] = aData[prop];
- }
-
- const kOptStringProps = ["label", "tooltiptext", "shortcutId"];
- for (let prop of kOptStringProps) {
- if (typeof aData[prop] == "string") {
- widget[prop] = aData[prop];
- }
- }
-
- const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows", "tabSpecific"];
- for (let prop of kOptBoolProps) {
- if (typeof aData[prop] == "boolean") {
- widget[prop] = aData[prop];
- }
- }
-
- // When we normalize builtin widgets, areas have not yet been registered:
- if (aData.defaultArea &&
- (aSource == CustomizableUI.SOURCE_BUILTIN || gAreas.has(aData.defaultArea))) {
- widget.defaultArea = aData.defaultArea;
- } else if (!widget.removable) {
- log.error("Widget '" + widget.id + "' is not removable but does not specify " +
- "a valid defaultArea. That's not possible; it must specify a " +
- "valid defaultArea as well.");
- return null;
- }
-
- if ("type" in aData && gSupportedWidgetTypes.has(aData.type)) {
- widget.type = aData.type;
- } else {
- widget.type = "button";
- }
-
- widget.disabled = aData.disabled === true;
-
- if (aSource == CustomizableUI.SOURCE_BUILTIN) {
- widget._introducedInVersion = aData.introducedInVersion || 0;
- }
-
- this.wrapWidgetEventHandler("onBeforeCreated", widget);
- this.wrapWidgetEventHandler("onClick", widget);
- this.wrapWidgetEventHandler("onCreated", widget);
- this.wrapWidgetEventHandler("onDestroyed", widget);
-
- if (widget.type == "button") {
- widget.onCommand = typeof aData.onCommand == "function" ?
- aData.onCommand :
- null;
- } else if (widget.type == "view") {
- if (typeof aData.viewId != "string") {
- log.error("Expected a string for widget " + widget.id + " viewId, but got "
- + aData.viewId);
- return null;
- }
- widget.viewId = aData.viewId;
-
- this.wrapWidgetEventHandler("onViewShowing", widget);
- this.wrapWidgetEventHandler("onViewHiding", widget);
- } else if (widget.type == "custom") {
- this.wrapWidgetEventHandler("onBuild", widget);
- }
-
- if (gPalette.has(widget.id)) {
- return null;
- }
-
- return widget;
- },
-
- wrapWidgetEventHandler: function(aEventName, aWidget) {
- if (typeof aWidget.implementation[aEventName] != "function") {
- aWidget[aEventName] = null;
- return;
- }
- aWidget[aEventName] = function(...aArgs) {
- // Wrap inside a try...catch to properly log errors, until bug 862627 is
- // fixed, which in turn might help bug 503244.
- try {
- // Don't copy the function to the normalized widget object, instead
- // keep it on the original object provided to the API so that
- // additional methods can be implemented and used by the event
- // handlers.
- return aWidget.implementation[aEventName].apply(aWidget.implementation,
- aArgs);
- } catch (e) {
- Cu.reportError(e);
- return undefined;
- }
- };
- },
-
- destroyWidget: function(aWidgetId) {
- let widget = gPalette.get(aWidgetId);
- if (!widget) {
- gGroupWrapperCache.delete(aWidgetId);
- for (let [window, ] of gBuildWindows) {
- let windowCache = gSingleWrapperCache.get(window);
- if (windowCache) {
- windowCache.delete(aWidgetId);
- }
- }
- return;
- }
-
- // Remove it from the default placements of an area if it was added there:
- if (widget.defaultArea) {
- let area = gAreas.get(widget.defaultArea);
- if (area) {
- let defaultPlacements = area.get("defaultPlacements");
- // We can assume this is present because if a widget has a defaultArea,
- // we automatically create a defaultPlacements array for that area.
- let widgetIndex = defaultPlacements.indexOf(aWidgetId);
- if (widgetIndex != -1) {
- defaultPlacements.splice(widgetIndex, 1);
- }
- }
- }
-
- // This will not remove the widget from gPlacements - we want to keep the
- // setting so the widget gets put back in it's old position if/when it
- // returns.
- for (let [window, ] of gBuildWindows) {
- let windowCache = gSingleWrapperCache.get(window);
- if (windowCache) {
- windowCache.delete(aWidgetId);
- }
- let widgetNode = window.document.getElementById(aWidgetId) ||
- window.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
- if (widgetNode) {
- let container = widgetNode.parentNode
- this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null,
- container, true);
- widgetNode.remove();
- this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null,
- container, true);
- }
- if (widget.type == "view") {
- let viewNode = window.document.getElementById(widget.viewId);
- if (viewNode) {
- for (let eventName of kSubviewEvents) {
- let handler = "on" + eventName;
- if (typeof widget[handler] == "function") {
- viewNode.removeEventListener(eventName, widget[handler], false);
- }
- }
- }
- }
- if (widgetNode && widget.onDestroyed) {
- widget.onDestroyed(window.document);
- }
- }
-
- gPalette.delete(aWidgetId);
- gGroupWrapperCache.delete(aWidgetId);
-
- this.notifyListeners("onWidgetDestroyed", aWidgetId);
- },
-
- getCustomizeTargetForArea: function(aArea, aWindow) {
- let buildAreaNodes = gBuildAreas.get(aArea);
- if (!buildAreaNodes) {
- return null;
- }
-
- for (let node of buildAreaNodes) {
- if (node.ownerGlobal == aWindow) {
- return node.customizationTarget ? node.customizationTarget : node;
- }
- }
-
- return null;
- },
-
- reset: function() {
- gResetting = true;
- this._resetUIState();
-
- // Rebuild each registered area (across windows) to reflect the state that
- // was reset above.
- this._rebuildRegisteredAreas();
-
- for (let [widgetId, widget] of gPalette) {
- if (widget.source == CustomizableUI.SOURCE_EXTERNAL) {
- gSeenWidgets.add(widgetId);
- }
- }
- if (gSeenWidgets.size) {
- gDirty = true;
- }
-
- gResetting = false;
- },
-
- _resetUIState: function() {
- try {
- gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
- gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
- gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
- } catch (e) { }
-
- this._resetExtraToolbars();
-
- Services.prefs.clearUserPref(kPrefCustomizationState);
- Services.prefs.clearUserPref(kPrefDrawInTitlebar);
- LightweightThemeManager.currentTheme = null;
- log.debug("State reset");
-
- // Reset placements to make restoring default placements possible.
- gPlacements = new Map();
- gDirtyAreaCache = new Set();
- gSeenWidgets = new Set();
- // Clear the saved state to ensure that defaults will be used.
- gSavedState = null;
- // Restore the state for each area to its defaults
- for (let [areaId, ] of gAreas) {
- this.restoreStateForArea(areaId);
- }
- },
-
- _resetExtraToolbars: function(aFilter = null) {
- let firstWindow = true; // Only need to unregister and persist once
- for (let [win, ] of gBuildWindows) {
- let toolbox = win.gNavToolbox;
- for (let child of toolbox.children) {
- let matchesFilter = !aFilter || aFilter == child.id;
- if (child.hasAttribute("customindex") && matchesFilter) {
- let toolbarId = "toolbar" + child.getAttribute("customindex");
- toolbox.toolbarset.removeAttribute(toolbarId);
- if (firstWindow) {
- win.document.persist(toolbox.toolbarset.id, toolbarId);
- // We have to unregister it properly to ensure we don't kill
- // XUL widgets which might be in here
- this.unregisterArea(child.id, true);
- }
- child.remove();
- }
- }
- firstWindow = false;
- }
- },
-
- _rebuildRegisteredAreas: function() {
- for (let [areaId, areaNodes] of gBuildAreas) {
- let placements = gPlacements.get(areaId);
- let isFirstChangedToolbar = true;
- for (let areaNode of areaNodes) {
- this.buildArea(areaId, placements, areaNode);
-
- let area = gAreas.get(areaId);
- if (area.get("type") == CustomizableUI.TYPE_TOOLBAR) {
- let defaultCollapsed = area.get("defaultCollapsed");
- let win = areaNode.ownerGlobal;
- if (defaultCollapsed !== null) {
- win.setToolbarVisibility(areaNode, !defaultCollapsed, isFirstChangedToolbar);
- }
- }
- isFirstChangedToolbar = false;
- }
- }
- },
-
- /**
- * Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
- */
- undoReset: function() {
- if (gUIStateBeforeReset.uiCustomizationState == null ||
- gUIStateBeforeReset.drawInTitlebar == null) {
- return;
- }
- gUndoResetting = true;
-
- let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
- let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
- let currentTheme = gUIStateBeforeReset.currentTheme;
-
- // Need to clear the previous state before setting the prefs
- // because pref observers may check if there is a previous UI state.
- this._clearPreviousUIState();
-
- Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
- Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
- LightweightThemeManager.currentTheme = currentTheme;
- this.loadSavedState();
- // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
- // and we don't need to do anything else here:
- if (gSavedState) {
- for (let areaId of Object.keys(gSavedState.placements)) {
- let placements = gSavedState.placements[areaId];
- gPlacements.set(areaId, placements);
- }
- this._rebuildRegisteredAreas();
- }
-
- gUndoResetting = false;
- },
-
- _clearPreviousUIState: function() {
- Object.getOwnPropertyNames(gUIStateBeforeReset).forEach((prop) => {
- gUIStateBeforeReset[prop] = null;
- });
- },
-
- removeExtraToolbar: function(aToolbarId) {
- this._resetExtraToolbars(aToolbarId);
- },
-
- /**
- * @param {String|Node} aWidget - widget ID or a widget node (preferred for performance).
- * @return {Boolean} whether the widget is removable
- */
- isWidgetRemovable: function(aWidget) {
- let widgetId;
- let widgetNode;
- if (typeof aWidget == "string") {
- widgetId = aWidget;
- } else {
- widgetId = aWidget.id;
- widgetNode = aWidget;
- }
- let provider = this.getWidgetProvider(widgetId);
-
- if (provider == CustomizableUI.PROVIDER_API) {
- return gPalette.get(widgetId).removable;
- }
-
- if (provider == CustomizableUI.PROVIDER_XUL) {
- if (gBuildWindows.size == 0) {
- // We don't have any build windows to look at, so just assume for now
- // that its removable.
- return true;
- }
-
- if (!widgetNode) {
- // Pick any of the build windows to look at.
- let [window, ] = [...gBuildWindows][0];
- [, widgetNode] = this.getWidgetNode(widgetId, window);
- }
- // If we don't have a node, we assume it's removable. This can happen because
- // getWidgetProvider returns PROVIDER_XUL by default, but this will also happen
- // for API-provided widgets which have been destroyed.
- if (!widgetNode) {
- return true;
- }
- return widgetNode.getAttribute("removable") == "true";
- }
-
- // Otherwise this is either a special widget, which is always removable, or
- // an API widget which has already been removed from gPalette. Returning true
- // here allows us to then remove its ID from any placements where it might
- // still occur.
- return true;
- },
-
- canWidgetMoveToArea: function(aWidgetId, aArea) {
- let placement = this.getPlacementOfWidget(aWidgetId);
- if (placement && placement.area != aArea) {
- // Special widgets can't move to the menu panel.
- if (this.isSpecialWidget(aWidgetId) && gAreas.has(aArea) &&
- gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL) {
- return false;
- }
- // For everything else, just return whether the widget is removable.
- return this.isWidgetRemovable(aWidgetId);
- }
-
- return true;
- },
-
- ensureWidgetPlacedInWindow: function(aWidgetId, aWindow) {
- let placement = this.getPlacementOfWidget(aWidgetId);
- if (!placement) {
- return false;
- }
- let areaNodes = gBuildAreas.get(placement.area);
- if (!areaNodes) {
- return false;
- }
- let container = [...areaNodes].filter((n) => n.ownerGlobal == aWindow);
- if (!container.length) {
- return false;
- }
- let existingNode = container[0].getElementsByAttribute("id", aWidgetId)[0];
- if (existingNode) {
- return true;
- }
-
- this.insertNodeInWindow(aWidgetId, container[0], true);
- return true;
- },
-
- get inDefaultState() {
- for (let [areaId, props] of gAreas) {
- let defaultPlacements = props.get("defaultPlacements");
- // Areas without default placements (like legacy ones?) get skipped
- if (!defaultPlacements) {
- continue;
- }
-
- let currentPlacements = gPlacements.get(areaId);
- // We're excluding all of the placement IDs for items that do not exist,
- // and items that have removable="false",
- // because we don't want to consider them when determining if we're
- // in the default state. This way, if an add-on introduces a widget
- // and is then uninstalled, the leftover placement doesn't cause us to
- // automatically assume that the buttons are not in the default state.
- let buildAreaNodes = gBuildAreas.get(areaId);
- if (buildAreaNodes && buildAreaNodes.size) {
- let container = [...buildAreaNodes][0];
- let removableOrDefault = (itemNodeOrItem) => {
- let item = (itemNodeOrItem && itemNodeOrItem.id) || itemNodeOrItem;
- let isRemovable = this.isWidgetRemovable(itemNodeOrItem);
- let isInDefault = defaultPlacements.indexOf(item) != -1;
- return isRemovable || isInDefault;
- };
- // Toolbars have a currentSet property which also deals correctly with overflown
- // widgets (if any) - use that instead:
- if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
- let currentSet = container.currentSet;
- currentPlacements = currentSet ? currentSet.split(',') : [];
- currentPlacements = currentPlacements.filter(removableOrDefault);
- } else {
- // Clone the array so we don't modify the actual placements...
- currentPlacements = [...currentPlacements];
- currentPlacements = currentPlacements.filter((item) => {
- let itemNode = container.getElementsByAttribute("id", item)[0];
- return itemNode && removableOrDefault(itemNode || item);
- });
- }
-
- if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
- let attribute = container.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
- let collapsed = container.getAttribute(attribute) == "true";
- let defaultCollapsed = props.get("defaultCollapsed");
- if (defaultCollapsed !== null && collapsed != defaultCollapsed) {
- log.debug("Found " + areaId + " had non-default toolbar visibility (expected " + defaultCollapsed + ", was " + collapsed + ")");
- return false;
- }
- }
- }
- log.debug("Checking default state for " + areaId + ":\n" + currentPlacements.join(",") +
- "\nvs.\n" + defaultPlacements.join(","));
-
- if (currentPlacements.length != defaultPlacements.length) {
- return false;
- }
-
- for (let i = 0; i < currentPlacements.length; ++i) {
- if (currentPlacements[i] != defaultPlacements[i]) {
- log.debug("Found " + currentPlacements[i] + " in " + areaId + " where " +
- defaultPlacements[i] + " was expected!");
- return false;
- }
- }
- }
-
- if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
- log.debug(kPrefDrawInTitlebar + " pref is non-default");
- return false;
- }
-
- if (LightweightThemeManager.currentTheme) {
- log.debug(LightweightThemeManager.currentTheme + " theme is non-default");
- return false;
- }
-
- return true;
- },
-
- setToolbarVisibility: function(aToolbarId, aIsVisible) {
- // We only persist the attribute the first time.
- let isFirstChangedToolbar = true;
- for (let window of CustomizableUI.windows) {
- let toolbar = window.document.getElementById(aToolbarId);
- if (toolbar) {
- window.setToolbarVisibility(toolbar, aIsVisible, isFirstChangedToolbar);
- isFirstChangedToolbar = false;
- }
- }
- },
-};
-Object.freeze(CustomizableUIInternal);
-
-this.CustomizableUI = {
- /**
- * Constant reference to the ID of the menu panel.
- */
- AREA_PANEL: "PanelUI-contents",
- /**
- * Constant reference to the ID of the navigation toolbar.
- */
- AREA_NAVBAR: "nav-bar",
- /**
- * Constant reference to the ID of the menubar's toolbar.
- */
- AREA_MENUBAR: "toolbar-menubar",
- /**
- * Constant reference to the ID of the tabstrip toolbar.
- */
- AREA_TABSTRIP: "TabsToolbar",
- /**
- * Constant reference to the ID of the bookmarks toolbar.
- */
- AREA_BOOKMARKS: "PersonalToolbar",
- /**
- * Constant reference to the ID of the addon-bar toolbar shim.
- * Do not use, this will be removed as soon as reasonably possible.
- * @deprecated
- */
- AREA_ADDONBAR: "addon-bar",
- /**
- * Constant indicating the area is a menu panel.
- */
- TYPE_MENU_PANEL: "menu-panel",
- /**
- * Constant indicating the area is a toolbar.
- */
- TYPE_TOOLBAR: "toolbar",
-
- /**
- * Constant indicating a XUL-type provider.
- */
- PROVIDER_XUL: "xul",
- /**
- * Constant indicating an API-type provider.
- */
- PROVIDER_API: "api",
- /**
- * Constant indicating dynamic (special) widgets: spring, spacer, and separator.
- */
- PROVIDER_SPECIAL: "special",
-
- /**
- * Constant indicating the widget is built-in
- */
- SOURCE_BUILTIN: "builtin",
- /**
- * Constant indicating the widget is externally provided
- * (e.g. by add-ons or other items not part of the builtin widget set).
- */
- SOURCE_EXTERNAL: "external",
-
- /**
- * The class used to distinguish items that span the entire menu panel.
- */
- WIDE_PANEL_CLASS: "panel-wide-item",
- /**
- * The (constant) number of columns in the menu panel.
- */
- PANEL_COLUMN_COUNT: 3,
-
- /**
- * Constant indicating the reason the event was fired was a window closing
- */
- REASON_WINDOW_CLOSED: "window-closed",
- /**
- * Constant indicating the reason the event was fired was an area being
- * unregistered separately from window closing mechanics.
- */
- REASON_AREA_UNREGISTERED: "area-unregistered",
-
-
- /**
- * An iteratable property of windows managed by CustomizableUI.
- * Note that this can *only* be used as an iterator. ie:
- * for (let window of CustomizableUI.windows) { ... }
- */
- windows: {
- *[Symbol.iterator]() {
- for (let [window, ] of gBuildWindows)
- yield window;
- }
- },
-
- /**
- * Add a listener object that will get fired for various events regarding
- * customization.
- *
- * @param aListener the listener object to add
- *
- * Not all event handler methods need to be defined.
- * CustomizableUI will catch exceptions. Events are dispatched
- * synchronously on the UI thread, so if you can delay any/some of your
- * processing, that is advisable. The following event handlers are supported:
- * - onWidgetAdded(aWidgetId, aArea, aPosition)
- * Fired when a widget is added to an area. aWidgetId is the widget that
- * was added, aArea the area it was added to, and aPosition the position
- * in which it was added.
- * - onWidgetMoved(aWidgetId, aArea, aOldPosition, aNewPosition)
- * Fired when a widget is moved within its area. aWidgetId is the widget
- * that was moved, aArea the area it was moved in, aOldPosition its old
- * position, and aNewPosition its new position.
- * - onWidgetRemoved(aWidgetId, aArea)
- * Fired when a widget is removed from its area. aWidgetId is the widget
- * that was removed, aArea the area it was removed from.
- *
- * - onWidgetBeforeDOMChange(aNode, aNextNode, aContainer, aIsRemoval)
- * Fired *before* a widget's DOM node is acted upon by CustomizableUI
- * (to add, move or remove it). aNode is the DOM node changed, aNextNode
- * the DOM node (if any) before which a widget will be inserted,
- * aContainer the *actual* DOM container (could be an overflow panel in
- * case of an overflowable toolbar), and aWasRemoval is true iff the
- * action about to happen is the removal of the DOM node.
- * - onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval)
- * Like onWidgetBeforeDOMChange, but fired after the change to the DOM
- * node of the widget.
- *
- * - onWidgetReset(aNode, aContainer)
- * Fired after a reset to default placements moves a widget's node to a
- * different location. aNode is the widget's node, aContainer is the
- * area it was moved into (NB: it might already have been there and been
- * moved to a different position!)
- * - onWidgetUndoMove(aNode, aContainer)
- * Fired after undoing a reset to default placements moves a widget's
- * node to a different location. aNode is the widget's node, aContainer
- * is the area it was moved into (NB: it might already have been there
- * and been moved to a different position!)
- * - onAreaReset(aArea, aContainer)
- * Fired after a reset to default placements is complete on an area's
- * DOM node. Note that this is fired for each DOM node. aArea is the area
- * that was reset, aContainer the DOM node that was reset.
- *
- * - onWidgetCreated(aWidgetId)
- * Fired when a widget with id aWidgetId has been created, but before it
- * is added to any placements or any DOM nodes have been constructed.
- * Only fired for API-based widgets.
- * - onWidgetAfterCreation(aWidgetId, aArea)
- * Fired after a widget with id aWidgetId has been created, and has been
- * added to either its default area or the area in which it was placed
- * previously. If the widget has no default area and/or it has never
- * been placed anywhere, aArea may be null. Only fired for API-based
- * widgets.
- * - onWidgetDestroyed(aWidgetId)
- * Fired when widgets are destroyed. aWidgetId is the widget that is
- * being destroyed. Only fired for API-based widgets.
- * - onWidgetInstanceRemoved(aWidgetId, aDocument)
- * Fired when a window is unloaded and a widget's instance is destroyed
- * because of this. Only fired for API-based widgets.
- *
- * - onWidgetDrag(aWidgetId, aArea)
- * Fired both when and after customize mode drag handling system tries
- * to determine the width and height of widget aWidgetId when dragged to a
- * different area. aArea will be the area the item is dragged to, or
- * undefined after the measurements have been done and the node has been
- * moved back to its 'regular' area.
- *
- * - onCustomizeStart(aWindow)
- * Fired when opening customize mode in aWindow.
- * - onCustomizeEnd(aWindow)
- * Fired when exiting customize mode in aWindow.
- *
- * - onWidgetOverflow(aNode, aContainer)
- * Fired when a widget's DOM node is overflowing its container, a toolbar,
- * and will be displayed in the overflow panel.
- * - onWidgetUnderflow(aNode, aContainer)
- * Fired when a widget's DOM node is *not* overflowing its container, a
- * toolbar, anymore.
- * - onWindowOpened(aWindow)
- * Fired when a window has been opened that is managed by CustomizableUI,
- * once all of the prerequisite setup has been done.
- * - onWindowClosed(aWindow)
- * Fired when a window that has been managed by CustomizableUI has been
- * closed.
- * - onAreaNodeRegistered(aArea, aContainer)
- * Fired after an area node is first built when it is registered. This
- * is often when the window has opened, but in the case of add-ons,
- * could fire when the node has just been registered with CustomizableUI
- * after an add-on update or disable/enable sequence.
- * - onAreaNodeUnregistered(aArea, aContainer, aReason)
- * Fired when an area node is explicitly unregistered by an API caller,
- * or by a window closing. The aReason parameter indicates which of
- * these is the case.
- */
- addListener: function(aListener) {
- CustomizableUIInternal.addListener(aListener);
- },
- /**
- * Remove a listener added with addListener
- * @param aListener the listener object to remove
- */
- removeListener: function(aListener) {
- CustomizableUIInternal.removeListener(aListener);
- },
-
- /**
- * Register a customizable area with CustomizableUI.
- * @param aName the name of the area to register. Can only contain
- * alphanumeric characters, dashes (-) and underscores (_).
- * @param aProps the properties of the area. The following properties are
- * recognized:
- * - type: the type of area. Either TYPE_TOOLBAR (default) or
- * TYPE_MENU_PANEL;
- * - anchor: for a menu panel or overflowable toolbar, the
- * anchoring node for the panel.
- * - legacy: set to true if you want customizableui to
- * automatically migrate the currentset attribute
- * - overflowable: set to true if your toolbar is overflowable.
- * This requires an anchor, and only has an
- * effect for toolbars.
- * - defaultPlacements: an array of widget IDs making up the
- * default contents of the area
- * - defaultCollapsed: (INTERNAL ONLY) applies if the type is TYPE_TOOLBAR, specifies
- * if toolbar is collapsed by default (default to true).
- * Specify null to ensure that reset/inDefaultArea don't care
- * about a toolbar's collapsed state
- */
- registerArea: function(aName, aProperties) {
- CustomizableUIInternal.registerArea(aName, aProperties);
- },
- /**
- * Register a concrete node for a registered area. This method is automatically
- * called from any toolbar in the main browser window that has its
- * "customizable" attribute set to true. There should normally be no need to
- * call it yourself.
- *
- * Note that ideally, you should register your toolbar using registerArea
- * before any of the toolbars have their XBL bindings constructed (which
- * will happen when they're added to the DOM and are not hidden). If you
- * don't, and your toolbar has a defaultset attribute, CustomizableUI will
- * register it automatically. If your toolbar does not have a defaultset
- * attribute, the node will be saved for processing when you call
- * registerArea. Note that CustomizableUI won't restore state in the area,
- * allow the user to customize it in customize mode, or otherwise deal
- * with it, until the area has been registered.
- */
- registerToolbarNode: function(aToolbar, aExistingChildren) {
- CustomizableUIInternal.registerToolbarNode(aToolbar, aExistingChildren);
- },
- /**
- * Register the menu panel node. This method should not be called by anyone
- * apart from the built-in PanelUI.
- * @param aPanel the panel DOM node being registered.
- */
- registerMenuPanel: function(aPanel) {
- CustomizableUIInternal.registerMenuPanel(aPanel);
- },
- /**
- * Unregister a customizable area. The inverse of registerArea.
- *
- * Unregistering an area will remove all the (removable) widgets in the
- * area, which will return to the panel, and destroy all other traces
- * of the area within CustomizableUI. Note that this means the *contents*
- * of the area's DOM nodes will be moved to the panel or removed, but
- * the area's DOM nodes *themselves* will stay.
- *
- * Furthermore, by default the placements of the area will be kept in the
- * saved state (!) and restored if you re-register the area at a later
- * point. This is useful for e.g. add-ons that get disabled and then
- * re-enabled (e.g. when they update).
- *
- * You can override this last behaviour (and destroy the placements
- * information in the saved state) by passing true for aDestroyPlacements.
- *
- * @param aName the name of the area to unregister
- * @param aDestroyPlacements whether to destroy the placements information
- * for the area, too.
- */
- unregisterArea: function(aName, aDestroyPlacements) {
- CustomizableUIInternal.unregisterArea(aName, aDestroyPlacements);
- },
- /**
- * Add a widget to an area.
- * If the area to which you try to add is not known to CustomizableUI,
- * this will throw.
- * If the area to which you try to add has not yet been restored from its
- * legacy state, this will postpone the addition.
- * If the area to which you try to add is the same as the area in which
- * the widget is currently placed, this will do the same as
- * moveWidgetWithinArea.
- * If the widget cannot be removed from its original location, this will
- * no-op.
- *
- * This will fire an onWidgetAdded notification,
- * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification
- * for each window CustomizableUI knows about.
- *
- * @param aWidgetId the ID of the widget to add
- * @param aArea the ID of the area to add the widget to
- * @param aPosition the position at which to add the widget. If you do not
- * pass a position, the widget will be added to the end
- * of the area.
- */
- addWidgetToArea: function(aWidgetId, aArea, aPosition) {
- CustomizableUIInternal.addWidgetToArea(aWidgetId, aArea, aPosition);
- },
- /**
- * Remove a widget from its area. If the widget cannot be removed from its
- * area, or is not in any area, this will no-op. Otherwise, this will fire an
- * onWidgetRemoved notification, and an onWidgetBeforeDOMChange and
- * onWidgetAfterDOMChange notification for each window CustomizableUI knows
- * about.
- *
- * @param aWidgetId the ID of the widget to remove
- */
- removeWidgetFromArea: function(aWidgetId) {
- CustomizableUIInternal.removeWidgetFromArea(aWidgetId);
- },
- /**
- * Move a widget within an area.
- * If the widget is not in any area, this will no-op.
- * If the widget is already at the indicated position, this will no-op.
- *
- * Otherwise, this will move the widget and fire an onWidgetMoved notification,
- * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification for
- * each window CustomizableUI knows about.
- *
- * @param aWidgetId the ID of the widget to move
- * @param aPosition the position to move the widget to.
- * Negative values or values greater than the number of
- * widgets will be interpreted to mean moving the widget to
- * respectively the first or last position.
- */
- moveWidgetWithinArea: function(aWidgetId, aPosition) {
- CustomizableUIInternal.moveWidgetWithinArea(aWidgetId, aPosition);
- },
- /**
- * Ensure a XUL-based widget created in a window after areas were
- * initialized moves to its correct position.
- * This is roughly equivalent to manually looking up the position and using
- * insertItem in the old API, but a lot less work for consumers.
- * Always prefer this over using toolbar.insertItem (which might no-op
- * because it delegates to addWidgetToArea) or, worse, moving items in the
- * DOM yourself.
- *
- * @param aWidgetId the ID of the widget that was just created
- * @param aWindow the window in which you want to ensure it was added.
- *
- * NB: why is this API per-window, you wonder? Because if you need this,
- * presumably you yourself need to create the widget in all the windows
- * and need to loop through them anyway.
- */
- ensureWidgetPlacedInWindow: function(aWidgetId, aWindow) {
- return CustomizableUIInternal.ensureWidgetPlacedInWindow(aWidgetId, aWindow);
- },
- /**
- * Start a batch update of items.
- * During a batch update, the customization state is not saved to the user's
- * preferences file, in order to reduce (possibly sync) IO.
- * Calls to begin/endBatchUpdate may be nested.
- *
- * Callers should ensure that NO MATTER WHAT they call endBatchUpdate once
- * for each call to beginBatchUpdate, even if there are exceptions in the
- * code in the batch update. Otherwise, for the duration of the
- * Firefox session, customization state is never saved. Typically, you
- * would do this using a try...finally block.
- */
- beginBatchUpdate: function() {
- CustomizableUIInternal.beginBatchUpdate();
- },
- /**
- * End a batch update. See the documentation for beginBatchUpdate above.
- *
- * State is not saved if we believe it is identical to the last known
- * saved state. State is only ever saved when all batch updates have
- * finished (ie there has been 1 endBatchUpdate call for each
- * beginBatchUpdate call). If any of the endBatchUpdate calls pass
- * aForceDirty=true, we will flush to the prefs file.
- *
- * @param aForceDirty force CustomizableUI to flush to the prefs file when
- * all batch updates have finished.
- */
- endBatchUpdate: function(aForceDirty) {
- CustomizableUIInternal.endBatchUpdate(aForceDirty);
- },
- /**
- * Create a widget.
- *
- * To create a widget, you should pass an object with its desired
- * properties. The following properties are supported:
- *
- * - id: the ID of the widget (required).
- * - type: a string indicating the type of widget. Possible types
- * are:
- * 'button' - for simple button widgets (the default)
- * 'view' - for buttons that open a panel or subview,
- * depending on where they are placed.
- * 'custom' - for fine-grained control over the creation
- * of the widget.
- * - viewId: Only useful for views (and required there): the id of the
- * <panelview> that should be shown when clicking the widget.
- * - onBuild(aDoc): Only useful for custom widgets (and required there); a
- * function that will be invoked with the document in which
- * to build a widget. Should return the DOM node that has
- * been constructed.
- * - onBeforeCreated(aDoc): Attached to all non-custom widgets; a function
- * that will be invoked before the widget gets a DOM node
- * constructed, passing the document in which that will happen.
- * This is useful especially for 'view' type widgets that need
- * to construct their views on the fly (e.g. from bootstrapped
- * add-ons)
- * - onCreated(aNode): Attached to all widgets; a function that will be invoked
- * whenever the widget has a DOM node constructed, passing the
- * constructed node as an argument.
- * - onDestroyed(aDoc): Attached to all non-custom widgets; a function that
- * will be invoked after the widget has a DOM node destroyed,
- * passing the document from which it was removed. This is
- * useful especially for 'view' type widgets that need to
- * cleanup after views that were constructed on the fly.
- * - onCommand(aEvt): Only useful for button widgets; a function that will be
- * invoked when the user activates the button.
- * - onClick(aEvt): Attached to all widgets; a function that will be invoked
- * when the user clicks the widget.
- * - onViewShowing(aEvt): Only useful for views; a function that will be
- * invoked when a user shows your view. If any event
- * handler calls aEvt.preventDefault(), the view will
- * not be shown.
- *
- * The event's `detail` property is an object with an
- * `addBlocker` method. Handlers which need to
- * perform asynchronous operations before the view is
- * shown may pass this method a Promise, which will
- * prevent the view from showing until it resolves.
- * Additionally, if the promise resolves to the exact
- * value `false`, the view will not be shown.
- * - onViewHiding(aEvt): Only useful for views; a function that will be
- * invoked when a user hides your view.
- * - tooltiptext: string to use for the tooltip of the widget
- * - label: string to use for the label of the widget
- * - removable: whether the widget is removable (optional, default: true)
- * NB: if you specify false here, you must provide a
- * defaultArea, too.
- * - overflows: whether widget can overflow when in an overflowable
- * toolbar (optional, default: true)
- * - defaultArea: default area to add the widget to
- * (optional, default: none; required if non-removable)
- * - shortcutId: id of an element that has a shortcut for this widget
- * (optional, default: null). This is only used to display
- * the shortcut as part of the tooltip for builtin widgets
- * (which have strings inside
- * customizableWidgets.properties). If you're in an add-on,
- * you should not set this property.
- * - showInPrivateBrowsing: whether to show the widget in private browsing
- * mode (optional, default: true)
- *
- * @param aProperties the specifications for the widget.
- * @return a wrapper around the created widget (see getWidget)
- */
- createWidget: function(aProperties) {
- return CustomizableUIInternal.wrapWidget(
- CustomizableUIInternal.createWidget(aProperties)
- );
- },
- /**
- * Destroy a widget
- *
- * If the widget is part of the default placements in an area, this will
- * remove it from there. It will also remove any DOM instances. However,
- * it will keep the widget in the placements for whatever area it was
- * in at the time. You can remove it from there yourself by calling
- * CustomizableUI.removeWidgetFromArea(aWidgetId).
- *
- * @param aWidgetId the ID of the widget to destroy
- */
- destroyWidget: function(aWidgetId) {
- CustomizableUIInternal.destroyWidget(aWidgetId);
- },
- /**
- * Get a wrapper object with information about the widget.
- * The object provides the following properties
- * (all read-only unless otherwise indicated):
- *
- * - id: the widget's ID;
- * - type: the type of widget (button, view, custom). For
- * XUL-provided widgets, this is always 'custom';
- * - provider: the provider type of the widget, id est one of
- * PROVIDER_API or PROVIDER_XUL;
- * - forWindow(w): a method to obtain a single window wrapper for a widget,
- * in the window w passed as the only argument;
- * - instances: an array of all instances (single window wrappers)
- * of the widget. This array is NOT live;
- * - areaType: the type of the widget's current area
- * - isGroup: true; will be false for wrappers around single widget nodes;
- * - source: for API-provided widgets, whether they are built-in to
- * Firefox or add-on-provided;
- * - disabled: for API-provided widgets, whether the widget is currently
- * disabled. NB: this property is writable, and will toggle
- * all the widgets' nodes' disabled states;
- * - label: for API-provied widgets, the label of the widget;
- * - tooltiptext: for API-provided widgets, the tooltip of the widget;
- * - showInPrivateBrowsing: for API-provided widgets, whether the widget is
- * visible in private browsing;
- *
- * Single window wrappers obtained through forWindow(someWindow) or from the
- * instances array have the following properties
- * (all read-only unless otherwise indicated):
- *
- * - id: the widget's ID;
- * - type: the type of widget (button, view, custom). For
- * XUL-provided widgets, this is always 'custom';
- * - provider: the provider type of the widget, id est one of
- * PROVIDER_API or PROVIDER_XUL;
- * - node: reference to the corresponding DOM node;
- * - anchor: the anchor on which to anchor panels opened from this
- * node. This will point to the overflow chevron on
- * overflowable toolbars if and only if your widget node
- * is overflowed, to the anchor for the panel menu
- * if your widget is inside the panel menu, and to the
- * node itself in all other cases;
- * - overflowed: boolean indicating whether the node is currently in the
- * overflow panel of the toolbar;
- * - isGroup: false; will be true for the group widget;
- * - label: for API-provided widgets, convenience getter for the
- * label attribute of the DOM node;
- * - tooltiptext: for API-provided widgets, convenience getter for the
- * tooltiptext attribute of the DOM node;
- * - disabled: for API-provided widgets, convenience getter *and setter*
- * for the disabled state of this single widget. Note that
- * you may prefer to use the group wrapper's getter/setter
- * instead.
- *
- * @param aWidgetId the ID of the widget whose information you need
- * @return a wrapper around the widget as described above, or null if the
- * widget is known not to exist (anymore). NB: non-null return
- * is no guarantee the widget exists because we cannot know in
- * advance if a XUL widget exists or not.
- */
- getWidget: function(aWidgetId) {
- return CustomizableUIInternal.wrapWidget(aWidgetId);
- },
- /**
- * Get an array of widget wrappers (see getWidget) for all the widgets
- * which are currently not in any area (so which are in the palette).
- *
- * @param aWindowPalette the palette (and by extension, the window) in which
- * CustomizableUI should look. This matters because of
- * course XUL-provided widgets could be available in
- * some windows but not others, and likewise
- * API-provided widgets might not exist in a private
- * window (because of the showInPrivateBrowsing
- * property).
- *
- * @return an array of widget wrappers (see getWidget)
- */
- getUnusedWidgets: function(aWindowPalette) {
- return CustomizableUIInternal.getUnusedWidgets(aWindowPalette).map(
- CustomizableUIInternal.wrapWidget,
- CustomizableUIInternal
- );
- },
- /**
- * Get an array of all the widget IDs placed in an area. This is roughly
- * equivalent to fetching the currentset attribute and splitting by commas
- * in the legacy APIs. Modifying the array will not affect CustomizableUI.
- *
- * @param aArea the ID of the area whose placements you want to obtain.
- * @return an array containing the widget IDs that are in the area.
- *
- * NB: will throw if called too early (before placements have been fetched)
- * or if the area is not currently known to CustomizableUI.
- */
- getWidgetIdsInArea: function(aArea) {
- if (!gAreas.has(aArea)) {
- throw new Error("Unknown customization area: " + aArea);
- }
- if (!gPlacements.has(aArea)) {
- throw new Error("Area not yet restored");
- }
-
- // We need to clone this, as we don't want to let consumers muck with placements
- return [...gPlacements.get(aArea)];
- },
- /**
- * Get an array of widget wrappers for all the widgets in an area. This is
- * the same as calling getWidgetIdsInArea and .map() ing the result through
- * CustomizableUI.getWidget. Careful: this means that if there are IDs in there
- * which don't have corresponding DOM nodes (like in the old-style currentset
- * attribute), there might be nulls in this array, or items for which
- * wrapper.forWindow(win) will return null.
- *
- * @param aArea the ID of the area whose widgets you want to obtain.
- * @return an array of widget wrappers and/or null values for the widget IDs
- * placed in an area.
- *
- * NB: will throw if called too early (before placements have been fetched)
- * or if the area is not currently known to CustomizableUI.
- */
- getWidgetsInArea: function(aArea) {
- return this.getWidgetIdsInArea(aArea).map(
- CustomizableUIInternal.wrapWidget,
- CustomizableUIInternal
- );
- },
- /**
- * Obtain an array of all the area IDs known to CustomizableUI.
- * This array is created for you, so is modifiable without CustomizableUI
- * being affected.
- */
- get areas() {
- return [...gAreas.keys()];
- },
- /**
- * Check what kind of area (toolbar or menu panel) an area is. This is
- * useful if you have a widget that needs to behave differently depending
- * on its location. Note that widget wrappers have a convenience getter
- * property (areaType) for this purpose.
- *
- * @param aArea the ID of the area whose type you want to know
- * @return TYPE_TOOLBAR or TYPE_MENU_PANEL depending on the area, null if
- * the area is unknown.
- */
- getAreaType: function(aArea) {
- let area = gAreas.get(aArea);
- return area ? area.get("type") : null;
- },
- /**
- * Check if a toolbar is collapsed by default.
- *
- * @param aArea the ID of the area whose default-collapsed state you want to know.
- * @return `true` or `false` depending on the area, null if the area is unknown,
- * or its collapsed state cannot normally be controlled by the user
- */
- isToolbarDefaultCollapsed: function(aArea) {
- let area = gAreas.get(aArea);
- return area ? area.get("defaultCollapsed") : null;
- },
- /**
- * Obtain the DOM node that is the customize target for an area in a
- * specific window.
- *
- * Areas can have a customization target that does not correspond to the
- * node itself. In particular, toolbars that have a customizationtarget
- * attribute set will have their customization target set to that node.
- * This means widgets will end up in the customization target, not in the
- * DOM node with the ID that corresponds to the area ID. This is useful
- * because it lets you have fixed content in a toolbar (e.g. the panel
- * menu item in the navbar) and have all the customizable widgets use
- * the customization target.
- *
- * Using this API yourself is discouraged; you should generally not need
- * to be asking for the DOM container node used for a particular area.
- * In particular, if you're wanting to check it in relation to a widget's
- * node, your DOM node might not be a direct child of the customize target
- * in a window if, for instance, the window is in customization mode, or if
- * this is an overflowable toolbar and the widget has been overflowed.
- *
- * @param aArea the ID of the area whose customize target you want to have
- * @param aWindow the window where you want to fetch the DOM node.
- * @return the customize target DOM node for aArea in aWindow
- */
- getCustomizeTargetForArea: function(aArea, aWindow) {
- return CustomizableUIInternal.getCustomizeTargetForArea(aArea, aWindow);
- },
- /**
- * Reset the customization state back to its default.
- *
- * This is the nuclear option. You should never call this except if the user
- * explicitly requests it. Firefox does this when the user clicks the
- * "Restore Defaults" button in customize mode.
- */
- reset: function() {
- CustomizableUIInternal.reset();
- },
-
- /**
- * Undo the previous reset, can only be called immediately after a reset.
- * @return a promise that will be resolved when the operation is complete.
- */
- undoReset: function() {
- CustomizableUIInternal.undoReset();
- },
-
- /**
- * Remove a custom toolbar added in a previous version of Firefox or using
- * an add-on. NB: only works on the customizable toolbars generated by
- * the toolbox itself. Intended for use from CustomizeMode, not by
- * other consumers.
- * @param aToolbarId the ID of the toolbar to remove
- */
- removeExtraToolbar: function(aToolbarId) {
- CustomizableUIInternal.removeExtraToolbar(aToolbarId);
- },
-
- /**
- * Can the last Restore Defaults operation be undone.
- *
- * @return A boolean stating whether an undo of the
- * Restore Defaults can be performed.
- */
- get canUndoReset() {
- return gUIStateBeforeReset.uiCustomizationState != null ||
- gUIStateBeforeReset.drawInTitlebar != null ||
- gUIStateBeforeReset.currentTheme != null;
- },
-
- /**
- * Get the placement of a widget. This is by far the best way to obtain
- * information about what the state of your widget is. The internals of
- * this call are cheap (no DOM necessary) and you will know where the user
- * has put your widget.
- *
- * @param aWidgetId the ID of the widget whose placement you want to know
- * @return
- * {
- * area: "somearea", // The ID of the area where the widget is placed
- * position: 42 // the index in the placements array corresponding to
- * // your widget.
- * }
- *
- * OR
- *
- * null // if the widget is not placed anywhere (ie in the palette)
- */
- getPlacementOfWidget: function(aWidgetId, aOnlyRegistered=true, aDeadAreas=false) {
- return CustomizableUIInternal.getPlacementOfWidget(aWidgetId, aOnlyRegistered, aDeadAreas);
- },
- /**
- * Check if a widget can be removed from the area it's in.
- *
- * Note that if you're wanting to move the widget somewhere, you should
- * generally be checking canWidgetMoveToArea, because that will return
- * true if the widget is already in the area where you want to move it (!).
- *
- * NB: oh, also, this method might lie if the widget in question is a
- * XUL-provided widget and there are no windows open, because it
- * can obviously not check anything in this case. It will return
- * true. You will be able to move the widget elsewhere. However,
- * once the user reopens a window, the widget will move back to its
- * 'proper' area automagically.
- *
- * @param aWidgetId a widget ID or DOM node to check
- * @return true if the widget can be removed from its area,
- * false otherwise.
- */
- isWidgetRemovable: function(aWidgetId) {
- return CustomizableUIInternal.isWidgetRemovable(aWidgetId);
- },
- /**
- * Check if a widget can be moved to a particular area. Like
- * isWidgetRemovable but better, because it'll return true if the widget
- * is already in the right area.
- *
- * @param aWidgetId the widget ID or DOM node you want to move somewhere
- * @param aArea the area ID you want to move it to.
- * @return true if this is possible, false if it is not. The same caveats as
- * for isWidgetRemovable apply, however, if no windows are open.
- */
- canWidgetMoveToArea: function(aWidgetId, aArea) {
- return CustomizableUIInternal.canWidgetMoveToArea(aWidgetId, aArea);
- },
- /**
- * Whether we're in a default state. Note that non-removable non-default
- * widgets and non-existing widgets are not taken into account in determining
- * whether we're in the default state.
- *
- * NB: this is a property with a getter. The getter is NOT cheap, because
- * it does smart things with non-removable non-default items, non-existent
- * items, and so forth. Please don't call unless necessary.
- */
- get inDefaultState() {
- return CustomizableUIInternal.inDefaultState;
- },
-
- /**
- * Set a toolbar's visibility state in all windows.
- * @param aToolbarId the toolbar whose visibility should be adjusted
- * @param aIsVisible whether the toolbar should be visible
- */
- setToolbarVisibility: function(aToolbarId, aIsVisible) {
- CustomizableUIInternal.setToolbarVisibility(aToolbarId, aIsVisible);
- },
-
- /**
- * Get a localized property off a (widget?) object.
- *
- * NB: this is unlikely to be useful unless you're in Firefox code, because
- * this code uses the builtin widget stringbundle, and can't be told
- * to use add-on-provided strings. It's mainly here as convenience for
- * custom builtin widgets that build their own DOM but use the same
- * stringbundle as the other builtin widgets.
- *
- * @param aWidget the object whose property we should use to fetch a
- * localizable string;
- * @param aProp the property on the object to use for the fetching;
- * @param aFormatArgs (optional) any extra arguments to use for a formatted
- * string;
- * @param aDef (optional) the default to return if we don't find the
- * string in the stringbundle;
- *
- * @return the localized string, or aDef if the string isn't in the bundle.
- * If no default is provided,
- * if aProp exists on aWidget, we'll return that,
- * otherwise we'll return the empty string
- *
- */
- getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
- return CustomizableUIInternal.getLocalizedProperty(aWidget, aProp,
- aFormatArgs, aDef);
- },
- /**
- * Utility function to detect, find and set a keyboard shortcut for a menuitem
- * or (toolbar)button.
- *
- * @param aShortcutNode the XUL node where the shortcut will be derived from;
- * @param aTargetNode (optional) the XUL node on which the `shortcut`
- * attribute will be set. If NULL, the shortcut will be
- * set on aShortcutNode;
- */
- addShortcut: function(aShortcutNode, aTargetNode) {
- return CustomizableUIInternal.addShortcut(aShortcutNode, aTargetNode);
- },
- /**
- * Given a node, walk up to the first panel in its ancestor chain, and
- * close it.
- *
- * @param aNode a node whose panel should be closed;
- */
- hidePanelForNode: function(aNode) {
- CustomizableUIInternal.hidePanelForNode(aNode);
- },
- /**
- * Check if a widget is a "special" widget: a spring, spacer or separator.
- *
- * @param aWidgetId the widget ID to check.
- * @return true if the widget is 'special', false otherwise.
- */
- isSpecialWidget: function(aWidgetId) {
- return CustomizableUIInternal.isSpecialWidget(aWidgetId);
- },
- /**
- * Add listeners to a panel that will close it. For use from the menu panel
- * and overflowable toolbar implementations, unlikely to be useful for
- * consumers.
- *
- * @param aPanel the panel to which listeners should be attached.
- */
- addPanelCloseListeners: function(aPanel) {
- CustomizableUIInternal.addPanelCloseListeners(aPanel);
- },
- /**
- * Remove close listeners that have been added to a panel with
- * addPanelCloseListeners. For use from the menu panel and overflowable
- * toolbar implementations, unlikely to be useful for consumers.
- *
- * @param aPanel the panel from which listeners should be removed.
- */
- removePanelCloseListeners: function(aPanel) {
- CustomizableUIInternal.removePanelCloseListeners(aPanel);
- },
- /**
- * Notify listeners a widget is about to be dragged to an area. For use from
- * Customize Mode only, do not use otherwise.
- *
- * @param aWidgetId the ID of the widget that is being dragged to an area.
- * @param aArea the ID of the area to which the widget is being dragged.
- */
- onWidgetDrag: function(aWidgetId, aArea) {
- CustomizableUIInternal.notifyListeners("onWidgetDrag", aWidgetId, aArea);
- },
- /**
- * Notify listeners that a window is entering customize mode. For use from
- * Customize Mode only, do not use otherwise.
- * @param aWindow the window entering customize mode
- */
- notifyStartCustomizing: function(aWindow) {
- CustomizableUIInternal.notifyListeners("onCustomizeStart", aWindow);
- },
- /**
- * Notify listeners that a window is exiting customize mode. For use from
- * Customize Mode only, do not use otherwise.
- * @param aWindow the window exiting customize mode
- */
- notifyEndCustomizing: function(aWindow) {
- CustomizableUIInternal.notifyListeners("onCustomizeEnd", aWindow);
- },
-
- /**
- * Notify toolbox(es) of a particular event. If you don't pass aWindow,
- * all toolboxes will be notified. For use from Customize Mode only,
- * do not use otherwise.
- * @param aEvent the name of the event to send.
- * @param aDetails optional, the details of the event.
- * @param aWindow optional, the window in which to send the event.
- */
- dispatchToolboxEvent: function(aEvent, aDetails={}, aWindow=null) {
- CustomizableUIInternal.dispatchToolboxEvent(aEvent, aDetails, aWindow);
- },
-
- /**
- * Check whether an area is overflowable.
- *
- * @param aAreaId the ID of an area to check for overflowable-ness
- * @return true if the area is overflowable, false otherwise.
- */
- isAreaOverflowable: function(aAreaId) {
- let area = gAreas.get(aAreaId);
- return area ? area.get("type") == this.TYPE_TOOLBAR && area.get("overflowable")
- : false;
- },
- /**
- * Obtain a string indicating the place of an element. This is intended
- * for use from customize mode; You should generally use getPlacementOfWidget
- * instead, which is cheaper because it does not use the DOM.
- *
- * @param aElement the DOM node whose place we need to check
- * @return "toolbar" if the node is in a toolbar, "panel" if it is in the
- * menu panel, "palette" if it is in the (visible!) customization
- * palette, undefined otherwise.
- */
- getPlaceForItem: function(aElement) {
- let place;
- let node = aElement;
- while (node && !place) {
- if (node.localName == "toolbar")
- place = "toolbar";
- else if (node.id == CustomizableUI.AREA_PANEL)
- place = "panel";
- else if (node.id == "customization-palette")
- place = "palette";
-
- node = node.parentNode;
- }
- return place;
- },
-
- /**
- * Check if a toolbar is builtin or not.
- * @param aToolbarId the ID of the toolbar you want to check
- */
- isBuiltinToolbar: function(aToolbarId) {
- return CustomizableUIInternal._builtinToolbars.has(aToolbarId);
- },
-};
-Object.freeze(this.CustomizableUI);
-Object.freeze(this.CustomizableUI.windows);
-
-/**
- * All external consumers of widgets are really interacting with these wrappers
- * which provide a common interface.
- */
-
-/**
- * WidgetGroupWrapper is the common interface for interacting with an entire
- * widget group - AKA, all instances of a widget across a series of windows.
- * This particular wrapper is only used for widgets created via the provider
- * API.
- */
-function WidgetGroupWrapper(aWidget) {
- this.isGroup = true;
-
- const kBareProps = ["id", "source", "type", "disabled", "label", "tooltiptext",
- "showInPrivateBrowsing", "viewId"];
- for (let prop of kBareProps) {
- let propertyName = prop;
- this.__defineGetter__(propertyName, () => aWidget[propertyName]);
- }
-
- this.__defineGetter__("provider", () => CustomizableUI.PROVIDER_API);
-
- this.__defineSetter__("disabled", function(aValue) {
- aValue = !!aValue;
- aWidget.disabled = aValue;
- for (let [, instance] of aWidget.instances) {
- instance.disabled = aValue;
- }
- });
-
- this.forWindow = function WidgetGroupWrapper_forWindow(aWindow) {
- let wrapperMap;
- if (!gSingleWrapperCache.has(aWindow)) {
- wrapperMap = new Map();
- gSingleWrapperCache.set(aWindow, wrapperMap);
- } else {
- wrapperMap = gSingleWrapperCache.get(aWindow);
- }
- if (wrapperMap.has(aWidget.id)) {
- return wrapperMap.get(aWidget.id);
- }
-
- let instance = aWidget.instances.get(aWindow.document);
- if (!instance &&
- (aWidget.showInPrivateBrowsing || !PrivateBrowsingUtils.isWindowPrivate(aWindow))) {
- instance = CustomizableUIInternal.buildWidget(aWindow.document,
- aWidget);
- }
-
- let wrapper = new WidgetSingleWrapper(aWidget, instance);
- wrapperMap.set(aWidget.id, wrapper);
- return wrapper;
- };
-
- this.__defineGetter__("instances", function() {
- // Can't use gBuildWindows here because some areas load lazily:
- let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
- if (!placement) {
- return [];
- }
- let area = placement.area;
- let buildAreas = gBuildAreas.get(area);
- if (!buildAreas) {
- return [];
- }
- return Array.from(buildAreas, (node) => this.forWindow(node.ownerGlobal));
- });
-
- this.__defineGetter__("areaType", function() {
- let areaProps = gAreas.get(aWidget.currentArea);
- return areaProps && areaProps.get("type");
- });
-
- Object.freeze(this);
-}
-
-/**
- * A WidgetSingleWrapper is a wrapper around a single instance of a widget in
- * a particular window.
- */
-function WidgetSingleWrapper(aWidget, aNode) {
- this.isGroup = false;
-
- this.node = aNode;
- this.provider = CustomizableUI.PROVIDER_API;
-
- const kGlobalProps = ["id", "type"];
- for (let prop of kGlobalProps) {
- this[prop] = aWidget[prop];
- }
-
- const kNodeProps = ["label", "tooltiptext"];
- for (let prop of kNodeProps) {
- let propertyName = prop;
- // Look at the node for these, instead of the widget data, to ensure the
- // wrapper always reflects this live instance.
- this.__defineGetter__(propertyName,
- () => aNode.getAttribute(propertyName));
- }
-
- this.__defineGetter__("disabled", () => aNode.disabled);
- this.__defineSetter__("disabled", function(aValue) {
- aNode.disabled = !!aValue;
- });
-
- this.__defineGetter__("anchor", function() {
- let anchorId;
- // First check for an anchor for the area:
- let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
- if (placement) {
- anchorId = gAreas.get(placement.area).get("anchor");
- }
- if (!anchorId) {
- anchorId = aNode.getAttribute("cui-anchorid");
- }
-
- return anchorId ? aNode.ownerDocument.getElementById(anchorId)
- : aNode;
- });
-
- this.__defineGetter__("overflowed", function() {
- return aNode.getAttribute("overflowedItem") == "true";
- });
-
- Object.freeze(this);
-}
-
-/**
- * XULWidgetGroupWrapper is the common interface for interacting with an entire
- * widget group - AKA, all instances of a widget across a series of windows.
- * This particular wrapper is only used for widgets created via the old-school
- * XUL method (overlays, or programmatically injecting toolbaritems, or other
- * such things).
- */
-// XXXunf Going to need to hook this up to some events to keep it all live.
-function XULWidgetGroupWrapper(aWidgetId) {
- this.isGroup = true;
- this.id = aWidgetId;
- this.type = "custom";
- this.provider = CustomizableUI.PROVIDER_XUL;
-
- this.forWindow = function XULWidgetGroupWrapper_forWindow(aWindow) {
- let wrapperMap;
- if (!gSingleWrapperCache.has(aWindow)) {
- wrapperMap = new Map();
- gSingleWrapperCache.set(aWindow, wrapperMap);
- } else {
- wrapperMap = gSingleWrapperCache.get(aWindow);
- }
- if (wrapperMap.has(aWidgetId)) {
- return wrapperMap.get(aWidgetId);
- }
-
- let instance = aWindow.document.getElementById(aWidgetId);
- if (!instance) {
- // Toolbar palettes aren't part of the document, so elements in there
- // won't be found via document.getElementById().
- instance = aWindow.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
- }
-
- let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance, aWindow.document);
- wrapperMap.set(aWidgetId, wrapper);
- return wrapper;
- };
-
- this.__defineGetter__("areaType", function() {
- let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
- if (!placement) {
- return null;
- }
-
- let areaProps = gAreas.get(placement.area);
- return areaProps && areaProps.get("type");
- });
-
- this.__defineGetter__("instances", function() {
- return Array.from(gBuildWindows, (wins) => this.forWindow(wins[0]));
- });
-
- Object.freeze(this);
-}
-
-/**
- * A XULWidgetSingleWrapper is a wrapper around a single instance of a XUL
- * widget in a particular window.
- */
-function XULWidgetSingleWrapper(aWidgetId, aNode, aDocument) {
- this.isGroup = false;
-
- this.id = aWidgetId;
- this.type = "custom";
- this.provider = CustomizableUI.PROVIDER_XUL;
-
- let weakDoc = Cu.getWeakReference(aDocument);
- // If we keep a strong ref, the weak ref will never die, so null it out:
- aDocument = null;
-
- this.__defineGetter__("node", function() {
- // If we've set this to null (further down), we're sure there's nothing to
- // be gotten here, so bail out early:
- if (!weakDoc) {
- return null;
- }
- if (aNode) {
- // Return the last known node if it's still in the DOM...
- if (aNode.ownerDocument.contains(aNode)) {
- return aNode;
- }
- // ... or the toolbox
- let toolbox = aNode.ownerGlobal.gNavToolbox;
- if (toolbox && toolbox.palette && aNode.parentNode == toolbox.palette) {
- return aNode;
- }
- // If it isn't, clear the cached value and fall through to the "slow" case:
- aNode = null;
- }
-
- let doc = weakDoc.get();
- if (doc) {
- // Store locally so we can cache the result:
- aNode = CustomizableUIInternal.findWidgetInWindow(aWidgetId, doc.defaultView);
- return aNode;
- }
- // The weakref to the document is dead, we're done here forever more:
- weakDoc = null;
- return null;
- });
-
- this.__defineGetter__("anchor", function() {
- let anchorId;
- // First check for an anchor for the area:
- let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
- if (placement) {
- anchorId = gAreas.get(placement.area).get("anchor");
- }
-
- let node = this.node;
- if (!anchorId && node) {
- anchorId = node.getAttribute("cui-anchorid");
- }
-
- return (anchorId && node) ? node.ownerDocument.getElementById(anchorId) : node;
- });
-
- this.__defineGetter__("overflowed", function() {
- let node = this.node;
- if (!node) {
- return false;
- }
- return node.getAttribute("overflowedItem") == "true";
- });
-
- Object.freeze(this);
-}
-
-const LAZY_RESIZE_INTERVAL_MS = 200;
-const OVERFLOW_PANEL_HIDE_DELAY_MS = 500;
-
-function OverflowableToolbar(aToolbarNode) {
- this._toolbar = aToolbarNode;
- this._collapsed = new Map();
- this._enabled = true;
-
- this._toolbar.setAttribute("overflowable", "true");
- let doc = this._toolbar.ownerDocument;
- this._target = this._toolbar.customizationTarget;
- this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
- this._list.toolbox = this._toolbar.toolbox;
- this._list.customizationTarget = this._list;
-
- let window = this._toolbar.ownerGlobal;
- if (window.gBrowserInit.delayedStartupFinished) {
- this.init();
- } else {
- Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
- }
-}
-
-OverflowableToolbar.prototype = {
- initialized: false,
- _forceOnOverflow: false,
-
- observe: function(aSubject, aTopic, aData) {
- if (aTopic == "browser-delayed-startup-finished" &&
- aSubject == this._toolbar.ownerGlobal) {
- Services.obs.removeObserver(this, "browser-delayed-startup-finished");
- this.init();
- }
- },
-
- init: function() {
- let doc = this._toolbar.ownerDocument;
- let window = doc.defaultView;
- window.addEventListener("resize", this);
- window.gNavToolbox.addEventListener("customizationstarting", this);
- window.gNavToolbox.addEventListener("aftercustomization", this);
-
- let chevronId = this._toolbar.getAttribute("overflowbutton");
- this._chevron = doc.getElementById(chevronId);
- this._chevron.addEventListener("command", this);
- this._chevron.addEventListener("dragover", this);
- this._chevron.addEventListener("dragend", this);
-
- let panelId = this._toolbar.getAttribute("overflowpanel");
- this._panel = doc.getElementById(panelId);
- this._panel.addEventListener("popuphiding", this);
- CustomizableUIInternal.addPanelCloseListeners(this._panel);
-
- CustomizableUI.addListener(this);
-
- // The 'overflow' event may have been fired before init was called.
- if (this._toolbar.overflowedDuringConstruction) {
- this.onOverflow(this._toolbar.overflowedDuringConstruction);
- this._toolbar.overflowedDuringConstruction = null;
- }
-
- this.initialized = true;
- },
-
- uninit: function() {
- this._toolbar.removeEventListener("overflow", this._toolbar);
- this._toolbar.removeEventListener("underflow", this._toolbar);
- this._toolbar.removeAttribute("overflowable");
-
- if (!this.initialized) {
- Services.obs.removeObserver(this, "browser-delayed-startup-finished");
- return;
- }
-
- this._disable();
-
- let window = this._toolbar.ownerGlobal;
- window.removeEventListener("resize", this);
- window.gNavToolbox.removeEventListener("customizationstarting", this);
- window.gNavToolbox.removeEventListener("aftercustomization", this);
- this._chevron.removeEventListener("command", this);
- this._chevron.removeEventListener("dragover", this);
- this._chevron.removeEventListener("dragend", this);
- this._panel.removeEventListener("popuphiding", this);
- CustomizableUI.removeListener(this);
- CustomizableUIInternal.removePanelCloseListeners(this._panel);
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "aftercustomization":
- this._enable();
- break;
- case "command":
- if (aEvent.target == this._chevron) {
- this._onClickChevron(aEvent);
- } else {
- this._panel.hidePopup();
- }
- break;
- case "customizationstarting":
- this._disable();
- break;
- case "dragover":
- this._showWithTimeout();
- break;
- case "dragend":
- this._panel.hidePopup();
- break;
- case "popuphiding":
- this._onPanelHiding(aEvent);
- break;
- case "resize":
- this._onResize(aEvent);
- }
- },
-
- show: function() {
- if (this._panel.state == "open") {
- return Promise.resolve();
- }
- return new Promise(resolve => {
- let doc = this._panel.ownerDocument;
- this._panel.hidden = false;
- let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
- gELS.addSystemEventListener(contextMenu, 'command', this, true);
- let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
- this._panel.openPopup(anchor || this._chevron);
- this._chevron.open = true;
-
- let overflowableToolbarInstance = this;
- this._panel.addEventListener("popupshown", function onPopupShown(aEvent) {
- this.removeEventListener("popupshown", onPopupShown);
- this.addEventListener("dragover", overflowableToolbarInstance);
- this.addEventListener("dragend", overflowableToolbarInstance);
- resolve();
- });
- });
- },
-
- _onClickChevron: function(aEvent) {
- if (this._chevron.open) {
- this._panel.hidePopup();
- this._chevron.open = false;
- } else {
- this.show();
- }
- },
-
- _onPanelHiding: function(aEvent) {
- this._chevron.open = false;
- this._panel.removeEventListener("dragover", this);
- this._panel.removeEventListener("dragend", this);
- let doc = aEvent.target.ownerDocument;
- let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
- gELS.removeSystemEventListener(contextMenu, 'command', this, true);
- },
-
- onOverflow: function(aEvent) {
- // The rangeParent check is here because of bug 1111986 and ensuring that
- // overflow events from the bookmarks toolbar items or similar things that
- // manage their own overflow don't trigger an overflow on the entire toolbar
- if (!this._enabled ||
- (aEvent && aEvent.target != this._toolbar.customizationTarget) ||
- (aEvent && aEvent.rangeParent))
- return;
-
- let child = this._target.lastChild;
-
- while (child && this._target.scrollLeftMin != this._target.scrollLeftMax) {
- let prevChild = child.previousSibling;
-
- if (child.getAttribute("overflows") != "false") {
- this._collapsed.set(child.id, this._target.clientWidth);
- child.setAttribute("overflowedItem", true);
- child.setAttribute("cui-anchorid", this._chevron.id);
- CustomizableUIInternal.notifyListeners("onWidgetOverflow", child, this._target);
-
- this._list.insertBefore(child, this._list.firstChild);
- if (!this._toolbar.hasAttribute("overflowing")) {
- CustomizableUI.addListener(this);
- }
- this._toolbar.setAttribute("overflowing", "true");
- }
- child = prevChild;
- }
-
- let win = this._target.ownerGlobal;
- win.UpdateUrlbarSearchSplitterState();
- },
-
- _onResize: function(aEvent) {
- if (!this._lazyResizeHandler) {
- this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
- LAZY_RESIZE_INTERVAL_MS);
- }
- this._lazyResizeHandler.arm();
- },
-
- _moveItemsBackToTheirOrigin: function(shouldMoveAllItems) {
- let placements = gPlacements.get(this._toolbar.id);
- while (this._list.firstChild) {
- let child = this._list.firstChild;
- let minSize = this._collapsed.get(child.id);
-
- if (!shouldMoveAllItems &&
- minSize &&
- this._target.clientWidth <= minSize) {
- return;
- }
-
- this._collapsed.delete(child.id);
- let beforeNodeIndex = placements.indexOf(child.id) + 1;
- // If this is a skipintoolbarset item, meaning it doesn't occur in the placements list,
- // we're inserting it at the end. This will mean first-in, first-out (more or less)
- // leading to as little change in order as possible.
- if (beforeNodeIndex == 0) {
- beforeNodeIndex = placements.length;
- }
- let inserted = false;
- for (; beforeNodeIndex < placements.length; beforeNodeIndex++) {
- let beforeNode = this._target.getElementsByAttribute("id", placements[beforeNodeIndex])[0];
- if (beforeNode) {
- this._target.insertBefore(child, beforeNode);
- inserted = true;
- break;
- }
- }
- if (!inserted) {
- this._target.appendChild(child);
- }
- child.removeAttribute("cui-anchorid");
- child.removeAttribute("overflowedItem");
- CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
- }
-
- let win = this._target.ownerGlobal;
- win.UpdateUrlbarSearchSplitterState();
-
- if (!this._collapsed.size) {
- this._toolbar.removeAttribute("overflowing");
- CustomizableUI.removeListener(this);
- }
- },
-
- _onLazyResize: function() {
- if (!this._enabled)
- return;
-
- if (this._target.scrollLeftMin != this._target.scrollLeftMax) {
- this.onOverflow();
- } else {
- this._moveItemsBackToTheirOrigin();
- }
- },
-
- _disable: function() {
- this._enabled = false;
- this._moveItemsBackToTheirOrigin(true);
- if (this._lazyResizeHandler) {
- this._lazyResizeHandler.disarm();
- }
- },
-
- _enable: function() {
- this._enabled = true;
- this.onOverflow();
- },
-
- onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer) {
- if (aContainer != this._target && aContainer != this._list) {
- return;
- }
- // When we (re)move an item, update all the items that come after it in the list
- // with the minsize *of the item before the to-be-removed node*. This way, we
- // ensure that we try to move items back as soon as that's possible.
- if (aNode.parentNode == this._list) {
- let updatedMinSize;
- if (aNode.previousSibling) {
- updatedMinSize = this._collapsed.get(aNode.previousSibling.id);
- } else {
- // Force (these) items to try to flow back into the bar:
- updatedMinSize = 1;
- }
- let nextItem = aNode.nextSibling;
- while (nextItem) {
- this._collapsed.set(nextItem.id, updatedMinSize);
- nextItem = nextItem.nextSibling;
- }
- }
- },
-
- onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
- if (aContainer != this._target && aContainer != this._list) {
- return;
- }
-
- let nowInBar = aNode.parentNode == aContainer;
- let nowOverflowed = aNode.parentNode == this._list;
- let wasOverflowed = this._collapsed.has(aNode.id);
-
- // If this wasn't overflowed before...
- if (!wasOverflowed) {
- // ... but it is now, then we added to the overflow panel. Exciting stuff:
- if (nowOverflowed) {
- // NB: we're guaranteed that it has a previousSibling, because if it didn't,
- // we would have added it to the toolbar instead. See getOverflowedNextNode.
- let prevId = aNode.previousSibling.id;
- let minSize = this._collapsed.get(prevId);
- this._collapsed.set(aNode.id, minSize);
- aNode.setAttribute("cui-anchorid", this._chevron.id);
- aNode.setAttribute("overflowedItem", true);
- CustomizableUIInternal.notifyListeners("onWidgetOverflow", aNode, this._target);
- }
- // If it is not overflowed and not in the toolbar, and was not overflowed
- // either, it moved out of the toolbar. That means there's now space in there!
- // Let's try to move stuff back:
- else if (!nowInBar) {
- this._moveItemsBackToTheirOrigin(true);
- }
- // If it's in the toolbar now, then we don't care. An overflow event may
- // fire afterwards; that's ok!
- }
- // If it used to be overflowed...
- else if (!nowOverflowed) {
- // ... and isn't anymore, let's remove our bookkeeping:
- this._collapsed.delete(aNode.id);
- aNode.removeAttribute("cui-anchorid");
- aNode.removeAttribute("overflowedItem");
- CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
-
- if (!this._collapsed.size) {
- this._toolbar.removeAttribute("overflowing");
- CustomizableUI.removeListener(this);
- }
- } else if (aNode.previousSibling) {
- // but if it still is, it must have changed places. Bookkeep:
- let prevId = aNode.previousSibling.id;
- let minSize = this._collapsed.get(prevId);
- this._collapsed.set(aNode.id, minSize);
- } else {
- // If it's now the first item in the overflow list,
- // maybe we can return it:
- this._moveItemsBackToTheirOrigin();
- }
- },
-
- findOverflowedInsertionPoints: function(aNode) {
- let newNodeCanOverflow = aNode.getAttribute("overflows") != "false";
- let areaId = this._toolbar.id;
- let placements = gPlacements.get(areaId);
- let nodeIndex = placements.indexOf(aNode.id);
- let nodeBeforeNewNodeIsOverflown = false;
-
- let loopIndex = -1;
- while (++loopIndex < placements.length) {
- let nextNodeId = placements[loopIndex];
- if (loopIndex > nodeIndex) {
- if (newNodeCanOverflow && this._collapsed.has(nextNodeId)) {
- let nextNode = this._list.getElementsByAttribute("id", nextNodeId).item(0);
- if (nextNode) {
- return [this._list, nextNode];
- }
- }
- if (!nodeBeforeNewNodeIsOverflown || !newNodeCanOverflow) {
- let nextNode = this._target.getElementsByAttribute("id", nextNodeId).item(0);
- if (nextNode) {
- return [this._target, nextNode];
- }
- }
- } else if (loopIndex < nodeIndex && this._collapsed.has(nextNodeId)) {
- nodeBeforeNewNodeIsOverflown = true;
- }
- }
-
- let containerForAppending = (this._collapsed.size && newNodeCanOverflow) ?
- this._list : this._target;
- return [containerForAppending, null];
- },
-
- getContainerFor: function(aNode) {
- if (aNode.getAttribute("overflowedItem") == "true") {
- return this._list;
- }
- return this._target;
- },
-
- _hideTimeoutId: null,
- _showWithTimeout: function() {
- this.show().then(function () {
- let window = this._toolbar.ownerGlobal;
- if (this._hideTimeoutId) {
- window.clearTimeout(this._hideTimeoutId);
- }
- this._hideTimeoutId = window.setTimeout(() => {
- if (!this._panel.firstChild.matches(":hover")) {
- this._panel.hidePopup();
- }
- }, OVERFLOW_PANEL_HIDE_DELAY_MS);
- }.bind(this));
- },
-};
-
-CustomizableUIInternal.initialize();
diff --git a/browser/components/customizableui/CustomizableWidgets.jsm b/browser/components/customizableui/CustomizableWidgets.jsm
deleted file mode 100644
index 3e00d385f..000000000
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ /dev/null
@@ -1,1219 +0,0 @@
-/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-this.EXPORTED_SYMBOLS = ["CustomizableWidgets"];
-
-Cu.import("resource:///modules/CustomizableUI.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
- "resource:///modules/BrowserUITelemetry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
- "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
- "resource:///modules/PlacesUIUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
- "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
- "resource://gre/modules/ShortcutUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
- "resource://gre/modules/CharsetMenu.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SyncedTabs",
- "resource://services-sync/SyncedTabs.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
- "resource://gre/modules/ContextualIdentityService.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
- const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
- return Services.strings.createBundle(kCharsetBundle);
-});
-XPCOMUtils.defineLazyGetter(this, "BrandBundle", function() {
- const kBrandBundle = "chrome://branding/locale/brand.properties";
- return Services.strings.createBundle(kBrandBundle);
-});
-
-const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const kPrefCustomizationDebug = "browser.uiCustomization.debug";
-const kWidePanelItemClass = "panel-wide-item";
-
-XPCOMUtils.defineLazyGetter(this, "log", () => {
- let scope = {};
- Cu.import("resource://gre/modules/Console.jsm", scope);
- let debug;
- try {
- debug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
- } catch (ex) {}
- let consoleOptions = {
- maxLogLevel: debug ? "all" : "log",
- prefix: "CustomizableWidgets",
- };
- return new scope.ConsoleAPI(consoleOptions);
-});
-
-
-
-function setAttributes(aNode, aAttrs) {
- let doc = aNode.ownerDocument;
- for (let [name, value] of Object.entries(aAttrs)) {
- if (!value) {
- if (aNode.hasAttribute(name))
- aNode.removeAttribute(name);
- } else {
- if (name == "shortcutId") {
- continue;
- }
- if (name == "label" || name == "tooltiptext") {
- let stringId = (typeof value == "string") ? value : name;
- let additionalArgs = [];
- if (aAttrs.shortcutId) {
- let shortcut = doc.getElementById(aAttrs.shortcutId);
- if (shortcut) {
- additionalArgs.push(ShortcutUtils.prettifyShortcut(shortcut));
- }
- }
- value = CustomizableUI.getLocalizedProperty({id: aAttrs.id}, stringId, additionalArgs);
- }
- aNode.setAttribute(name, value);
- }
- }
-}
-
-function updateCombinedWidgetStyle(aNode, aArea, aModifyCloseMenu) {
- let inPanel = (aArea == CustomizableUI.AREA_PANEL);
- let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1 toolbarbutton-combined";
- let attrs = {class: cls};
- if (aModifyCloseMenu) {
- attrs.closemenu = inPanel ? "none" : null;
- }
- for (let i = 0, l = aNode.childNodes.length; i < l; ++i) {
- if (aNode.childNodes[i].localName == "separator")
- continue;
- setAttributes(aNode.childNodes[i], attrs);
- }
-}
-
-function fillSubviewFromMenuItems(aMenuItems, aSubview) {
- let attrs = ["oncommand", "onclick", "label", "key", "disabled",
- "command", "observes", "hidden", "class", "origin",
- "image", "checked"];
-
- let doc = aSubview.ownerDocument;
- let fragment = doc.createDocumentFragment();
- for (let menuChild of aMenuItems) {
- if (menuChild.hidden)
- continue;
-
- let subviewItem;
- if (menuChild.localName == "menuseparator") {
- // Don't insert duplicate or leading separators. This can happen if there are
- // menus (which we don't copy) above the separator.
- if (!fragment.lastChild || fragment.lastChild.localName == "menuseparator") {
- continue;
- }
- subviewItem = doc.createElementNS(kNSXUL, "menuseparator");
- } else if (menuChild.localName == "menuitem") {
- subviewItem = doc.createElementNS(kNSXUL, "toolbarbutton");
- CustomizableUI.addShortcut(menuChild, subviewItem);
-
- let item = menuChild;
- if (!item.hasAttribute("onclick")) {
- subviewItem.addEventListener("click", event => {
- let newEvent = new doc.defaultView.MouseEvent(event.type, event);
- item.dispatchEvent(newEvent);
- });
- }
-
- if (!item.hasAttribute("oncommand")) {
- subviewItem.addEventListener("command", event => {
- let newEvent = doc.createEvent("XULCommandEvent");
- newEvent.initCommandEvent(
- event.type, event.bubbles, event.cancelable, event.view,
- event.detail, event.ctrlKey, event.altKey, event.shiftKey,
- event.metaKey, event.sourceEvent);
- item.dispatchEvent(newEvent);
- });
- }
- } else {
- continue;
- }
- for (let attr of attrs) {
- let attrVal = menuChild.getAttribute(attr);
- if (attrVal)
- subviewItem.setAttribute(attr, attrVal);
- }
- // We do this after so the .subviewbutton class doesn't get overriden.
- if (menuChild.localName == "menuitem") {
- subviewItem.classList.add("subviewbutton");
- }
- fragment.appendChild(subviewItem);
- }
- aSubview.appendChild(fragment);
-}
-
-function clearSubview(aSubview) {
- let parent = aSubview.parentNode;
- // We'll take the container out of the document before cleaning it out
- // to avoid reflowing each time we remove something.
- parent.removeChild(aSubview);
-
- while (aSubview.firstChild) {
- aSubview.firstChild.remove();
- }
-
- parent.appendChild(aSubview);
-}
-
-const CustomizableWidgets = [
- {
- id: "history-panelmenu",
- type: "view",
- viewId: "PanelUI-history",
- shortcutId: "key_gotoHistory",
- tooltiptext: "history-panelmenu.tooltiptext2",
- defaultArea: CustomizableUI.AREA_PANEL,
- onViewShowing: function(aEvent) {
- // Populate our list of history
- const kMaxResults = 15;
- let doc = aEvent.target.ownerDocument;
- let win = doc.defaultView;
-
- let options = PlacesUtils.history.getNewQueryOptions();
- options.excludeQueries = true;
- options.queryType = options.QUERY_TYPE_HISTORY;
- options.sortingMode = options.SORT_BY_DATE_DESCENDING;
- options.maxResults = kMaxResults;
- let query = PlacesUtils.history.getNewQuery();
-
- let items = doc.getElementById("PanelUI-historyItems");
- // Clear previous history items.
- while (items.firstChild) {
- items.firstChild.remove();
- }
-
- // Get all statically placed buttons to supply them with keyboard shortcuts.
- let staticButtons = items.parentNode.getElementsByTagNameNS(kNSXUL, "toolbarbutton");
- for (let i = 0, l = staticButtons.length; i < l; ++i)
- CustomizableUI.addShortcut(staticButtons[i]);
-
- PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
- .asyncExecuteLegacyQueries([query], 1, options, {
- handleResult: function (aResultSet) {
- let onItemCommand = function (aEvent) {
- // Only handle the click event for middle clicks, we're using the command
- // event otherwise.
- if (aEvent.type == "click" && aEvent.button != 1) {
- return;
- }
- let item = aEvent.target;
- win.openUILink(item.getAttribute("targetURI"), aEvent);
- CustomizableUI.hidePanelForNode(item);
- };
- let fragment = doc.createDocumentFragment();
- let row;
- while ((row = aResultSet.getNextRow())) {
- let uri = row.getResultByIndex(1);
- let title = row.getResultByIndex(2);
- let icon = row.getResultByIndex(6);
-
- let item = doc.createElementNS(kNSXUL, "toolbarbutton");
- item.setAttribute("label", title || uri);
- item.setAttribute("targetURI", uri);
- item.setAttribute("class", "subviewbutton");
- item.addEventListener("command", onItemCommand);
- item.addEventListener("click", onItemCommand);
- if (icon) {
- let iconURL = "moz-anno:favicon:" + icon;
- item.setAttribute("image", iconURL);
- }
- fragment.appendChild(item);
- }
- items.appendChild(fragment);
- },
- handleError: function (aError) {
- log.debug("History view tried to show but had an error: " + aError);
- },
- handleCompletion: function (aReason) {
- log.debug("History view is being shown!");
- },
- });
-
- let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
- while (recentlyClosedTabs.firstChild) {
- recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild);
- }
-
- let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
- while (recentlyClosedWindows.firstChild) {
- recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild);
- }
-
- let utils = RecentlyClosedTabsAndWindowsMenuUtils;
- let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
- "menuRestoreAllTabsSubview.label");
- let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
- let elementCount = tabsFragment.childElementCount;
- separator.hidden = !elementCount;
- while (--elementCount >= 0) {
- tabsFragment.children[elementCount].classList.add("subviewbutton", "cui-withicon");
- }
- recentlyClosedTabs.appendChild(tabsFragment);
-
- let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true,
- "menuRestoreAllWindowsSubview.label");
- separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
- elementCount = windowsFragment.childElementCount;
- separator.hidden = !elementCount;
- while (--elementCount >= 0) {
- windowsFragment.children[elementCount].classList.add("subviewbutton", "cui-withicon");
- }
- recentlyClosedWindows.appendChild(windowsFragment);
- },
- onCreated: function(aNode) {
- // Middle clicking recently closed items won't close the panel - cope:
- let onRecentlyClosedClick = function(aEvent) {
- if (aEvent.button == 1) {
- CustomizableUI.hidePanelForNode(this);
- }
- };
- let doc = aNode.ownerDocument;
- let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
- let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
- recentlyClosedTabs.addEventListener("click", onRecentlyClosedClick);
- recentlyClosedWindows.addEventListener("click", onRecentlyClosedClick);
- },
- onViewHiding: function(aEvent) {
- log.debug("History view is being hidden!");
- }
- }, {
- id: "sync-button",
- label: "remotetabs-panelmenu.label",
- tooltiptext: "remotetabs-panelmenu.tooltiptext2",
- type: "view",
- viewId: "PanelUI-remotetabs",
- defaultArea: CustomizableUI.AREA_PANEL,
- deckIndices: {
- DECKINDEX_TABS: 0,
- DECKINDEX_TABSDISABLED: 1,
- DECKINDEX_FETCHING: 2,
- DECKINDEX_NOCLIENTS: 3,
- },
- onCreated(aNode) {
- // Add an observer to the button so we get the animation during sync.
- // (Note the observer sets many attributes, including label and
- // tooltiptext, but we only want the 'syncstatus' attribute for the
- // animation)
- let doc = aNode.ownerDocument;
- let obnode = doc.createElementNS(kNSXUL, "observes");
- obnode.setAttribute("element", "sync-status");
- obnode.setAttribute("attribute", "syncstatus");
- aNode.appendChild(obnode);
-
- // A somewhat complicated dance to format the mobilepromo label.
- let bundle = doc.getElementById("bundle_browser");
- let formatArgs = ["android", "ios"].map(os => {
- let link = doc.createElement("label");
- link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
- link.setAttribute("mobile-promo-os", os);
- link.className = "text-link remotetabs-promo-link";
- return link.outerHTML;
- });
- let promoParentElt = doc.getElementById("PanelUI-remotetabs-mobile-promo");
- // Put it all together...
- let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
- promoParentElt.innerHTML = contents;
- // We manually manage the "click" event to open the promo links because
- // allowing the "text-link" widget handle it has 2 problems: (1) it only
- // supports button 0 and (2) it's tricky to intercept when it does the
- // open and auto-close the panel. (1) can probably be fixed, but (2) is
- // trickier without hard-coding here the knowledge of exactly what buttons
- // it does support.
- // So we allow left and middle clicks to open the link in a new tab and
- // close the panel; not setting a "href" attribute prevents the text-link
- // widget handling it, and we build the final URL in the click handler to
- // make testing easier (ie, so tests can change the pref after the links
- // were created and have the new pref value used.)
- promoParentElt.addEventListener("click", e => {
- let os = e.target.getAttribute("mobile-promo-os");
- if (!os || e.button > 1) {
- return;
- }
- let link = Services.prefs.getCharPref(`identity.mobilepromo.${os}`) + "synced-tabs";
- doc.defaultView.openUILinkIn(link, "tab");
- CustomizableUI.hidePanelForNode(e.target);
- });
- },
- onViewShowing(aEvent) {
- let doc = aEvent.target.ownerDocument;
- this._tabsList = doc.getElementById("PanelUI-remotetabs-tabslist");
- Services.obs.addObserver(this, SyncedTabs.TOPIC_TABS_CHANGED, false);
-
- if (SyncedTabs.isConfiguredToSyncTabs) {
- if (SyncedTabs.hasSyncedThisSession) {
- this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
- } else {
- // Sync hasn't synced tabs yet, so show the "fetching" panel.
- this.setDeckIndex(this.deckIndices.DECKINDEX_FETCHING);
- }
- // force a background sync.
- SyncedTabs.syncTabs().catch(ex => {
- Cu.reportError(ex);
- });
- // show the current list - it will be updated by our observer.
- this._showTabs();
- } else {
- // not configured to sync tabs, so no point updating the list.
- this.setDeckIndex(this.deckIndices.DECKINDEX_TABSDISABLED);
- }
- },
- onViewHiding() {
- Services.obs.removeObserver(this, SyncedTabs.TOPIC_TABS_CHANGED);
- this._tabsList = null;
- },
- _tabsList: null,
- observe(subject, topic, data) {
- switch (topic) {
- case SyncedTabs.TOPIC_TABS_CHANGED:
- this._showTabs();
- break;
- default:
- break;
- }
- },
- setDeckIndex(index) {
- let deck = this._tabsList.ownerDocument.getElementById("PanelUI-remotetabs-deck");
- // We call setAttribute instead of relying on the XBL property setter due
- // to things going wrong when we try and set the index before the XBL
- // binding has been created - see bug 1241851 for the gory details.
- deck.setAttribute("selectedIndex", index);
- },
-
- _showTabsPromise: Promise.resolve(),
- // Update the tab list after any existing in-flight updates are complete.
- _showTabs() {
- this._showTabsPromise = this._showTabsPromise.then(() => {
- return this.__showTabs();
- });
- },
- // Return a new promise to update the tab list.
- __showTabs() {
- let doc = this._tabsList.ownerDocument;
- return SyncedTabs.getTabClients().then(clients => {
- // The view may have been hidden while the promise was resolving.
- if (!this._tabsList) {
- return;
- }
- if (clients.length === 0 && !SyncedTabs.hasSyncedThisSession) {
- // the "fetching tabs" deck is being shown - let's leave it there.
- // When that first sync completes we'll be notified and update.
- return;
- }
-
- if (clients.length === 0) {
- this.setDeckIndex(this.deckIndices.DECKINDEX_NOCLIENTS);
- return;
- }
-
- this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
- this._clearTabList();
- SyncedTabs.sortTabClientsByLastUsed(clients, 50 /* maxTabs */);
- let fragment = doc.createDocumentFragment();
-
- for (let client of clients) {
- // add a menu separator for all clients other than the first.
- if (fragment.lastChild) {
- let separator = doc.createElementNS(kNSXUL, "menuseparator");
- fragment.appendChild(separator);
- }
- this._appendClient(client, fragment);
- }
- this._tabsList.appendChild(fragment);
- }).catch(err => {
- Cu.reportError(err);
- }).then(() => {
- // an observer for tests.
- Services.obs.notifyObservers(null, "synced-tabs-menu:test:tabs-updated", null);
- });
- },
- _clearTabList () {
- let list = this._tabsList;
- while (list.lastChild) {
- list.lastChild.remove();
- }
- },
- _showNoClientMessage() {
- this._appendMessageLabel("notabslabel");
- },
- _appendMessageLabel(messageAttr, appendTo = null) {
- if (!appendTo) {
- appendTo = this._tabsList;
- }
- let message = this._tabsList.getAttribute(messageAttr);
- let doc = this._tabsList.ownerDocument;
- let messageLabel = doc.createElementNS(kNSXUL, "label");
- messageLabel.textContent = message;
- appendTo.appendChild(messageLabel);
- return messageLabel;
- },
- _appendClient: function (client, attachFragment) {
- let doc = attachFragment.ownerDocument;
- // Create the element for the remote client.
- let clientItem = doc.createElementNS(kNSXUL, "label");
- clientItem.setAttribute("itemtype", "client");
- let window = doc.defaultView;
- clientItem.setAttribute("tooltiptext",
- window.gSyncUI.formatLastSyncDate(new Date(client.lastModified)));
- clientItem.textContent = client.name;
-
- attachFragment.appendChild(clientItem);
-
- if (client.tabs.length == 0) {
- let label = this._appendMessageLabel("notabsforclientlabel", attachFragment);
- label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label");
- } else {
- for (let tab of client.tabs) {
- let tabEnt = this._createTabElement(doc, tab);
- attachFragment.appendChild(tabEnt);
- }
- }
- },
- _createTabElement(doc, tabInfo) {
- let item = doc.createElementNS(kNSXUL, "toolbarbutton");
- let tooltipText = (tabInfo.title ? tabInfo.title + "\n" : "") + tabInfo.url;
- item.setAttribute("itemtype", "tab");
- item.setAttribute("class", "subviewbutton");
- item.setAttribute("targetURI", tabInfo.url);
- item.setAttribute("label", tabInfo.title != "" ? tabInfo.title : tabInfo.url);
- item.setAttribute("image", tabInfo.icon);
- item.setAttribute("tooltiptext", tooltipText);
- // We need to use "click" instead of "command" here so openUILink
- // respects different buttons (eg, to open in a new tab).
- item.addEventListener("click", e => {
- doc.defaultView.openUILink(tabInfo.url, e);
- CustomizableUI.hidePanelForNode(item);
- BrowserUITelemetry.countSyncedTabEvent("open", "toolbarbutton-subview");
- });
- return item;
- },
- }, {
- id: "privatebrowsing-button",
- shortcutId: "key_privatebrowsing",
- defaultArea: CustomizableUI.AREA_PANEL,
- onCommand: function(e) {
- let win = e.target.ownerGlobal;
- win.OpenBrowserWindow({private: true});
- }
- }, {
- id: "save-page-button",
- shortcutId: "key_savePage",
- tooltiptext: "save-page-button.tooltiptext3",
- defaultArea: CustomizableUI.AREA_PANEL,
- onCommand: function(aEvent) {
- let win = aEvent.target.ownerGlobal;
- win.saveBrowser(win.gBrowser.selectedBrowser);
- }
- }, {
- id: "find-button",
- shortcutId: "key_find",
- tooltiptext: "find-button.tooltiptext3",
- defaultArea: CustomizableUI.AREA_PANEL,
- onCommand: function(aEvent) {
- let win = aEvent.target.ownerGlobal;
- if (win.gFindBar) {
- win.gFindBar.onFindCommand();
- }
- }
- }, {
- id: "open-file-button",
- shortcutId: "openFileKb",
- tooltiptext: "open-file-button.tooltiptext3",
- defaultArea: CustomizableUI.AREA_PANEL,
- onCommand: function(aEvent) {
- let win = aEvent.target.ownerGlobal;
- win.BrowserOpenFileWindow();
- }
- }, {
- id: "sidebar-button",
- type: "view",
- viewId: "PanelUI-sidebar",
- tooltiptext: "sidebar-button.tooltiptext2",
- onViewShowing: function(aEvent) {
- // Populate the subview with whatever menuitems are in the
- // sidebar menu. We skip menu elements, because the menu panel has no way
- // of dealing with those right now.
- let doc = aEvent.target.ownerDocument;
- let menu = doc.getElementById("viewSidebarMenu");
-
- // First clear any existing menuitems then populate. Add it to the
- // standard menu first, then copy all sidebar options to the panel.
- let sidebarItems = doc.getElementById("PanelUI-sidebarItems");
- clearSubview(sidebarItems);
- fillSubviewFromMenuItems([...menu.children], sidebarItems);
- }
- }, {
- id: "add-ons-button",
- shortcutId: "key_openAddons",
- tooltiptext: "add-ons-button.tooltiptext3",
- defaultArea: CustomizableUI.AREA_PANEL,
- onCommand: function(aEvent) {
- let win = aEvent.target.ownerGlobal;
- win.BrowserOpenAddonsMgr();
- }
- }, {
- id: "zoom-controls",
- type: "custom",
- tooltiptext: "zoom-controls.tooltiptext2",
- defaultArea: CustomizableUI.AREA_PANEL,
- onBuild: function(aDocument) {
- const kPanelId = "PanelUI-popup";
- let areaType = CustomizableUI.getAreaType(this.currentArea);
- let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL;
- let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR;
-
- let buttons = [{
- id: "zoom-out-button",
- command: "cmd_fullZoomReduce",
- label: true,
- tooltiptext: "tooltiptext2",
- shortcutId: "key_fullZoomReduce",
- }, {
- id: "zoom-reset-button",
- command: "cmd_fullZoomReset",
- tooltiptext: "tooltiptext2",
- shortcutId: "key_fullZoomReset",
- }, {
- id: "zoom-in-button",
- command: "cmd_fullZoomEnlarge",
- label: true,
- tooltiptext: "tooltiptext2",
- shortcutId: "key_fullZoomEnlarge",
- }];
-
- let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
- node.setAttribute("id", "zoom-controls");
- node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
- node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
- // Set this as an attribute in addition to the property to make sure we can style correctly.
- node.setAttribute("removable", "true");
- node.classList.add("chromeclass-toolbar-additional");
- node.classList.add("toolbaritem-combined-buttons");
- node.classList.add(kWidePanelItemClass);
-
- buttons.forEach(function(aButton, aIndex) {
- if (aIndex != 0)
- node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
- let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
- setAttributes(btnNode, aButton);
- node.appendChild(btnNode);
- });
-
- // The middle node is the 'Reset Zoom' button.
- let zoomResetButton = node.childNodes[2];
- let window = aDocument.defaultView;
- function updateZoomResetButton() {
- let updateDisplay = true;
- // Label should always show 100% in customize mode, so don't update:
- if (aDocument.documentElement.hasAttribute("customizing")) {
- updateDisplay = false;
- }
- // XXXgijs in some tests we get called very early, and there's no docShell on the
- // tabbrowser. This breaks the zoom toolkit code (see bug 897410). Don't let that happen:
- let zoomFactor = 100;
- try {
- zoomFactor = Math.round(window.ZoomManager.zoom * 100);
- } catch (e) {}
- zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty(
- buttons[1], "label", [updateDisplay ? zoomFactor : 100]
- ));
- }
-
- // Register ourselves with the service so we know when the zoom prefs change.
- Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomChange", false);
- Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomReset", false);
- Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:location-change", false);
-
- if (inPanel) {
- let panel = aDocument.getElementById(kPanelId);
- panel.addEventListener("popupshowing", updateZoomResetButton);
- } else {
- if (inToolbar) {
- let container = window.gBrowser.tabContainer;
- container.addEventListener("TabSelect", updateZoomResetButton);
- }
- updateZoomResetButton();
- }
- updateCombinedWidgetStyle(node, this.currentArea, true);
-
- let listener = {
- onWidgetAdded: function(aWidgetId, aArea, aPosition) {
- if (aWidgetId != this.id)
- return;
-
- updateCombinedWidgetStyle(node, aArea, true);
- updateZoomResetButton();
-
- let areaType = CustomizableUI.getAreaType(aArea);
- if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
- let panel = aDocument.getElementById(kPanelId);
- panel.addEventListener("popupshowing", updateZoomResetButton);
- } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
- let container = window.gBrowser.tabContainer;
- container.addEventListener("TabSelect", updateZoomResetButton);
- }
- }.bind(this),
-
- onWidgetRemoved: function(aWidgetId, aPrevArea) {
- if (aWidgetId != this.id)
- return;
-
- let areaType = CustomizableUI.getAreaType(aPrevArea);
- if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
- let panel = aDocument.getElementById(kPanelId);
- panel.removeEventListener("popupshowing", updateZoomResetButton);
- } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
- let container = window.gBrowser.tabContainer;
- container.removeEventListener("TabSelect", updateZoomResetButton);
- }
-
- // When a widget is demoted to the palette ('removed'), it's visual
- // style should change.
- updateCombinedWidgetStyle(node, null, true);
- updateZoomResetButton();
- }.bind(this),
-
- onWidgetReset: function(aWidgetNode) {
- if (aWidgetNode != node)
- return;
- updateCombinedWidgetStyle(node, this.currentArea, true);
- updateZoomResetButton();
- }.bind(this),
-
- onWidgetUndoMove: function(aWidgetNode) {
- if (aWidgetNode != node)
- return;
- updateCombinedWidgetStyle(node, this.currentArea, true);
- updateZoomResetButton();
- }.bind(this),
-
- onWidgetMoved: function(aWidgetId, aArea) {
- if (aWidgetId != this.id)
- return;
- updateCombinedWidgetStyle(node, aArea, true);
- updateZoomResetButton();
- }.bind(this),
-
- onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
- if (aWidgetId != this.id || aDoc != aDocument)
- return;
-
- CustomizableUI.removeListener(listener);
- Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomChange");
- Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomReset");
- Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:location-change");
- let panel = aDoc.getElementById(kPanelId);
- panel.removeEventListener("popupshowing", updateZoomResetButton);
- let container = aDoc.defaultView.gBrowser.tabContainer;
- container.removeEventListener("TabSelect", updateZoomResetButton);
- }.bind(this),
-
- onCustomizeStart: function(aWindow) {
- if (aWindow.document == aDocument) {
- updateZoomResetButton();
- }
- },
-
- onCustomizeEnd: function(aWindow) {
- if (aWindow.document == aDocument) {
- updateZoomResetButton();
- }
- },
-
- onWidgetDrag: function(aWidgetId, aArea) {
- if (aWidgetId != this.id)
- return;
- aArea = aArea || this.currentArea;
- updateCombinedWidgetStyle(node, aArea, true);
- }.bind(this)
- };
- CustomizableUI.addListener(listener);
-
- return node;
- }
- }, {
- id: "edit-controls",
- type: "custom",
- tooltiptext: "edit-controls.tooltiptext2",
- defaultArea: CustomizableUI.AREA_PANEL,
- onBuild: function(aDocument) {
- let buttons = [{
- id: "cut-button",
- command: "cmd_cut",
- label: true,
- tooltiptext: "tooltiptext2",
- shortcutId: "key_cut",
- }, {
- id: "copy-button",
- command: "cmd_copy",
- label: true,
- tooltiptext: "tooltiptext2",
- shortcutId: "key_copy",
- }, {
- id: "paste-button",
- command: "cmd_paste",
- label: true,
- tooltiptext: "tooltiptext2",
- shortcutId: "key_paste",
- }];
-
- let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
- node.setAttribute("id", "edit-controls");
- node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
- node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
- // Set this as an attribute in addition to the property to make sure we can style correctly.
- node.setAttribute("removable", "true");
- node.classList.add("chromeclass-toolbar-additional");
- node.classList.add("toolbaritem-combined-buttons");
- node.classList.add(kWidePanelItemClass);
-
- buttons.forEach(function(aButton, aIndex) {
- if (aIndex != 0)
- node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
- let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
- setAttributes(btnNode, aButton);
- node.appendChild(btnNode);
- });
-
- updateCombinedWidgetStyle(node, this.currentArea);
-
- let listener = {
- onWidgetAdded: function(aWidgetId, aArea, aPosition) {
- if (aWidgetId != this.id)
- return;
- updateCombinedWidgetStyle(node, aArea);
- }.bind(this),
-
- onWidgetRemoved: function(aWidgetId, aPrevArea) {
- if (aWidgetId != this.id)
- return;
- // When a widget is demoted to the palette ('removed'), it's visual
- // style should change.
- updateCombinedWidgetStyle(node);
- }.bind(this),
-
- onWidgetReset: function(aWidgetNode) {
- if (aWidgetNode != node)
- return;
- updateCombinedWidgetStyle(node, this.currentArea);
- }.bind(this),
-
- onWidgetUndoMove: function(aWidgetNode) {
- if (aWidgetNode != node)
- return;
- updateCombinedWidgetStyle(node, this.currentArea);
- }.bind(this),
-
- onWidgetMoved: function(aWidgetId, aArea) {
- if (aWidgetId != this.id)
- return;
- updateCombinedWidgetStyle(node, aArea);
- }.bind(this),
-
- onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
- if (aWidgetId != this.id || aDoc != aDocument)
- return;
- CustomizableUI.removeListener(listener);
- }.bind(this),
-
- onWidgetDrag: function(aWidgetId, aArea) {
- if (aWidgetId != this.id)
- return;
- aArea = aArea || this.currentArea;
- updateCombinedWidgetStyle(node, aArea);
- }.bind(this)
- };
- CustomizableUI.addListener(listener);
-
- return node;
- }
- },
- {
- id: "feed-button",
- type: "view",
- viewId: "PanelUI-feeds",
- tooltiptext: "feed-button.tooltiptext2",
- defaultArea: CustomizableUI.AREA_PANEL,
- onClick: function(aEvent) {
- let win = aEvent.target.ownerGlobal;
- let feeds = win.gBrowser.selectedBrowser.feeds;
-
- // Here, we only care about the case where we have exactly 1 feed and the
- // user clicked...
- let isClick = (aEvent.button == 0 || aEvent.button == 1);
- if (feeds && feeds.length == 1 && isClick) {
- aEvent.preventDefault();
- aEvent.stopPropagation();
- win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent);
- CustomizableUI.hidePanelForNode(aEvent.target);
- }
- },
- onViewShowing: function(aEvent) {
- let doc = aEvent.target.ownerDocument;
- let container = doc.getElementById("PanelUI-feeds");
- let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true);
-
- // For no feeds or only a single one, don't show the panel.
- if (!gotView) {
- aEvent.preventDefault();
- aEvent.stopPropagation();
- return;
- }
- },
- onCreated: function(node) {
- let win = node.ownerGlobal;
- let selectedBrowser = win.gBrowser.selectedBrowser;
- let feeds = selectedBrowser && selectedBrowser.feeds;
- if (!feeds || !feeds.length) {
- node.setAttribute("disabled", "true");
- }
- }
- }, {
- id: "characterencoding-button",
- label: "characterencoding-button2.label",
- type: "view",
- viewId: "PanelUI-characterEncodingView",
- tooltiptext: "characterencoding-button2.tooltiptext",
- defaultArea: CustomizableUI.AREA_PANEL,
- maybeDisableMenu: function(aDocument) {
- let window = aDocument.defaultView;
- return !(window.gBrowser &&
- window.gBrowser.selectedBrowser.mayEnableCharacterEncodingMenu);
- },
- populateList: function(aDocument, aContainerId, aSection) {
- let containerElem = aDocument.getElementById(aContainerId);
-
- containerElem.addEventListener("command", this.onCommand, false);
-
- let list = this.charsetInfo[aSection];
-
- for (let item of list) {
- let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
- elem.setAttribute("label", item.label);
- elem.setAttribute("type", "checkbox");
- elem.section = aSection;
- elem.value = item.value;
- elem.setAttribute("class", "subviewbutton");
- containerElem.appendChild(elem);
- }
- },
- updateCurrentCharset: function(aDocument) {
- let currentCharset = aDocument.defaultView.gBrowser.selectedBrowser.characterSet;
- currentCharset = CharsetMenu.foldCharset(currentCharset);
-
- let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned");
- let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets");
- let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)];
-
- this._updateElements(elements, currentCharset);
- },
- updateCurrentDetector: function(aDocument) {
- let detectorContainer = aDocument.getElementById("PanelUI-characterEncodingView-autodetect");
- let currentDetector;
- try {
- currentDetector = Services.prefs.getComplexValue(
- "intl.charset.detector", Ci.nsIPrefLocalizedString).data;
- } catch (e) {}
-
- this._updateElements(detectorContainer.childNodes, currentDetector);
- },
- _updateElements: function(aElements, aCurrentItem) {
- if (!aElements.length) {
- return;
- }
- let disabled = this.maybeDisableMenu(aElements[0].ownerDocument);
- for (let elem of aElements) {
- if (disabled) {
- elem.setAttribute("disabled", "true");
- } else {
- elem.removeAttribute("disabled");
- }
- if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
- elem.setAttribute("checked", "true");
- } else {
- elem.removeAttribute("checked");
- }
- }
- },
- onViewShowing: function(aEvent) {
- let document = aEvent.target.ownerDocument;
-
- let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label";
- let autoDetectLabel = document.getElementById(autoDetectLabelId);
- if (!autoDetectLabel.hasAttribute("value")) {
- let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
- autoDetectLabel.setAttribute("value", label);
- this.populateList(document,
- "PanelUI-characterEncodingView-pinned",
- "pinnedCharsets");
- this.populateList(document,
- "PanelUI-characterEncodingView-charsets",
- "otherCharsets");
- this.populateList(document,
- "PanelUI-characterEncodingView-autodetect",
- "detectors");
- }
- this.updateCurrentDetector(document);
- this.updateCurrentCharset(document);
- },
- onCommand: function(aEvent) {
- let node = aEvent.target;
- if (!node.hasAttribute || !node.section) {
- return;
- }
-
- let window = node.ownerGlobal;
- let section = node.section;
- let value = node.value;
-
- // The behavior as implemented here is directly based off of the
- // `MultiplexHandler()` method in browser.js.
- if (section != "detectors") {
- window.BrowserSetForcedCharacterSet(value);
- } else {
- // Set the detector pref.
- try {
- let str = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- str.data = value;
- Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
- } catch (e) {
- Cu.reportError("Failed to set the intl.charset.detector preference.");
- }
- // Prepare a browser page reload with a changed charset.
- window.BrowserCharsetReload();
- }
- },
- onCreated: function(aNode) {
- const kPanelId = "PanelUI-popup";
- let document = aNode.ownerDocument;
-
- let updateButton = () => {
- if (this.maybeDisableMenu(document))
- aNode.setAttribute("disabled", "true");
- else
- aNode.removeAttribute("disabled");
- };
-
- if (this.currentArea == CustomizableUI.AREA_PANEL) {
- let panel = document.getElementById(kPanelId);
- panel.addEventListener("popupshowing", updateButton);
- }
-
- let listener = {
- onWidgetAdded: (aWidgetId, aArea) => {
- if (aWidgetId != this.id)
- return;
- if (aArea == CustomizableUI.AREA_PANEL) {
- let panel = document.getElementById(kPanelId);
- panel.addEventListener("popupshowing", updateButton);
- }
- },
- onWidgetRemoved: (aWidgetId, aPrevArea) => {
- if (aWidgetId != this.id)
- return;
- aNode.removeAttribute("disabled");
- if (aPrevArea == CustomizableUI.AREA_PANEL) {
- let panel = document.getElementById(kPanelId);
- panel.removeEventListener("popupshowing", updateButton);
- }
- },
- onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
- if (aWidgetId != this.id || aDoc != document)
- return;
-
- CustomizableUI.removeListener(listener);
- let panel = aDoc.getElementById(kPanelId);
- panel.removeEventListener("popupshowing", updateButton);
- }
- };
- CustomizableUI.addListener(listener);
- if (!this.charsetInfo) {
- this.charsetInfo = CharsetMenu.getData();
- }
- }
- }, {
- id: "email-link-button",
- tooltiptext: "email-link-button.tooltiptext3",
- onCommand: function(aEvent) {
- let win = aEvent.view;
- win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
- }
- }, {
- id: "containers-panelmenu",
- type: "view",
- viewId: "PanelUI-containers",
- hasObserver: false,
- onCreated: function(aNode) {
- let doc = aNode.ownerDocument;
- let win = doc.defaultView;
- let items = doc.getElementById("PanelUI-containersItems");
-
- let onItemCommand = function (aEvent) {
- let item = aEvent.target;
- if (item.hasAttribute("usercontextid")) {
- let userContextId = parseInt(item.getAttribute("usercontextid"));
- win.openUILinkIn(win.BROWSER_NEW_TAB_URL, "tab", {userContextId});
- }
- };
- items.addEventListener("command", onItemCommand);
-
- if (PrivateBrowsingUtils.isWindowPrivate(win)) {
- aNode.setAttribute("disabled", "true");
- }
-
- this.updateVisibility(aNode);
-
- if (!this.hasObserver) {
- Services.prefs.addObserver("privacy.userContext.enabled", this, true);
- this.hasObserver = true;
- }
- },
- onViewShowing: function(aEvent) {
- let doc = aEvent.target.ownerDocument;
-
- let items = doc.getElementById("PanelUI-containersItems");
-
- while (items.firstChild) {
- items.firstChild.remove();
- }
-
- let fragment = doc.createDocumentFragment();
- let bundle = doc.getElementById("bundle_browser");
-
- ContextualIdentityService.getIdentities().forEach(identity => {
- let label = ContextualIdentityService.getUserContextLabel(identity.userContextId);
-
- let item = doc.createElementNS(kNSXUL, "toolbarbutton");
- item.setAttribute("label", label);
- item.setAttribute("usercontextid", identity.userContextId);
- item.setAttribute("class", "subviewbutton");
- item.setAttribute("data-identity-color", identity.color);
- item.setAttribute("data-identity-icon", identity.icon);
-
- fragment.appendChild(item);
- });
-
- fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
-
- let item = doc.createElementNS(kNSXUL, "toolbarbutton");
- item.setAttribute("label", bundle.getString("userContext.aboutPage.label"));
- item.setAttribute("command", "Browser:OpenAboutContainers");
- item.setAttribute("class", "subviewbutton");
- fragment.appendChild(item);
-
- items.appendChild(fragment);
- },
-
- updateVisibility(aNode) {
- aNode.hidden = !Services.prefs.getBoolPref("privacy.userContext.enabled");
- },
-
- observe(aSubject, aTopic, aData) {
- let {instances} = CustomizableUI.getWidget("containers-panelmenu");
- for (let {node} of instances) {
- if (node) {
- this.updateVisibility(node);
- }
- }
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsISupportsWeakReference,
- Ci.nsIObserver
- ]),
- }];
-
-let preferencesButton = {
- id: "preferences-button",
- defaultArea: CustomizableUI.AREA_PANEL,
- onCommand: function(aEvent) {
- let win = aEvent.target.ownerGlobal;
- win.openPreferences();
- }
-};
-if (AppConstants.platform == "macosx") {
- preferencesButton.tooltiptext = "preferences-button.tooltiptext.withshortcut";
- preferencesButton.shortcutId = "key_preferencesCmdMac";
-} else {
- preferencesButton.tooltiptext = "preferences-button.tooltiptext2";
-}
-CustomizableWidgets.push(preferencesButton);
-
-if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
- CustomizableWidgets.push({
- id: "panic-button",
- type: "view",
- viewId: "PanelUI-panicView",
- _sanitizer: null,
- _ensureSanitizer: function() {
- if (!this.sanitizer) {
- let scope = {};
- Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js",
- scope);
- this._Sanitizer = scope.Sanitizer;
- this._sanitizer = new scope.Sanitizer();
- this._sanitizer.ignoreTimespan = false;
- }
- },
- _getSanitizeRange: function(aDocument) {
- let group = aDocument.getElementById("PanelUI-panic-timeSpan");
- return this._Sanitizer.getClearRange(+group.value);
- },
- forgetButtonCalled: function(aEvent) {
- let doc = aEvent.target.ownerDocument;
- this._ensureSanitizer();
- this._sanitizer.range = this._getSanitizeRange(doc);
- let group = doc.getElementById("PanelUI-panic-timeSpan");
- BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
- group.selectedItem = doc.getElementById("PanelUI-panic-5min");
- let itemsToClear = [
- "cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
- ];
- let newWindowPrivateState = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView) ?
- "private" : "non-private";
- this._sanitizer.items.openWindows.privateStateForNewWindow = newWindowPrivateState;
- let promise = this._sanitizer.sanitize(itemsToClear);
- promise.then(function() {
- let otherWindow = Services.wm.getMostRecentWindow("navigator:browser");
- if (otherWindow.closed) {
- Cu.reportError("Got a closed window!");
- }
- if (otherWindow.PanicButtonNotifier) {
- otherWindow.PanicButtonNotifier.notify();
- } else {
- otherWindow.PanicButtonNotifierShouldNotify = true;
- }
- });
- },
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "command":
- this.forgetButtonCalled(aEvent);
- break;
- }
- },
- onViewShowing: function(aEvent) {
- let forgetButton = aEvent.target.querySelector("#PanelUI-panic-view-button");
- forgetButton.addEventListener("command", this);
- },
- onViewHiding: function(aEvent) {
- let forgetButton = aEvent.target.querySelector("#PanelUI-panic-view-button");
- forgetButton.removeEventListener("command", this);
- },
- });
-}
diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm
deleted file mode 100644
index e63e25b0a..000000000
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ /dev/null
@@ -1,2297 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["CustomizeMode"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-const kPrefCustomizationDebug = "browser.uiCustomization.debug";
-const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation";
-const kPaletteId = "customization-palette";
-const kDragDataTypePrefix = "text/toolbarwrapper-id/";
-const kPlaceholderClass = "panel-customization-placeholder";
-const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
-const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
-const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
-const kMaxTransitionDurationMs = 2000;
-
-const kPanelItemContextMenu = "customizationPanelItemContextMenu";
-const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/CustomizableUI.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/AddonManager.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
- "resource:///modules/DragPositionManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
- "resource:///modules/BrowserUITelemetry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
- "resource://gre/modules/LightweightThemeManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
- "resource:///modules/sessionstore/SessionStore.jsm");
-
-let gDebug;
-XPCOMUtils.defineLazyGetter(this, "log", () => {
- let scope = {};
- Cu.import("resource://gre/modules/Console.jsm", scope);
- try {
- gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
- } catch (ex) {}
- let consoleOptions = {
- maxLogLevel: gDebug ? "all" : "log",
- prefix: "CustomizeMode",
- };
- return new scope.ConsoleAPI(consoleOptions);
-});
-
-var gDisableAnimation = null;
-
-var gDraggingInToolbars;
-
-var gTab;
-
-function closeGlobalTab() {
- let win = gTab.ownerGlobal;
- if (win.gBrowser.browsers.length == 1) {
- win.BrowserOpenTab();
- }
- win.gBrowser.removeTab(gTab);
- gTab = null;
-}
-
-function unregisterGlobalTab() {
- gTab.removeEventListener("TabClose", unregisterGlobalTab);
- gTab.ownerGlobal.removeEventListener("unload", unregisterGlobalTab);
- gTab.removeAttribute("customizemode");
- gTab = null;
-}
-
-function CustomizeMode(aWindow) {
- if (gDisableAnimation === null) {
- gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
- Services.prefs.getBoolPref(kPrefCustomizationAnimation);
- }
- this.window = aWindow;
- this.document = aWindow.document;
- this.browser = aWindow.gBrowser;
- this.areas = new Set();
-
- // There are two palettes - there's the palette that can be overlayed with
- // toolbar items in browser.xul. This is invisible, and never seen by the
- // user. Then there's the visible palette, which gets populated and displayed
- // to the user when in customizing mode.
- this.visiblePalette = this.document.getElementById(kPaletteId);
- this.paletteEmptyNotice = this.document.getElementById("customization-empty");
- if (Services.prefs.getCharPref("general.skins.selectedSkin") != "classic/1.0") {
- let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
- lwthemeButton.setAttribute("hidden", "true");
- }
- if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
- this._updateTitlebarButton();
- Services.prefs.addObserver(kDrawInTitlebarPref, this, false);
- }
- this.window.addEventListener("unload", this);
-}
-
-CustomizeMode.prototype = {
- _changed: false,
- _transitioning: false,
- window: null,
- document: null,
- // areas is used to cache the customizable areas when in customization mode.
- areas: null,
- // When in customizing mode, we swap out the reference to the invisible
- // palette in gNavToolbox.palette for our visiblePalette. This way, for the
- // customizing browser window, when widgets are removed from customizable
- // areas and added to the palette, they're added to the visible palette.
- // _stowedPalette is a reference to the old invisible palette so we can
- // restore gNavToolbox.palette to its original state after exiting
- // customization mode.
- _stowedPalette: null,
- _dragOverItem: null,
- _customizing: false,
- _skipSourceNodeCheck: null,
- _mainViewContext: null,
-
- get panelUIContents() {
- return this.document.getElementById("PanelUI-contents");
- },
-
- get _handler() {
- return this.window.CustomizationHandler;
- },
-
- uninit: function() {
- if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
- Services.prefs.removeObserver(kDrawInTitlebarPref, this);
- }
- },
-
- toggle: function() {
- if (this._handler.isEnteringCustomizeMode || this._handler.isExitingCustomizeMode) {
- this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode;
- return;
- }
- if (this._customizing) {
- this.exit();
- } else {
- this.enter();
- }
- },
-
- _updateLWThemeButtonIcon: function() {
- let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
- let lwthemeIcon = this.document.getAnonymousElementByAttribute(lwthemeButton,
- "class", "button-icon");
- lwthemeIcon.style.backgroundImage = LightweightThemeManager.currentTheme ?
- "url(" + LightweightThemeManager.currentTheme.iconURL + ")" : "";
- },
-
- setTab: function(aTab) {
- if (gTab == aTab) {
- return;
- }
-
- if (gTab) {
- closeGlobalTab();
- }
-
- gTab = aTab;
-
- gTab.setAttribute("customizemode", "true");
- SessionStore.persistTabAttribute("customizemode");
-
- gTab.linkedBrowser.stop();
-
- let win = gTab.ownerGlobal;
-
- win.gBrowser.setTabTitle(gTab);
- win.gBrowser.setIcon(gTab,
- "chrome://browser/skin/customizableui/customizeFavicon.ico");
-
- gTab.addEventListener("TabClose", unregisterGlobalTab);
- win.addEventListener("unload", unregisterGlobalTab);
-
- if (gTab.selected) {
- win.gCustomizeMode.enter();
- }
- },
-
- enter: function() {
- this._wantToBeInCustomizeMode = true;
-
- if (this._customizing || this._handler.isEnteringCustomizeMode) {
- return;
- }
-
- // Exiting; want to re-enter once we've done that.
- if (this._handler.isExitingCustomizeMode) {
- log.debug("Attempted to enter while we're in the middle of exiting. " +
- "We'll exit after we've entered");
- return;
- }
-
- if (!gTab) {
- this.setTab(this.browser.loadOneTab("about:blank",
- { inBackground: false,
- forceNotRemote: true,
- skipAnimation: true }));
- return;
- }
- if (!gTab.selected) {
- // This will force another .enter() to be called via the
- // onlocationchange handler of the tabbrowser, so we return early.
- gTab.ownerGlobal.gBrowser.selectedTab = gTab;
- return;
- }
- gTab.ownerGlobal.focus();
- if (gTab.ownerDocument != this.document) {
- return;
- }
-
- let window = this.window;
- let document = this.document;
-
- this._handler.isEnteringCustomizeMode = true;
-
- // Always disable the reset button at the start of customize mode, it'll be re-enabled
- // if necessary when we finish entering:
- let resetButton = this.document.getElementById("customization-reset-button");
- resetButton.setAttribute("disabled", "true");
-
- Task.spawn(function*() {
- // We shouldn't start customize mode until after browser-delayed-startup has finished:
- if (!this.window.gBrowserInit.delayedStartupFinished) {
- yield new Promise(resolve => {
- let delayedStartupObserver = aSubject => {
- if (aSubject == this.window) {
- Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
- resolve();
- }
- };
-
- Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
- });
- }
-
- let toolbarVisibilityBtn = document.getElementById(kToolbarVisibilityBtn);
- let togglableToolbars = window.getTogglableToolbars();
- if (togglableToolbars.length == 0) {
- toolbarVisibilityBtn.setAttribute("hidden", "true");
- } else {
- toolbarVisibilityBtn.removeAttribute("hidden");
- }
-
- this.updateLWTStyling();
-
- CustomizableUI.dispatchToolboxEvent("beforecustomization", {}, window);
- CustomizableUI.notifyStartCustomizing(this.window);
-
- // Add a keypress listener to the document so that we can quickly exit
- // customization mode when pressing ESC.
- document.addEventListener("keypress", this);
-
- // Same goes for the menu button - if we're customizing, a click on the
- // menu button means a quick exit from customization mode.
- window.PanelUI.hide();
- window.PanelUI.menuButton.addEventListener("command", this);
- window.PanelUI.menuButton.open = true;
- window.PanelUI.beginBatchUpdate();
-
- // The menu panel is lazy, and registers itself when the popup shows. We
- // need to force the menu panel to register itself, or else customization
- // is really not going to work. We pass "true" to ensureReady to
- // indicate that we're handling calling startBatchUpdate and
- // endBatchUpdate.
- if (!window.PanelUI.isReady) {
- yield window.PanelUI.ensureReady(true);
- }
-
- // Hide the palette before starting the transition for increased perf.
- this.visiblePalette.hidden = true;
- this.visiblePalette.removeAttribute("showing");
-
- // Disable the button-text fade-out mask
- // during the transition for increased perf.
- let panelContents = window.PanelUI.contents;
- panelContents.setAttribute("customize-transitioning", "true");
-
- // Move the mainView in the panel to the holder so that we can see it
- // while customizing.
- let mainView = window.PanelUI.mainView;
- let panelHolder = document.getElementById("customization-panelHolder");
- panelHolder.appendChild(mainView);
-
- let customizeButton = document.getElementById("PanelUI-customize");
- customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label"));
- customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel"));
- customizeButton.setAttribute("enterTooltiptext", customizeButton.getAttribute("tooltiptext"));
- customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("exitTooltiptext"));
-
- this._transitioning = true;
-
- let customizer = document.getElementById("customization-container");
- customizer.parentNode.selectedPanel = customizer;
- customizer.hidden = false;
-
- this._wrapToolbarItemSync(CustomizableUI.AREA_TABSTRIP);
-
- let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true]):not([collapsed=true])");
- for (let toolbar of customizableToolbars)
- toolbar.setAttribute("customizing", true);
-
- yield this._doTransition(true);
-
- Services.obs.addObserver(this, "lightweight-theme-window-updated", false);
-
- // Let everybody in this window know that we're about to customize.
- CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
-
- this._mainViewContext = mainView.getAttribute("context");
- if (this._mainViewContext) {
- mainView.removeAttribute("context");
- }
-
- this._showPanelCustomizationPlaceholders();
-
- yield this._wrapToolbarItems();
- this.populatePalette();
-
- this._addDragHandlers(this.visiblePalette);
-
- window.gNavToolbox.addEventListener("toolbarvisibilitychange", this);
-
- document.getElementById("PanelUI-help").setAttribute("disabled", true);
- document.getElementById("PanelUI-quit").setAttribute("disabled", true);
-
- this._updateResetButton();
- this._updateUndoResetButton();
-
- this._skipSourceNodeCheck = Services.prefs.getPrefType(kSkipSourceNodePref) == Ci.nsIPrefBranch.PREF_BOOL &&
- Services.prefs.getBoolPref(kSkipSourceNodePref);
-
- CustomizableUI.addListener(this);
- window.PanelUI.endBatchUpdate();
- this._customizing = true;
- this._transitioning = false;
-
- // Show the palette now that the transition has finished.
- this.visiblePalette.hidden = false;
- window.setTimeout(() => {
- // Force layout reflow to ensure the animation runs,
- // and make it async so it doesn't affect the timing.
- this.visiblePalette.clientTop;
- this.visiblePalette.setAttribute("showing", "true");
- }, 0);
- this._updateEmptyPaletteNotice();
-
- this._updateLWThemeButtonIcon();
-
- this._handler.isEnteringCustomizeMode = false;
- panelContents.removeAttribute("customize-transitioning");
-
- CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
- this._enableOutlinesTimeout = window.setTimeout(() => {
- this.document.getElementById("nav-bar").setAttribute("showoutline", "true");
- this.panelUIContents.setAttribute("showoutline", "true");
- delete this._enableOutlinesTimeout;
- }, 0);
-
- if (!this._wantToBeInCustomizeMode) {
- this.exit();
- }
- }.bind(this)).then(null, function(e) {
- log.error("Error entering customize mode", e);
- // We should ensure this has been called, and calling it again doesn't hurt:
- window.PanelUI.endBatchUpdate();
- this._handler.isEnteringCustomizeMode = false;
- // Exit customize mode to ensure proper clean-up when entering failed.
- this.exit();
- }.bind(this));
- },
-
- exit: function() {
- this._wantToBeInCustomizeMode = false;
-
- if (!this._customizing || this._handler.isExitingCustomizeMode) {
- return;
- }
-
- // Entering; want to exit once we've done that.
- if (this._handler.isEnteringCustomizeMode) {
- log.debug("Attempted to exit while we're in the middle of entering. " +
- "We'll exit after we've entered");
- return;
- }
-
- if (this.resetting) {
- log.debug("Attempted to exit while we're resetting. " +
- "We'll exit after resetting has finished.");
- return;
- }
-
- this._handler.isExitingCustomizeMode = true;
-
- if (this._enableOutlinesTimeout) {
- this.window.clearTimeout(this._enableOutlinesTimeout);
- } else {
- this.document.getElementById("nav-bar").removeAttribute("showoutline");
- this.panelUIContents.removeAttribute("showoutline");
- }
-
- this._removeExtraToolbarsIfEmpty();
-
- CustomizableUI.removeListener(this);
-
- this.document.removeEventListener("keypress", this);
- this.window.PanelUI.menuButton.removeEventListener("command", this);
- this.window.PanelUI.menuButton.open = false;
-
- this.window.PanelUI.beginBatchUpdate();
-
- this._removePanelCustomizationPlaceholders();
-
- let window = this.window;
- let document = this.document;
-
- // Hide the palette before starting the transition for increased perf.
- this.visiblePalette.hidden = true;
- this.visiblePalette.removeAttribute("showing");
- this.paletteEmptyNotice.hidden = true;
-
- // Disable the button-text fade-out mask
- // during the transition for increased perf.
- let panelContents = window.PanelUI.contents;
- panelContents.setAttribute("customize-transitioning", "true");
-
- // Disable the reset and undo reset buttons while transitioning:
- let resetButton = this.document.getElementById("customization-reset-button");
- let undoResetButton = this.document.getElementById("customization-undo-reset-button");
- undoResetButton.hidden = resetButton.disabled = true;
-
- this._transitioning = true;
-
- Task.spawn(function*() {
- yield this.depopulatePalette();
-
- yield this._doTransition(false);
- this.removeLWTStyling();
-
- Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
-
- if (this.browser.selectedTab == gTab) {
- if (gTab.linkedBrowser.currentURI.spec == "about:blank") {
- closeGlobalTab();
- } else {
- unregisterGlobalTab();
- }
- }
- let browser = document.getElementById("browser");
- browser.parentNode.selectedPanel = browser;
- let customizer = document.getElementById("customization-container");
- customizer.hidden = true;
-
- window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
-
- DragPositionManager.stop();
- this._removeDragHandlers(this.visiblePalette);
-
- yield this._unwrapToolbarItems();
-
- if (this._changed) {
- // XXXmconley: At first, it seems strange to also persist the old way with
- // currentset - but this might actually be useful for switching
- // to old builds. We might want to keep this around for a little
- // bit.
- this.persistCurrentSets();
- }
-
- // And drop all area references.
- this.areas.clear();
-
- // Let everybody in this window know that we're starting to
- // exit customization mode.
- CustomizableUI.dispatchToolboxEvent("customizationending", {}, window);
-
- window.PanelUI.setMainView(window.PanelUI.mainView);
- window.PanelUI.menuButton.disabled = false;
-
- let customizeButton = document.getElementById("PanelUI-customize");
- customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
- customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
- customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext"));
- customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext"));
-
- // We have to use setAttribute/removeAttribute here instead of the
- // property because the XBL property will be set later, and right
- // now we'd be setting an expando, which breaks the XBL property.
- document.getElementById("PanelUI-help").removeAttribute("disabled");
- document.getElementById("PanelUI-quit").removeAttribute("disabled");
-
- panelContents.removeAttribute("customize-transitioning");
-
- // We need to set this._customizing to false before removing the tab
- // or the TabSelect event handler will think that we are exiting
- // customization mode for a second time.
- this._customizing = false;
-
- let mainView = window.PanelUI.mainView;
- if (this._mainViewContext) {
- mainView.setAttribute("context", this._mainViewContext);
- }
-
- let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])");
- for (let toolbar of customizableToolbars)
- toolbar.removeAttribute("customizing");
-
- this.window.PanelUI.endBatchUpdate();
- delete this._lastLightweightTheme;
- this._changed = false;
- this._transitioning = false;
- this._handler.isExitingCustomizeMode = false;
- CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
- CustomizableUI.notifyEndCustomizing(window);
-
- if (this._wantToBeInCustomizeMode) {
- this.enter();
- }
- }.bind(this)).then(null, function(e) {
- log.error("Error exiting customize mode", e);
- // We should ensure this has been called, and calling it again doesn't hurt:
- window.PanelUI.endBatchUpdate();
- this._handler.isExitingCustomizeMode = false;
- }.bind(this));
- },
-
- /**
- * The customize mode transition has 4 phases when entering:
- * 1) Pre-customization mode
- * This is the starting phase of the browser.
- * 2) LWT swapping
- * This is where we swap some of the lightweight theme styles in order
- * to make them work in customize mode. We set/unset a customization-
- * lwtheme attribute iff we're using a lightweight theme.
- * 3) customize-entering
- * This phase is a transition, optimized for smoothness.
- * 4) customize-entered
- * After the transition completes, this phase draws all of the
- * expensive detail that isn't necessary during the second phase.
- *
- * Exiting customization mode has a similar set of phases, but in reverse
- * order - customize-entered, customize-exiting, remove LWT swapping,
- * pre-customization mode.
- *
- * When in the customize-entering, customize-entered, or customize-exiting
- * phases, there is a "customizing" attribute set on the main-window to simplify
- * excluding certain styles while in any phase of customize mode.
- */
- _doTransition: function(aEntering) {
- let deck = this.document.getElementById("content-deck");
- let customizeTransitionEndPromise = new Promise(resolve => {
- let customizeTransitionEnd = (aEvent) => {
- if (aEvent != "timedout" &&
- (aEvent.originalTarget != deck || aEvent.propertyName != "margin-left")) {
- return;
- }
- this.window.clearTimeout(catchAllTimeout);
- // We request an animation frame to do the final stage of the transition
- // to improve perceived performance. (bug 962677)
- this.window.requestAnimationFrame(() => {
- deck.removeEventListener("transitionend", customizeTransitionEnd);
-
- if (!aEntering) {
- this.document.documentElement.removeAttribute("customize-exiting");
- this.document.documentElement.removeAttribute("customizing");
- } else {
- this.document.documentElement.setAttribute("customize-entered", true);
- this.document.documentElement.removeAttribute("customize-entering");
- }
- CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window);
-
- resolve();
- });
- };
- deck.addEventListener("transitionend", customizeTransitionEnd);
- let catchAll = () => customizeTransitionEnd("timedout");
- let catchAllTimeout = this.window.setTimeout(catchAll, kMaxTransitionDurationMs);
- });
-
- if (gDisableAnimation) {
- this.document.getElementById("tab-view-deck").setAttribute("fastcustomizeanimation", true);
- }
-
- if (aEntering) {
- this.document.documentElement.setAttribute("customizing", true);
- this.document.documentElement.setAttribute("customize-entering", true);
- } else {
- this.document.documentElement.setAttribute("customize-exiting", true);
- this.document.documentElement.removeAttribute("customize-entered");
- }
-
- return customizeTransitionEndPromise;
- },
-
- updateLWTStyling: function(aData) {
- let docElement = this.document.documentElement;
- if (!aData) {
- let lwt = docElement._lightweightTheme;
- aData = lwt.getData();
- }
- let headerURL = aData && aData.headerURL;
- if (!headerURL) {
- this.removeLWTStyling();
- return;
- }
-
- let deck = this.document.getElementById("tab-view-deck");
- let headerImageRef = this._getHeaderImageRef(aData);
- docElement.setAttribute("customization-lwtheme", "true");
-
- let toolboxRect = this.window.gNavToolbox.getBoundingClientRect();
- let height = toolboxRect.bottom;
-
- if (AppConstants.platform == "macosx") {
- let drawingInTitlebar = !docElement.hasAttribute("drawtitle");
- let titlebar = this.document.getElementById("titlebar");
- if (drawingInTitlebar) {
- titlebar.style.backgroundImage = headerImageRef;
- } else {
- titlebar.style.removeProperty("background-image");
- }
- }
-
- let limitedBG = "-moz-image-rect(" + headerImageRef + ", 0, 100%, " +
- height + ", 0)";
-
- let ridgeStart = height - 1;
- let ridgeCenter = (ridgeStart + 1) + "px";
- let ridgeEnd = (ridgeStart + 2) + "px";
- ridgeStart = ridgeStart + "px";
-
- let ridge = "linear-gradient(to bottom, " +
- "transparent " + ridgeStart +
- ", rgba(0,0,0,0.25) " + ridgeStart +
- ", rgba(0,0,0,0.25) " + ridgeCenter +
- ", rgba(255,255,255,0.5) " + ridgeCenter +
- ", rgba(255,255,255,0.5) " + ridgeEnd + ", " +
- "transparent " + ridgeEnd + ")";
- deck.style.backgroundImage = ridge + ", " + limitedBG;
-
- /* Remove the background styles from the <window> so we can style it instead. */
- docElement.style.removeProperty("background-image");
- docElement.style.removeProperty("background-color");
- },
-
- removeLWTStyling: function() {
- let affectedNodes = AppConstants.platform == "macosx" ?
- ["tab-view-deck", "titlebar"] :
- ["tab-view-deck"];
- for (let id of affectedNodes) {
- let node = this.document.getElementById(id);
- node.style.removeProperty("background-image");
- }
- let docElement = this.document.documentElement;
- docElement.removeAttribute("customization-lwtheme");
- let data = docElement._lightweightTheme.getData();
- if (data && data.headerURL) {
- docElement.style.backgroundImage = this._getHeaderImageRef(data);
- docElement.style.backgroundColor = data.accentcolor || "white";
- }
- },
-
- _getHeaderImageRef: function(aData) {
- return "url(\"" + aData.headerURL.replace(/"/g, '\\"') + "\")";
- },
-
- _getCustomizableChildForNode: function(aNode) {
- // NB: adjusted from _getCustomizableParent to keep that method fast
- // (it's used during drags), and avoid multiple DOM loops
- let areas = CustomizableUI.areas;
- // Caching this length is important because otherwise we'll also iterate
- // over items we add to the end from within the loop.
- let numberOfAreas = areas.length;
- for (let i = 0; i < numberOfAreas; i++) {
- let area = areas[i];
- let areaNode = aNode.ownerDocument.getElementById(area);
- let customizationTarget = areaNode && areaNode.customizationTarget;
- if (customizationTarget && customizationTarget != areaNode) {
- areas.push(customizationTarget.id);
- }
- let overflowTarget = areaNode && areaNode.getAttribute("overflowtarget");
- if (overflowTarget) {
- areas.push(overflowTarget);
- }
- }
- areas.push(kPaletteId);
-
- while (aNode && aNode.parentNode) {
- let parent = aNode.parentNode;
- if (areas.indexOf(parent.id) != -1) {
- return aNode;
- }
- aNode = parent;
- }
- return null;
- },
-
- addToToolbar: function(aNode) {
- aNode = this._getCustomizableChildForNode(aNode);
- if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
- aNode = aNode.firstChild;
- }
- CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR);
- if (!this._customizing) {
- CustomizableUI.dispatchToolboxEvent("customizationchange");
- }
- },
-
- addToPanel: function(aNode) {
- aNode = this._getCustomizableChildForNode(aNode);
- if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
- aNode = aNode.firstChild;
- }
- CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_PANEL);
- if (!this._customizing) {
- CustomizableUI.dispatchToolboxEvent("customizationchange");
- }
- },
-
- removeFromArea: function(aNode) {
- aNode = this._getCustomizableChildForNode(aNode);
- if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
- aNode = aNode.firstChild;
- }
- CustomizableUI.removeWidgetFromArea(aNode.id);
- if (!this._customizing) {
- CustomizableUI.dispatchToolboxEvent("customizationchange");
- }
- },
-
- populatePalette: function() {
- let fragment = this.document.createDocumentFragment();
- let toolboxPalette = this.window.gNavToolbox.palette;
-
- try {
- let unusedWidgets = CustomizableUI.getUnusedWidgets(toolboxPalette);
- for (let widget of unusedWidgets) {
- let paletteItem = this.makePaletteItem(widget, "palette");
- if (!paletteItem) {
- continue;
- }
- fragment.appendChild(paletteItem);
- }
-
- this.visiblePalette.appendChild(fragment);
- this._stowedPalette = this.window.gNavToolbox.palette;
- this.window.gNavToolbox.palette = this.visiblePalette;
- } catch (ex) {
- log.error(ex);
- }
- },
-
- // XXXunf Maybe this should use -moz-element instead of wrapping the node?
- // Would ensure no weird interactions/event handling from original node,
- // and makes it possible to put this in a lazy-loaded iframe/real tab
- // while still getting rid of the need for overlays.
- makePaletteItem: function(aWidget, aPlace) {
- let widgetNode = aWidget.forWindow(this.window).node;
- if (!widgetNode) {
- log.error("Widget with id " + aWidget.id + " does not return a valid node");
- return null;
- }
- // Do not build a palette item for hidden widgets; there's not much to show.
- if (widgetNode.hidden) {
- return null;
- }
-
- let wrapper = this.createOrUpdateWrapper(widgetNode, aPlace);
- wrapper.appendChild(widgetNode);
- return wrapper;
- },
-
- depopulatePalette: function() {
- return Task.spawn(function*() {
- this.visiblePalette.hidden = true;
- let paletteChild = this.visiblePalette.firstChild;
- let nextChild;
- while (paletteChild) {
- nextChild = paletteChild.nextElementSibling;
- let provider = CustomizableUI.getWidget(paletteChild.id).provider;
- if (provider == CustomizableUI.PROVIDER_XUL) {
- let unwrappedPaletteItem =
- yield this.deferredUnwrapToolbarItem(paletteChild);
- this._stowedPalette.appendChild(unwrappedPaletteItem);
- } else if (provider == CustomizableUI.PROVIDER_API) {
- // XXXunf Currently this doesn't destroy the (now unused) node. It would
- // be good to do so, but we need to keep strong refs to it in
- // CustomizableUI (can't iterate of WeakMaps), and there's the
- // question of what behavior wrappers should have if consumers
- // keep hold of them.
- // widget.destroyInstance(widgetNode);
- } else if (provider == CustomizableUI.PROVIDER_SPECIAL) {
- this.visiblePalette.removeChild(paletteChild);
- }
-
- paletteChild = nextChild;
- }
- this.visiblePalette.hidden = false;
- this.window.gNavToolbox.palette = this._stowedPalette;
- }.bind(this)).then(null, log.error);
- },
-
- isCustomizableItem: function(aNode) {
- return aNode.localName == "toolbarbutton" ||
- aNode.localName == "toolbaritem" ||
- aNode.localName == "toolbarseparator" ||
- aNode.localName == "toolbarspring" ||
- aNode.localName == "toolbarspacer";
- },
-
- isWrappedToolbarItem: function(aNode) {
- return aNode.localName == "toolbarpaletteitem";
- },
-
- deferredWrapToolbarItem: function(aNode, aPlace) {
- return new Promise(resolve => {
- dispatchFunction(() => {
- let wrapper = this.wrapToolbarItem(aNode, aPlace);
- resolve(wrapper);
- });
- });
- },
-
- wrapToolbarItem: function(aNode, aPlace) {
- if (!this.isCustomizableItem(aNode)) {
- return aNode;
- }
- let wrapper = this.createOrUpdateWrapper(aNode, aPlace);
-
- // It's possible that this toolbar node is "mid-flight" and doesn't have
- // a parent, in which case we skip replacing it. This can happen if a
- // toolbar item has been dragged into the palette. In that case, we tell
- // CustomizableUI to remove the widget from its area before putting the
- // widget in the palette - so the node will have no parent.
- if (aNode.parentNode) {
- aNode = aNode.parentNode.replaceChild(wrapper, aNode);
- }
- wrapper.appendChild(aNode);
- return wrapper;
- },
-
- createOrUpdateWrapper: function(aNode, aPlace, aIsUpdate) {
- let wrapper;
- if (aIsUpdate && aNode.parentNode && aNode.parentNode.localName == "toolbarpaletteitem") {
- wrapper = aNode.parentNode;
- aPlace = wrapper.getAttribute("place");
- } else {
- wrapper = this.document.createElement("toolbarpaletteitem");
- // "place" is used by toolkit to add the toolbarpaletteitem-palette
- // binding to a toolbarpaletteitem, which gives it a label node for when
- // it's sitting in the palette.
- wrapper.setAttribute("place", aPlace);
- }
-
-
- // Ensure the wrapped item doesn't look like it's in any special state, and
- // can't be interactved with when in the customization palette.
- if (aNode.hasAttribute("command")) {
- wrapper.setAttribute("itemcommand", aNode.getAttribute("command"));
- aNode.removeAttribute("command");
- }
-
- if (aNode.hasAttribute("observes")) {
- wrapper.setAttribute("itemobserves", aNode.getAttribute("observes"));
- aNode.removeAttribute("observes");
- }
-
- if (aNode.getAttribute("checked") == "true") {
- wrapper.setAttribute("itemchecked", "true");
- aNode.removeAttribute("checked");
- }
-
- if (aNode.hasAttribute("id")) {
- wrapper.setAttribute("id", "wrapper-" + aNode.getAttribute("id"));
- }
-
- if (aNode.hasAttribute("label")) {
- wrapper.setAttribute("title", aNode.getAttribute("label"));
- wrapper.setAttribute("tooltiptext", aNode.getAttribute("label"));
- } else if (aNode.hasAttribute("title")) {
- wrapper.setAttribute("title", aNode.getAttribute("title"));
- wrapper.setAttribute("tooltiptext", aNode.getAttribute("title"));
- }
-
- if (aNode.hasAttribute("flex")) {
- wrapper.setAttribute("flex", aNode.getAttribute("flex"));
- }
-
- if (aPlace == "panel") {
- if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
- wrapper.setAttribute("haswideitem", "true");
- } else if (wrapper.hasAttribute("haswideitem")) {
- wrapper.removeAttribute("haswideitem");
- }
- }
-
- let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
- wrapper.setAttribute("removable", removable);
-
- let contextMenuAttrName = "";
- if (aNode.getAttribute("context")) {
- contextMenuAttrName = "context";
- } else if (aNode.getAttribute("contextmenu")) {
- contextMenuAttrName = "contextmenu";
- }
- let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
- let contextMenuForPlace = aPlace == "panel" ?
- kPanelItemContextMenu :
- kPaletteItemContextMenu;
- if (aPlace != "toolbar") {
- wrapper.setAttribute("context", contextMenuForPlace);
- }
- // Only keep track of the menu if it is non-default.
- if (currentContextMenu &&
- currentContextMenu != contextMenuForPlace) {
- aNode.setAttribute("wrapped-context", currentContextMenu);
- aNode.setAttribute("wrapped-contextAttrName", contextMenuAttrName)
- aNode.removeAttribute(contextMenuAttrName);
- } else if (currentContextMenu == contextMenuForPlace) {
- aNode.removeAttribute(contextMenuAttrName);
- }
-
- // Only add listeners for newly created wrappers:
- if (!aIsUpdate) {
- wrapper.addEventListener("mousedown", this);
- wrapper.addEventListener("mouseup", this);
- }
-
- return wrapper;
- },
-
- deferredUnwrapToolbarItem: function(aWrapper) {
- return new Promise(resolve => {
- dispatchFunction(() => {
- let item = null;
- try {
- item = this.unwrapToolbarItem(aWrapper);
- } catch (ex) {
- Cu.reportError(ex);
- }
- resolve(item);
- });
- });
- },
-
- unwrapToolbarItem: function(aWrapper) {
- if (aWrapper.nodeName != "toolbarpaletteitem") {
- return aWrapper;
- }
- aWrapper.removeEventListener("mousedown", this);
- aWrapper.removeEventListener("mouseup", this);
-
- let place = aWrapper.getAttribute("place");
-
- let toolbarItem = aWrapper.firstChild;
- if (!toolbarItem) {
- log.error("no toolbarItem child for " + aWrapper.tagName + "#" + aWrapper.id);
- aWrapper.remove();
- return null;
- }
-
- if (aWrapper.hasAttribute("itemobserves")) {
- toolbarItem.setAttribute("observes", aWrapper.getAttribute("itemobserves"));
- }
-
- if (aWrapper.hasAttribute("itemchecked")) {
- toolbarItem.checked = true;
- }
-
- if (aWrapper.hasAttribute("itemcommand")) {
- let commandID = aWrapper.getAttribute("itemcommand");
- toolbarItem.setAttribute("command", commandID);
-
- // XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing
- let command = this.document.getElementById(commandID);
- if (command && command.hasAttribute("disabled")) {
- toolbarItem.setAttribute("disabled", command.getAttribute("disabled"));
- }
- }
-
- let wrappedContext = toolbarItem.getAttribute("wrapped-context");
- if (wrappedContext) {
- let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName");
- toolbarItem.setAttribute(contextAttrName, wrappedContext);
- toolbarItem.removeAttribute("wrapped-contextAttrName");
- toolbarItem.removeAttribute("wrapped-context");
- } else if (place == "panel") {
- toolbarItem.setAttribute("context", kPanelItemContextMenu);
- }
-
- if (aWrapper.parentNode) {
- aWrapper.parentNode.replaceChild(toolbarItem, aWrapper);
- }
- return toolbarItem;
- },
-
- _wrapToolbarItem: function*(aArea) {
- let target = CustomizableUI.getCustomizeTargetForArea(aArea, this.window);
- if (!target || this.areas.has(target)) {
- return null;
- }
-
- this._addDragHandlers(target);
- for (let child of target.children) {
- if (this.isCustomizableItem(child) && !this.isWrappedToolbarItem(child)) {
- yield this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child)).then(null, log.error);
- }
- }
- this.areas.add(target);
- return target;
- },
-
- _wrapToolbarItemSync: function(aArea) {
- let target = CustomizableUI.getCustomizeTargetForArea(aArea, this.window);
- if (!target || this.areas.has(target)) {
- return null;
- }
-
- this._addDragHandlers(target);
- try {
- for (let child of target.children) {
- if (this.isCustomizableItem(child) && !this.isWrappedToolbarItem(child)) {
- this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
- }
- }
- } catch (ex) {
- log.error(ex, ex.stack);
- }
-
- this.areas.add(target);
- return target;
- },
-
- _wrapToolbarItems: function*() {
- for (let area of CustomizableUI.areas) {
- yield this._wrapToolbarItem(area);
- }
- },
-
- _addDragHandlers: function(aTarget) {
- aTarget.addEventListener("dragstart", this, true);
- aTarget.addEventListener("dragover", this, true);
- aTarget.addEventListener("dragexit", this, true);
- aTarget.addEventListener("drop", this, true);
- aTarget.addEventListener("dragend", this, true);
- },
-
- _wrapItemsInArea: function(target) {
- for (let child of target.children) {
- if (this.isCustomizableItem(child)) {
- this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
- }
- }
- },
-
- _removeDragHandlers: function(aTarget) {
- aTarget.removeEventListener("dragstart", this, true);
- aTarget.removeEventListener("dragover", this, true);
- aTarget.removeEventListener("dragexit", this, true);
- aTarget.removeEventListener("drop", this, true);
- aTarget.removeEventListener("dragend", this, true);
- },
-
- _unwrapItemsInArea: function(target) {
- for (let toolbarItem of target.children) {
- if (this.isWrappedToolbarItem(toolbarItem)) {
- this.unwrapToolbarItem(toolbarItem);
- }
- }
- },
-
- _unwrapToolbarItems: function() {
- return Task.spawn(function*() {
- for (let target of this.areas) {
- for (let toolbarItem of target.children) {
- if (this.isWrappedToolbarItem(toolbarItem)) {
- yield this.deferredUnwrapToolbarItem(toolbarItem);
- }
- }
- this._removeDragHandlers(target);
- }
- this.areas.clear();
- }.bind(this)).then(null, log.error);
- },
-
- _removeExtraToolbarsIfEmpty: function() {
- let toolbox = this.window.gNavToolbox;
- for (let child of toolbox.children) {
- if (child.hasAttribute("customindex")) {
- let placements = CustomizableUI.getWidgetIdsInArea(child.id);
- if (!placements.length) {
- CustomizableUI.removeExtraToolbar(child.id);
- }
- }
- }
- },
-
- persistCurrentSets: function(aSetBeforePersisting) {
- let document = this.document;
- let toolbars = document.querySelectorAll("toolbar[customizable='true'][currentset]");
- for (let toolbar of toolbars) {
- if (aSetBeforePersisting) {
- let set = toolbar.currentSet;
- toolbar.setAttribute("currentset", set);
- }
- // Persist the currentset attribute directly on hardcoded toolbars.
- document.persist(toolbar.id, "currentset");
- }
- },
-
- reset: function() {
- this.resetting = true;
- // Disable the reset button temporarily while resetting:
- let btn = this.document.getElementById("customization-reset-button");
- BrowserUITelemetry.countCustomizationEvent("reset");
- btn.disabled = true;
- return Task.spawn(function*() {
- this._removePanelCustomizationPlaceholders();
- yield this.depopulatePalette();
- yield this._unwrapToolbarItems();
-
- CustomizableUI.reset();
-
- this._updateLWThemeButtonIcon();
-
- yield this._wrapToolbarItems();
- this.populatePalette();
-
- this.persistCurrentSets(true);
-
- this._updateResetButton();
- this._updateUndoResetButton();
- this._updateEmptyPaletteNotice();
- this._showPanelCustomizationPlaceholders();
- this.resetting = false;
- if (!this._wantToBeInCustomizeMode) {
- this.exit();
- }
- }.bind(this)).then(null, log.error);
- },
-
- undoReset: function() {
- this.resetting = true;
-
- return Task.spawn(function*() {
- this._removePanelCustomizationPlaceholders();
- yield this.depopulatePalette();
- yield this._unwrapToolbarItems();
-
- CustomizableUI.undoReset();
-
- this._updateLWThemeButtonIcon();
-
- yield this._wrapToolbarItems();
- this.populatePalette();
-
- this.persistCurrentSets(true);
-
- this._updateResetButton();
- this._updateUndoResetButton();
- this._updateEmptyPaletteNotice();
- this.resetting = false;
- }.bind(this)).then(null, log.error);
- },
-
- _onToolbarVisibilityChange: function(aEvent) {
- let toolbar = aEvent.target;
- if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
- toolbar.setAttribute("customizing", "true");
- } else {
- toolbar.removeAttribute("customizing");
- }
- this._onUIChange();
- this.updateLWTStyling();
- },
-
- onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
- this._onUIChange();
- },
-
- onWidgetAdded: function(aWidgetId, aArea, aPosition) {
- this._onUIChange();
- },
-
- onWidgetRemoved: function(aWidgetId, aArea) {
- this._onUIChange();
- },
-
- onWidgetBeforeDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
- if (aContainer.ownerGlobal != this.window || this.resetting) {
- return;
- }
- if (aContainer.id == CustomizableUI.AREA_PANEL) {
- this._removePanelCustomizationPlaceholders();
- }
- // If we get called for widgets that aren't in the window yet, they might not have
- // a parentNode at all.
- if (aNodeToChange.parentNode) {
- this.unwrapToolbarItem(aNodeToChange.parentNode);
- }
- if (aSecondaryNode) {
- this.unwrapToolbarItem(aSecondaryNode.parentNode);
- }
- },
-
- onWidgetAfterDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
- if (aContainer.ownerGlobal != this.window || this.resetting) {
- return;
- }
- // If the node is still attached to the container, wrap it again:
- if (aNodeToChange.parentNode) {
- let place = CustomizableUI.getPlaceForItem(aNodeToChange);
- this.wrapToolbarItem(aNodeToChange, place);
- if (aSecondaryNode) {
- this.wrapToolbarItem(aSecondaryNode, place);
- }
- } else {
- // If not, it got removed.
-
- // If an API-based widget is removed while customizing, append it to the palette.
- // The _applyDrop code itself will take care of positioning it correctly, if
- // applicable. We need the code to be here so removing widgets using CustomizableUI's
- // API also does the right thing (and adds it to the palette)
- let widgetId = aNodeToChange.id;
- let widget = CustomizableUI.getWidget(widgetId);
- if (widget.provider == CustomizableUI.PROVIDER_API) {
- let paletteItem = this.makePaletteItem(widget, "palette");
- this.visiblePalette.appendChild(paletteItem);
- }
- }
- if (aContainer.id == CustomizableUI.AREA_PANEL) {
- this._showPanelCustomizationPlaceholders();
- }
- },
-
- onWidgetDestroyed: function(aWidgetId) {
- let wrapper = this.document.getElementById("wrapper-" + aWidgetId);
- if (wrapper) {
- let wasInPanel = wrapper.parentNode == this.panelUIContents;
- wrapper.remove();
- if (wasInPanel) {
- this._showPanelCustomizationPlaceholders();
- }
- }
- },
-
- onWidgetAfterCreation: function(aWidgetId, aArea) {
- // If the node was added to an area, we would have gotten an onWidgetAdded notification,
- // plus associated DOM change notifications, so only do stuff for the palette:
- if (!aArea) {
- let widgetNode = this.document.getElementById(aWidgetId);
- if (widgetNode) {
- this.wrapToolbarItem(widgetNode, "palette");
- } else {
- let widget = CustomizableUI.getWidget(aWidgetId);
- this.visiblePalette.appendChild(this.makePaletteItem(widget, "palette"));
- }
- }
- },
-
- onAreaNodeRegistered: function(aArea, aContainer) {
- if (aContainer.ownerDocument == this.document) {
- this._wrapItemsInArea(aContainer);
- this._addDragHandlers(aContainer);
- DragPositionManager.add(this.window, aArea, aContainer);
- this.areas.add(aContainer);
- }
- },
-
- onAreaNodeUnregistered: function(aArea, aContainer, aReason) {
- if (aContainer.ownerDocument == this.document && aReason == CustomizableUI.REASON_AREA_UNREGISTERED) {
- this._unwrapItemsInArea(aContainer);
- this._removeDragHandlers(aContainer);
- DragPositionManager.remove(this.window, aArea, aContainer);
- this.areas.delete(aContainer);
- }
- },
-
- openAddonsManagerThemes: function(aEvent) {
- aEvent.target.parentNode.parentNode.hidePopup();
- this.window.BrowserOpenAddonsMgr('addons://list/theme');
- },
-
- getMoreThemes: function(aEvent) {
- aEvent.target.parentNode.parentNode.hidePopup();
- let getMoreURL = Services.urlFormatter.formatURLPref("lightweightThemes.getMoreURL");
- this.window.openUILinkIn(getMoreURL, "tab");
- },
-
- onLWThemesMenuShowing: function(aEvent) {
- const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
- const RECENT_LWT_COUNT = 5;
-
- this._clearLWThemesMenu(aEvent.target);
-
- function previewTheme(aEvent) {
- LightweightThemeManager.previewTheme(aEvent.target.theme.id != DEFAULT_THEME_ID ?
- aEvent.target.theme : null);
- }
-
- function resetPreview() {
- LightweightThemeManager.resetPreview();
- }
-
- let onThemeSelected = panel => {
- this._updateLWThemeButtonIcon();
- this._onUIChange();
- panel.hidePopup();
- };
-
- AddonManager.getAddonByID(DEFAULT_THEME_ID, function(aDefaultTheme) {
- let doc = this.window.document;
-
- function buildToolbarButton(aTheme) {
- let tbb = doc.createElement("toolbarbutton");
- tbb.theme = aTheme;
- tbb.setAttribute("label", aTheme.name);
- if (aDefaultTheme == aTheme) {
- // The actual icon is set up so it looks nice in about:addons, but
- // we'd like the version that's correct for the OS we're on, so we set
- // an attribute that our styling will then use to display the icon.
- tbb.setAttribute("defaulttheme", "true");
- } else {
- tbb.setAttribute("image", aTheme.iconURL);
- }
- if (aTheme.description)
- tbb.setAttribute("tooltiptext", aTheme.description);
- tbb.setAttribute("tabindex", "0");
- tbb.classList.add("customization-lwtheme-menu-theme");
- tbb.setAttribute("aria-checked", aTheme.isActive);
- tbb.setAttribute("role", "menuitemradio");
- if (aTheme.isActive) {
- tbb.setAttribute("active", "true");
- }
- tbb.addEventListener("focus", previewTheme);
- tbb.addEventListener("mouseover", previewTheme);
- tbb.addEventListener("blur", resetPreview);
- tbb.addEventListener("mouseout", resetPreview);
-
- return tbb;
- }
-
- let themes = [aDefaultTheme];
- let lwts = LightweightThemeManager.usedThemes;
- if (lwts.length > RECENT_LWT_COUNT)
- lwts.length = RECENT_LWT_COUNT;
- let currentLwt = LightweightThemeManager.currentTheme;
- for (let lwt of lwts) {
- lwt.isActive = !!currentLwt && (lwt.id == currentLwt.id);
- themes.push(lwt);
- }
-
- let footer = doc.getElementById("customization-lwtheme-menu-footer");
- let panel = footer.parentNode;
- let recommendedLabel = doc.getElementById("customization-lwtheme-menu-recommended");
- for (let theme of themes) {
- let button = buildToolbarButton(theme);
- button.addEventListener("command", () => {
- if ("userDisabled" in button.theme)
- button.theme.userDisabled = false;
- else
- LightweightThemeManager.currentTheme = button.theme;
- onThemeSelected(panel);
- });
- panel.insertBefore(button, recommendedLabel);
- }
-
- let lwthemePrefs = Services.prefs.getBranch("lightweightThemes.");
- let recommendedThemes = lwthemePrefs.getComplexValue("recommendedThemes",
- Ci.nsISupportsString).data;
- recommendedThemes = JSON.parse(recommendedThemes);
- let sb = Services.strings.createBundle("chrome://browser/locale/lightweightThemes.properties");
- for (let theme of recommendedThemes) {
- theme.name = sb.GetStringFromName("lightweightThemes." + theme.id + ".name");
- theme.description = sb.GetStringFromName("lightweightThemes." + theme.id + ".description");
- let button = buildToolbarButton(theme);
- button.addEventListener("command", () => {
- LightweightThemeManager.setLocalTheme(button.theme);
- recommendedThemes = recommendedThemes.filter((aTheme) => { return aTheme.id != button.theme.id; });
- let string = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- string.data = JSON.stringify(recommendedThemes);
- lwthemePrefs.setComplexValue("recommendedThemes",
- Ci.nsISupportsString, string);
- onThemeSelected(panel);
- });
- panel.insertBefore(button, footer);
- }
- let hideRecommendedLabel = (footer.previousSibling == recommendedLabel);
- recommendedLabel.hidden = hideRecommendedLabel;
- }.bind(this));
- },
-
- _clearLWThemesMenu: function(panel) {
- let footer = this.document.getElementById("customization-lwtheme-menu-footer");
- let recommendedLabel = this.document.getElementById("customization-lwtheme-menu-recommended");
- for (let element of [footer, recommendedLabel]) {
- while (element.previousSibling &&
- element.previousSibling.localName == "toolbarbutton") {
- element.previousSibling.remove();
- }
- }
-
- // Workaround for bug 1059934
- panel.removeAttribute("height");
- },
-
- _onUIChange: function() {
- this._changed = true;
- if (!this.resetting) {
- this._updateResetButton();
- this._updateUndoResetButton();
- this._updateEmptyPaletteNotice();
- }
- CustomizableUI.dispatchToolboxEvent("customizationchange");
- },
-
- _updateEmptyPaletteNotice: function() {
- let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
- this.paletteEmptyNotice.hidden = !!paletteItems.length;
- },
-
- _updateResetButton: function() {
- let btn = this.document.getElementById("customization-reset-button");
- btn.disabled = CustomizableUI.inDefaultState;
- },
-
- _updateUndoResetButton: function() {
- let undoResetButton = this.document.getElementById("customization-undo-reset-button");
- undoResetButton.hidden = !CustomizableUI.canUndoReset;
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "toolbarvisibilitychange":
- this._onToolbarVisibilityChange(aEvent);
- break;
- case "dragstart":
- this._onDragStart(aEvent);
- break;
- case "dragover":
- this._onDragOver(aEvent);
- break;
- case "drop":
- this._onDragDrop(aEvent);
- break;
- case "dragexit":
- this._onDragExit(aEvent);
- break;
- case "dragend":
- this._onDragEnd(aEvent);
- break;
- case "command":
- if (aEvent.originalTarget == this.window.PanelUI.menuButton) {
- this.exit();
- aEvent.preventDefault();
- }
- break;
- case "mousedown":
- this._onMouseDown(aEvent);
- break;
- case "mouseup":
- this._onMouseUp(aEvent);
- break;
- case "keypress":
- if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
- this.exit();
- }
- break;
- case "unload":
- this.uninit();
- break;
- }
- },
-
- observe: function(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "nsPref:changed":
- this._updateResetButton();
- this._updateUndoResetButton();
- if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
- this._updateTitlebarButton();
- }
- break;
- case "lightweight-theme-window-updated":
- if (aSubject == this.window) {
- aData = JSON.parse(aData);
- if (!aData) {
- this.removeLWTStyling();
- } else {
- this.updateLWTStyling(aData);
- }
- }
- break;
- }
- },
-
- _updateTitlebarButton: function() {
- if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
- return;
- }
- let drawInTitlebar = true;
- try {
- drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref);
- } catch (ex) { }
- let button = this.document.getElementById("customization-titlebar-visibility-button");
- // Drawing in the titlebar means 'hiding' the titlebar:
- if (drawInTitlebar) {
- button.removeAttribute("checked");
- } else {
- button.setAttribute("checked", "true");
- }
- },
-
- toggleTitlebar: function(aShouldShowTitlebar) {
- if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
- return;
- }
- // Drawing in the titlebar means not showing the titlebar, hence the negation:
- Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
- },
-
- _onDragStart: function(aEvent) {
- __dumpDragData(aEvent);
- let item = aEvent.target;
- while (item && item.localName != "toolbarpaletteitem") {
- if (item.localName == "toolbar") {
- return;
- }
- item = item.parentNode;
- }
-
- let draggedItem = item.firstChild;
- let placeForItem = CustomizableUI.getPlaceForItem(item);
- let isRemovable = placeForItem == "palette" ||
- CustomizableUI.isWidgetRemovable(draggedItem);
- if (item.classList.contains(kPlaceholderClass) || !isRemovable) {
- return;
- }
-
- let dt = aEvent.dataTransfer;
- let documentId = aEvent.target.ownerDocument.documentElement.id;
- let isInToolbar = placeForItem == "toolbar";
-
- dt.mozSetDataAt(kDragDataTypePrefix + documentId, draggedItem.id, 0);
- dt.effectAllowed = "move";
-
- let itemRect = draggedItem.getBoundingClientRect();
- let itemCenter = {x: itemRect.left + itemRect.width / 2,
- y: itemRect.top + itemRect.height / 2};
- this._dragOffset = {x: aEvent.clientX - itemCenter.x,
- y: aEvent.clientY - itemCenter.y};
-
- gDraggingInToolbars = new Set();
-
- // Hack needed so that the dragimage will still show the
- // item as it appeared before it was hidden.
- this._initializeDragAfterMove = function() {
- // For automated tests, we sometimes start exiting customization mode
- // before this fires, which leaves us with placeholders inserted after
- // we've exited. So we need to check that we are indeed customizing.
- if (this._customizing && !this._transitioning) {
- item.hidden = true;
- this._showPanelCustomizationPlaceholders();
- DragPositionManager.start(this.window);
- if (item.nextSibling) {
- this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
- this._dragOverItem = item.nextSibling;
- } else if (isInToolbar && item.previousSibling) {
- this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
- this._dragOverItem = item.previousSibling;
- }
- }
- this._initializeDragAfterMove = null;
- this.window.clearTimeout(this._dragInitializeTimeout);
- }.bind(this);
- this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
- },
-
- _onDragOver: function(aEvent) {
- if (this._isUnwantedDragDrop(aEvent)) {
- return;
- }
- if (this._initializeDragAfterMove) {
- this._initializeDragAfterMove();
- }
-
- __dumpDragData(aEvent);
-
- let document = aEvent.target.ownerDocument;
- let documentId = document.documentElement.id;
- if (!aEvent.dataTransfer.mozTypesAt(0)) {
- return;
- }
-
- let draggedItemId =
- aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
- let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
- let targetArea = this._getCustomizableParent(aEvent.currentTarget);
- let originArea = this._getCustomizableParent(draggedWrapper);
-
- // Do nothing if the target or origin are not customizable.
- if (!targetArea || !originArea) {
- return;
- }
-
- // Do nothing if the widget is not allowed to be removed.
- if (targetArea.id == kPaletteId &&
- !CustomizableUI.isWidgetRemovable(draggedItemId)) {
- return;
- }
-
- // Do nothing if the widget is not allowed to move to the target area.
- if (targetArea.id != kPaletteId &&
- !CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) {
- return;
- }
-
- let targetIsToolbar = CustomizableUI.getAreaType(targetArea.id) == "toolbar";
- let targetNode = this._getDragOverNode(aEvent, targetArea, targetIsToolbar, draggedItemId);
-
- // We need to determine the place that the widget is being dropped in
- // the target.
- let dragOverItem, dragValue;
- if (targetNode == targetArea.customizationTarget) {
- // We'll assume if the user is dragging directly over the target, that
- // they're attempting to append a child to that target.
- dragOverItem = (targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
- targetNode.lastChild) || targetNode;
- dragValue = "after";
- } else {
- let targetParent = targetNode.parentNode;
- let position = Array.indexOf(targetParent.children, targetNode);
- if (position == -1) {
- dragOverItem = targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
- targetParent.lastChild;
- dragValue = "after";
- } else {
- dragOverItem = targetParent.children[position];
- if (!targetIsToolbar) {
- dragValue = "before";
- } else {
- // Check if the aDraggedItem is hovered past the first half of dragOverItem
- let window = dragOverItem.ownerGlobal;
- let direction = window.getComputedStyle(dragOverItem, null).direction;
- let itemRect = dragOverItem.getBoundingClientRect();
- let dropTargetCenter = itemRect.left + (itemRect.width / 2);
- let existingDir = dragOverItem.getAttribute("dragover");
- if ((existingDir == "before") == (direction == "ltr")) {
- dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
- } else {
- dropTargetCenter -= (parseInt(dragOverItem.style.borderRightWidth) || 0) / 2;
- }
- let before = direction == "ltr" ? aEvent.clientX < dropTargetCenter : aEvent.clientX > dropTargetCenter;
- dragValue = before ? "before" : "after";
- }
- }
- }
-
- if (this._dragOverItem && dragOverItem != this._dragOverItem) {
- this._cancelDragActive(this._dragOverItem, dragOverItem);
- }
-
- if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) {
- if (dragOverItem != targetArea.customizationTarget) {
- this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar);
- } else if (targetIsToolbar) {
- this._updateToolbarCustomizationOutline(this.window, targetArea);
- }
- this._dragOverItem = dragOverItem;
- }
-
- aEvent.preventDefault();
- aEvent.stopPropagation();
- },
-
- _onDragDrop: function(aEvent) {
- if (this._isUnwantedDragDrop(aEvent)) {
- return;
- }
-
- __dumpDragData(aEvent);
- this._initializeDragAfterMove = null;
- this.window.clearTimeout(this._dragInitializeTimeout);
-
- let targetArea = this._getCustomizableParent(aEvent.currentTarget);
- let document = aEvent.target.ownerDocument;
- let documentId = document.documentElement.id;
- let draggedItemId =
- aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
- let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
- let originArea = this._getCustomizableParent(draggedWrapper);
- if (this._dragSizeMap) {
- this._dragSizeMap = new WeakMap();
- }
- // Do nothing if the target area or origin area are not customizable.
- if (!targetArea || !originArea) {
- return;
- }
- let targetNode = this._dragOverItem;
- let dropDir = targetNode.getAttribute("dragover");
- // Need to insert *after* this node if we promised the user that:
- if (targetNode != targetArea && dropDir == "after") {
- if (targetNode.nextSibling) {
- targetNode = targetNode.nextSibling;
- } else {
- targetNode = targetArea;
- }
- }
- // If the target node is a placeholder, get its sibling as the real target.
- while (targetNode.classList.contains(kPlaceholderClass) && targetNode.nextSibling) {
- targetNode = targetNode.nextSibling;
- }
- if (targetNode.tagName == "toolbarpaletteitem") {
- targetNode = targetNode.firstChild;
- }
-
- this._cancelDragActive(this._dragOverItem, null, true);
- this._removePanelCustomizationPlaceholders();
-
- try {
- this._applyDrop(aEvent, targetArea, originArea, draggedItemId, targetNode);
- } catch (ex) {
- log.error(ex, ex.stack);
- }
-
- this._showPanelCustomizationPlaceholders();
- },
-
- _applyDrop: function(aEvent, aTargetArea, aOriginArea, aDraggedItemId, aTargetNode) {
- let document = aEvent.target.ownerDocument;
- let draggedItem = document.getElementById(aDraggedItemId);
- draggedItem.hidden = false;
- draggedItem.removeAttribute("mousedown");
-
- // Do nothing if the target was dropped onto itself (ie, no change in area
- // or position).
- if (draggedItem == aTargetNode) {
- return;
- }
-
- // Is the target area the customization palette?
- if (aTargetArea.id == kPaletteId) {
- // Did we drag from outside the palette?
- if (aOriginArea.id !== kPaletteId) {
- if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) {
- return;
- }
-
- CustomizableUI.removeWidgetFromArea(aDraggedItemId);
- BrowserUITelemetry.countCustomizationEvent("remove");
- // Special widgets are removed outright, we can return here:
- if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
- return;
- }
- }
- draggedItem = draggedItem.parentNode;
-
- // If the target node is the palette itself, just append
- if (aTargetNode == this.visiblePalette) {
- this.visiblePalette.appendChild(draggedItem);
- } else {
- // The items in the palette are wrapped, so we need the target node's parent here:
- this.visiblePalette.insertBefore(draggedItem, aTargetNode.parentNode);
- }
- if (aOriginArea.id !== kPaletteId) {
- // The dragend event already fires when the item moves within the palette.
- this._onDragEnd(aEvent);
- }
- return;
- }
-
- if (!CustomizableUI.canWidgetMoveToArea(aDraggedItemId, aTargetArea.id)) {
- return;
- }
-
- // Skipintoolbarset items won't really be moved:
- if (draggedItem.getAttribute("skipintoolbarset") == "true") {
- // These items should never leave their area:
- if (aTargetArea != aOriginArea) {
- return;
- }
- let place = draggedItem.parentNode.getAttribute("place");
- this.unwrapToolbarItem(draggedItem.parentNode);
- if (aTargetNode == aTargetArea.customizationTarget) {
- aTargetArea.customizationTarget.appendChild(draggedItem);
- } else {
- this.unwrapToolbarItem(aTargetNode.parentNode);
- aTargetArea.customizationTarget.insertBefore(draggedItem, aTargetNode);
- this.wrapToolbarItem(aTargetNode, place);
- }
- this.wrapToolbarItem(draggedItem, place);
- BrowserUITelemetry.countCustomizationEvent("move");
- return;
- }
-
- // Is the target the customization area itself? If so, we just add the
- // widget to the end of the area.
- if (aTargetNode == aTargetArea.customizationTarget) {
- CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id);
- // For the purposes of BrowserUITelemetry, we consider both moving a widget
- // within the same area, and adding a widget from one area to another area
- // as a "move". An "add" is only when we move an item from the palette into
- // an area.
- let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
- BrowserUITelemetry.countCustomizationEvent(custEventType);
- this._onDragEnd(aEvent);
- return;
- }
-
- // We need to determine the place that the widget is being dropped in
- // the target.
- let placement;
- let itemForPlacement = aTargetNode;
- // Skip the skipintoolbarset items when determining the place of the item:
- while (itemForPlacement && itemForPlacement.getAttribute("skipintoolbarset") == "true" &&
- itemForPlacement.parentNode &&
- itemForPlacement.parentNode.nodeName == "toolbarpaletteitem") {
- itemForPlacement = itemForPlacement.parentNode.nextSibling;
- if (itemForPlacement && itemForPlacement.nodeName == "toolbarpaletteitem") {
- itemForPlacement = itemForPlacement.firstChild;
- }
- }
- if (itemForPlacement && !itemForPlacement.classList.contains(kPlaceholderClass)) {
- let targetNodeId = (itemForPlacement.nodeName == "toolbarpaletteitem") ?
- itemForPlacement.firstChild && itemForPlacement.firstChild.id :
- itemForPlacement.id;
- placement = CustomizableUI.getPlacementOfWidget(targetNodeId);
- }
- if (!placement) {
- log.debug("Could not get a position for " + aTargetNode.nodeName + "#" + aTargetNode.id + "." + aTargetNode.className);
- }
- let position = placement ? placement.position : null;
-
- // Is the target area the same as the origin? Since we've already handled
- // the possibility that the target is the customization palette, we know
- // that the widget is moving within a customizable area.
- if (aTargetArea == aOriginArea) {
- CustomizableUI.moveWidgetWithinArea(aDraggedItemId, position);
- } else {
- CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position);
- }
-
- this._onDragEnd(aEvent);
-
- // For BrowserUITelemetry, an "add" is only when we move an item from the palette
- // into an area. Otherwise, it's a move.
- let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
- BrowserUITelemetry.countCustomizationEvent(custEventType);
-
- // If we dropped onto a skipintoolbarset item, manually correct the drop location:
- if (aTargetNode != itemForPlacement) {
- let draggedWrapper = draggedItem.parentNode;
- let container = draggedWrapper.parentNode;
- container.insertBefore(draggedWrapper, aTargetNode.parentNode);
- }
- },
-
- _onDragExit: function(aEvent) {
- if (this._isUnwantedDragDrop(aEvent)) {
- return;
- }
-
- __dumpDragData(aEvent);
-
- // When leaving customization areas, cancel the drag on the last dragover item
- // We've attached the listener to areas, so aEvent.currentTarget will be the area.
- // We don't care about dragexit events fired on descendants of the area,
- // so we check that the event's target is the same as the area to which the listener
- // was attached.
- if (this._dragOverItem && aEvent.target == aEvent.currentTarget) {
- this._cancelDragActive(this._dragOverItem);
- this._dragOverItem = null;
- }
- },
-
- /**
- * To workaround bug 460801 we manually forward the drop event here when dragend wouldn't be fired.
- */
- _onDragEnd: function(aEvent) {
- if (this._isUnwantedDragDrop(aEvent)) {
- return;
- }
- this._initializeDragAfterMove = null;
- this.window.clearTimeout(this._dragInitializeTimeout);
- __dumpDragData(aEvent, "_onDragEnd");
-
- let document = aEvent.target.ownerDocument;
- document.documentElement.removeAttribute("customizing-movingItem");
-
- let documentId = document.documentElement.id;
- if (!aEvent.dataTransfer.mozTypesAt(0)) {
- return;
- }
-
- let draggedItemId =
- aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
-
- let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
-
- // DraggedWrapper might no longer available if a widget node is
- // destroyed after starting (but before stopping) a drag.
- if (draggedWrapper) {
- draggedWrapper.hidden = false;
- draggedWrapper.removeAttribute("mousedown");
- }
-
- if (this._dragOverItem) {
- this._cancelDragActive(this._dragOverItem);
- this._dragOverItem = null;
- }
- this._updateToolbarCustomizationOutline(this.window);
- this._showPanelCustomizationPlaceholders();
- DragPositionManager.stop();
- },
-
- _isUnwantedDragDrop: function(aEvent) {
- // The simulated events generated by synthesizeDragStart/synthesizeDrop in
- // mochitests are used only for testing whether the right data is being put
- // into the dataTransfer. Neither cause a real drop to occur, so they don't
- // set the source node. There isn't a means of testing real drag and drops,
- // so this pref skips the check but it should only be set by test code.
- if (this._skipSourceNodeCheck) {
- return false;
- }
-
- /* Discard drag events that originated from a separate window to
- prevent content->chrome privilege escalations. */
- let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
- // mozSourceNode is null in the dragStart event handler or if
- // the drag event originated in an external application.
- return !mozSourceNode ||
- mozSourceNode.ownerGlobal != this.window;
- },
-
- _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
- if (!aItem) {
- return;
- }
-
- if (aItem.getAttribute("dragover") != aValue) {
- aItem.setAttribute("dragover", aValue);
-
- let window = aItem.ownerGlobal;
- let draggedItem = window.document.getElementById(aDraggedItemId);
- if (!aInToolbar) {
- this._setGridDragActive(aItem, draggedItem, aValue);
- } else {
- let targetArea = this._getCustomizableParent(aItem);
- this._updateToolbarCustomizationOutline(window, targetArea);
- let makeSpaceImmediately = false;
- if (!gDraggingInToolbars.has(targetArea.id)) {
- gDraggingInToolbars.add(targetArea.id);
- let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId);
- let originArea = this._getCustomizableParent(draggedWrapper);
- makeSpaceImmediately = originArea == targetArea;
- }
- // Calculate width of the item when it'd be dropped in this position
- let width = this._getDragItemSize(aItem, draggedItem).width;
- let direction = window.getComputedStyle(aItem).direction;
- let prop, otherProp;
- // If we're inserting before in ltr, or after in rtl:
- if ((aValue == "before") == (direction == "ltr")) {
- prop = "borderLeftWidth";
- otherProp = "border-right-width";
- } else {
- // otherwise:
- prop = "borderRightWidth";
- otherProp = "border-left-width";
- }
- if (makeSpaceImmediately) {
- aItem.setAttribute("notransition", "true");
- }
- aItem.style[prop] = width + 'px';
- aItem.style.removeProperty(otherProp);
- if (makeSpaceImmediately) {
- // Force a layout flush:
- aItem.getBoundingClientRect();
- aItem.removeAttribute("notransition");
- }
- }
- }
- },
- _cancelDragActive: function(aItem, aNextItem, aNoTransition) {
- this._updateToolbarCustomizationOutline(aItem.ownerGlobal);
- let currentArea = this._getCustomizableParent(aItem);
- if (!currentArea) {
- return;
- }
- let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
- if (isToolbar) {
- if (aNoTransition) {
- aItem.setAttribute("notransition", "true");
- }
- aItem.removeAttribute("dragover");
- // Remove both property values in the case that the end padding
- // had been set.
- aItem.style.removeProperty("border-left-width");
- aItem.style.removeProperty("border-right-width");
- if (aNoTransition) {
- // Force a layout flush:
- aItem.getBoundingClientRect();
- aItem.removeAttribute("notransition");
- }
- } else {
- aItem.removeAttribute("dragover");
- if (aNextItem) {
- let nextArea = this._getCustomizableParent(aNextItem);
- if (nextArea == currentArea) {
- // No need to do anything if we're still dragging in this area:
- return;
- }
- }
- // Otherwise, clear everything out:
- let positionManager = DragPositionManager.getManagerForArea(currentArea);
- positionManager.clearPlaceholders(currentArea, aNoTransition);
- }
- },
-
- _setGridDragActive: function(aDragOverNode, aDraggedItem, aValue) {
- let targetArea = this._getCustomizableParent(aDragOverNode);
- let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItem.id);
- let originArea = this._getCustomizableParent(draggedWrapper);
- let positionManager = DragPositionManager.getManagerForArea(targetArea);
- let draggedSize = this._getDragItemSize(aDragOverNode, aDraggedItem);
- let isWide = aDraggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
- positionManager.insertPlaceholder(targetArea, aDragOverNode, isWide, draggedSize,
- originArea == targetArea);
- },
-
- _getDragItemSize: function(aDragOverNode, aDraggedItem) {
- // Cache it good, cache it real good.
- if (!this._dragSizeMap)
- this._dragSizeMap = new WeakMap();
- if (!this._dragSizeMap.has(aDraggedItem))
- this._dragSizeMap.set(aDraggedItem, new WeakMap());
- let itemMap = this._dragSizeMap.get(aDraggedItem);
- let targetArea = this._getCustomizableParent(aDragOverNode);
- let currentArea = this._getCustomizableParent(aDraggedItem);
- // Return the size for this target from cache, if it exists.
- let size = itemMap.get(targetArea);
- if (size)
- return size;
-
- // Calculate size of the item when it'd be dropped in this position.
- let currentParent = aDraggedItem.parentNode;
- let currentSibling = aDraggedItem.nextSibling;
- const kAreaType = "cui-areatype";
- let areaType, currentType;
-
- if (targetArea != currentArea) {
- // Move the widget temporarily next to the placeholder.
- aDragOverNode.parentNode.insertBefore(aDraggedItem, aDragOverNode);
- // Update the node's areaType.
- areaType = CustomizableUI.getAreaType(targetArea.id);
- currentType = aDraggedItem.hasAttribute(kAreaType) &&
- aDraggedItem.getAttribute(kAreaType);
- if (areaType)
- aDraggedItem.setAttribute(kAreaType, areaType);
- this.wrapToolbarItem(aDraggedItem, areaType || "palette");
- CustomizableUI.onWidgetDrag(aDraggedItem.id, targetArea.id);
- } else {
- aDraggedItem.parentNode.hidden = false;
- }
-
- // Fetch the new size.
- let rect = aDraggedItem.parentNode.getBoundingClientRect();
- size = {width: rect.width, height: rect.height};
- // Cache the found value of size for this target.
- itemMap.set(targetArea, size);
-
- if (targetArea != currentArea) {
- this.unwrapToolbarItem(aDraggedItem.parentNode);
- // Put the item back into its previous position.
- currentParent.insertBefore(aDraggedItem, currentSibling);
- // restore the areaType
- if (areaType) {
- if (currentType === false)
- aDraggedItem.removeAttribute(kAreaType);
- else
- aDraggedItem.setAttribute(kAreaType, currentType);
- }
- this.createOrUpdateWrapper(aDraggedItem, null, true);
- CustomizableUI.onWidgetDrag(aDraggedItem.id);
- } else {
- aDraggedItem.parentNode.hidden = true;
- }
- return size;
- },
-
- _getCustomizableParent: function(aElement) {
- let areas = CustomizableUI.areas;
- areas.push(kPaletteId);
- while (aElement) {
- if (areas.indexOf(aElement.id) != -1) {
- return aElement;
- }
- aElement = aElement.parentNode;
- }
- return null;
- },
-
- _getDragOverNode: function(aEvent, aAreaElement, aInToolbar, aDraggedItemId) {
- let expectedParent = aAreaElement.customizationTarget || aAreaElement;
- // Our tests are stupid. Cope:
- if (!aEvent.clientX && !aEvent.clientY) {
- return aEvent.target;
- }
- // Offset the drag event's position with the offset to the center of
- // the thing we're dragging
- let dragX = aEvent.clientX - this._dragOffset.x;
- let dragY = aEvent.clientY - this._dragOffset.y;
-
- // Ensure this is within the container
- let boundsContainer = expectedParent;
- // NB: because the panel UI itself is inside a scrolling container, we need
- // to use the parent bounds (otherwise, if the panel UI is scrolled down,
- // the numbers we get are in window coordinates which leads to various kinds
- // of weirdness)
- if (boundsContainer == this.panelUIContents) {
- boundsContainer = boundsContainer.parentNode;
- }
- let bounds = boundsContainer.getBoundingClientRect();
- dragX = Math.min(bounds.right, Math.max(dragX, bounds.left));
- dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top));
-
- let targetNode;
- if (aInToolbar) {
- targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
- while (targetNode && targetNode.parentNode != expectedParent) {
- targetNode = targetNode.parentNode;
- }
- } else {
- let positionManager = DragPositionManager.getManagerForArea(aAreaElement);
- // Make it relative to the container:
- dragX -= bounds.left;
- // NB: but if we're in the panel UI, we need to use the actual panel
- // contents instead of the scrolling container to determine our origin
- // offset against:
- if (expectedParent == this.panelUIContents) {
- dragY -= this.panelUIContents.getBoundingClientRect().top;
- } else {
- dragY -= bounds.top;
- }
- // Find the closest node:
- targetNode = positionManager.find(aAreaElement, dragX, dragY, aDraggedItemId);
- }
- return targetNode || aEvent.target;
- },
-
- _onMouseDown: function(aEvent) {
- log.debug("_onMouseDown");
- if (aEvent.button != 0) {
- return;
- }
- let doc = aEvent.target.ownerDocument;
- doc.documentElement.setAttribute("customizing-movingItem", true);
- let item = this._getWrapper(aEvent.target);
- if (item && !item.classList.contains(kPlaceholderClass) &&
- item.getAttribute("removable") == "true") {
- item.setAttribute("mousedown", "true");
- }
- },
-
- _onMouseUp: function(aEvent) {
- log.debug("_onMouseUp");
- if (aEvent.button != 0) {
- return;
- }
- let doc = aEvent.target.ownerDocument;
- doc.documentElement.removeAttribute("customizing-movingItem");
- let item = this._getWrapper(aEvent.target);
- if (item) {
- item.removeAttribute("mousedown");
- }
- },
-
- _getWrapper: function(aElement) {
- while (aElement && aElement.localName != "toolbarpaletteitem") {
- if (aElement.localName == "toolbar")
- return null;
- aElement = aElement.parentNode;
- }
- return aElement;
- },
-
- _showPanelCustomizationPlaceholders: function() {
- let doc = this.document;
- let contents = this.panelUIContents;
- let narrowItemsAfterWideItem = 0;
- let node = contents.lastChild;
- while (node && !node.classList.contains(CustomizableUI.WIDE_PANEL_CLASS) &&
- (!node.firstChild || !node.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS))) {
- if (!node.hidden && !node.classList.contains(kPlaceholderClass)) {
- narrowItemsAfterWideItem++;
- }
- node = node.previousSibling;
- }
-
- let orphanedItems = narrowItemsAfterWideItem % CustomizableUI.PANEL_COLUMN_COUNT;
- let placeholders = CustomizableUI.PANEL_COLUMN_COUNT - orphanedItems;
-
- let currentPlaceholderCount = contents.querySelectorAll("." + kPlaceholderClass).length;
- if (placeholders > currentPlaceholderCount) {
- while (placeholders-- > currentPlaceholderCount) {
- let placeholder = doc.createElement("toolbarpaletteitem");
- placeholder.classList.add(kPlaceholderClass);
- // XXXjaws The toolbarbutton child here is only necessary to get
- // the styling right here.
- let placeholderChild = doc.createElement("toolbarbutton");
- placeholderChild.classList.add(kPlaceholderClass + "-child");
- placeholder.appendChild(placeholderChild);
- contents.appendChild(placeholder);
- }
- } else if (placeholders < currentPlaceholderCount) {
- while (placeholders++ < currentPlaceholderCount) {
- contents.querySelectorAll("." + kPlaceholderClass)[0].remove();
- }
- }
- },
-
- _removePanelCustomizationPlaceholders: function() {
- let contents = this.panelUIContents;
- let oldPlaceholders = contents.getElementsByClassName(kPlaceholderClass);
- while (oldPlaceholders.length) {
- contents.removeChild(oldPlaceholders[0]);
- }
- },
-
- /**
- * Update toolbar customization targets during drag events to add or remove
- * outlines to indicate that an area is customizable.
- *
- * @param aWindow The XUL window in which outlines should be updated.
- * @param {Element} [aToolbarArea=null] The element of the customizable toolbar area to add the
- * outline to. If aToolbarArea is falsy, the outline will be
- * removed from all toolbar areas.
- */
- _updateToolbarCustomizationOutline: function(aWindow, aToolbarArea = null) {
- // Remove the attribute from existing customization targets
- for (let area of CustomizableUI.areas) {
- if (CustomizableUI.getAreaType(area) != CustomizableUI.TYPE_TOOLBAR) {
- continue;
- }
- let target = CustomizableUI.getCustomizeTargetForArea(area, aWindow);
- target.removeAttribute("customizing-dragovertarget");
- }
-
- // Now set the attribute on the desired target
- if (aToolbarArea) {
- if (CustomizableUI.getAreaType(aToolbarArea.id) != CustomizableUI.TYPE_TOOLBAR)
- return;
- let target = CustomizableUI.getCustomizeTargetForArea(aToolbarArea.id, aWindow);
- target.setAttribute("customizing-dragovertarget", true);
- }
- },
-
- _findVisiblePreviousSiblingNode: function(aReferenceNode) {
- while (aReferenceNode &&
- aReferenceNode.localName == "toolbarpaletteitem" &&
- aReferenceNode.firstChild.hidden) {
- aReferenceNode = aReferenceNode.previousSibling;
- }
- return aReferenceNode;
- },
-};
-
-function __dumpDragData(aEvent, caller) {
- if (!gDebug) {
- return;
- }
- let str = "Dumping drag data (" + (caller ? caller + " in " : "") + "CustomizeMode.jsm) {\n";
- str += " type: " + aEvent["type"] + "\n";
- for (let el of ["target", "currentTarget", "relatedTarget"]) {
- if (aEvent[el]) {
- str += " " + el + ": " + aEvent[el] + "(localName=" + aEvent[el].localName + "; id=" + aEvent[el].id + ")\n";
- }
- }
- for (let prop in aEvent.dataTransfer) {
- if (typeof aEvent.dataTransfer[prop] != "function") {
- str += " dataTransfer[" + prop + "]: " + aEvent.dataTransfer[prop] + "\n";
- }
- }
- str += "}";
- log.debug(str);
-}
-
-function dispatchFunction(aFunc) {
- Services.tm.currentThread.dispatch(aFunc, Ci.nsIThread.DISPATCH_NORMAL);
-}
diff --git a/browser/components/customizableui/DragPositionManager.jsm b/browser/components/customizableui/DragPositionManager.jsm
deleted file mode 100644
index 1b4eb59dc..000000000
--- a/browser/components/customizableui/DragPositionManager.jsm
+++ /dev/null
@@ -1,420 +0,0 @@
-/* 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";
-
-Components.utils.import("resource:///modules/CustomizableUI.jsm");
-
-var gManagers = new WeakMap();
-
-const kPaletteId = "customization-palette";
-const kPlaceholderClass = "panel-customization-placeholder";
-
-this.EXPORTED_SYMBOLS = ["DragPositionManager"];
-
-function AreaPositionManager(aContainer) {
- // Caching the direction and bounds of the container for quick access later:
- let window = aContainer.ownerGlobal;
- this._dir = window.getComputedStyle(aContainer).direction;
- let containerRect = aContainer.getBoundingClientRect();
- this._containerInfo = {
- left: containerRect.left,
- right: containerRect.right,
- top: containerRect.top,
- width: containerRect.width
- };
- this._inPanel = aContainer.id == CustomizableUI.AREA_PANEL;
- this._horizontalDistance = null;
- this.update(aContainer);
-}
-
-AreaPositionManager.prototype = {
- _nodePositionStore: null,
- _wideCache: null,
-
- update: function(aContainer) {
- this._nodePositionStore = new WeakMap();
- this._wideCache = new Set();
- let last = null;
- let singleItemHeight;
- for (let child of aContainer.children) {
- if (child.hidden) {
- continue;
- }
- let isNodeWide = this._checkIfWide(child);
- if (isNodeWide) {
- this._wideCache.add(child.id);
- }
- let coordinates = this._lazyStoreGet(child);
- // We keep a baseline horizontal distance between non-wide nodes around
- // for use when we can't compare with previous/next nodes
- if (!this._horizontalDistance && last && !isNodeWide) {
- this._horizontalDistance = coordinates.left - last.left;
- }
- // We also keep the basic height of non-wide items for use below:
- if (!isNodeWide && !singleItemHeight) {
- singleItemHeight = coordinates.height;
- }
- last = !isNodeWide ? coordinates : null;
- }
- if (this._inPanel) {
- this._heightToWidthFactor = CustomizableUI.PANEL_COLUMN_COUNT;
- } else {
- this._heightToWidthFactor = this._containerInfo.width / singleItemHeight;
- }
- },
-
- /**
- * Find the closest node in the container given the coordinates.
- * "Closest" is defined in a somewhat strange manner: we prefer nodes
- * which are in the same row over nodes that are in a different row.
- * In order to implement this, we use a weighted cartesian distance
- * where dy is more heavily weighted by a factor corresponding to the
- * ratio between the container's width and the height of its elements.
- */
- find: function(aContainer, aX, aY, aDraggedItemId) {
- let closest = null;
- let minCartesian = Number.MAX_VALUE;
- let containerX = this._containerInfo.left;
- let containerY = this._containerInfo.top;
- for (let node of aContainer.children) {
- let coordinates = this._lazyStoreGet(node);
- let offsetX = coordinates.x - containerX;
- let offsetY = coordinates.y - containerY;
- let hDiff = offsetX - aX;
- let vDiff = offsetY - aY;
- // For wide widgets, we're always going to be further from the center
- // horizontally. Compensate:
- if (this.isWide(node)) {
- hDiff /= CustomizableUI.PANEL_COLUMN_COUNT;
- }
- // Then compensate for the height/width ratio so that we prefer items
- // which are in the same row:
- hDiff /= this._heightToWidthFactor;
-
- let cartesianDiff = hDiff * hDiff + vDiff * vDiff;
- if (cartesianDiff < minCartesian) {
- minCartesian = cartesianDiff;
- closest = node;
- }
- }
-
- // Now correct this node based on what we're dragging
- if (closest) {
- let doc = aContainer.ownerDocument;
- let draggedItem = doc.getElementById(aDraggedItemId);
- // If dragging a wide item, always pick the first item in a row:
- if (this._inPanel && draggedItem &&
- draggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
- return this._firstInRow(closest);
- }
- let targetBounds = this._lazyStoreGet(closest);
- let farSide = this._dir == "ltr" ? "right" : "left";
- let outsideX = targetBounds[farSide];
- // Check if we're closer to the next target than to this one:
- // Only move if we're not targeting a node in a different row:
- if (aY > targetBounds.top && aY < targetBounds.bottom) {
- if ((this._dir == "ltr" && aX > outsideX) ||
- (this._dir == "rtl" && aX < outsideX)) {
- return closest.nextSibling || aContainer;
- }
- }
- }
- return closest;
- },
-
- /**
- * "Insert" a "placeholder" by shifting the subsequent children out of the
- * way. We go through all the children, and shift them based on the position
- * they would have if we had inserted something before aBefore. We use CSS
- * transforms for this, which are CSS transitioned.
- */
- insertPlaceholder: function(aContainer, aBefore, aWide, aSize, aIsFromThisArea) {
- let isShifted = false;
- let shiftDown = aWide;
- for (let child of aContainer.children) {
- // Don't need to shift hidden nodes:
- if (child.getAttribute("hidden") == "true") {
- continue;
- }
- // If this is the node before which we're inserting, start shifting
- // everything that comes after. One exception is inserting at the end
- // of the menupanel, in which case we do not shift the placeholders:
- if (child == aBefore && !child.classList.contains(kPlaceholderClass)) {
- isShifted = true;
- // If the node before which we're inserting is wide, we should
- // shift everything one row down:
- if (!shiftDown && this.isWide(child)) {
- shiftDown = true;
- }
- }
- // If we're moving items before a wide node that were already there,
- // it's possible it's not necessary to shift nodes
- // including & after the wide node.
- if (this.__undoShift) {
- isShifted = false;
- }
- if (isShifted) {
- // Conversely, if we're adding something before a wide node, for
- // simplicity's sake we move everything including the wide node down:
- if (this.__moveDown) {
- shiftDown = true;
- }
- if (aIsFromThisArea && !this._lastPlaceholderInsertion) {
- child.setAttribute("notransition", "true");
- }
- // Determine the CSS transform based on the next node:
- child.style.transform = this._getNextPos(child, shiftDown, aSize);
- } else {
- // If we're not shifting this node, reset the transform
- child.style.transform = "";
- }
- }
- if (aContainer.lastChild && aIsFromThisArea &&
- !this._lastPlaceholderInsertion) {
- // Flush layout:
- aContainer.lastChild.getBoundingClientRect();
- // then remove all the [notransition]
- for (let child of aContainer.children) {
- child.removeAttribute("notransition");
- }
- }
- delete this.__moveDown;
- delete this.__undoShift;
- this._lastPlaceholderInsertion = aBefore;
- },
-
- isWide: function(aNode) {
- return this._wideCache.has(aNode.id);
- },
-
- _checkIfWide: function(aNode) {
- return this._inPanel && aNode && aNode.firstChild &&
- aNode.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
- },
-
- /**
- * Reset all the transforms in this container, optionally without
- * transitioning them.
- * @param aContainer the container in which to reset transforms
- * @param aNoTransition if truthy, adds a notransition attribute to the node
- * while resetting the transform.
- */
- clearPlaceholders: function(aContainer, aNoTransition) {
- for (let child of aContainer.children) {
- if (aNoTransition) {
- child.setAttribute("notransition", true);
- }
- child.style.transform = "";
- if (aNoTransition) {
- // Need to force a reflow otherwise this won't work.
- child.getBoundingClientRect();
- child.removeAttribute("notransition");
- }
- }
- // We snapped back, so we can assume there's no more
- // "last" placeholder insertion point to keep track of.
- if (aNoTransition) {
- this._lastPlaceholderInsertion = null;
- }
- },
-
- _getNextPos: function(aNode, aShiftDown, aSize) {
- // Shifting down is easy:
- if (this._inPanel && aShiftDown) {
- return "translate(0, " + aSize.height + "px)";
- }
- return this._diffWithNext(aNode, aSize);
- },
-
- _diffWithNext: function(aNode, aSize) {
- let xDiff;
- let yDiff = null;
- let nodeBounds = this._lazyStoreGet(aNode);
- let side = this._dir == "ltr" ? "left" : "right";
- let next = this._getVisibleSiblingForDirection(aNode, "next");
- // First we determine the transform along the x axis.
- // Usually, there will be a next node to base this on:
- if (next) {
- let otherBounds = this._lazyStoreGet(next);
- xDiff = otherBounds[side] - nodeBounds[side];
- // If the next node is a wide item in the panel, check if we could maybe
- // just move further out in the same row, without snapping to the next
- // one. This happens, for example, if moving an item that's before a wide
- // node within its own row of items. There will be space to drop this
- // item within the row, and the rest of the items do not need to shift.
- if (this.isWide(next)) {
- let otherXDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds,
- this._firstInRow(aNode));
- // If this has the same sign as our original shift, we're still
- // snapping to the start of the row. In this case, we should move
- // everything after us a row down, so as not to display two nodes on
- // top of each other:
- // (we would be able to get away with checking for equality instead of
- // equal signs here, but one of these is based on the x coordinate of
- // the first item in row N and one on that for row N - 1, so this is
- // safer, as their margins might differ)
- if ((otherXDiff < 0) == (xDiff < 0)) {
- this.__moveDown = true;
- } else {
- // Otherwise, we succeeded and can move further out. This also means
- // we can stop shifting the rest of the content:
- xDiff = otherXDiff;
- this.__undoShift = true;
- }
- } else {
- // We set this explicitly because otherwise some strange difference
- // between the height and the actual difference between line creeps in
- // and messes with alignments
- yDiff = otherBounds.top - nodeBounds.top;
- }
- } else {
- // We don't have a sibling whose position we can use. First, let's see
- // if we're also the first item (which complicates things):
- let firstNode = this._firstInRow(aNode);
- if (aNode == firstNode) {
- // Maybe we stored the horizontal distance between non-wide nodes,
- // if not, we'll use the width of the incoming node as a proxy:
- xDiff = this._horizontalDistance || aSize.width;
- } else {
- // If not, we should be able to get the distance to the previous node
- // and use the inverse, unless there's no room for another node (ie we
- // are the last node and there's no room for another one)
- xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode);
- }
- }
-
- // If we've not determined the vertical difference yet, check it here
- if (yDiff === null) {
- // If the next node is behind rather than in front, we must have moved
- // vertically:
- if ((xDiff > 0 && this._dir == "rtl") || (xDiff < 0 && this._dir == "ltr")) {
- yDiff = aSize.height;
- } else {
- // Otherwise, we haven't
- yDiff = 0;
- }
- }
- return "translate(" + xDiff + "px, " + yDiff + "px)";
- },
-
- /**
- * Helper function to find the transform a node if there isn't a next node
- * to base that on.
- * @param aNode the node to transform
- * @param aNodeBounds the bounding rect info of this node
- * @param aFirstNodeInRow the first node in aNode's row
- */
- _moveNextBasedOnPrevious: function(aNode, aNodeBounds, aFirstNodeInRow) {
- let next = this._getVisibleSiblingForDirection(aNode, "previous");
- let otherBounds = this._lazyStoreGet(next);
- let side = this._dir == "ltr" ? "left" : "right";
- let xDiff = aNodeBounds[side] - otherBounds[side];
- // If, however, this means we move outside the container's box
- // (i.e. the row in which this item is placed is full)
- // we should move it to align with the first item in the next row instead
- let bound = this._containerInfo[this._dir == "ltr" ? "right" : "left"];
- if ((this._dir == "ltr" && xDiff + aNodeBounds.right > bound) ||
- (this._dir == "rtl" && xDiff + aNodeBounds.left < bound)) {
- xDiff = this._lazyStoreGet(aFirstNodeInRow)[side] - aNodeBounds[side];
- }
- return xDiff;
- },
-
- /**
- * Get position details from our cache. If the node is not yet cached, get its position
- * information and cache it now.
- * @param aNode the node whose position info we want
- * @return the position info
- */
- _lazyStoreGet: function(aNode) {
- let rect = this._nodePositionStore.get(aNode);
- if (!rect) {
- // getBoundingClientRect() returns a DOMRect that is live, meaning that
- // as the element moves around, the rects values change. We don't want
- // that - we want a snapshot of what the rect values are right at this
- // moment, and nothing else. So we have to clone the values.
- let clientRect = aNode.getBoundingClientRect();
- rect = {
- left: clientRect.left,
- right: clientRect.right,
- width: clientRect.width,
- height: clientRect.height,
- top: clientRect.top,
- bottom: clientRect.bottom,
- };
- rect.x = rect.left + rect.width / 2;
- rect.y = rect.top + rect.height / 2;
- Object.freeze(rect);
- this._nodePositionStore.set(aNode, rect);
- }
- return rect;
- },
-
- _firstInRow: function(aNode) {
- // XXXmconley: I'm not entirely sure why we need to take the floor of these
- // values - it looks like, periodically, we're getting fractional pixels back
- // from lazyStoreGet. I've filed bug 994247 to investigate.
- let bound = Math.floor(this._lazyStoreGet(aNode).top);
- let rv = aNode;
- let prev;
- while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
- if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) {
- return rv;
- }
- rv = prev;
- }
- return rv;
- },
-
- _getVisibleSiblingForDirection: function(aNode, aDirection) {
- let rv = aNode;
- do {
- rv = rv[aDirection + "Sibling"];
- } while (rv && rv.getAttribute("hidden") == "true")
- return rv;
- }
-}
-
-var DragPositionManager = {
- start: function(aWindow) {
- let areas = CustomizableUI.areas.filter((area) => CustomizableUI.getAreaType(area) != "toolbar");
- areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
- areas.push(aWindow.document.getElementById(kPaletteId));
- for (let areaNode of areas) {
- let positionManager = gManagers.get(areaNode);
- if (positionManager) {
- positionManager.update(areaNode);
- } else {
- gManagers.set(areaNode, new AreaPositionManager(areaNode));
- }
- }
- },
-
- add: function(aWindow, aArea, aContainer) {
- if (CustomizableUI.getAreaType(aArea) != "toolbar") {
- return;
- }
-
- gManagers.set(aContainer, new AreaPositionManager(aContainer));
- },
-
- remove: function(aWindow, aArea, aContainer) {
- if (CustomizableUI.getAreaType(aArea) != "toolbar") {
- return;
- }
-
- gManagers.delete(aContainer);
- },
-
- stop: function() {
- gManagers = new WeakMap();
- },
-
- getManagerForArea: function(aArea) {
- return gManagers.get(aArea);
- }
-};
-
-Object.freeze(DragPositionManager);
diff --git a/browser/components/customizableui/PanelWideWidgetTracker.jsm b/browser/components/customizableui/PanelWideWidgetTracker.jsm
deleted file mode 100644
index 768cebbca..000000000
--- a/browser/components/customizableui/PanelWideWidgetTracker.jsm
+++ /dev/null
@@ -1,172 +0,0 @@
-/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
- "resource:///modules/CustomizableUI.jsm");
-
-var gPanel = CustomizableUI.AREA_PANEL;
-// We keep track of the widget placements for the panel locally:
-var gPanelPlacements = [];
-
-// All the wide widgets we know of:
-var gWideWidgets = new Set();
-// All the widgets we know of:
-var gSeenWidgets = new Set();
-
-var PanelWideWidgetTracker = {
- // Listeners used to validate panel contents whenever they change:
- onWidgetAdded: function(aWidgetId, aArea, aPosition) {
- if (aArea == gPanel) {
- gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
- let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
- this.adjustWidgets(aWidgetId, moveForward);
- }
- },
- onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
- if (aArea == gPanel) {
- gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
- let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
- this.adjustWidgets(aWidgetId, moveForward);
- }
- },
- onWidgetRemoved: function(aWidgetId, aPrevArea) {
- if (aPrevArea == gPanel) {
- gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
- this.adjustWidgets(aWidgetId, false);
- }
- },
- onWidgetReset: function(aWidgetId) {
- gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
- },
- // Listener to keep abreast of any new nodes. We use the DOM one because
- // we need access to the actual node's classlist, so we can't use the ones above.
- // Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
- onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
- if (!gSeenWidgets.has(aNode.id)) {
- if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
- gWideWidgets.add(aNode.id);
- }
- gSeenWidgets.add(aNode.id);
- }
- },
- // When widgets get destroyed, we remove them from our sets of stuff we care about:
- onWidgetDestroyed: function(aWidgetId) {
- gSeenWidgets.delete(aWidgetId);
- gWideWidgets.delete(aWidgetId);
- },
- shouldMoveForward: function(aWidgetId, aPosition) {
- let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
- let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
- // We might now think we can move forward, but for that we need at least 2 more small
- // widgets to be present:
- if (rv) {
- let furtherWidgets = gPanelPlacements.slice(aPosition + 2);
- let realWidgets = 0;
- if (furtherWidgets.length >= 2) {
- while (furtherWidgets.length && realWidgets < 2) {
- let w = furtherWidgets.shift();
- if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) {
- realWidgets++;
- }
- }
- }
- if (realWidgets < 2) {
- rv = false;
- }
- }
- return rv;
- },
- adjustWidgets: function(aWidgetId, aMoveForwards) {
- if (this.adjusting) {
- return;
- }
- this.adjusting = true;
- let widgetsAffected = gPanelPlacements.filter((w) => gWideWidgets.has(w));
- // If we're moving the wide widgets forwards (down/to the right in the panel)
- // we want to start with the last widgets. Otherwise we move widgets over other wide
- // widgets, which might mess up their order. Likewise, if moving backwards we should start with
- // the first widget and work our way down/right from there.
- let compareFn = aMoveForwards ? ((a, b) => a < b) : ((a, b) => a > b);
- widgetsAffected.sort((a, b) => compareFn(gPanelPlacements.indexOf(a),
- gPanelPlacements.indexOf(b)));
- for (let widget of widgetsAffected) {
- this.adjustPosition(widget, aMoveForwards);
- }
- this.adjusting = false;
- },
- // This function is called whenever an item gets moved in the menu panel. It
- // adjusts the position of widgets within the panel to prevent "gaps" between
- // wide widgets that could be filled up with single column widgets
- adjustPosition: function(aWidgetId, aMoveForwards) {
- // Make sure that there are n % columns = 0 narrow buttons before the widget.
- let placementIndex = gPanelPlacements.indexOf(aWidgetId);
- let prevSiblingCount = 0;
- let fixedPos = null;
- while (placementIndex--) {
- let thisWidgetId = gPanelPlacements[placementIndex];
- if (gWideWidgets.has(thisWidgetId)) {
- continue;
- }
- let widgetStatus = this.checkWidgetStatus(thisWidgetId);
- if (!widgetStatus) {
- continue;
- }
- if (widgetStatus == "public-only") {
- fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex);
- prevSiblingCount = 0;
- } else {
- prevSiblingCount++;
- }
- }
-
- if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
- let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
- let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
- if (aMoveForwards && fixedPos == null) {
- // +1 because otherwise we'd count ourselves:
- desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
- }
- desiredPos += desiredChange;
- CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
- }
- },
-
- /*
- * Check whether a widget id is actually known anywhere.
- * @returns false if the widget doesn't exist,
- * "public-only" if it's not shown in private windows
- * "real" if it does exist and is shown even in private windows
- */
- checkWidgetStatus: function(aWidgetId) {
- let widgetWrapper = CustomizableUI.getWidget(aWidgetId);
- // This widget might not actually exist:
- if (!widgetWrapper) {
- return false;
- }
- // This widget might still not actually exist:
- if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
- widgetWrapper.instances.length == 0) {
- return false;
- }
-
- // Or it might only be there some of the time:
- if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
- widgetWrapper.showInPrivateBrowsing === false) {
- return "public-only";
- }
- return "real";
- },
-
- init: function() {
- // Initialize our local placements copy and register the listener
- gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
- CustomizableUI.addListener(this);
- },
-};
diff --git a/browser/components/customizableui/ScrollbarSampler.jsm b/browser/components/customizableui/ScrollbarSampler.jsm
deleted file mode 100644
index 44736e4c4..000000000
--- a/browser/components/customizableui/ScrollbarSampler.jsm
+++ /dev/null
@@ -1,65 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["ScrollbarSampler"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-var gSystemScrollbarWidth = null;
-
-this.ScrollbarSampler = {
- getSystemScrollbarWidth: function() {
- if (gSystemScrollbarWidth !== null) {
- return Promise.resolve(gSystemScrollbarWidth);
- }
-
- return new Promise(resolve => {
- this._sampleSystemScrollbarWidth().then(function(systemScrollbarWidth) {
- gSystemScrollbarWidth = systemScrollbarWidth;
- resolve(gSystemScrollbarWidth);
- });
- });
- },
-
- resetSystemScrollbarWidth: function() {
- gSystemScrollbarWidth = null;
- },
-
- _sampleSystemScrollbarWidth: function() {
- let hwin = Services.appShell.hiddenDOMWindow;
- let hdoc = hwin.document.documentElement;
- let iframe = hwin.document.createElementNS("http://www.w3.org/1999/xhtml",
- "html:iframe");
- iframe.setAttribute("srcdoc", '<body style="overflow-y: scroll"></body>');
- hdoc.appendChild(iframe);
-
- let cwindow = iframe.contentWindow;
- let utils = cwindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
-
- return new Promise(resolve => {
- cwindow.addEventListener("load", function onLoad(aEvent) {
- cwindow.removeEventListener("load", onLoad);
- let sbWidth = {};
- try {
- utils.getScrollbarSize(true, sbWidth, {});
- } catch (e) {
- Cu.reportError("Could not sample scrollbar size: " + e + " -- " +
- e.stack);
- sbWidth.value = 0;
- }
- // Minimum width of 10 so that we have enough padding:
- sbWidth.value = Math.max(sbWidth.value, 10);
- resolve(sbWidth.value);
- iframe.remove();
- });
- });
- }
-};
-Object.freeze(this.ScrollbarSampler);
diff --git a/browser/components/customizableui/content/customizeMode.inc.xul b/browser/components/customizableui/content/customizeMode.inc.xul
deleted file mode 100644
index b665630a2..000000000
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ /dev/null
@@ -1,82 +0,0 @@
-<!-- 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/. -->
-
-<hbox id="customization-container" flex="1" hidden="true">
- <vbox flex="1" id="customization-palette-container">
- <label id="customization-header">
- &customizeMode.menuAndToolbars.header2;
- </label>
- <hbox id="customization-empty" hidden="true">
- <label>&customizeMode.menuAndToolbars.empty;</label>
- <label onclick="BrowserOpenAddonsMgr('addons://discover/');"
- onkeypress="BrowserOpenAddonsMgr('addons://discover/');"
- id="customization-more-tools"
- class="text-link">
- &customizeMode.menuAndToolbars.emptyLink;
- </label>
- </hbox>
- <vbox id="customization-palette" class="customization-palette"/>
- <spacer id="customization-spacer"/>
- <hbox id="customization-footer">
-#ifdef CAN_DRAW_IN_TITLEBAR
- <button id="customization-titlebar-visibility-button" class="customizationmode-button"
- label="&customizeMode.titlebar;" type="checkbox"
-#NB: because oncommand fires after click, by the time we've fired, the checkbox binding
-# will already have switched the button's state, so this is correct:
- oncommand="gCustomizeMode.toggleTitlebar(this.hasAttribute('checked'))"/>
-#endif
- <button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
- <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
- </button>
- <button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
- <panel type="arrow" id="customization-lwtheme-menu"
- onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);"
- position="topcenter bottomleft"
- flip="none"
- role="menu">
- <label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
- <label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
- <hbox id="customization-lwtheme-menu-footer">
- <toolbarbutton class="customization-lwtheme-menu-footeritem"
- label="&customizeMode.lwthemes.menuManage;"
- accesskey="&customizeMode.lwthemes.menuManage.accessKey;"
- tabindex="0"
- oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
- <toolbarbutton class="customization-lwtheme-menu-footeritem"
- label="&customizeMode.lwthemes.menuGetMore;"
- accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
- tabindex="0"
- oncommand="gCustomizeMode.getMoreThemes(event);"/>
- </hbox>
- </panel>
- </button>
-
- <spacer id="customization-footer-spacer"/>
- <button id="customization-undo-reset-button"
- class="customizationmode-button"
- hidden="true"
- oncommand="gCustomizeMode.undoReset();"
- label="&undoCmd.label;"/>
- <button id="customization-reset-button"
- oncommand="gCustomizeMode.reset();"
- label="&customizeMode.restoreDefaults;"
- class="customizationmode-button"/>
- </hbox>
- </vbox>
- <vbox id="customization-panel-container">
- <vbox id="customization-panelWrapper">
- <html:style html:type="text/html" scoped="scoped">
- @import url(chrome://global/skin/popup.css);
- </html:style>
- <box class="panel-arrowbox">
- <box flex="1"/>
- <image class="panel-arrow" side="top"/>
- </box>
- <box class="panel-arrowcontent" side="top" flex="1">
- <hbox id="customization-panelHolder"/>
- <box class="panel-inner-arrowcontentfooter" hidden="true"/>
- </box>
- </vbox>
- </vbox>
-</hbox>
diff --git a/browser/components/customizableui/content/jar.mn b/browser/components/customizableui/content/jar.mn
deleted file mode 100644
index 05c0112cd..000000000
--- a/browser/components/customizableui/content/jar.mn
+++ /dev/null
@@ -1,10 +0,0 @@
-# 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/.
-
-browser.jar:
- content/browser/customizableui/panelUI.css
- content/browser/customizableui/panelUI.js
- content/browser/customizableui/panelUI.xml
- content/browser/customizableui/toolbar.xml
-
diff --git a/browser/components/customizableui/content/moz.build b/browser/components/customizableui/content/moz.build
deleted file mode 100644
index eb4454d28..000000000
--- a/browser/components/customizableui/content/moz.build
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/components/customizableui/content/panelUI.css b/browser/components/customizableui/content/panelUI.css
deleted file mode 100644
index ba44636f1..000000000
--- a/browser/components/customizableui/content/panelUI.css
+++ /dev/null
@@ -1,31 +0,0 @@
-/* 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/. */
-
-.panel-viewstack[viewtype="main"] > .panel-clickcapturer {
- pointer-events: none;
-}
-
-.panel-mainview,
-.panel-viewcontainer,
-.panel-viewstack {
- overflow: hidden;
-}
-
-.panel-viewstack {
- position: relative;
-}
-
-.panel-subviews {
- -moz-stack-sizing: ignore;
- transform: translateX(0);
- overflow-y: auto;
-}
-
-.panel-subviews[panelopen] {
- transition: transform var(--panelui-subview-transition-duration);
-}
-
-.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
- transition: height var(--panelui-subview-transition-duration);
-}
diff --git a/browser/components/customizableui/content/panelUI.inc.xul b/browser/components/customizableui/content/panelUI.inc.xul
deleted file mode 100644
index e389b14bf..000000000
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ /dev/null
@@ -1,386 +0,0 @@
-<!-- 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/. -->
-
-<panel id="PanelUI-popup"
- role="group"
- type="arrow"
- hidden="true"
- flip="slide"
- position="bottomcenter topright"
- noautofocus="true">
- <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
- <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
- <vbox id="PanelUI-contents-scroller">
- <vbox id="PanelUI-contents" class="panelUI-grid"/>
- </vbox>
-
- <footer id="PanelUI-footer">
- <toolbarbutton id="PanelUI-update-status"
- oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
- wrap="true"
- hidden="true"/>
- <hbox id="PanelUI-footer-fxa">
- <hbox id="PanelUI-fxa-status"
- defaultlabel="&fxaSignIn.label;"
- signedinTooltiptext="&fxaSignedIn.tooltip;"
- tooltiptext="&fxaSignedIn.tooltip;"
- errorlabel="&fxaSignInError.label;"
- unverifiedlabel="&fxaUnverified.label;"
- onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
- <image id="PanelUI-fxa-avatar"/>
- <toolbarbutton id="PanelUI-fxa-label"
- fxabrandname="&syncBrand.fxAccount.label;"/>
- </hbox>
- <toolbarseparator/>
- <toolbarbutton id="PanelUI-fxa-icon"
- oncommand="gSyncUI.doSync();"
- closemenu="none">
- <observes element="sync-status" attribute="syncstatus"/>
- <observes element="sync-status" attribute="tooltiptext"/>
- </toolbarbutton>
- </hbox>
-
- <hbox id="PanelUI-footer-inner">
- <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
- exitLabel="&appMenuCustomizeExit.label;"
- tooltiptext="&appMenuCustomize.tooltip;"
- exitTooltiptext="&appMenuCustomizeExit.tooltip;"
- closemenu="none"
- oncommand="gCustomizeMode.toggle();"/>
- <toolbarseparator/>
- <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
- closemenu="none"
- tooltiptext="&appMenuHelp.tooltip;"
- oncommand="PanelUI.showHelpView(this);"/>
- <toolbarseparator/>
- <toolbarbutton id="PanelUI-quit"
-#ifdef XP_WIN
- label="&quitApplicationCmdWin2.label;"
- tooltiptext="&quitApplicationCmdWin2.tooltip;"
-#else
-#ifdef XP_MACOSX
- label="&quitApplicationCmdMac2.label;"
-#else
- label="&quitApplicationCmd.label;"
-#endif
-#endif
- command="cmd_quitApplication"/>
- </hbox>
- </footer>
- </panelview>
-
- <panelview id="PanelUI-history" flex="1">
- <label value="&appMenuHistory.label;" class="panel-subview-header"/>
- <vbox class="panel-subview-body">
- <toolbarbutton id="appMenuViewHistorySidebar"
- label="&appMenuHistory.viewSidebar.label;"
- type="checkbox"
- class="subviewbutton"
- key="key_gotoHistory"
- oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
- <observes element="viewHistorySidebar" attribute="checked"/>
- </toolbarbutton>
- <toolbarbutton id="appMenuClearRecentHistory"
- label="&appMenuHistory.clearRecent.label;"
- class="subviewbutton"
- command="Tools:Sanitize"/>
- <toolbarbutton id="appMenuRestoreLastSession"
- label="&appMenuHistory.restoreSession.label;"
- class="subviewbutton"
- command="Browser:RestoreLastSession"/>
- <menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
- <vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
- <menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
- <vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
- <menuseparator id="PanelUI-historyItems-separator"/>
- <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
- </vbox>
- <toolbarbutton id="PanelUI-historyMore"
- class="panel-subview-footer subviewbutton"
- label="&appMenuHistory.showAll.label;"
- oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
- </panelview>
-
- <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
- <label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
- <vbox class="panel-subview-body">
- <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
- <!-- When Sync is ready to sync -->
- <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
- <vbox id="PanelUI-remotetabs-buttons">
- <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
- class="subviewbutton"
- observes="viewTabsSidebar"
- label="&appMenuRemoteTabs.sidebar.label;"/>
- <toolbarbutton id="PanelUI-remotetabs-syncnow"
- observes="sync-status"
- class="subviewbutton"
- oncommand="gSyncUI.doSync();"
- closemenu="none"/>
- <menuseparator id="PanelUI-remotetabs-separator"/>
- </vbox>
- <deck id="PanelUI-remotetabs-deck">
- <!-- Sync is ready to Sync and the "tabs" engine is enabled -->
- <vbox id="PanelUI-remotetabs-tabspane">
- <vbox id="PanelUI-remotetabs-tabslist"
- notabsforclientlabel="&appMenuRemoteTabs.notabs.label;"
- />
- </vbox>
- <!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
- <hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
- <vbox class="PanelUI-remotetabs-instruction-box">
- <hbox pack="center">
- <image class="fxaSyncIllustration" alt=""/>
- </hbox>
- <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
- <hbox pack="center">
- <toolbarbutton class="PanelUI-remotetabs-prefs-button"
- label="&appMenuRemoteTabs.openprefs.label;"
- oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
- </hbox>
- </vbox>
- </hbox>
- <!-- Sync is ready to Sync but we are still fetching the tabs to show -->
- <vbox id="PanelUI-remotetabs-fetching">
- <!-- Show intentionally blank panel, see bug 1239845 -->
- </vbox>
- <!-- Sync has only 1 (ie, this) device connected -->
- <hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
- <vbox class="PanelUI-remotetabs-instruction-box">
- <hbox pack="center">
- <image class="fxaSyncIllustration" alt=""/>
- </hbox>
- <label class="PanelUI-remotetabs-instruction-title">&appMenuRemoteTabs.noclients.title;</label>
- <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
- <!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
- <label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
- </vbox>
- </hbox>
- </deck>
- </vbox>
- <!-- a box to ensure contained boxes are centered horizonally -->
- <hbox pack="center" flex="1">
- <!-- When Sync is not configured -->
- <vbox id="PanelUI-remotetabs-setupsync"
- flex="1"
- align="center"
- class="PanelUI-remotetabs-instruction-box"
- observes="sync-setup-state">
- <image class="fxaSyncIllustration" alt=""/>
- <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
- <toolbarbutton class="PanelUI-remotetabs-prefs-button"
- label="&appMenuRemoteTabs.signin.label;"
- oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
- </vbox>
- <!-- When Sync needs re-authentication. This uses the exact same messaging
- as "Sync is not configured" but remains a separate box so we get
- the goodness of observing broadcasters to manage the hidden states -->
- <vbox id="PanelUI-remotetabs-reauthsync"
- flex="1"
- align="center"
- class="PanelUI-remotetabs-instruction-box"
- observes="sync-reauth-state">
- <image class="fxaSyncIllustration" alt=""/>
- <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
- <toolbarbutton class="PanelUI-remotetabs-prefs-button"
- label="&appMenuRemoteTabs.signin.label;"
- oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
- </vbox>
- </hbox>
- </vbox>
- </panelview>
-
- <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
- <label value="&bookmarksMenu.label;" class="panel-subview-header"/>
- <vbox class="panel-subview-body">
- <toolbarbutton id="panelMenuBookmarkThisPage"
- class="subviewbutton"
- observes="bookmarkThisPageBroadcaster"
- command="Browser:AddBookmarkAs"
- onclick="PanelUI.hide();"/>
- <toolbarseparator/>
- <toolbarbutton id="panelMenu_viewBookmarksSidebar"
- label="&viewBookmarksSidebar2.label;"
- class="subviewbutton"
- key="viewBookmarksSidebarKb"
- oncommand="SidebarUI.toggle('viewBookmarksSidebar'); PanelUI.hide();">
- <observes element="viewBookmarksSidebar" attribute="checked"/>
- </toolbarbutton>
- <toolbarbutton id="panelMenu_viewBookmarksToolbar"
- label="&viewBookmarksToolbar.label;"
- type="checkbox"
- toolbarId="PersonalToolbar"
- class="subviewbutton"
- oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
- <toolbarseparator/>
- <toolbarbutton id="panelMenu_bookmarksToolbar"
- label="&personalbarCmd.label;"
- class="subviewbutton cui-withicon"
- oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
- <toolbarbutton id="panelMenu_unsortedBookmarks"
- label="&otherBookmarksCmd.label;"
- class="subviewbutton cui-withicon"
- oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
- <toolbarseparator class="small-separator"/>
- <toolbaritem id="panelMenu_bookmarksMenu"
- orient="vertical"
- smoothscroll="false"
- onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
- oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
- flatList="true"
- tooltip="bhTooltip">
- <!-- bookmarks menu items will go here -->
- </toolbaritem>
- </vbox>
- <toolbarbutton id="panelMenu_showAllBookmarks"
- label="&showAllBookmarks2.label;"
- class="subviewbutton panel-subview-footer"
- command="Browser:ShowAllBookmarks"
- onclick="PanelUI.hide();"/>
- </panelview>
-
- <panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
- <label value="&feedsMenu2.label;" class="panel-subview-header"/>
- </panelview>
-
- <panelview id="PanelUI-containers" flex="1">
- <label value="&containersMenu.label;" class="panel-subview-header"/>
- <vbox id="PanelUI-containersItems"/>
- </panelview>
-
- <panelview id="PanelUI-helpView" flex="1" class="PanelUI-subView">
- <label value="&helpMenu.label;" class="panel-subview-header"/>
- <vbox id="PanelUI-helpItems" class="panel-subview-body"/>
- </panelview>
-
- <panelview id="PanelUI-developer" flex="1">
- <label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
- <vbox id="PanelUI-developerItems" class="panel-subview-body"/>
- </panelview>
-
- <panelview id="PanelUI-sidebar" flex="1">
- <label value="&appMenuSidebars.label;" class="panel-subview-header"/>
- <vbox id="PanelUI-sidebarItems" class="panel-subview-body"/>
- </panelview>
-
- <panelview id="PanelUI-characterEncodingView" flex="1">
- <label value="&charsetMenu2.label;" class="panel-subview-header"/>
- <vbox class="panel-subview-body">
- <vbox id="PanelUI-characterEncodingView-pinned"
- class="PanelUI-characterEncodingView-list"/>
- <toolbarseparator/>
- <vbox id="PanelUI-characterEncodingView-charsets"
- class="PanelUI-characterEncodingView-list"/>
- <toolbarseparator/>
- <vbox>
- <label id="PanelUI-characterEncodingView-autodetect-label"/>
- <vbox id="PanelUI-characterEncodingView-autodetect"
- class="PanelUI-characterEncodingView-list"/>
- </vbox>
- </vbox>
- </panelview>
-
- <panelview id="PanelUI-panicView" flex="1">
- <vbox class="panel-subview-body">
- <hbox id="PanelUI-panic-timeframe">
- <image id="PanelUI-panic-timeframe-icon" alt=""/>
- <vbox flex="1">
- <hbox id="PanelUI-panic-header">
- <image id="PanelUI-panic-timeframe-icon-small" alt=""/>
- <description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
- </hbox>
- <radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
- <radio id="PanelUI-panic-5min" label="&panicButton.view.5min;" selected="true"
- value="5" class="subviewradio"/>
- <radio id="PanelUI-panic-2hr" label="&panicButton.view.2hr;"
- value="2" class="subviewradio"/>
- <radio id="PanelUI-panic-day" label="&panicButton.view.day;"
- value="6" class="subviewradio"/>
- </radiogroup>
- </vbox>
- </hbox>
- <vbox id="PanelUI-panic-explanations">
- <label id="PanelUI-panic-actionlist-main-label">&panicButton.view.mainActionDesc;</label>
-
- <label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
- <label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
- <label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
- <label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
-
- <label id="PanelUI-panic-warning">&panicButton.view.undoWarning;</label>
- </vbox>
- <button id="PanelUI-panic-view-button"
- label="&panicButton.view.forgetButton;"/>
- </vbox>
- </panelview>
-
- </panelmultiview>
- <!-- These menupopups are located here to prevent flickering,
- see bug 492960 comment 20. -->
- <menupopup id="customizationPanelItemContextMenu">
- <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
- closemenu="single"
- class="customize-context-moveToToolbar"
- accesskey="&customizeMenu.moveToToolbar.accesskey;"
- label="&customizeMenu.moveToToolbar.label;"/>
- <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
- closemenu="single"
- class="customize-context-removeFromPanel"
- accesskey="&customizeMenu.removeFromMenu.accesskey;"
- label="&customizeMenu.removeFromMenu.label;"/>
- <menuseparator/>
- <menuitem command="cmd_CustomizeToolbars"
- class="viewCustomizeToolbar"
- accesskey="&viewCustomizeToolbar.accesskey;"
- label="&viewCustomizeToolbar.label;"/>
- </menupopup>
-
- <menupopup id="customizationPaletteItemContextMenu">
- <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
- class="customize-context-addToToolbar"
- accesskey="&customizeMenu.addToToolbar.accesskey;"
- label="&customizeMenu.addToToolbar.label;"/>
- <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
- class="customize-context-addToPanel"
- accesskey="&customizeMenu.addToPanel.accesskey;"
- label="&customizeMenu.addToPanel.label;"/>
- </menupopup>
-
- <menupopup id="customizationPanelContextMenu">
- <menuitem command="cmd_CustomizeToolbars"
- accesskey="&customizeMenu.addMoreItems.accesskey;"
- label="&customizeMenu.addMoreItems.label;"/>
- </menupopup>
-</panel>
-
-<panel id="widget-overflow"
- role="group"
- type="arrow"
- noautofocus="true"
- context="toolbar-context-menu"
- position="bottomcenter topright"
- hidden="true">
- <vbox id="widget-overflow-scroller">
- <vbox id="widget-overflow-list" class="widget-overflow-list"
- overflowfortoolbar="nav-bar"/>
- </vbox>
-</panel>
-
-<panel id="panic-button-success-notification"
- type="arrow"
- position="bottomcenter topright"
- hidden="true"
- role="alert"
- orient="vertical">
- <hbox id="panic-button-success-header">
- <image id="panic-button-success-icon" alt=""/>
- <vbox>
- <description>&panicButton.thankyou.msg1;</description>
- <description>&panicButton.thankyou.msg2;</description>
- </vbox>
- </hbox>
- <button label="&panicButton.thankyou.buttonlabel;"
- id="panic-button-success-closebutton"
- oncommand="PanicButtonNotifier.close()"/>
-</panel>
diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js
deleted file mode 100644
index 66fa0c184..000000000
--- a/browser/components/customizableui/content/panelUI.js
+++ /dev/null
@@ -1,558 +0,0 @@
-/* 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/. */
-
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
- "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
- "resource:///modules/ScrollbarSampler.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
- "resource://gre/modules/ShortcutUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-
-/**
- * Maintains the state and dispatches events for the main menu panel.
- */
-
-const PanelUI = {
- /** Panel events that we listen for. **/
- get kEvents() {
- return ["popupshowing", "popupshown", "popuphiding", "popuphidden"];
- },
- /**
- * Used for lazily getting and memoizing elements from the document. Lazy
- * getters are set in init, and memoizing happens after the first retrieval.
- */
- get kElements() {
- return {
- contents: "PanelUI-contents",
- mainView: "PanelUI-mainView",
- multiView: "PanelUI-multiView",
- helpView: "PanelUI-helpView",
- menuButton: "PanelUI-menu-button",
- panel: "PanelUI-popup",
- scroller: "PanelUI-contents-scroller"
- };
- },
-
- _initialized: false,
- init: function() {
- for (let [k, v] of Object.entries(this.kElements)) {
- // Need to do fresh let-bindings per iteration
- let getKey = k;
- let id = v;
- this.__defineGetter__(getKey, function() {
- delete this[getKey];
- return this[getKey] = document.getElementById(id);
- });
- }
-
- this.menuButton.addEventListener("mousedown", this);
- this.menuButton.addEventListener("keypress", this);
- this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
- window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
- CustomizableUI.addListener(this);
- this._initialized = true;
- },
-
- _eventListenersAdded: false,
- _ensureEventListenersAdded: function() {
- if (this._eventListenersAdded)
- return;
- this._addEventListeners();
- },
-
- _addEventListeners: function() {
- for (let event of this.kEvents) {
- this.panel.addEventListener(event, this);
- }
-
- this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
- this._eventListenersAdded = true;
- },
-
- uninit: function() {
- for (let event of this.kEvents) {
- this.panel.removeEventListener(event, this);
- }
- this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
- this.menuButton.removeEventListener("mousedown", this);
- this.menuButton.removeEventListener("keypress", this);
- window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
- CustomizableUI.removeListener(this);
- this._overlayScrollListenerBoundFn = null;
- },
-
- /**
- * Customize mode extracts the mainView and puts it somewhere else while the
- * user customizes. Upon completion, this function can be called to put the
- * panel back to where it belongs in normal browsing mode.
- *
- * @param aMainView
- * The mainView node to put back into place.
- */
- setMainView: function(aMainView) {
- this._ensureEventListenersAdded();
- this.multiView.setMainView(aMainView);
- },
-
- /**
- * Opens the menu panel if it's closed, or closes it if it's
- * open.
- *
- * @param aEvent the event that triggers the toggle.
- */
- toggle: function(aEvent) {
- // Don't show the panel if the window is in customization mode,
- // since this button doubles as an exit path for the user in this case.
- if (document.documentElement.hasAttribute("customizing")) {
- return;
- }
- this._ensureEventListenersAdded();
- if (this.panel.state == "open") {
- this.hide();
- } else if (this.panel.state == "closed") {
- this.show(aEvent);
- }
- },
-
- /**
- * Opens the menu panel. If the event target has a child with the
- * toolbarbutton-icon attribute, the panel will be anchored on that child.
- * Otherwise, the panel is anchored on the event target itself.
- *
- * @param aEvent the event (if any) that triggers showing the menu.
- */
- show: function(aEvent) {
- return new Promise(resolve => {
- this.ensureReady().then(() => {
- if (this.panel.state == "open" ||
- document.documentElement.hasAttribute("customizing")) {
- resolve();
- return;
- }
-
- let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
- if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
- updateEditUIVisibility();
- }
-
- let personalBookmarksPlacement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
- if (personalBookmarksPlacement &&
- personalBookmarksPlacement.area == CustomizableUI.AREA_PANEL) {
- PlacesToolbarHelper.customizeChange();
- }
-
- let anchor;
- if (!aEvent ||
- aEvent.type == "command") {
- anchor = this.menuButton;
- } else {
- anchor = aEvent.target;
- }
-
- this.panel.addEventListener("popupshown", function onPopupShown() {
- this.removeEventListener("popupshown", onPopupShown);
- resolve();
- });
-
- let iconAnchor =
- document.getAnonymousElementByAttribute(anchor, "class",
- "toolbarbutton-icon");
- this.panel.openPopup(iconAnchor || anchor);
- }, (reason) => {
- console.error("Error showing the PanelUI menu", reason);
- });
- });
- },
-
- /**
- * If the menu panel is being shown, hide it.
- */
- hide: function() {
- if (document.documentElement.hasAttribute("customizing")) {
- return;
- }
-
- this.panel.hidePopup();
- },
-
- handleEvent: function(aEvent) {
- // Ignore context menus and menu button menus showing and hiding:
- if (aEvent.type.startsWith("popup") &&
- aEvent.target != this.panel) {
- return;
- }
- switch (aEvent.type) {
- case "popupshowing":
- this._adjustLabelsForAutoHyphens();
- // Fall through
- case "popupshown":
- // Fall through
- case "popuphiding":
- // Fall through
- case "popuphidden":
- this._updatePanelButton(aEvent.target);
- break;
- case "mousedown":
- if (aEvent.button == 0)
- this.toggle(aEvent);
- break;
- case "keypress":
- this.toggle(aEvent);
- break;
- }
- },
-
- get isReady() {
- return !!this._isReady;
- },
-
- /**
- * Registering the menu panel is done lazily for performance reasons. This
- * method is exposed so that CustomizationMode can force panel-readyness in the
- * event that customization mode is started before the panel has been opened
- * by the user.
- *
- * @param aCustomizing (optional) set to true if this was called while entering
- * customization mode. If that's the case, we trust that customization
- * mode will handle calling beginBatchUpdate and endBatchUpdate.
- *
- * @return a Promise that resolves once the panel is ready to roll.
- */
- ensureReady: function(aCustomizing=false) {
- if (this._readyPromise) {
- return this._readyPromise;
- }
- this._readyPromise = Task.spawn(function*() {
- if (!this._initialized) {
- yield new Promise(resolve => {
- let delayedStartupObserver = (aSubject, aTopic, aData) => {
- if (aSubject == window) {
- Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
- resolve();
- }
- };
- Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
- });
- }
-
- this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
- getLocale());
- if (!this._scrollWidth) {
- // In order to properly center the contents of the panel, while ensuring
- // that we have enough space on either side to show a scrollbar, we have to
- // do a bit of hackery. In particular, we calculate a new width for the
- // scroller, based on the system scrollbar width.
- this._scrollWidth =
- (yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
- let cstyle = window.getComputedStyle(this.scroller);
- let widthStr = cstyle.width;
- // Get the calculated padding on the left and right sides of
- // the scroller too. We'll use that in our final calculation so
- // that if a scrollbar appears, we don't have the contents right
- // up against the edge of the scroller.
- let paddingLeft = cstyle.paddingLeft;
- let paddingRight = cstyle.paddingRight;
- let calcStr = [widthStr, this._scrollWidth,
- paddingLeft, paddingRight].join(" + ");
- this.scroller.style.width = "calc(" + calcStr + ")";
- }
-
- if (aCustomizing) {
- CustomizableUI.registerMenuPanel(this.contents);
- } else {
- this.beginBatchUpdate();
- try {
- CustomizableUI.registerMenuPanel(this.contents);
- } finally {
- this.endBatchUpdate();
- }
- }
- this._updateQuitTooltip();
- this.panel.hidden = false;
- this._isReady = true;
- }.bind(this)).then(null, Cu.reportError);
-
- return this._readyPromise;
- },
-
- /**
- * Switch the panel to the main view if it's not already
- * in that view.
- */
- showMainView: function() {
- this._ensureEventListenersAdded();
- this.multiView.showMainView();
- },
-
- /**
- * Switch the panel to the help view if it's not already
- * in that view.
- */
- showHelpView: function(aAnchor) {
- this._ensureEventListenersAdded();
- this.multiView.showSubView("PanelUI-helpView", aAnchor);
- },
-
- /**
- * Shows a subview in the panel with a given ID.
- *
- * @param aViewId the ID of the subview to show.
- * @param aAnchor the element that spawned the subview.
- * @param aPlacementArea the CustomizableUI area that aAnchor is in.
- */
- showSubView: Task.async(function*(aViewId, aAnchor, aPlacementArea) {
- this._ensureEventListenersAdded();
- let viewNode = document.getElementById(aViewId);
- if (!viewNode) {
- Cu.reportError("Could not show panel subview with id: " + aViewId);
- return;
- }
-
- if (!aAnchor) {
- Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
- return;
- }
-
- if (aPlacementArea == CustomizableUI.AREA_PANEL) {
- this.multiView.showSubView(aViewId, aAnchor);
- } else if (!aAnchor.open) {
- aAnchor.open = true;
-
- let tempPanel = document.createElement("panel");
- tempPanel.setAttribute("type", "arrow");
- tempPanel.setAttribute("id", "customizationui-widget-panel");
- tempPanel.setAttribute("class", "cui-widget-panel");
- tempPanel.setAttribute("viewId", aViewId);
- if (aAnchor.getAttribute("tabspecific")) {
- tempPanel.setAttribute("tabspecific", true);
- }
- if (this._disableAnimations) {
- tempPanel.setAttribute("animate", "false");
- }
- tempPanel.setAttribute("context", "");
- document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
- // If the view has a footer, set a convenience class on the panel.
- tempPanel.classList.toggle("cui-widget-panelWithFooter",
- viewNode.querySelector(".panel-subview-footer"));
-
- let multiView = document.createElement("panelmultiview");
- multiView.setAttribute("id", "customizationui-widget-multiview");
- multiView.setAttribute("nosubviews", "true");
- tempPanel.appendChild(multiView);
- multiView.setAttribute("mainViewIsSubView", "true");
- multiView.setMainView(viewNode);
- viewNode.classList.add("cui-widget-panelview");
-
- let viewShown = false;
- let panelRemover = () => {
- viewNode.classList.remove("cui-widget-panelview");
- if (viewShown) {
- CustomizableUI.removePanelCloseListeners(tempPanel);
- tempPanel.removeEventListener("popuphidden", panelRemover);
-
- let evt = new CustomEvent("ViewHiding", {detail: viewNode});
- viewNode.dispatchEvent(evt);
- }
- aAnchor.open = false;
-
- this.multiView.appendChild(viewNode);
- tempPanel.remove();
- };
-
- // Emit the ViewShowing event so that the widget definition has a chance
- // to lazily populate the subview with things.
- let detail = {
- blockers: new Set(),
- addBlocker(aPromise) {
- this.blockers.add(aPromise);
- },
- };
-
- let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
- viewNode.dispatchEvent(evt);
-
- let cancel = evt.defaultPrevented;
- if (detail.blockers.size) {
- try {
- let results = yield Promise.all(detail.blockers);
- cancel = cancel || results.some(val => val === false);
- } catch (e) {
- Components.utils.reportError(e);
- cancel = true;
- }
- }
-
- if (cancel) {
- panelRemover();
- return;
- }
-
- viewShown = true;
- CustomizableUI.addPanelCloseListeners(tempPanel);
- tempPanel.addEventListener("popuphidden", panelRemover);
-
- let iconAnchor =
- document.getAnonymousElementByAttribute(aAnchor, "class",
- "toolbarbutton-icon");
-
- if (iconAnchor && aAnchor.id) {
- iconAnchor.setAttribute("consumeanchor", aAnchor.id);
- }
- tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
- }
- }),
-
- /**
- * NB: The enable- and disableSingleSubviewPanelAnimations methods only
- * affect the hiding/showing animations of single-subview panels (tempPanel
- * in the showSubView method).
- */
- disableSingleSubviewPanelAnimations: function() {
- this._disableAnimations = true;
- },
-
- enableSingleSubviewPanelAnimations: function() {
- this._disableAnimations = false;
- },
-
- onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
- if (aContainer != this.contents) {
- return;
- }
- if (aWasRemoval) {
- aNode.removeAttribute("auto-hyphens");
- }
- },
-
- onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
- if (aContainer != this.contents) {
- return;
- }
- if (!aIsRemoval &&
- (this.panel.state == "open" ||
- document.documentElement.hasAttribute("customizing"))) {
- this._adjustLabelsForAutoHyphens(aNode);
- }
- },
-
- /**
- * Signal that we're about to make a lot of changes to the contents of the
- * panels all at once. For performance, we ignore the mutations.
- */
- beginBatchUpdate: function() {
- this._ensureEventListenersAdded();
- this.multiView.ignoreMutations = true;
- },
-
- /**
- * Signal that we're done making bulk changes to the panel. We now pay
- * attention to mutations. This automatically synchronizes the multiview
- * container with whichever view is displayed if the panel is open.
- */
- endBatchUpdate: function(aReason) {
- this._ensureEventListenersAdded();
- this.multiView.ignoreMutations = false;
- },
-
- _adjustLabelsForAutoHyphens: function(aNode) {
- let toolbarButtons = aNode ? [aNode] :
- this.contents.querySelectorAll(".toolbarbutton-1");
- for (let node of toolbarButtons) {
- let label = node.getAttribute("label");
- if (!label) {
- continue;
- }
- if (label.includes("\u00ad")) {
- node.setAttribute("auto-hyphens", "off");
- } else {
- node.removeAttribute("auto-hyphens");
- }
- }
- },
-
- /**
- * Sets the anchor node into the open or closed state, depending
- * on the state of the panel.
- */
- _updatePanelButton: function() {
- this.menuButton.open = this.panel.state == "open" ||
- this.panel.state == "showing";
- },
-
- _onHelpViewShow: function(aEvent) {
- // Call global menu setup function
- buildHelpMenu();
-
- let helpMenu = document.getElementById("menu_HelpPopup");
- let items = this.getElementsByTagName("vbox")[0];
- let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
- let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
- // Remove all buttons from the view
- while (items.firstChild) {
- items.removeChild(items.firstChild);
- }
-
- // Add the current set of menuitems of the Help menu to this view
- let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem"));
- let fragment = document.createDocumentFragment();
- for (let node of menuItems) {
- if (node.hidden)
- continue;
- let button = document.createElementNS(NSXUL, "toolbarbutton");
- // Copy specific attributes from a menuitem of the Help menu
- for (let attrName of attrs) {
- if (!node.hasAttribute(attrName))
- continue;
- button.setAttribute(attrName, node.getAttribute(attrName));
- }
- button.setAttribute("class", "subviewbutton");
- fragment.appendChild(button);
- }
- items.appendChild(fragment);
- },
-
- _updateQuitTooltip: function() {
- if (AppConstants.platform == "win") {
- return;
- }
-
- let tooltipId = AppConstants.platform == "macosx" ?
- "quit-button.tooltiptext.mac" :
- "quit-button.tooltiptext.linux2";
-
- let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
- let stringArgs = [brands.GetStringFromName("brandShortName")];
-
- let key = document.getElementById("key_quitApplication");
- stringArgs.push(ShortcutUtils.prettifyShortcut(key));
- let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
- let quitButton = document.getElementById("PanelUI-quit");
- quitButton.setAttribute("tooltiptext", tooltipString);
- },
-
- _overlayScrollListenerBoundFn: null,
- _overlayScrollListener: function(aMQL) {
- ScrollbarSampler.resetSystemScrollbarWidth();
- this._scrollWidth = null;
- },
-};
-
-XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
-
-/**
- * Gets the currently selected locale for display.
- * @return the selected locale or "en-US" if none is selected
- */
-function getLocale() {
- try {
- let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
- .getService(Ci.nsIXULChromeRegistry);
- return chromeRegistry.getSelectedLocale("browser");
- } catch (ex) {
- return "en-US";
- }
-}
diff --git a/browser/components/customizableui/content/panelUI.xml b/browser/components/customizableui/content/panelUI.xml
deleted file mode 100644
index 6893bd8ff..000000000
--- a/browser/components/customizableui/content/panelUI.xml
+++ /dev/null
@@ -1,509 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-
-<bindings id="browserPanelUIBindings"
- xmlns="http://www.mozilla.org/xbl"
- xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- xmlns:xbl="http://www.mozilla.org/xbl">
-
- <binding id="panelmultiview">
- <resources>
- <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
- </resources>
- <content>
- <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
- <xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
- <xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
-
- <!-- Used to capture click events over the PanelUI-mainView if we're in
- subview mode. That way, any click on the PanelUI-mainView causes us
- to revert to the mainView mode, whereupon PanelUI-click-capture then
- allows click events to go through it. -->
- <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
-
- <!-- We manually set display: none (via a CSS attribute selector) on the
- subviews that are not being displayed. We're using this over a deck
- because a deck assumes the size of its largest child, regardless of
- whether or not it is shown. That's not good for our case, since we
- want to allow each subview to be uniquely sized. -->
- <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
- <children includes="panelview"/>
- </xul:vbox>
- </xul:stack>
- </xul:box>
- </content>
- <implementation implements="nsIDOMEventListener">
- <field name="_clickCapturer" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
- </field>
- <field name="_viewContainer" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
- </field>
- <field name="_mainViewContainer" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
- </field>
- <field name="_subViews" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "subViews");
- </field>
- <field name="_viewStack" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
- </field>
- <field name="_panel" readonly="true">
- this.parentNode;
- </field>
-
- <field name="_currentSubView">null</field>
- <field name="_anchorElement">null</field>
- <field name="_mainViewHeight">0</field>
- <field name="_subViewObserver">null</field>
- <field name="__transitioning">false</field>
- <field name="_ignoreMutations">false</field>
-
- <property name="showingSubView" readonly="true"
- onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
- <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
- <property name="_mainView" readonly="true"
- onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
- <property name="showingSubViewAsMainView" readonly="true"
- onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
-
- <property name="ignoreMutations">
- <getter>
- return this._ignoreMutations;
- </getter>
- <setter><![CDATA[
- this._ignoreMutations = val;
- if (!val && this._panel.state == "open") {
- if (this.showingSubView) {
- this._syncContainerWithSubView();
- } else {
- this._syncContainerWithMainView();
- }
- }
- ]]></setter>
- </property>
-
- <property name="_transitioning">
- <getter>
- return this.__transitioning;
- </getter>
- <setter><![CDATA[
- this.__transitioning = val;
- if (val) {
- this.setAttribute("transitioning", "true");
- } else {
- this.removeAttribute("transitioning");
- }
- ]]></setter>
- </property>
- <constructor><![CDATA[
- this._clickCapturer.addEventListener("click", this);
- this._panel.addEventListener("popupshowing", this);
- this._panel.addEventListener("popupshown", this);
- this._panel.addEventListener("popuphidden", this);
- this._subViews.addEventListener("overflow", this);
- this._mainViewContainer.addEventListener("overflow", this);
-
- // Get a MutationObserver ready to react to subview size changes. We
- // only attach this MutationObserver when a subview is being displayed.
- this._subViewObserver =
- new MutationObserver(this._syncContainerWithSubView.bind(this));
- this._mainViewObserver =
- new MutationObserver(this._syncContainerWithMainView.bind(this));
-
- this._mainViewContainer.setAttribute("panelid",
- this._panel.id);
-
- if (this._mainView) {
- this.setMainView(this._mainView);
- }
- this.setAttribute("viewtype", "main");
- ]]></constructor>
-
- <destructor><![CDATA[
- if (this._mainView) {
- this._mainView.removeAttribute("mainview");
- }
- this._mainViewObserver.disconnect();
- this._subViewObserver.disconnect();
- this._panel.removeEventListener("popupshowing", this);
- this._panel.removeEventListener("popupshown", this);
- this._panel.removeEventListener("popuphidden", this);
- this._subViews.removeEventListener("overflow", this);
- this._mainViewContainer.removeEventListener("overflow", this);
- this._clickCapturer.removeEventListener("click", this);
- ]]></destructor>
-
- <method name="setMainView">
- <parameter name="aNewMainView"/>
- <body><![CDATA[
- if (this._mainView) {
- this._mainViewObserver.disconnect();
- this._subViews.appendChild(this._mainView);
- this._mainView.removeAttribute("mainview");
- }
- this._mainViewId = aNewMainView.id;
- aNewMainView.setAttribute("mainview", "true");
- this._mainViewContainer.appendChild(aNewMainView);
- ]]></body>
- </method>
-
- <method name="showMainView">
- <body><![CDATA[
- if (this.showingSubView) {
- let viewNode = this._currentSubView;
- let evt = document.createEvent("CustomEvent");
- evt.initCustomEvent("ViewHiding", true, true, viewNode);
- viewNode.dispatchEvent(evt);
-
- viewNode.removeAttribute("current");
- this._currentSubView = null;
-
- this._subViewObserver.disconnect();
-
- this._setViewContainerHeight(this._mainViewHeight);
-
- this.setAttribute("viewtype", "main");
- }
-
- this._shiftMainView();
- ]]></body>
- </method>
-
- <method name="showSubView">
- <parameter name="aViewId"/>
- <parameter name="aAnchor"/>
- <body><![CDATA[
- Task.spawn(function*() {
- let viewNode = this.querySelector("#" + aViewId);
- viewNode.setAttribute("current", true);
- // Emit the ViewShowing event so that the widget definition has a chance
- // to lazily populate the subview with things.
- let detail = {
- blockers: new Set(),
- addBlocker(aPromise) {
- this.blockers.add(aPromise);
- },
- };
-
- let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
- viewNode.dispatchEvent(evt);
-
- let cancel = evt.defaultPrevented;
- if (detail.blockers.size) {
- try {
- let results = yield Promise.all(detail.blockers);
- cancel = cancel || results.some(val => val === false);
- } catch (e) {
- Components.utils.reportError(e);
- cancel = true;
- }
- }
-
- if (cancel) {
- return;
- }
-
- this._currentSubView = viewNode;
-
- // Now we have to transition the panel. There are a few parts to this:
- //
- // 1) The main view content gets shifted so that the center of the anchor
- // node is at the left-most edge of the panel.
- // 2) The subview deck slides in so that it takes up almost all of the
- // panel.
- // 3) If the subview is taller then the main panel contents, then the panel
- // must grow to meet that new height. Otherwise, it must shrink.
- //
- // All three of these actions make use of CSS transformations, so they
- // should all occur simultaneously.
- this.setAttribute("viewtype", "subview");
- this._shiftMainView(aAnchor);
-
- this._mainViewHeight = this._viewStack.clientHeight;
-
- let newHeight = this._heightOfSubview(viewNode, this._subViews);
- this._setViewContainerHeight(newHeight);
-
- this._subViewObserver.observe(viewNode, {
- attributes: true,
- characterData: true,
- childList: true,
- subtree: true
- });
- }.bind(this));
- ]]></body>
- </method>
-
- <method name="_setViewContainerHeight">
- <parameter name="aHeight"/>
- <body><![CDATA[
- let container = this._viewContainer;
- this._transitioning = true;
-
- let onTransitionEnd = () => {
- container.removeEventListener("transitionend", onTransitionEnd);
- this._transitioning = false;
- };
-
- container.addEventListener("transitionend", onTransitionEnd);
- container.style.height = `${aHeight}px`;
- ]]></body>
- </method>
-
- <method name="_shiftMainView">
- <parameter name="aAnchor"/>
- <body><![CDATA[
- if (aAnchor) {
- // We need to find the edge of the anchor, relative to the main panel.
- // Then we need to add half the width of the anchor. This is the target
- // that we need to transition to.
- let anchorRect = aAnchor.getBoundingClientRect();
- let mainViewRect = this._mainViewContainer.getBoundingClientRect();
- let center = aAnchor.clientWidth / 2;
- let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
- let edge;
- if (direction == "ltr") {
- edge = anchorRect.left - mainViewRect.left;
- } else {
- edge = mainViewRect.right - anchorRect.right;
- }
-
- // If the anchor is an element on the far end of the mainView we
- // don't want to shift the mainView too far, we would reveal empty
- // space otherwise.
- let cstyle = window.getComputedStyle(document.documentElement, null);
- let exitSubViewGutterWidth =
- cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
- let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
- let target = Math.min(maxShift, edge + center);
-
- let neg = direction == "ltr" ? "-" : "";
- this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
- aAnchor.setAttribute("panel-multiview-anchor", true);
- } else {
- this._mainViewContainer.style.transform = "";
- if (this.anchorElement)
- this.anchorElement.removeAttribute("panel-multiview-anchor");
- }
- this.anchorElement = aAnchor;
- ]]></body>
- </method>
-
- <method name="handleEvent">
- <parameter name="aEvent"/>
- <body><![CDATA[
- if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
- // Shouldn't act on e.g. context menus being shown from within the panel.
- return;
- }
- switch (aEvent.type) {
- case "click":
- if (aEvent.originalTarget == this._clickCapturer) {
- this.showMainView();
- }
- break;
- case "overflow":
- if (aEvent.target.localName == "vbox") {
- // Resize the right view on the next tick.
- if (this.showingSubView) {
- setTimeout(this._syncContainerWithSubView.bind(this), 0);
- } else if (!this.transitioning) {
- setTimeout(this._syncContainerWithMainView.bind(this), 0);
- }
- }
- break;
- case "popupshowing":
- this.setAttribute("panelopen", "true");
- // Bug 941196 - The panel can get taller when opening a subview. Disabling
- // autoPositioning means that the panel won't jump around if an opened
- // subview causes the panel to exceed the dimensions of the screen in the
- // direction that the panel originally opened in. This property resets
- // every time the popup closes, which is why we have to set it each time.
- this._panel.autoPosition = false;
- this._syncContainerWithMainView();
-
- this._mainViewObserver.observe(this._mainView, {
- attributes: true,
- characterData: true,
- childList: true,
- subtree: true
- });
-
- break;
- case "popupshown":
- this._setMaxHeight();
- break;
- case "popuphidden":
- this.removeAttribute("panelopen");
- this._mainView.style.removeProperty("height");
- this.showMainView();
- this._mainViewObserver.disconnect();
- break;
- }
- ]]></body>
- </method>
-
- <method name="_shouldSetPosition">
- <body><![CDATA[
- return this.getAttribute("nosubviews") == "true";
- ]]></body>
- </method>
-
- <method name="_shouldSetHeight">
- <body><![CDATA[
- return this.getAttribute("nosubviews") != "true";
- ]]></body>
- </method>
-
- <method name="_setMaxHeight">
- <body><![CDATA[
- if (!this._shouldSetHeight())
- return;
-
- // Ignore the mutation that'll fire when we set the height of
- // the main view.
- this.ignoreMutations = true;
- this._mainView.style.height =
- this.getBoundingClientRect().height + "px";
- this.ignoreMutations = false;
- ]]></body>
- </method>
- <method name="_adjustContainerHeight">
- <body><![CDATA[
- if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
- let height;
- if (this.showingSubViewAsMainView) {
- height = this._heightOfSubview(this._mainView);
- } else {
- height = this._mainView.scrollHeight;
- }
- this._viewContainer.style.height = height + "px";
- }
- ]]></body>
- </method>
- <method name="_syncContainerWithSubView">
- <body><![CDATA[
- // Check that this panel is still alive:
- if (!this._panel || !this._panel.parentNode) {
- return;
- }
-
- if (!this.ignoreMutations && this.showingSubView) {
- let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
- this._viewContainer.style.height = newHeight + "px";
- }
- ]]></body>
- </method>
- <method name="_syncContainerWithMainView">
- <body><![CDATA[
- // Check that this panel is still alive:
- if (!this._panel || !this._panel.parentNode) {
- return;
- }
-
- if (this._shouldSetPosition()) {
- this._panel.adjustArrowPosition();
- }
-
- if (this._shouldSetHeight()) {
- this._adjustContainerHeight();
- }
- ]]></body>
- </method>
-
- <!-- Call this when the height of one of your views (the main view or a
- subview) changes and you want the heights of the multiview and panel
- to be the same as the view's height.
- If the caller can give a hint of the expected height change with the
- optional aExpectedChange parameter, it prevents flicker. -->
- <method name="setHeightToFit">
- <parameter name="aExpectedChange"/>
- <body><![CDATA[
- // Set the max-height to zero, wait until the height is actually
- // updated, and then remove it. If it's not removed, weird things can
- // happen, like widgets in the panel won't respond to clicks even
- // though they're visible.
- let count = 5;
- let height = getComputedStyle(this).height;
- if (aExpectedChange)
- this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
- else
- this.style.maxHeight = "0";
- let interval = setInterval(() => {
- if (height != getComputedStyle(this).height || --count == 0) {
- clearInterval(interval);
- this.style.removeProperty("max-height");
- }
- }, 0);
- ]]></body>
- </method>
-
- <method name="_heightOfSubview">
- <parameter name="aSubview"/>
- <parameter name="aContainerToCheck"/>
- <body><![CDATA[
- function getFullHeight(element) {
- // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
- // that works with overflow: auto elements. Fortunately for us,
- // we have exactly 1 (potentially) scrolling element in here (the subview body),
- // and rounding 1 value is OK - rounding more than 1 and adding them means we get
- // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
- // So, use scrollHeight *only* if the element is vertically scrollable.
- let height;
- let elementCS;
- if (element.scrollTopMax) {
- height = element.scrollHeight;
- // Bounding client rects include borders, scrollHeight doesn't:
- elementCS = win.getComputedStyle(element);
- height += parseFloat(elementCS.borderTopWidth) +
- parseFloat(elementCS.borderBottomWidth);
- } else {
- height = element.getBoundingClientRect().height;
- if (height > 0) {
- elementCS = win.getComputedStyle(element);
- }
- }
- if (elementCS) {
- // Include margins - but not borders or paddings because they
- // were dealt with above.
- height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
- }
- return height;
- }
- let win = aSubview.ownerDocument.defaultView;
- let body = aSubview.querySelector(".panel-subview-body");
- let height = getFullHeight(body || aSubview);
- if (body) {
- let header = aSubview.querySelector(".panel-subview-header");
- let footer = aSubview.querySelector(".panel-subview-footer");
- height += (header ? getFullHeight(header) : 0) +
- (footer ? getFullHeight(footer) : 0);
- }
- if (aContainerToCheck) {
- let containerCS = win.getComputedStyle(aContainerToCheck);
- height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
- }
- return Math.ceil(height);
- ]]></body>
- </method>
-
- </implementation>
- </binding>
-
- <binding id="panelview">
- <implementation>
- <property name="panelMultiView" readonly="true">
- <getter><![CDATA[
- if (this.parentNode.localName != "panelmultiview") {
- return document.getBindingParent(this.parentNode);
- }
-
- return this.parentNode;
- ]]></getter>
- </property>
- </implementation>
- </binding>
-</bindings>
diff --git a/browser/components/customizableui/content/toolbar.xml b/browser/components/customizableui/content/toolbar.xml
deleted file mode 100644
index 4e6964c9f..000000000
--- a/browser/components/customizableui/content/toolbar.xml
+++ /dev/null
@@ -1,618 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-
-<bindings id="browserToolbarBindings"
- xmlns="http://www.mozilla.org/xbl"
- xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- xmlns:xbl="http://www.mozilla.org/xbl">
-
- <binding id="toolbar" role="xul:toolbar">
- <resources>
- <stylesheet src="chrome://global/skin/toolbar.css"/>
- </resources>
- <implementation>
- <field name="overflowedDuringConstruction">null</field>
-
- <constructor><![CDATA[
- let scope = {};
- Cu.import("resource:///modules/CustomizableUI.jsm", scope);
- // Add an early overflow event listener that will mark if the
- // toolbar overflowed during construction.
- if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
- this.addEventListener("overflow", this);
- this.addEventListener("underflow", this);
- }
-
- if (document.readyState == "complete") {
- this._init();
- } else {
- // Need to wait until XUL overlays are loaded. See bug 554279.
- let self = this;
- document.addEventListener("readystatechange", function onReadyStateChange() {
- if (document.readyState != "complete")
- return;
- document.removeEventListener("readystatechange", onReadyStateChange, false);
- self._init();
- }, false);
- }
- ]]></constructor>
-
- <method name="_init">
- <body><![CDATA[
- let scope = {};
- Cu.import("resource:///modules/CustomizableUI.jsm", scope);
- let CustomizableUI = scope.CustomizableUI;
-
- // Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
- // attributes, just in case they accidentally get restored from
- // persistence from a user that's been upgrading and downgrading.
- if (CustomizableUI.isBuiltinToolbar(this.id)) {
- const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
- for (let [attribute, value] of kAttributes) {
- if (this.getAttribute(attribute) != value) {
- this.setAttribute(attribute, value);
- document.persist(this.id, attribute);
- }
- if (this.toolbox) {
- if (this.toolbox.getAttribute(attribute) != value) {
- this.toolbox.setAttribute(attribute, value);
- document.persist(this.toolbox.id, attribute);
- }
- }
- }
- }
-
- // Searching for the toolbox palette in the toolbar binding because
- // toolbars are constructed first.
- let toolbox = this.toolbox;
- if (toolbox && !toolbox.palette) {
- for (let node of toolbox.children) {
- if (node.localName == "toolbarpalette") {
- // Hold on to the palette but remove it from the document.
- toolbox.palette = node;
- toolbox.removeChild(node);
- break;
- }
- }
- }
-
- // pass the current set of children for comparison with placements:
- let children = Array.from(this.childNodes)
- .filter(node => node.getAttribute("skipintoolbarset") != "true" && node.id)
- .map(node => node.id);
- CustomizableUI.registerToolbarNode(this, children);
- ]]></body>
- </method>
-
- <method name="handleEvent">
- <parameter name="aEvent"/>
- <body><![CDATA[
- if (aEvent.type == "overflow" && aEvent.detail > 0) {
- if (this.overflowable && this.overflowable.initialized) {
- this.overflowable.onOverflow(aEvent);
- } else {
- this.overflowedDuringConstruction = aEvent;
- }
- } else if (aEvent.type == "underflow" && aEvent.detail > 0) {
- this.overflowedDuringConstruction = null;
- }
- ]]></body>
- </method>
-
- <method name="insertItem">
- <parameter name="aId"/>
- <parameter name="aBeforeElt"/>
- <parameter name="aWrapper"/>
- <body><![CDATA[
- if (aWrapper) {
- Cu.reportError("Can't insert " + aId + ": using insertItem " +
- "no longer supports wrapper elements.");
- return null;
- }
-
- // Hack, the customizable UI code makes this be the last position
- let pos = null;
- if (aBeforeElt) {
- let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
- if (beforeInfo.area != this.id) {
- Cu.reportError("Can't insert " + aId + " before " +
- aBeforeElt.id + " which isn't in this area (" +
- this.id + ").");
- return null;
- }
- pos = beforeInfo.position;
- }
-
- CustomizableUI.addWidgetToArea(aId, this.id, pos);
- return this.ownerDocument.getElementById(aId);
- ]]></body>
- </method>
-
- <property name="toolbarName"
- onget="return this.getAttribute('toolbarname');"
- onset="this.setAttribute('toolbarname', val); return val;"/>
-
- <property name="customizationTarget" readonly="true">
- <getter><![CDATA[
- if (this._customizationTarget)
- return this._customizationTarget;
-
- let id = this.getAttribute("customizationtarget");
- if (id)
- this._customizationTarget = document.getElementById(id);
-
- if (this._customizationTarget)
- this._customizationTarget.insertItem = this.insertItem.bind(this);
- else
- this._customizationTarget = this;
-
- return this._customizationTarget;
- ]]></getter>
- </property>
-
- <property name="toolbox" readonly="true">
- <getter><![CDATA[
- if (this._toolbox)
- return this._toolbox;
-
- let toolboxId = this.getAttribute("toolboxid");
- if (toolboxId) {
- let toolbox = document.getElementById(toolboxId);
- if (toolbox) {
- if (toolbox.externalToolbars.indexOf(this) == -1)
- toolbox.externalToolbars.push(this);
-
- this._toolbox = toolbox;
- }
- }
-
- if (!this._toolbox && this.parentNode &&
- this.parentNode.localName == "toolbox") {
- this._toolbox = this.parentNode;
- }
-
- return this._toolbox;
- ]]></getter>
- </property>
-
- <property name="currentSet">
- <getter><![CDATA[
- let currentWidgets = new Set();
- for (let node of this.customizationTarget.children) {
- let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
- if (realNode.getAttribute("skipintoolbarset") != "true") {
- currentWidgets.add(realNode.id);
- }
- }
- if (this.getAttribute("overflowing") == "true") {
- let overflowTarget = this.getAttribute("overflowtarget");
- let overflowList = this.ownerDocument.getElementById(overflowTarget);
- for (let node of overflowList.children) {
- let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
- if (realNode.getAttribute("skipintoolbarset") != "true") {
- currentWidgets.add(realNode.id);
- }
- }
- }
- let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
- return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
- ]]></getter>
- <setter><![CDATA[
- // Get list of new and old ids:
- let newVal = (val || '').split(',').filter(x => x);
- let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
-
- // Get a list of items only in the new list
- let newIds = newVal.filter(id => oldIds.indexOf(id) == -1);
- CustomizableUI.beginBatchUpdate();
- try {
- for (let newId of newIds) {
- oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
- let nextId = newId;
- let pos;
- do {
- // Get the next item
- nextId = newVal[newVal.indexOf(nextId) + 1];
- // Figure out where it is in the old list
- pos = oldIds.indexOf(nextId);
- // If it's not in the old list, repeat:
- } while (pos == -1 && nextId);
- if (pos == -1) {
- pos = null; // We didn't find anything, insert at the end
- }
- CustomizableUI.addWidgetToArea(newId, this.id, pos);
- }
-
- let currentIds = this.currentSet.split(',');
- let removedIds = currentIds.filter(id => newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1);
- for (let removedId of removedIds) {
- CustomizableUI.removeWidgetFromArea(removedId);
- }
- } finally {
- CustomizableUI.endBatchUpdate();
- }
- ]]></setter>
- </property>
-
-
- </implementation>
- </binding>
-
- <binding id="toolbar-menubar-stub">
- <implementation>
- <property name="toolbox" readonly="true">
- <getter><![CDATA[
- if (this._toolbox)
- return this._toolbox;
-
- if (this.parentNode && this.parentNode.localName == "toolbox") {
- this._toolbox = this.parentNode;
- }
-
- return this._toolbox;
- ]]></getter>
- </property>
- <property name="currentSet" readonly="true">
- <getter><![CDATA[
- return this.getAttribute("defaultset");
- ]]></getter>
- </property>
- <method name="insertItem">
- <body><![CDATA[
- return null;
- ]]></body>
- </method>
- </implementation>
- </binding>
-
- <!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
- verbatim copies of their toolkit counterparts - they just inherit from
- the customizableui's toolbar binding instead of toolkit's. We're currently
- OK with the maintainance burden of having two copies of a binding, since
- the long term goal is to move the customization framework into toolkit. -->
-
- <binding id="toolbar-menubar-autohide"
- extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
- <implementation>
- <constructor>
- this._setInactive();
- </constructor>
- <destructor>
- this._setActive();
- </destructor>
-
- <field name="_inactiveTimeout">null</field>
-
- <field name="_contextMenuListener"><![CDATA[({
- toolbar: this,
- contextMenu: null,
-
- get active () {
- return !!this.contextMenu;
- },
-
- init: function (event) {
- let node = event.target;
- while (node != this.toolbar) {
- if (node.localName == "menupopup")
- return;
- node = node.parentNode;
- }
-
- let contextMenuId = this.toolbar.getAttribute("context");
- if (!contextMenuId)
- return;
-
- this.contextMenu = document.getElementById(contextMenuId);
- if (!this.contextMenu)
- return;
-
- this.contextMenu.addEventListener("popupshown", this, false);
- this.contextMenu.addEventListener("popuphiding", this, false);
- this.toolbar.addEventListener("mousemove", this, false);
- },
- handleEvent: function (event) {
- switch (event.type) {
- case "popupshown":
- this.toolbar.removeEventListener("mousemove", this, false);
- break;
- case "popuphiding":
- case "mousemove":
- this.toolbar._setInactiveAsync();
- this.toolbar.removeEventListener("mousemove", this, false);
- this.contextMenu.removeEventListener("popuphiding", this, false);
- this.contextMenu.removeEventListener("popupshown", this, false);
- this.contextMenu = null;
- break;
- }
- }
- })]]></field>
-
- <method name="_setInactive">
- <body><![CDATA[
- this.setAttribute("inactive", "true");
- ]]></body>
- </method>
-
- <method name="_setInactiveAsync">
- <body><![CDATA[
- this._inactiveTimeout = setTimeout(function (self) {
- if (self.getAttribute("autohide") == "true") {
- self._inactiveTimeout = null;
- self._setInactive();
- }
- }, 0, this);
- ]]></body>
- </method>
-
- <method name="_setActive">
- <body><![CDATA[
- if (this._inactiveTimeout) {
- clearTimeout(this._inactiveTimeout);
- this._inactiveTimeout = null;
- }
- this.removeAttribute("inactive");
- ]]></body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="DOMMenuBarActive" action="this._setActive();"/>
- <handler event="popupshowing" action="this._setActive();"/>
- <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
- <handler event="DOMMenuBarInactive"><![CDATA[
- if (!this._contextMenuListener.active)
- this._setInactiveAsync();
- ]]></handler>
- </handlers>
- </binding>
-
- <binding id="toolbar-drag"
- extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
- <implementation>
- <field name="_dragBindingAlive">true</field>
- <constructor><![CDATA[
- if (!this._draggableStarted) {
- this._draggableStarted = true;
- try {
- let tmp = {};
- Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
- let draggableThis = new tmp.WindowDraggingElement(this);
- draggableThis.mouseDownCheck = function(e) {
- return this._dragBindingAlive;
- };
- } catch (e) {}
- }
- ]]></constructor>
- </implementation>
- </binding>
-
-
-<!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
- and immediately direct such content elsewhere. -->
- <binding id="addonbar-delegating">
- <implementation>
- <constructor><![CDATA[
- // Reading these immediately so nobody messes with them anymore:
- this._delegatingToolbar = this.getAttribute("toolbar-delegate");
- this._wasCollapsed = this.getAttribute("collapsed") == "true";
- // Leaving those in here to unbreak some code:
- if (document.readyState == "complete") {
- this._init();
- } else {
- // Need to wait until XUL overlays are loaded. See bug 554279.
- let self = this;
- document.addEventListener("readystatechange", function onReadyStateChange() {
- if (document.readyState != "complete")
- return;
- document.removeEventListener("readystatechange", onReadyStateChange, false);
- self._init();
- }, false);
- }
- ]]></constructor>
-
- <method name="_init">
- <body><![CDATA[
- // Searching for the toolbox palette in the toolbar binding because
- // toolbars are constructed first.
- let toolbox = this.toolbox;
- if (toolbox && !toolbox.palette) {
- for (let node of toolbox.children) {
- if (node.localName == "toolbarpalette") {
- // Hold on to the palette but remove it from the document.
- toolbox.palette = node;
- toolbox.removeChild(node);
- }
- }
- }
-
- // pass the current set of children for comparison with placements:
- let children = [];
- for (let node of this.childNodes) {
- if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
- // Force everything to be removable so that buildArea can chuck stuff
- // out if the user has customized things / we've been here before:
- if (!this._whiteListed.has(node.id)) {
- node.setAttribute("removable", "true");
- }
- children.push(node);
- }
- }
- CustomizableUI.registerToolbarNode(this, children);
- let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
- for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
- this._currentSetMigrated.add(migratedItem);
- }
- this.evictNodes();
- // We can't easily use |this| or strong bindings for the observer fn here
- // because that creates leaky circular references when the node goes away,
- // and XBL destructors are unreliable.
- let mutationObserver = new MutationObserver(function(mutations) {
- if (!mutations.length) {
- return;
- }
- let toolbar = mutations[0].target;
- // Can't use our own attribute because we might not have one if we're set to
- // collapsed
- let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
- if (!toolbar._isModifying && !areCustomizing) {
- toolbar.evictNodes();
- }
- });
- mutationObserver.observe(this, {childList: true});
- ]]></body>
- </method>
- <method name="evictNodes">
- <body><![CDATA[
- this._isModifying = true;
- let i = this.childNodes.length;
- while (i--) {
- let node = this.childNodes[i];
- if (this.childNodes[i].id) {
- this.evictNode(this.childNodes[i]);
- } else {
- node.remove();
- }
- }
- this._isModifying = false;
- this._updateMigratedSet();
- ]]></body>
- </method>
- <method name="evictNode">
- <parameter name="aNode"/>
- <body>
- <![CDATA[
- if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
- return;
- }
- const kItemMaxWidth = 100;
- let oldParent = aNode.parentNode;
- aNode.setAttribute("removable", "true");
- this._currentSetMigrated.add(aNode.id);
-
- let movedOut = false;
- if (!this._wasCollapsed) {
- try {
- let nodeWidth = aNode.getBoundingClientRect().width;
- if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
- throw new Error(aNode.id + " is too big (" + nodeWidth +
- "px wide), moving to the palette");
- }
- CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
- movedOut = true;
- } catch (ex) {
- // This will throw if the node is too big, or can't be moved there for
- // some reason. Report this:
- Cu.reportError(ex);
- }
- }
-
- /* We won't have moved the widget if either the add-on bar was collapsed,
- * or if it was too wide to be inserted into the navbar. */
- if (!movedOut) {
- try {
- CustomizableUI.removeWidgetFromArea(aNode.id);
- } catch (ex) {
- Cu.reportError(ex);
- aNode.remove();
- }
- }
-
- // Surprise: addWidgetToArea(palette) will get you nothing if the palette
- // is not constructed yet. Fix:
- if (aNode.parentNode == oldParent) {
- let palette = this.toolbox.palette;
- if (palette && oldParent != palette) {
- palette.appendChild(aNode);
- }
- }
- ]]></body>
- </method>
- <method name="insertItem">
- <parameter name="aId"/>
- <parameter name="aBeforeElt"/>
- <parameter name="aWrapper"/>
- <body><![CDATA[
- if (aWrapper) {
- Cu.reportError("Can't insert " + aId + ": using insertItem " +
- "no longer supports wrapper elements.");
- return null;
- }
-
- let widget = CustomizableUI.getWidget(aId);
- widget = widget && widget.forWindow(window);
- let node = widget && widget.node;
- if (!node) {
- return null;
- }
-
- this._isModifying = true;
- // Temporarily add it here so it can have a width, then ditch it:
- this.appendChild(node);
- this.evictNode(node);
- this._isModifying = false;
- this._updateMigratedSet();
- // We will now have moved stuff around; kick off some events
- // so add-ons know we've just moved their stuff:
- // XXXgijs: only in this window. It's hard to know for sure what's the right
- // thing to do here - typically insertItem is used on each window, so
- // this seems to make the most sense, even if some of the effects of
- // evictNode might affect multiple windows.
- CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
- CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
- return node;
- ]]></body>
- </method>
- <method name="getMigratedItems">
- <body><![CDATA[
- return [... this._currentSetMigrated];
- ]]></body>
- </method>
- <method name="_updateMigratedSet">
- <body><![CDATA[
- let newMigratedItems = this.getMigratedItems().join(',');
- if (this.getAttribute("migratedset") != newMigratedItems) {
- this.setAttribute("migratedset", newMigratedItems);
- this.ownerDocument.persist(this.id, "migratedset");
- }
- ]]></body>
- </method>
- <property name="customizationTarget" readonly="true">
- <getter><![CDATA[
- return this;
- ]]></getter>
- </property>
- <property name="currentSet">
- <getter><![CDATA[
- return Array.from(this.children, node => node.id).join(",");
- ]]></getter>
- <setter><![CDATA[
- let v = val.split(',');
- let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
- !CustomizableUI.isSpecialWidget(x) &&
- !this._currentSetMigrated.has(x)));
- for (let newButton of newButtons) {
- this._currentSetMigrated.add(newButton);
- this.insertItem(newButton);
- }
- this._updateMigratedSet();
- ]]></setter>
- </property>
- <property name="toolbox" readonly="true">
- <getter><![CDATA[
- if (!this._toolbox && this.parentNode &&
- this.parentNode.localName == "toolbox") {
- this._toolbox = this.parentNode;
- }
-
- return this._toolbox;
- ]]></getter>
- </property>
- <field name="_whiteListed" readonly="true">new Set(["addonbar-closebutton", "status-bar"])</field>
- <field name="_isModifying">false</field>
- <field name="_currentSetMigrated">new Set()</field>
- </implementation>
- </binding>
-</bindings>
diff --git a/browser/components/customizableui/moz.build b/browser/components/customizableui/moz.build
deleted file mode 100644
index 034630dc9..000000000
--- a/browser/components/customizableui/moz.build
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-DIRS += [
- 'content',
-]
-
-EXTRA_JS_MODULES += [
- 'CustomizableUI.jsm',
- 'CustomizableWidgets.jsm',
- 'CustomizeMode.jsm',
- 'DragPositionManager.jsm',
- 'PanelWideWidgetTracker.jsm',
- 'ScrollbarSampler.jsm',
-]
-
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
- DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1