forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2971 lines
		
	
	
	
		
			94 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2971 lines
		
	
	
	
		
			94 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| const kPrefCustomizationDebug = "browser.uiCustomization.debug";
 | |
| const kPaletteId = "customization-palette";
 | |
| const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 | |
| const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 | |
| const kDrawInTitlebarPref = "browser.tabs.inTitlebar";
 | |
| const kCompactModeShowPref = "browser.compactmode.show";
 | |
| const kBookmarksToolbarPref = "browser.toolbars.bookmarks.visibility";
 | |
| const kKeepBroadcastAttributes = "keepbroadcastattributeswhencustomizing";
 | |
| 
 | |
| const kPanelItemContextMenu = "customizationPanelItemContextMenu";
 | |
| const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
 | |
| 
 | |
| const kDownloadAutohideCheckboxId = "downloads-button-autohide-checkbox";
 | |
| const kDownloadAutohidePanelId = "downloads-button-autohide-panel";
 | |
| const kDownloadAutoHidePref = "browser.download.autohideButton";
 | |
| 
 | |
| import { CustomizableUI } from "resource:///modules/CustomizableUI.sys.mjs";
 | |
| import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | |
| import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
 | |
|   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
 | |
|   DragPositionManager: "resource:///modules/DragPositionManager.sys.mjs",
 | |
|   SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
 | |
|   URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
 | |
| });
 | |
| ChromeUtils.defineLazyGetter(lazy, "gWidgetsBundle", function () {
 | |
|   const kUrl =
 | |
|     "chrome://browser/locale/customizableui/customizableWidgets.properties";
 | |
|   return Services.strings.createBundle(kUrl);
 | |
| });
 | |
| XPCOMUtils.defineLazyServiceGetter(
 | |
|   lazy,
 | |
|   "gTouchBarUpdater",
 | |
|   "@mozilla.org/widget/touchbarupdater;1",
 | |
|   "nsITouchBarUpdater"
 | |
| );
 | |
| 
 | |
| let gDebug;
 | |
| ChromeUtils.defineLazyGetter(lazy, "log", () => {
 | |
|   let { ConsoleAPI } = ChromeUtils.importESModule(
 | |
|     "resource://gre/modules/Console.sys.mjs"
 | |
|   );
 | |
|   gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
 | |
|   let consoleOptions = {
 | |
|     maxLogLevel: gDebug ? "all" : "log",
 | |
|     prefix: "CustomizeMode",
 | |
|   };
 | |
|   return new ConsoleAPI(consoleOptions);
 | |
| });
 | |
| 
 | |
| var gDraggingInToolbars;
 | |
| 
 | |
| var gTab;
 | |
| 
 | |
| function closeGlobalTab() {
 | |
|   let win = gTab.ownerGlobal;
 | |
|   if (win.gBrowser.browsers.length == 1) {
 | |
|     win.BrowserOpenTab();
 | |
|   }
 | |
|   win.gBrowser.removeTab(gTab, { animate: true });
 | |
|   gTab = null;
 | |
| }
 | |
| 
 | |
| var gTabsProgressListener = {
 | |
|   onLocationChange(aBrowser, aWebProgress, aRequest, aLocation, aFlags) {
 | |
|     // Tear down customize mode when the customize mode tab loads some other page.
 | |
|     // Customize mode will be re-entered if "about:blank" is loaded again, so
 | |
|     // don't tear down in this case.
 | |
|     if (
 | |
|       !gTab ||
 | |
|       gTab.linkedBrowser != aBrowser ||
 | |
|       aLocation.spec == "about:blank"
 | |
|     ) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     unregisterGlobalTab();
 | |
|   },
 | |
| };
 | |
| 
 | |
| function unregisterGlobalTab() {
 | |
|   gTab.removeEventListener("TabClose", unregisterGlobalTab);
 | |
|   let win = gTab.ownerGlobal;
 | |
|   win.removeEventListener("unload", unregisterGlobalTab);
 | |
|   win.gBrowser.removeTabsProgressListener(gTabsProgressListener);
 | |
| 
 | |
|   gTab.removeAttribute("customizemode");
 | |
| 
 | |
|   gTab = null;
 | |
| }
 | |
| 
 | |
| export function CustomizeMode(aWindow) {
 | |
|   this.window = aWindow;
 | |
|   this.document = aWindow.document;
 | |
|   this.browser = aWindow.gBrowser;
 | |
|   this.areas = new Set();
 | |
| 
 | |
|   this._translationObserver = new aWindow.MutationObserver(mutations =>
 | |
|     this._onTranslations(mutations)
 | |
|   );
 | |
|   this._ensureCustomizationPanels();
 | |
| 
 | |
|   let content = this.$("customization-content-container");
 | |
|   if (!content) {
 | |
|     this.window.MozXULElement.insertFTLIfNeeded("browser/customizeMode.ftl");
 | |
|     let container = this.$("customization-container");
 | |
|     container.replaceChild(
 | |
|       this.window.MozXULElement.parseXULToFragment(container.firstChild.data),
 | |
|       container.lastChild
 | |
|     );
 | |
|   }
 | |
|   // There are two palettes - there's the palette that can be overlayed with
 | |
|   // toolbar items in browser.xhtml. 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.$(kPaletteId);
 | |
|   this.pongArena = this.$("customization-pong-arena");
 | |
| 
 | |
|   if (this._canDrawInTitlebar()) {
 | |
|     this._updateTitlebarCheckbox();
 | |
|     Services.prefs.addObserver(kDrawInTitlebarPref, this);
 | |
|   } else {
 | |
|     this.$("customization-titlebar-visibility-checkbox").hidden = true;
 | |
|   }
 | |
| 
 | |
|   // Observe pref changes to the bookmarks toolbar visibility,
 | |
|   // since we won't get a toolbarvisibilitychange event if the
 | |
|   // toolbar is changing from 'newtab' to 'always' in Customize mode
 | |
|   // since the toolbar is shown with the 'newtab' setting.
 | |
|   Services.prefs.addObserver(kBookmarksToolbarPref, this);
 | |
| 
 | |
|   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,
 | |
| 
 | |
|   // These are the commands we continue to leave enabled while in customize mode.
 | |
|   // All other commands are disabled, and we remove the disabled attribute when
 | |
|   // leaving customize mode.
 | |
|   _enabledCommands: new Set([
 | |
|     "cmd_newNavigator",
 | |
|     "cmd_newNavigatorTab",
 | |
|     "cmd_newNavigatorTabNoEvent",
 | |
|     "cmd_close",
 | |
|     "cmd_closeWindow",
 | |
|     "cmd_quitApplication",
 | |
|     "View:FullScreen",
 | |
|     "Browser:NextTab",
 | |
|     "Browser:PrevTab",
 | |
|     "Browser:NewUserContextTab",
 | |
|     "Tools:PrivateBrowsing",
 | |
|     "minimizeWindow",
 | |
|     "zoomWindow",
 | |
|   ]),
 | |
| 
 | |
|   get _handler() {
 | |
|     return this.window.CustomizationHandler;
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     if (this._canDrawInTitlebar()) {
 | |
|       Services.prefs.removeObserver(kDrawInTitlebarPref, this);
 | |
|     }
 | |
|     Services.prefs.removeObserver(kBookmarksToolbarPref, this);
 | |
|   },
 | |
| 
 | |
|   $(id) {
 | |
|     return this.document.getElementById(id);
 | |
|   },
 | |
| 
 | |
|   toggle() {
 | |
|     if (
 | |
|       this._handler.isEnteringCustomizeMode ||
 | |
|       this._handler.isExitingCustomizeMode
 | |
|     ) {
 | |
|       this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode;
 | |
|       return;
 | |
|     }
 | |
|     if (this._customizing) {
 | |
|       this.exit();
 | |
|     } else {
 | |
|       this.enter();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   setTab(aTab) {
 | |
|     if (gTab == aTab) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (gTab) {
 | |
|       closeGlobalTab();
 | |
|     }
 | |
| 
 | |
|     gTab = aTab;
 | |
| 
 | |
|     gTab.setAttribute("customizemode", "true");
 | |
|     lazy.SessionStore.persistTabAttribute("customizemode");
 | |
| 
 | |
|     if (gTab.linkedPanel) {
 | |
|       gTab.linkedBrowser.stop();
 | |
|     }
 | |
| 
 | |
|     let win = gTab.ownerGlobal;
 | |
| 
 | |
|     win.gBrowser.setTabTitle(gTab);
 | |
|     win.gBrowser.setIcon(gTab, "chrome://browser/skin/customize.svg");
 | |
| 
 | |
|     gTab.addEventListener("TabClose", unregisterGlobalTab);
 | |
| 
 | |
|     win.gBrowser.addTabsProgressListener(gTabsProgressListener);
 | |
| 
 | |
|     win.addEventListener("unload", unregisterGlobalTab);
 | |
| 
 | |
|     if (gTab.selected) {
 | |
|       win.gCustomizeMode.enter();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   enter() {
 | |
|     if (!this.window.toolbar.visible) {
 | |
|       let w = lazy.URILoadingHelper.getTargetWindow(this.window, {
 | |
|         skipPopups: true,
 | |
|       });
 | |
|       if (w) {
 | |
|         w.gCustomizeMode.enter();
 | |
|         return;
 | |
|       }
 | |
|       let obs = () => {
 | |
|         Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
 | |
|         w = lazy.URILoadingHelper.getTargetWindow(this.window, {
 | |
|           skipPopups: true,
 | |
|         });
 | |
|         w.gCustomizeMode.enter();
 | |
|       };
 | |
|       Services.obs.addObserver(obs, "browser-delayed-startup-finished");
 | |
|       this.window.openTrustedLinkIn("about:newtab", "window");
 | |
|       return;
 | |
|     }
 | |
|     this._wantToBeInCustomizeMode = true;
 | |
| 
 | |
|     if (this._customizing || this._handler.isEnteringCustomizeMode) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Exiting; want to re-enter once we've done that.
 | |
|     if (this._handler.isExitingCustomizeMode) {
 | |
|       lazy.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.addTab("about:blank", {
 | |
|           inBackground: false,
 | |
|           forceNotRemote: true,
 | |
|           skipAnimation: true,
 | |
|           triggeringPrincipal:
 | |
|             Services.scriptSecurityManager.getSystemPrincipal(),
 | |
|         })
 | |
|       );
 | |
|       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.$("customization-reset-button");
 | |
|     resetButton.setAttribute("disabled", "true");
 | |
| 
 | |
|     (async () => {
 | |
|       // We shouldn't start customize mode until after browser-delayed-startup has finished:
 | |
|       if (!this.window.gBrowserInit.delayedStartupFinished) {
 | |
|         await 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"
 | |
|           );
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       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();
 | |
| 
 | |
|       let panelHolder = document.getElementById("customization-panelHolder");
 | |
|       let panelContextMenu = document.getElementById(kPanelItemContextMenu);
 | |
|       this._previousPanelContextMenuParent = panelContextMenu.parentNode;
 | |
|       document.getElementById("mainPopupSet").appendChild(panelContextMenu);
 | |
|       panelHolder.appendChild(window.PanelUI.overflowFixedList);
 | |
| 
 | |
|       window.PanelUI.overflowFixedList.setAttribute("customizing", true);
 | |
|       window.PanelUI.menuButton.disabled = true;
 | |
|       document.getElementById("nav-bar-overflow-button").disabled = true;
 | |
| 
 | |
|       this._transitioning = true;
 | |
| 
 | |
|       let customizer = document.getElementById("customization-container");
 | |
|       let browser = document.getElementById("browser");
 | |
|       browser.hidden = true;
 | |
|       customizer.hidden = false;
 | |
| 
 | |
|       this._wrapToolbarItemSync(CustomizableUI.AREA_TABSTRIP);
 | |
| 
 | |
|       this.document.documentElement.setAttribute("customizing", true);
 | |
| 
 | |
|       let customizableToolbars = document.querySelectorAll(
 | |
|         "toolbar[customizable=true]:not([autohide=true], [collapsed=true])"
 | |
|       );
 | |
|       for (let toolbar of customizableToolbars) {
 | |
|         toolbar.setAttribute("customizing", true);
 | |
|       }
 | |
| 
 | |
|       this._updateOverflowPanelArrowOffset();
 | |
| 
 | |
|       // Let everybody in this window know that we're about to customize.
 | |
|       CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
 | |
| 
 | |
|       await this._wrapToolbarItems();
 | |
|       this.populatePalette();
 | |
| 
 | |
|       this._setupPaletteDragging();
 | |
| 
 | |
|       window.gNavToolbox.addEventListener("toolbarvisibilitychange", this);
 | |
| 
 | |
|       this._updateResetButton();
 | |
|       this._updateUndoResetButton();
 | |
|       this._updateTouchBarButton();
 | |
|       this._updateDensityMenu();
 | |
| 
 | |
|       this._skipSourceNodeCheck =
 | |
|         Services.prefs.getPrefType(kSkipSourceNodePref) ==
 | |
|           Ci.nsIPrefBranch.PREF_BOOL &&
 | |
|         Services.prefs.getBoolPref(kSkipSourceNodePref);
 | |
| 
 | |
|       CustomizableUI.addListener(this);
 | |
|       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();
 | |
| 
 | |
|       lazy.AddonManager.addAddonListener(this);
 | |
| 
 | |
|       this._setupDownloadAutoHideToggle();
 | |
| 
 | |
|       this._handler.isEnteringCustomizeMode = false;
 | |
| 
 | |
|       CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
 | |
| 
 | |
|       if (!this._wantToBeInCustomizeMode) {
 | |
|         this.exit();
 | |
|       }
 | |
|     })().catch(e => {
 | |
|       lazy.log.error("Error entering customize mode", e);
 | |
|       this._handler.isEnteringCustomizeMode = false;
 | |
|       // Exit customize mode to ensure proper clean-up when entering failed.
 | |
|       this.exit();
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   exit() {
 | |
|     this._wantToBeInCustomizeMode = false;
 | |
| 
 | |
|     if (!this._customizing || this._handler.isExitingCustomizeMode) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Entering; want to exit once we've done that.
 | |
|     if (this._handler.isEnteringCustomizeMode) {
 | |
|       lazy.log.debug(
 | |
|         "Attempted to exit while we're in the middle of entering. " +
 | |
|           "We'll exit after we've entered"
 | |
|       );
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this.resetting) {
 | |
|       lazy.log.debug(
 | |
|         "Attempted to exit while we're resetting. " +
 | |
|           "We'll exit after resetting has finished."
 | |
|       );
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this._handler.isExitingCustomizeMode = true;
 | |
| 
 | |
|     this._translationObserver.disconnect();
 | |
| 
 | |
|     this._teardownDownloadAutoHideToggle();
 | |
| 
 | |
|     lazy.AddonManager.removeAddonListener(this);
 | |
|     CustomizableUI.removeListener(this);
 | |
| 
 | |
|     let window = this.window;
 | |
|     let document = this.document;
 | |
| 
 | |
|     document.removeEventListener("keypress", this);
 | |
| 
 | |
|     this.togglePong(false);
 | |
| 
 | |
|     // Disable the reset and undo reset buttons while transitioning:
 | |
|     let resetButton = this.$("customization-reset-button");
 | |
|     let undoResetButton = this.$("customization-undo-reset-button");
 | |
|     undoResetButton.hidden = resetButton.disabled = true;
 | |
| 
 | |
|     this._transitioning = true;
 | |
| 
 | |
|     this._depopulatePalette();
 | |
| 
 | |
|     // We need to set this._customizing to false and remove the `customizing`
 | |
|     // attribute before removing the tab or else
 | |
|     // XULBrowserWindow.onLocationChange might think that we're still in
 | |
|     // customization mode and need to exit it for a second time.
 | |
|     this._customizing = false;
 | |
|     document.documentElement.removeAttribute("customizing");
 | |
| 
 | |
|     if (this.browser.selectedTab == gTab) {
 | |
|       closeGlobalTab();
 | |
|     }
 | |
| 
 | |
|     let customizer = document.getElementById("customization-container");
 | |
|     let browser = document.getElementById("browser");
 | |
|     customizer.hidden = true;
 | |
|     browser.hidden = false;
 | |
| 
 | |
|     window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
 | |
| 
 | |
|     this._teardownPaletteDragging();
 | |
| 
 | |
|     (async () => {
 | |
|       await this._unwrapToolbarItems();
 | |
| 
 | |
|       // 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.menuButton.disabled = false;
 | |
|       let overflowContainer = document.getElementById(
 | |
|         "widget-overflow-mainView"
 | |
|       ).firstElementChild;
 | |
|       overflowContainer.appendChild(window.PanelUI.overflowFixedList);
 | |
|       document.getElementById("nav-bar-overflow-button").disabled = false;
 | |
|       let panelContextMenu = document.getElementById(kPanelItemContextMenu);
 | |
|       this._previousPanelContextMenuParent.appendChild(panelContextMenu);
 | |
| 
 | |
|       let customizableToolbars = document.querySelectorAll(
 | |
|         "toolbar[customizable=true]:not([autohide=true])"
 | |
|       );
 | |
|       for (let toolbar of customizableToolbars) {
 | |
|         toolbar.removeAttribute("customizing");
 | |
|       }
 | |
| 
 | |
|       this._maybeMoveDownloadsButtonToNavBar();
 | |
| 
 | |
|       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();
 | |
|       }
 | |
|     })().catch(e => {
 | |
|       lazy.log.error("Error exiting customize mode", e);
 | |
|       this._handler.isExitingCustomizeMode = false;
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The overflow panel in customize mode should have its arrow pointing
 | |
|    * at the overflow button. In order to do this correctly, we pass the
 | |
|    * distance between the inside of window and the middle of the button
 | |
|    * to the customize mode markup in which the arrow and panel are placed.
 | |
|    */
 | |
|   async _updateOverflowPanelArrowOffset() {
 | |
|     let currentDensity =
 | |
|       this.document.documentElement.getAttribute("uidensity");
 | |
|     let offset = await this.window.promiseDocumentFlushed(() => {
 | |
|       let overflowButton = this.$("nav-bar-overflow-button");
 | |
|       let buttonRect = overflowButton.getBoundingClientRect();
 | |
|       let endDistance;
 | |
|       if (this.window.RTL_UI) {
 | |
|         endDistance = buttonRect.left;
 | |
|       } else {
 | |
|         endDistance = this.window.innerWidth - buttonRect.right;
 | |
|       }
 | |
|       return endDistance + buttonRect.width / 2;
 | |
|     });
 | |
|     if (
 | |
|       !this.document ||
 | |
|       currentDensity != this.document.documentElement.getAttribute("uidensity")
 | |
|     ) {
 | |
|       return;
 | |
|     }
 | |
|     this.$("customization-panelWrapper").style.setProperty(
 | |
|       "--panel-arrow-offset",
 | |
|       offset + "px"
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _getCustomizableChildForNode(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 = CustomizableUI.getCustomizationTarget(areaNode);
 | |
|       if (customizationTarget && customizationTarget != areaNode) {
 | |
|         areas.push(customizationTarget.id);
 | |
|       }
 | |
|       let overflowTarget =
 | |
|         areaNode && areaNode.getAttribute("default-overflowtarget");
 | |
|       if (overflowTarget) {
 | |
|         areas.push(overflowTarget);
 | |
|       }
 | |
|     }
 | |
|     areas.push(kPaletteId);
 | |
| 
 | |
|     while (aNode && aNode.parentNode) {
 | |
|       let parent = aNode.parentNode;
 | |
|       if (areas.includes(parent.id)) {
 | |
|         return aNode;
 | |
|       }
 | |
|       aNode = parent;
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   _promiseWidgetAnimationOut(aNode) {
 | |
|     if (
 | |
|       this.window.gReduceMotion ||
 | |
|       aNode.getAttribute("cui-anchorid") == "nav-bar-overflow-button" ||
 | |
|       (aNode.tagName != "toolbaritem" && aNode.tagName != "toolbarbutton") ||
 | |
|       (aNode.id == "downloads-button" && aNode.hidden)
 | |
|     ) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     let animationNode;
 | |
|     if (aNode.parentNode && aNode.parentNode.id.startsWith("wrapper-")) {
 | |
|       animationNode = aNode.parentNode;
 | |
|     } else {
 | |
|       animationNode = aNode;
 | |
|     }
 | |
|     return new Promise(resolve => {
 | |
|       function cleanupCustomizationExit() {
 | |
|         resolveAnimationPromise();
 | |
|       }
 | |
| 
 | |
|       function cleanupWidgetAnimationEnd(e) {
 | |
|         if (
 | |
|           e.animationName == "widget-animate-out" &&
 | |
|           e.target.id == animationNode.id
 | |
|         ) {
 | |
|           resolveAnimationPromise();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       function resolveAnimationPromise() {
 | |
|         animationNode.removeEventListener(
 | |
|           "animationend",
 | |
|           cleanupWidgetAnimationEnd
 | |
|         );
 | |
|         animationNode.removeEventListener(
 | |
|           "customizationending",
 | |
|           cleanupCustomizationExit
 | |
|         );
 | |
|         resolve(animationNode);
 | |
|       }
 | |
| 
 | |
|       // Wait until the next frame before setting the class to ensure
 | |
|       // we do start the animation.
 | |
|       this.window.requestAnimationFrame(() => {
 | |
|         this.window.requestAnimationFrame(() => {
 | |
|           animationNode.classList.add("animate-out");
 | |
|           animationNode.ownerGlobal.gNavToolbox.addEventListener(
 | |
|             "customizationending",
 | |
|             cleanupCustomizationExit
 | |
|           );
 | |
|           animationNode.addEventListener(
 | |
|             "animationend",
 | |
|             cleanupWidgetAnimationEnd
 | |
|           );
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   async addToToolbar(aNode, aReason) {
 | |
|     aNode = this._getCustomizableChildForNode(aNode);
 | |
|     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
 | |
|       aNode = aNode.firstElementChild;
 | |
|     }
 | |
|     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
 | |
|     let animationNode;
 | |
|     if (widgetAnimationPromise) {
 | |
|       animationNode = await widgetAnimationPromise;
 | |
|     }
 | |
| 
 | |
|     let widgetToAdd = aNode.id;
 | |
|     if (
 | |
|       CustomizableUI.isSpecialWidget(widgetToAdd) &&
 | |
|       aNode.closest("#customization-palette")
 | |
|     ) {
 | |
|       widgetToAdd = widgetToAdd.match(
 | |
|         /^customizableui-special-(spring|spacer|separator)/
 | |
|       )[1];
 | |
|     }
 | |
| 
 | |
|     CustomizableUI.addWidgetToArea(widgetToAdd, CustomizableUI.AREA_NAVBAR);
 | |
|     lazy.BrowserUsageTelemetry.recordWidgetChange(
 | |
|       widgetToAdd,
 | |
|       CustomizableUI.AREA_NAVBAR
 | |
|     );
 | |
|     if (!this._customizing) {
 | |
|       CustomizableUI.dispatchToolboxEvent("customizationchange");
 | |
|     }
 | |
| 
 | |
|     // If the user explicitly moves this item, turn off autohide.
 | |
|     if (aNode.id == "downloads-button") {
 | |
|       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
 | |
|       if (this._customizing) {
 | |
|         this._showDownloadsAutoHidePanel();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (animationNode) {
 | |
|       animationNode.classList.remove("animate-out");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   async addToPanel(aNode, aReason) {
 | |
|     aNode = this._getCustomizableChildForNode(aNode);
 | |
|     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
 | |
|       aNode = aNode.firstElementChild;
 | |
|     }
 | |
|     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
 | |
|     let animationNode;
 | |
|     if (widgetAnimationPromise) {
 | |
|       animationNode = await widgetAnimationPromise;
 | |
|     }
 | |
| 
 | |
|     let panel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
 | |
|     CustomizableUI.addWidgetToArea(aNode.id, panel);
 | |
|     lazy.BrowserUsageTelemetry.recordWidgetChange(aNode.id, panel, aReason);
 | |
|     if (!this._customizing) {
 | |
|       CustomizableUI.dispatchToolboxEvent("customizationchange");
 | |
|     }
 | |
| 
 | |
|     // If the user explicitly moves this item, turn off autohide.
 | |
|     if (aNode.id == "downloads-button") {
 | |
|       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
 | |
|       if (this._customizing) {
 | |
|         this._showDownloadsAutoHidePanel();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (animationNode) {
 | |
|       animationNode.classList.remove("animate-out");
 | |
|     }
 | |
|     if (!this.window.gReduceMotion) {
 | |
|       let overflowButton = this.$("nav-bar-overflow-button");
 | |
|       overflowButton.setAttribute("animate", "true");
 | |
|       overflowButton.addEventListener(
 | |
|         "animationend",
 | |
|         function onAnimationEnd(event) {
 | |
|           if (event.animationName.startsWith("overflow-animation")) {
 | |
|             this.removeEventListener("animationend", onAnimationEnd);
 | |
|             this.removeAttribute("animate");
 | |
|           }
 | |
|         }
 | |
|       );
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   async removeFromArea(aNode, aReason) {
 | |
|     aNode = this._getCustomizableChildForNode(aNode);
 | |
|     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
 | |
|       aNode = aNode.firstElementChild;
 | |
|     }
 | |
|     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
 | |
|     let animationNode;
 | |
|     if (widgetAnimationPromise) {
 | |
|       animationNode = await widgetAnimationPromise;
 | |
|     }
 | |
| 
 | |
|     CustomizableUI.removeWidgetFromArea(aNode.id);
 | |
|     lazy.BrowserUsageTelemetry.recordWidgetChange(aNode.id, null, aReason);
 | |
|     if (!this._customizing) {
 | |
|       CustomizableUI.dispatchToolboxEvent("customizationchange");
 | |
|     }
 | |
| 
 | |
|     // If the user explicitly removes this item, turn off autohide.
 | |
|     if (aNode.id == "downloads-button") {
 | |
|       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
 | |
|       if (this._customizing) {
 | |
|         this._showDownloadsAutoHidePanel();
 | |
|       }
 | |
|     }
 | |
|     if (animationNode) {
 | |
|       animationNode.classList.remove("animate-out");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   populatePalette() {
 | |
|     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);
 | |
|       }
 | |
| 
 | |
|       let flexSpace = CustomizableUI.createSpecialWidget(
 | |
|         "spring",
 | |
|         this.document
 | |
|       );
 | |
|       fragment.appendChild(this.wrapToolbarItem(flexSpace, "palette"));
 | |
| 
 | |
|       this.visiblePalette.appendChild(fragment);
 | |
|       this._stowedPalette = this.window.gNavToolbox.palette;
 | |
|       this.window.gNavToolbox.palette = this.visiblePalette;
 | |
| 
 | |
|       // Now that the palette items are all here, disable all commands.
 | |
|       // We do this here rather than directly in `enter` because we
 | |
|       // need to do/undo this when we're called from reset(), too.
 | |
|       this._updateCommandsDisabledState(true);
 | |
|     } catch (ex) {
 | |
|       lazy.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(aWidget, aPlace) {
 | |
|     let widgetNode = aWidget.forWindow(this.window).node;
 | |
|     if (!widgetNode) {
 | |
|       lazy.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() {
 | |
|     // Quick, undo the command disabling before we depopulate completely:
 | |
|     this._updateCommandsDisabledState(false);
 | |
| 
 | |
|     this.visiblePalette.hidden = true;
 | |
|     let paletteChild = this.visiblePalette.firstElementChild;
 | |
|     let nextChild;
 | |
|     while (paletteChild) {
 | |
|       nextChild = paletteChild.nextElementSibling;
 | |
|       let itemId = paletteChild.firstElementChild.id;
 | |
|       if (CustomizableUI.isSpecialWidget(itemId)) {
 | |
|         this.visiblePalette.removeChild(paletteChild);
 | |
|       } else {
 | |
|         // XXXunf Currently this doesn't destroy the (now unused) node in the
 | |
|         //       API provider case. 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.
 | |
|         let unwrappedPaletteItem = this.unwrapToolbarItem(paletteChild);
 | |
|         this._stowedPalette.appendChild(unwrappedPaletteItem);
 | |
|       }
 | |
| 
 | |
|       paletteChild = nextChild;
 | |
|     }
 | |
|     this.visiblePalette.hidden = false;
 | |
|     this.window.gNavToolbox.palette = this._stowedPalette;
 | |
|   },
 | |
| 
 | |
|   _updateCommandsDisabledState(shouldBeDisabled) {
 | |
|     for (let command of this.document.querySelectorAll("command")) {
 | |
|       if (!command.id || !this._enabledCommands.has(command.id)) {
 | |
|         if (shouldBeDisabled) {
 | |
|           if (command.getAttribute("disabled") != "true") {
 | |
|             command.setAttribute("disabled", true);
 | |
|           } else {
 | |
|             command.setAttribute("wasdisabled", true);
 | |
|           }
 | |
|         } else if (command.getAttribute("wasdisabled") != "true") {
 | |
|           command.removeAttribute("disabled");
 | |
|         } else {
 | |
|           command.removeAttribute("wasdisabled");
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   isCustomizableItem(aNode) {
 | |
|     return (
 | |
|       aNode.localName == "toolbarbutton" ||
 | |
|       aNode.localName == "toolbaritem" ||
 | |
|       aNode.localName == "toolbarseparator" ||
 | |
|       aNode.localName == "toolbarspring" ||
 | |
|       aNode.localName == "toolbarspacer"
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   isWrappedToolbarItem(aNode) {
 | |
|     return aNode.localName == "toolbarpaletteitem";
 | |
|   },
 | |
| 
 | |
|   deferredWrapToolbarItem(aNode, aPlace) {
 | |
|     return new Promise(resolve => {
 | |
|       dispatchFunction(() => {
 | |
|         let wrapper = this.wrapToolbarItem(aNode, aPlace);
 | |
|         resolve(wrapper);
 | |
|       });
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   wrapToolbarItem(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;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Helper to set the label, either directly or to set up the translation
 | |
|    * observer so we can set the label once it's available.
 | |
|    */
 | |
|   _updateWrapperLabel(aNode, aIsUpdate, aWrapper = aNode.parentElement) {
 | |
|     if (aNode.hasAttribute("label")) {
 | |
|       aWrapper.setAttribute("title", aNode.getAttribute("label"));
 | |
|       aWrapper.setAttribute("tooltiptext", aNode.getAttribute("label"));
 | |
|     } else if (aNode.hasAttribute("title")) {
 | |
|       aWrapper.setAttribute("title", aNode.getAttribute("title"));
 | |
|       aWrapper.setAttribute("tooltiptext", aNode.getAttribute("title"));
 | |
|     } else if (aNode.hasAttribute("data-l10n-id") && !aIsUpdate) {
 | |
|       this._translationObserver.observe(aNode, {
 | |
|         attributes: true,
 | |
|         attributeFilter: ["label", "title"],
 | |
|       });
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Called when a node without a label or title is updated.
 | |
|    */
 | |
|   _onTranslations(aMutations) {
 | |
|     for (let mut of aMutations) {
 | |
|       let { target } = mut;
 | |
|       if (
 | |
|         target.parentElement?.localName == "toolbarpaletteitem" &&
 | |
|         (target.hasAttribute("label") || mut.target.hasAttribute("title"))
 | |
|       ) {
 | |
|         this._updateWrapperLabel(target, true);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   createOrUpdateWrapper(aNode, aPlace, aIsUpdate) {
 | |
|     let wrapper;
 | |
|     if (
 | |
|       aIsUpdate &&
 | |
|       aNode.parentNode &&
 | |
|       aNode.parentNode.localName == "toolbarpaletteitem"
 | |
|     ) {
 | |
|       wrapper = aNode.parentNode;
 | |
|       aPlace = wrapper.getAttribute("place");
 | |
|     } else {
 | |
|       wrapper = this.document.createXULElement("toolbarpaletteitem");
 | |
|       // "place" is used to show the label 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.
 | |
|     // Note that some buttons opt out of this with the
 | |
|     // keepbroadcastattributeswhencustomizing attribute.
 | |
|     if (
 | |
|       aNode.hasAttribute("command") &&
 | |
|       aNode.getAttribute(kKeepBroadcastAttributes) != "true"
 | |
|     ) {
 | |
|       wrapper.setAttribute("itemcommand", aNode.getAttribute("command"));
 | |
|       aNode.removeAttribute("command");
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       aNode.hasAttribute("observes") &&
 | |
|       aNode.getAttribute(kKeepBroadcastAttributes) != "true"
 | |
|     ) {
 | |
|       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"));
 | |
|     }
 | |
| 
 | |
|     this._updateWrapperLabel(aNode, aIsUpdate, wrapper);
 | |
| 
 | |
|     if (aNode.hasAttribute("flex")) {
 | |
|       wrapper.setAttribute("flex", aNode.getAttribute("flex"));
 | |
|     }
 | |
| 
 | |
|     let removable =
 | |
|       aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
 | |
|     wrapper.setAttribute("removable", removable);
 | |
| 
 | |
|     // Allow touch events to initiate dragging in customize mode.
 | |
|     // This is only supported on Windows for now.
 | |
|     wrapper.setAttribute("touchdownstartsdrag", "true");
 | |
| 
 | |
|     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);
 | |
|     }
 | |
| 
 | |
|     if (CustomizableUI.isSpecialWidget(aNode.id)) {
 | |
|       wrapper.setAttribute(
 | |
|         "title",
 | |
|         lazy.gWidgetsBundle.GetStringFromName(aNode.nodeName + ".label")
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return wrapper;
 | |
|   },
 | |
| 
 | |
|   deferredUnwrapToolbarItem(aWrapper) {
 | |
|     return new Promise(resolve => {
 | |
|       dispatchFunction(() => {
 | |
|         let item = null;
 | |
|         try {
 | |
|           item = this.unwrapToolbarItem(aWrapper);
 | |
|         } catch (ex) {
 | |
|           console.error(ex);
 | |
|         }
 | |
|         resolve(item);
 | |
|       });
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   unwrapToolbarItem(aWrapper) {
 | |
|     if (aWrapper.nodeName != "toolbarpaletteitem") {
 | |
|       return aWrapper;
 | |
|     }
 | |
|     aWrapper.removeEventListener("mousedown", this);
 | |
|     aWrapper.removeEventListener("mouseup", this);
 | |
| 
 | |
|     let place = aWrapper.getAttribute("place");
 | |
| 
 | |
|     let toolbarItem = aWrapper.firstElementChild;
 | |
|     if (!toolbarItem) {
 | |
|       lazy.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.$(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;
 | |
|   },
 | |
| 
 | |
|   async _wrapToolbarItem(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)) {
 | |
|         await this.deferredWrapToolbarItem(
 | |
|           child,
 | |
|           CustomizableUI.getPlaceForItem(child)
 | |
|         ).catch(lazy.log.error);
 | |
|       }
 | |
|     }
 | |
|     this.areas.add(target);
 | |
|     return target;
 | |
|   },
 | |
| 
 | |
|   _wrapToolbarItemSync(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) {
 | |
|       lazy.log.error(ex, ex.stack);
 | |
|     }
 | |
| 
 | |
|     this.areas.add(target);
 | |
|     return target;
 | |
|   },
 | |
| 
 | |
|   async _wrapToolbarItems() {
 | |
|     for (let area of CustomizableUI.areas) {
 | |
|       await this._wrapToolbarItem(area);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _addDragHandlers(aTarget) {
 | |
|     // Allow dropping on the padding of the arrow panel.
 | |
|     if (aTarget.id == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL) {
 | |
|       aTarget = this.$("customization-panelHolder");
 | |
|     }
 | |
|     aTarget.addEventListener("dragstart", this, true);
 | |
|     aTarget.addEventListener("dragover", this, true);
 | |
|     aTarget.addEventListener("dragleave", this, true);
 | |
|     aTarget.addEventListener("drop", this, true);
 | |
|     aTarget.addEventListener("dragend", this, true);
 | |
|   },
 | |
| 
 | |
|   _wrapItemsInArea(target) {
 | |
|     for (let child of target.children) {
 | |
|       if (this.isCustomizableItem(child)) {
 | |
|         this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removeDragHandlers(aTarget) {
 | |
|     // Remove handler from different target if it was added to
 | |
|     // allow dropping on the padding of the arrow panel.
 | |
|     if (aTarget.id == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL) {
 | |
|       aTarget = this.$("customization-panelHolder");
 | |
|     }
 | |
|     aTarget.removeEventListener("dragstart", this, true);
 | |
|     aTarget.removeEventListener("dragover", this, true);
 | |
|     aTarget.removeEventListener("dragleave", this, true);
 | |
|     aTarget.removeEventListener("drop", this, true);
 | |
|     aTarget.removeEventListener("dragend", this, true);
 | |
|   },
 | |
| 
 | |
|   _unwrapItemsInArea(target) {
 | |
|     for (let toolbarItem of target.children) {
 | |
|       if (this.isWrappedToolbarItem(toolbarItem)) {
 | |
|         this.unwrapToolbarItem(toolbarItem);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _unwrapToolbarItems() {
 | |
|     return (async () => {
 | |
|       for (let target of this.areas) {
 | |
|         for (let toolbarItem of target.children) {
 | |
|           if (this.isWrappedToolbarItem(toolbarItem)) {
 | |
|             await this.deferredUnwrapToolbarItem(toolbarItem);
 | |
|           }
 | |
|         }
 | |
|         this._removeDragHandlers(target);
 | |
|       }
 | |
|       this.areas.clear();
 | |
|     })().catch(lazy.log.error);
 | |
|   },
 | |
| 
 | |
|   reset() {
 | |
|     this.resetting = true;
 | |
|     // Disable the reset button temporarily while resetting:
 | |
|     let btn = this.$("customization-reset-button");
 | |
|     btn.disabled = true;
 | |
|     return (async () => {
 | |
|       this._depopulatePalette();
 | |
|       await this._unwrapToolbarItems();
 | |
| 
 | |
|       CustomizableUI.reset();
 | |
| 
 | |
|       await this._wrapToolbarItems();
 | |
|       this.populatePalette();
 | |
| 
 | |
|       this._updateResetButton();
 | |
|       this._updateUndoResetButton();
 | |
|       this._updateEmptyPaletteNotice();
 | |
|       this._moveDownloadsButtonToNavBar = false;
 | |
|       this.resetting = false;
 | |
|       if (!this._wantToBeInCustomizeMode) {
 | |
|         this.exit();
 | |
|       }
 | |
|     })().catch(lazy.log.error);
 | |
|   },
 | |
| 
 | |
|   undoReset() {
 | |
|     this.resetting = true;
 | |
| 
 | |
|     return (async () => {
 | |
|       this._depopulatePalette();
 | |
|       await this._unwrapToolbarItems();
 | |
| 
 | |
|       CustomizableUI.undoReset();
 | |
| 
 | |
|       await this._wrapToolbarItems();
 | |
|       this.populatePalette();
 | |
| 
 | |
|       this._updateResetButton();
 | |
|       this._updateUndoResetButton();
 | |
|       this._updateEmptyPaletteNotice();
 | |
|       this._moveDownloadsButtonToNavBar = false;
 | |
|       this.resetting = false;
 | |
|     })().catch(lazy.log.error);
 | |
|   },
 | |
| 
 | |
|   _onToolbarVisibilityChange(aEvent) {
 | |
|     let toolbar = aEvent.target;
 | |
|     if (
 | |
|       aEvent.detail.visible &&
 | |
|       toolbar.getAttribute("customizable") == "true"
 | |
|     ) {
 | |
|       toolbar.setAttribute("customizing", "true");
 | |
|     } else {
 | |
|       toolbar.removeAttribute("customizing");
 | |
|     }
 | |
|     this._onUIChange();
 | |
|   },
 | |
| 
 | |
|   onWidgetMoved(aWidgetId, aArea, aOldPosition, aNewPosition) {
 | |
|     this._onUIChange();
 | |
|   },
 | |
| 
 | |
|   onWidgetAdded(aWidgetId, aArea, aPosition) {
 | |
|     this._onUIChange();
 | |
|   },
 | |
| 
 | |
|   onWidgetRemoved(aWidgetId, aArea) {
 | |
|     this._onUIChange();
 | |
|   },
 | |
| 
 | |
|   onWidgetBeforeDOMChange(aNodeToChange, aSecondaryNode, aContainer) {
 | |
|     if (aContainer.ownerGlobal != this.window || this.resetting) {
 | |
|       return;
 | |
|     }
 | |
|     // 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(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);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onWidgetDestroyed(aWidgetId) {
 | |
|     let wrapper = this.$("wrapper-" + aWidgetId);
 | |
|     if (wrapper) {
 | |
|       wrapper.remove();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onWidgetAfterCreation(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.$(aWidgetId);
 | |
|       if (widgetNode) {
 | |
|         this.wrapToolbarItem(widgetNode, "palette");
 | |
|       } else {
 | |
|         let widget = CustomizableUI.getWidget(aWidgetId);
 | |
|         this.visiblePalette.appendChild(
 | |
|           this.makePaletteItem(widget, "palette")
 | |
|         );
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onAreaNodeRegistered(aArea, aContainer) {
 | |
|     if (aContainer.ownerDocument == this.document) {
 | |
|       this._wrapItemsInArea(aContainer);
 | |
|       this._addDragHandlers(aContainer);
 | |
|       this.areas.add(aContainer);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onAreaNodeUnregistered(aArea, aContainer, aReason) {
 | |
|     if (
 | |
|       aContainer.ownerDocument == this.document &&
 | |
|       aReason == CustomizableUI.REASON_AREA_UNREGISTERED
 | |
|     ) {
 | |
|       this._unwrapItemsInArea(aContainer);
 | |
|       this._removeDragHandlers(aContainer);
 | |
|       this.areas.delete(aContainer);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   openAddonsManagerThemes() {
 | |
|     this.window.BrowserOpenAddonsMgr("addons://list/theme");
 | |
|   },
 | |
| 
 | |
|   getMoreThemes(aEvent) {
 | |
|     aEvent.target.parentNode.parentNode.hidePopup();
 | |
|     let getMoreURL = Services.urlFormatter.formatURLPref(
 | |
|       "lightweightThemes.getMoreURL"
 | |
|     );
 | |
|     this.window.openTrustedLinkIn(getMoreURL, "tab");
 | |
|   },
 | |
| 
 | |
|   updateUIDensity(mode) {
 | |
|     this.window.gUIDensity.update(mode);
 | |
|     this._updateOverflowPanelArrowOffset();
 | |
|   },
 | |
| 
 | |
|   setUIDensity(mode) {
 | |
|     let win = this.window;
 | |
|     let gUIDensity = win.gUIDensity;
 | |
|     let currentDensity = gUIDensity.getCurrentDensity();
 | |
|     let panel = win.document.getElementById("customization-uidensity-menu");
 | |
| 
 | |
|     Services.prefs.setIntPref(gUIDensity.uiDensityPref, mode);
 | |
| 
 | |
|     // If the user is choosing a different UI density mode while
 | |
|     // the mode is overriden to Touch, remove the override.
 | |
|     if (currentDensity.overridden) {
 | |
|       Services.prefs.setBoolPref(gUIDensity.autoTouchModePref, false);
 | |
|     }
 | |
| 
 | |
|     this._onUIChange();
 | |
|     panel.hidePopup();
 | |
|     this._updateOverflowPanelArrowOffset();
 | |
|   },
 | |
| 
 | |
|   resetUIDensity() {
 | |
|     this.window.gUIDensity.update();
 | |
|     this._updateOverflowPanelArrowOffset();
 | |
|   },
 | |
| 
 | |
|   onUIDensityMenuShowing() {
 | |
|     let win = this.window;
 | |
|     let doc = win.document;
 | |
|     let gUIDensity = win.gUIDensity;
 | |
|     let currentDensity = gUIDensity.getCurrentDensity();
 | |
| 
 | |
|     let normalItem = doc.getElementById(
 | |
|       "customization-uidensity-menuitem-normal"
 | |
|     );
 | |
|     normalItem.mode = gUIDensity.MODE_NORMAL;
 | |
| 
 | |
|     let items = [normalItem];
 | |
| 
 | |
|     let compactItem = doc.getElementById(
 | |
|       "customization-uidensity-menuitem-compact"
 | |
|     );
 | |
|     compactItem.mode = gUIDensity.MODE_COMPACT;
 | |
| 
 | |
|     if (Services.prefs.getBoolPref(kCompactModeShowPref)) {
 | |
|       compactItem.hidden = false;
 | |
|       items.push(compactItem);
 | |
|     } else {
 | |
|       compactItem.hidden = true;
 | |
|     }
 | |
| 
 | |
|     let touchItem = doc.getElementById(
 | |
|       "customization-uidensity-menuitem-touch"
 | |
|     );
 | |
|     // Touch mode can not be enabled in OSX right now.
 | |
|     if (touchItem) {
 | |
|       touchItem.mode = gUIDensity.MODE_TOUCH;
 | |
|       items.push(touchItem);
 | |
|     }
 | |
| 
 | |
|     // Mark the active mode menuitem.
 | |
|     for (let item of items) {
 | |
|       if (item.mode == currentDensity.mode) {
 | |
|         item.setAttribute("aria-checked", "true");
 | |
|         item.setAttribute("active", "true");
 | |
|       } else {
 | |
|         item.removeAttribute("aria-checked");
 | |
|         item.removeAttribute("active");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Add menu items for automatically switching to Touch mode in Windows Tablet Mode.
 | |
|     if (AppConstants.platform == "win") {
 | |
|       let spacer = doc.getElementById("customization-uidensity-touch-spacer");
 | |
|       let checkbox = doc.getElementById(
 | |
|         "customization-uidensity-autotouchmode-checkbox"
 | |
|       );
 | |
|       spacer.removeAttribute("hidden");
 | |
|       checkbox.removeAttribute("hidden");
 | |
| 
 | |
|       // Show a hint that the UI density was overridden automatically.
 | |
|       if (currentDensity.overridden) {
 | |
|         let sb = Services.strings.createBundle(
 | |
|           "chrome://browser/locale/uiDensity.properties"
 | |
|         );
 | |
|         touchItem.setAttribute(
 | |
|           "acceltext",
 | |
|           sb.GetStringFromName("uiDensity.menuitem-touch.acceltext")
 | |
|         );
 | |
|       } else {
 | |
|         touchItem.removeAttribute("acceltext");
 | |
|       }
 | |
| 
 | |
|       let autoTouchMode = Services.prefs.getBoolPref(
 | |
|         win.gUIDensity.autoTouchModePref
 | |
|       );
 | |
|       if (autoTouchMode) {
 | |
|         checkbox.setAttribute("checked", "true");
 | |
|       } else {
 | |
|         checkbox.removeAttribute("checked");
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   updateAutoTouchMode(checked) {
 | |
|     Services.prefs.setBoolPref("browser.touchmode.auto", checked);
 | |
|     // Re-render the menu items since the active mode might have
 | |
|     // change because of this.
 | |
|     this.onUIDensityMenuShowing();
 | |
|     this._onUIChange();
 | |
|   },
 | |
| 
 | |
|   _onUIChange() {
 | |
|     this._changed = true;
 | |
|     if (!this.resetting) {
 | |
|       this._updateResetButton();
 | |
|       this._updateUndoResetButton();
 | |
|       this._updateEmptyPaletteNotice();
 | |
|     }
 | |
|     CustomizableUI.dispatchToolboxEvent("customizationchange");
 | |
|   },
 | |
| 
 | |
|   _updateEmptyPaletteNotice() {
 | |
|     let paletteItems =
 | |
|       this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
 | |
|     let whimsyButton = this.$("whimsy-button");
 | |
| 
 | |
|     if (
 | |
|       paletteItems.length == 1 &&
 | |
|       paletteItems[0].id.includes("wrapper-customizableui-special-spring")
 | |
|     ) {
 | |
|       whimsyButton.hidden = false;
 | |
|     } else {
 | |
|       this.togglePong(false);
 | |
|       whimsyButton.hidden = true;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _updateResetButton() {
 | |
|     let btn = this.$("customization-reset-button");
 | |
|     btn.disabled = CustomizableUI.inDefaultState;
 | |
|   },
 | |
| 
 | |
|   _updateUndoResetButton() {
 | |
|     let undoResetButton = this.$("customization-undo-reset-button");
 | |
|     undoResetButton.hidden = !CustomizableUI.canUndoReset;
 | |
|   },
 | |
| 
 | |
|   _updateTouchBarButton() {
 | |
|     if (AppConstants.platform != "macosx") {
 | |
|       return;
 | |
|     }
 | |
|     let touchBarButton = this.$("customization-touchbar-button");
 | |
|     let touchBarSpacer = this.$("customization-touchbar-spacer");
 | |
| 
 | |
|     let isTouchBarInitialized = lazy.gTouchBarUpdater.isTouchBarInitialized();
 | |
|     touchBarButton.hidden = !isTouchBarInitialized;
 | |
|     touchBarSpacer.hidden = !isTouchBarInitialized;
 | |
|   },
 | |
| 
 | |
|   _updateDensityMenu() {
 | |
|     // If we're entering Customize Mode, and we're using compact mode,
 | |
|     // then show the button after that.
 | |
|     let gUIDensity = this.window.gUIDensity;
 | |
|     if (gUIDensity.getCurrentDensity().mode == gUIDensity.MODE_COMPACT) {
 | |
|       Services.prefs.setBoolPref(kCompactModeShowPref, true);
 | |
|     }
 | |
| 
 | |
|     let button = this.document.getElementById("customization-uidensity-button");
 | |
|     button.hidden =
 | |
|       !Services.prefs.getBoolPref(kCompactModeShowPref) &&
 | |
|       !button.querySelector("#customization-uidensity-menuitem-touch");
 | |
|   },
 | |
| 
 | |
|   handleEvent(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 "dragleave":
 | |
|         this._onDragLeave(aEvent);
 | |
|         break;
 | |
|       case "dragend":
 | |
|         this._onDragEnd(aEvent);
 | |
|         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;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * We handle dragover/drop on the outer palette separately
 | |
|    * to avoid overlap with other drag/drop handlers.
 | |
|    */
 | |
|   _setupPaletteDragging() {
 | |
|     this._addDragHandlers(this.visiblePalette);
 | |
| 
 | |
|     this.paletteDragHandler = aEvent => {
 | |
|       let originalTarget = aEvent.originalTarget;
 | |
|       if (
 | |
|         this._isUnwantedDragDrop(aEvent) ||
 | |
|         this.visiblePalette.contains(originalTarget) ||
 | |
|         this.$("customization-panelHolder").contains(originalTarget)
 | |
|       ) {
 | |
|         return;
 | |
|       }
 | |
|       // We have a dragover/drop on the palette.
 | |
|       if (aEvent.type == "dragover") {
 | |
|         this._onDragOver(aEvent, this.visiblePalette);
 | |
|       } else {
 | |
|         this._onDragDrop(aEvent, this.visiblePalette);
 | |
|       }
 | |
|     };
 | |
|     let contentContainer = this.$("customization-content-container");
 | |
|     contentContainer.addEventListener(
 | |
|       "dragover",
 | |
|       this.paletteDragHandler,
 | |
|       true
 | |
|     );
 | |
|     contentContainer.addEventListener("drop", this.paletteDragHandler, true);
 | |
|   },
 | |
| 
 | |
|   _teardownPaletteDragging() {
 | |
|     lazy.DragPositionManager.stop();
 | |
|     this._removeDragHandlers(this.visiblePalette);
 | |
| 
 | |
|     let contentContainer = this.$("customization-content-container");
 | |
|     contentContainer.removeEventListener(
 | |
|       "dragover",
 | |
|       this.paletteDragHandler,
 | |
|       true
 | |
|     );
 | |
|     contentContainer.removeEventListener("drop", this.paletteDragHandler, true);
 | |
|     delete this.paletteDragHandler;
 | |
|   },
 | |
| 
 | |
|   observe(aSubject, aTopic, aData) {
 | |
|     switch (aTopic) {
 | |
|       case "nsPref:changed":
 | |
|         this._updateResetButton();
 | |
|         this._updateUndoResetButton();
 | |
|         if (this._canDrawInTitlebar()) {
 | |
|           this._updateTitlebarCheckbox();
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   async onInstalled(addon) {
 | |
|     await this.onEnabled(addon);
 | |
|   },
 | |
| 
 | |
|   async onEnabled(addon) {
 | |
|     if (addon.type != "theme") {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this._nextThemeChangeUserTriggered) {
 | |
|       this._onUIChange();
 | |
|     }
 | |
|     this._nextThemeChangeUserTriggered = false;
 | |
|   },
 | |
| 
 | |
|   _canDrawInTitlebar() {
 | |
|     return this.window.TabsInTitlebar.systemSupported;
 | |
|   },
 | |
| 
 | |
|   _ensureCustomizationPanels() {
 | |
|     let template = this.$("customizationPanel");
 | |
|     template.replaceWith(template.content);
 | |
| 
 | |
|     let wrapper = this.$("customModeWrapper");
 | |
|     wrapper.replaceWith(wrapper.content);
 | |
|   },
 | |
| 
 | |
|   _updateTitlebarCheckbox() {
 | |
|     let drawInTitlebar = Services.appinfo.drawInTitlebar;
 | |
|     let checkbox = this.$("customization-titlebar-visibility-checkbox");
 | |
|     // Drawing in the titlebar means 'hiding' the titlebar.
 | |
|     // We use the attribute rather than a property because if we're not in
 | |
|     // customize mode the button is hidden and properties don't work.
 | |
|     if (drawInTitlebar) {
 | |
|       checkbox.removeAttribute("checked");
 | |
|     } else {
 | |
|       checkbox.setAttribute("checked", "true");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   toggleTitlebar(aShouldShowTitlebar) {
 | |
|     // Drawing in the titlebar means not showing the titlebar, hence the negation:
 | |
|     Services.prefs.setIntPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
 | |
|   },
 | |
| 
 | |
|   _getBoundsWithoutFlushing(element) {
 | |
|     return this.window.windowUtils.getBoundsWithoutFlushing(element);
 | |
|   },
 | |
| 
 | |
|   _onDragStart(aEvent) {
 | |
|     __dumpDragData(aEvent);
 | |
|     let item = aEvent.target;
 | |
|     while (item && item.localName != "toolbarpaletteitem") {
 | |
|       if (
 | |
|         item.localName == "toolbar" ||
 | |
|         item.id == kPaletteId ||
 | |
|         item.id == "customization-panelHolder"
 | |
|       ) {
 | |
|         return;
 | |
|       }
 | |
|       item = item.parentNode;
 | |
|     }
 | |
| 
 | |
|     let draggedItem = item.firstElementChild;
 | |
|     let placeForItem = CustomizableUI.getPlaceForItem(item);
 | |
| 
 | |
|     let dt = aEvent.dataTransfer;
 | |
|     let documentId = aEvent.target.ownerDocument.documentElement.id;
 | |
| 
 | |
|     dt.mozSetDataAt(kDragDataTypePrefix + documentId, draggedItem.id, 0);
 | |
|     dt.effectAllowed = "move";
 | |
| 
 | |
|     let itemRect = this._getBoundsWithoutFlushing(draggedItem);
 | |
|     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,
 | |
|     };
 | |
| 
 | |
|     let toolbarParent = draggedItem.closest("toolbar");
 | |
|     if (toolbarParent) {
 | |
|       let toolbarRect = this._getBoundsWithoutFlushing(toolbarParent);
 | |
|       toolbarParent.style.minHeight = toolbarRect.height + "px";
 | |
|     }
 | |
| 
 | |
|     gDraggingInToolbars = new Set();
 | |
| 
 | |
|     // Hack needed so that the dragimage will still show the
 | |
|     // item as it appeared before it was hidden.
 | |
|     this._initializeDragAfterMove = () => {
 | |
|       // 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;
 | |
|         lazy.DragPositionManager.start(this.window);
 | |
|         let canUsePrevSibling =
 | |
|           placeForItem == "toolbar" || placeForItem == "panel";
 | |
|         if (item.nextElementSibling) {
 | |
|           this._setDragActive(
 | |
|             item.nextElementSibling,
 | |
|             "before",
 | |
|             draggedItem.id,
 | |
|             placeForItem
 | |
|           );
 | |
|           this._dragOverItem = item.nextElementSibling;
 | |
|         } else if (canUsePrevSibling && item.previousElementSibling) {
 | |
|           this._setDragActive(
 | |
|             item.previousElementSibling,
 | |
|             "after",
 | |
|             draggedItem.id,
 | |
|             placeForItem
 | |
|           );
 | |
|           this._dragOverItem = item.previousElementSibling;
 | |
|         }
 | |
|         let currentArea = this._getCustomizableParent(item);
 | |
|         currentArea.setAttribute("draggingover", "true");
 | |
|       }
 | |
|       this._initializeDragAfterMove = null;
 | |
|       this.window.clearTimeout(this._dragInitializeTimeout);
 | |
|     };
 | |
|     this._dragInitializeTimeout = this.window.setTimeout(
 | |
|       this._initializeDragAfterMove,
 | |
|       0
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _onDragOver(aEvent, aOverrideTarget) {
 | |
|     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).length) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let draggedItemId = aEvent.dataTransfer.mozGetDataAt(
 | |
|       kDragDataTypePrefix + documentId,
 | |
|       0
 | |
|     );
 | |
|     let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
 | |
|     let targetArea = this._getCustomizableParent(
 | |
|       aOverrideTarget || 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 (!CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let targetAreaType = CustomizableUI.getPlaceForItem(targetArea);
 | |
|     let targetNode = this._getDragOverNode(
 | |
|       aEvent,
 | |
|       targetArea,
 | |
|       targetAreaType,
 | |
|       draggedItemId
 | |
|     );
 | |
| 
 | |
|     // We need to determine the place that the widget is being dropped in
 | |
|     // the target.
 | |
|     let dragOverItem, dragValue;
 | |
|     if (targetNode == CustomizableUI.getCustomizationTarget(targetArea)) {
 | |
|       // We'll assume if the user is dragging directly over the target, that
 | |
|       // they're attempting to append a child to that target.
 | |
|       dragOverItem =
 | |
|         (targetAreaType == "toolbar"
 | |
|           ? this._findVisiblePreviousSiblingNode(targetNode.lastElementChild)
 | |
|           : targetNode.lastElementChild) || targetNode;
 | |
|       dragValue = "after";
 | |
|     } else {
 | |
|       let targetParent = targetNode.parentNode;
 | |
|       let position = Array.prototype.indexOf.call(
 | |
|         targetParent.children,
 | |
|         targetNode
 | |
|       );
 | |
|       if (position == -1) {
 | |
|         dragOverItem =
 | |
|           targetAreaType == "toolbar"
 | |
|             ? this._findVisiblePreviousSiblingNode(targetNode.lastElementChild)
 | |
|             : targetNode.lastElementChild;
 | |
|         dragValue = "after";
 | |
|       } else {
 | |
|         dragOverItem = targetParent.children[position];
 | |
|         if (targetAreaType == "toolbar") {
 | |
|           // Check if the aDraggedItem is hovered past the first half of dragOverItem
 | |
|           let itemRect = this._getBoundsWithoutFlushing(dragOverItem);
 | |
|           let dropTargetCenter = itemRect.left + itemRect.width / 2;
 | |
|           let existingDir = dragOverItem.getAttribute("dragover");
 | |
|           let dirFactor = this.window.RTL_UI ? -1 : 1;
 | |
|           if (existingDir == "before") {
 | |
|             dropTargetCenter +=
 | |
|               ((parseInt(dragOverItem.style.borderInlineStartWidth) || 0) / 2) *
 | |
|               dirFactor;
 | |
|           } else {
 | |
|             dropTargetCenter -=
 | |
|               ((parseInt(dragOverItem.style.borderInlineEndWidth) || 0) / 2) *
 | |
|               dirFactor;
 | |
|           }
 | |
|           let before = this.window.RTL_UI
 | |
|             ? aEvent.clientX > dropTargetCenter
 | |
|             : aEvent.clientX < dropTargetCenter;
 | |
|           dragValue = before ? "before" : "after";
 | |
|         } else if (targetAreaType == "panel") {
 | |
|           let itemRect = this._getBoundsWithoutFlushing(dragOverItem);
 | |
|           let dropTargetCenter = itemRect.top + itemRect.height / 2;
 | |
|           let existingDir = dragOverItem.getAttribute("dragover");
 | |
|           if (existingDir == "before") {
 | |
|             dropTargetCenter +=
 | |
|               (parseInt(dragOverItem.style.borderBlockStartWidth) || 0) / 2;
 | |
|           } else {
 | |
|             dropTargetCenter -=
 | |
|               (parseInt(dragOverItem.style.borderBlockEndWidth) || 0) / 2;
 | |
|           }
 | |
|           dragValue = aEvent.clientY < dropTargetCenter ? "before" : "after";
 | |
|         } else {
 | |
|           dragValue = "before";
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (this._dragOverItem && dragOverItem != this._dragOverItem) {
 | |
|       this._cancelDragActive(this._dragOverItem, dragOverItem);
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       dragOverItem != this._dragOverItem ||
 | |
|       dragValue != dragOverItem.getAttribute("dragover")
 | |
|     ) {
 | |
|       if (dragOverItem != CustomizableUI.getCustomizationTarget(targetArea)) {
 | |
|         this._setDragActive(
 | |
|           dragOverItem,
 | |
|           dragValue,
 | |
|           draggedItemId,
 | |
|           targetAreaType
 | |
|         );
 | |
|       }
 | |
|       this._dragOverItem = dragOverItem;
 | |
|       targetArea.setAttribute("draggingover", "true");
 | |
|     }
 | |
| 
 | |
|     aEvent.preventDefault();
 | |
|     aEvent.stopPropagation();
 | |
|   },
 | |
| 
 | |
|   _onDragDrop(aEvent, aOverrideTarget) {
 | |
|     if (this._isUnwantedDragDrop(aEvent)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     __dumpDragData(aEvent);
 | |
|     this._initializeDragAfterMove = null;
 | |
|     this.window.clearTimeout(this._dragInitializeTimeout);
 | |
| 
 | |
|     let targetArea = this._getCustomizableParent(
 | |
|       aOverrideTarget || 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.nextElementSibling) {
 | |
|         targetNode = targetNode.nextElementSibling;
 | |
|       } else {
 | |
|         targetNode = targetArea;
 | |
|       }
 | |
|     }
 | |
|     if (targetNode.tagName == "toolbarpaletteitem") {
 | |
|       targetNode = targetNode.firstElementChild;
 | |
|     }
 | |
| 
 | |
|     this._cancelDragActive(this._dragOverItem, null, true);
 | |
| 
 | |
|     try {
 | |
|       this._applyDrop(
 | |
|         aEvent,
 | |
|         targetArea,
 | |
|         originArea,
 | |
|         draggedItemId,
 | |
|         targetNode
 | |
|       );
 | |
|     } catch (ex) {
 | |
|       lazy.log.error(ex, ex.stack);
 | |
|     }
 | |
| 
 | |
|     // If the user explicitly moves this item, turn off autohide.
 | |
|     if (draggedItemId == "downloads-button") {
 | |
|       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
 | |
|       this._showDownloadsAutoHidePanel();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _applyDrop(aEvent, aTargetArea, aOriginArea, aDraggedItemId, aTargetNode) {
 | |
|     let document = aEvent.target.ownerDocument;
 | |
|     let draggedItem = document.getElementById(aDraggedItemId);
 | |
|     draggedItem.hidden = false;
 | |
|     draggedItem.removeAttribute("mousedown");
 | |
| 
 | |
|     let toolbarParent = draggedItem.closest("toolbar");
 | |
|     if (toolbarParent) {
 | |
|       toolbarParent.style.removeProperty("min-height");
 | |
|     }
 | |
| 
 | |
|     // Do nothing if the target was dropped onto itself (ie, no change in area
 | |
|     // or position).
 | |
|     if (draggedItem == aTargetNode) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!CustomizableUI.canWidgetMoveToArea(aDraggedItemId, aTargetArea.id)) {
 | |
|       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, "drag");
 | |
|         lazy.BrowserUsageTelemetry.recordWidgetChange(
 | |
|           aDraggedItemId,
 | |
|           null,
 | |
|           "drag"
 | |
|         );
 | |
|         // 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);
 | |
|       }
 | |
|       this._onDragEnd(aEvent);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Skipintoolbarset items won't really be moved:
 | |
|     let areaCustomizationTarget =
 | |
|       CustomizableUI.getCustomizationTarget(aTargetArea);
 | |
|     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 == areaCustomizationTarget) {
 | |
|         areaCustomizationTarget.appendChild(draggedItem);
 | |
|       } else {
 | |
|         this.unwrapToolbarItem(aTargetNode.parentNode);
 | |
|         areaCustomizationTarget.insertBefore(draggedItem, aTargetNode);
 | |
|         this.wrapToolbarItem(aTargetNode, place);
 | |
|       }
 | |
|       this.wrapToolbarItem(draggedItem, place);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Force creating a new spacer/spring/separator if dragging from the palette
 | |
|     if (
 | |
|       CustomizableUI.isSpecialWidget(aDraggedItemId) &&
 | |
|       aOriginArea.id == kPaletteId
 | |
|     ) {
 | |
|       aDraggedItemId = aDraggedItemId.match(
 | |
|         /^customizableui-special-(spring|spacer|separator)/
 | |
|       )[1];
 | |
|     }
 | |
| 
 | |
|     // Is the target the customization area itself? If so, we just add the
 | |
|     // widget to the end of the area.
 | |
|     if (aTargetNode == areaCustomizationTarget) {
 | |
|       CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id);
 | |
|       lazy.BrowserUsageTelemetry.recordWidgetChange(
 | |
|         aDraggedItemId,
 | |
|         aTargetArea.id,
 | |
|         "drag"
 | |
|       );
 | |
|       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.nextElementSibling;
 | |
|       if (
 | |
|         itemForPlacement &&
 | |
|         itemForPlacement.nodeName == "toolbarpaletteitem"
 | |
|       ) {
 | |
|         itemForPlacement = itemForPlacement.firstElementChild;
 | |
|       }
 | |
|     }
 | |
|     if (itemForPlacement) {
 | |
|       let targetNodeId =
 | |
|         itemForPlacement.nodeName == "toolbarpaletteitem"
 | |
|           ? itemForPlacement.firstElementChild &&
 | |
|             itemForPlacement.firstElementChild.id
 | |
|           : itemForPlacement.id;
 | |
|       placement = CustomizableUI.getPlacementOfWidget(targetNodeId);
 | |
|     }
 | |
|     if (!placement) {
 | |
|       lazy.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);
 | |
|       lazy.BrowserUsageTelemetry.recordWidgetChange(
 | |
|         aDraggedItemId,
 | |
|         aTargetArea.id,
 | |
|         "drag"
 | |
|       );
 | |
|     } else {
 | |
|       CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position);
 | |
|       lazy.BrowserUsageTelemetry.recordWidgetChange(
 | |
|         aDraggedItemId,
 | |
|         aTargetArea.id,
 | |
|         "drag"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this._onDragEnd(aEvent);
 | |
| 
 | |
|     // 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);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _onDragLeave(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 dragleave 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.
 | |
|    *
 | |
|    * Note that that means that this function may be called multiple times by a single drag operation.
 | |
|    */
 | |
|   _onDragEnd(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");
 | |
| 
 | |
|       let toolbarParent = draggedWrapper.closest("toolbar");
 | |
|       if (toolbarParent) {
 | |
|         toolbarParent.style.removeProperty("min-height");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (this._dragOverItem) {
 | |
|       this._cancelDragActive(this._dragOverItem);
 | |
|       this._dragOverItem = null;
 | |
|     }
 | |
|     lazy.DragPositionManager.stop();
 | |
|   },
 | |
| 
 | |
|   _isUnwantedDragDrop(aEvent) {
 | |
|     // The synthesized events for tests generated by synthesizePlainDragAndDrop
 | |
|     // and 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(aItem, aValue, aDraggedItemId, aAreaType) {
 | |
|     if (!aItem) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (aItem.getAttribute("dragover") != aValue) {
 | |
|       aItem.setAttribute("dragover", aValue);
 | |
| 
 | |
|       let window = aItem.ownerGlobal;
 | |
|       let draggedItem = window.document.getElementById(aDraggedItemId);
 | |
|       if (aAreaType == "palette") {
 | |
|         this._setGridDragActive(aItem, draggedItem, aValue);
 | |
|       } else {
 | |
|         let targetArea = this._getCustomizableParent(aItem);
 | |
|         let makeSpaceImmediately = false;
 | |
|         if (!gDraggingInToolbars.has(targetArea.id)) {
 | |
|           gDraggingInToolbars.add(targetArea.id);
 | |
|           let draggedWrapper = this.$("wrapper-" + aDraggedItemId);
 | |
|           let originArea = this._getCustomizableParent(draggedWrapper);
 | |
|           makeSpaceImmediately = originArea == targetArea;
 | |
|         }
 | |
|         let propertyToMeasure = aAreaType == "toolbar" ? "width" : "height";
 | |
|         // Calculate width/height of the item when it'd be dropped in this position.
 | |
|         let borderWidth = this._getDragItemSize(aItem, draggedItem)[
 | |
|           propertyToMeasure
 | |
|         ];
 | |
|         let layoutSide = aAreaType == "toolbar" ? "Inline" : "Block";
 | |
|         let prop, otherProp;
 | |
|         if (aValue == "before") {
 | |
|           prop = "border" + layoutSide + "StartWidth";
 | |
|           otherProp = "border-" + layoutSide.toLowerCase() + "-end-width";
 | |
|         } else {
 | |
|           prop = "border" + layoutSide + "EndWidth";
 | |
|           otherProp = "border-" + layoutSide.toLowerCase() + "-start-width";
 | |
|         }
 | |
|         if (makeSpaceImmediately) {
 | |
|           aItem.setAttribute("notransition", "true");
 | |
|         }
 | |
|         aItem.style[prop] = borderWidth + "px";
 | |
|         aItem.style.removeProperty(otherProp);
 | |
|         if (makeSpaceImmediately) {
 | |
|           // Force a layout flush:
 | |
|           aItem.getBoundingClientRect();
 | |
|           aItem.removeAttribute("notransition");
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   _cancelDragActive(aItem, aNextItem, aNoTransition) {
 | |
|     let currentArea = this._getCustomizableParent(aItem);
 | |
|     if (!currentArea) {
 | |
|       return;
 | |
|     }
 | |
|     let nextArea = aNextItem ? this._getCustomizableParent(aNextItem) : null;
 | |
|     if (currentArea != nextArea) {
 | |
|       currentArea.removeAttribute("draggingover");
 | |
|     }
 | |
|     let areaType = CustomizableUI.getAreaType(currentArea.id);
 | |
|     if (areaType) {
 | |
|       if (aNoTransition) {
 | |
|         aItem.setAttribute("notransition", "true");
 | |
|       }
 | |
|       aItem.removeAttribute("dragover");
 | |
|       // Remove all property values in the case that the end padding
 | |
|       // had been set.
 | |
|       aItem.style.removeProperty("border-inline-start-width");
 | |
|       aItem.style.removeProperty("border-inline-end-width");
 | |
|       aItem.style.removeProperty("border-block-start-width");
 | |
|       aItem.style.removeProperty("border-block-end-width");
 | |
|       if (aNoTransition) {
 | |
|         // Force a layout flush:
 | |
|         aItem.getBoundingClientRect();
 | |
|         aItem.removeAttribute("notransition");
 | |
|       }
 | |
|     } else {
 | |
|       aItem.removeAttribute("dragover");
 | |
|       if (aNextItem) {
 | |
|         if (nextArea == currentArea) {
 | |
|           // No need to do anything if we're still dragging in this area:
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|       // Otherwise, clear everything out:
 | |
|       let positionManager =
 | |
|         lazy.DragPositionManager.getManagerForArea(currentArea);
 | |
|       positionManager.clearPlaceholders(currentArea, aNoTransition);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _setGridDragActive(aDragOverNode, aDraggedItem, aValue) {
 | |
|     let targetArea = this._getCustomizableParent(aDragOverNode);
 | |
|     let draggedWrapper = this.$("wrapper-" + aDraggedItem.id);
 | |
|     let originArea = this._getCustomizableParent(draggedWrapper);
 | |
|     let positionManager =
 | |
|       lazy.DragPositionManager.getManagerForArea(targetArea);
 | |
|     let draggedSize = this._getDragItemSize(aDragOverNode, aDraggedItem);
 | |
|     positionManager.insertPlaceholder(
 | |
|       targetArea,
 | |
|       aDragOverNode,
 | |
|       draggedSize,
 | |
|       originArea == targetArea
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _getDragItemSize(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.nextElementSibling;
 | |
|     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(aElement) {
 | |
|     if (aElement) {
 | |
|       // Deal with drag/drop on the padding of the panel.
 | |
|       let containingPanelHolder = aElement.closest(
 | |
|         "#customization-panelHolder"
 | |
|       );
 | |
|       if (containingPanelHolder) {
 | |
|         return containingPanelHolder.querySelector(
 | |
|           "#widget-overflow-fixed-list"
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let areas = CustomizableUI.areas;
 | |
|     areas.push(kPaletteId);
 | |
|     return aElement.closest(areas.map(a => "#" + CSS.escape(a)).join(","));
 | |
|   },
 | |
| 
 | |
|   _getDragOverNode(aEvent, aAreaElement, aAreaType, aDraggedItemId) {
 | |
|     let expectedParent =
 | |
|       CustomizableUI.getCustomizationTarget(aAreaElement) || aAreaElement;
 | |
|     if (!expectedParent.contains(aEvent.target)) {
 | |
|       return expectedParent;
 | |
|     }
 | |
|     // 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;
 | |
|     let bounds = this._getBoundsWithoutFlushing(boundsContainer);
 | |
|     dragX = Math.min(bounds.right, Math.max(dragX, bounds.left));
 | |
|     dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top));
 | |
| 
 | |
|     let targetNode;
 | |
|     if (aAreaType == "toolbar" || aAreaType == "panel") {
 | |
|       targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
 | |
|       while (targetNode && targetNode.parentNode != expectedParent) {
 | |
|         targetNode = targetNode.parentNode;
 | |
|       }
 | |
|     } else {
 | |
|       let positionManager =
 | |
|         lazy.DragPositionManager.getManagerForArea(aAreaElement);
 | |
|       // Make it relative to the container:
 | |
|       dragX -= bounds.left;
 | |
|       dragY -= bounds.top;
 | |
|       // Find the closest node:
 | |
|       targetNode = positionManager.find(aAreaElement, dragX, dragY);
 | |
|     }
 | |
|     return targetNode || aEvent.target;
 | |
|   },
 | |
| 
 | |
|   _onMouseDown(aEvent) {
 | |
|     lazy.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.setAttribute("mousedown", "true");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _onMouseUp(aEvent) {
 | |
|     lazy.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(aElement) {
 | |
|     while (aElement && aElement.localName != "toolbarpaletteitem") {
 | |
|       if (aElement.localName == "toolbar") {
 | |
|         return null;
 | |
|       }
 | |
|       aElement = aElement.parentNode;
 | |
|     }
 | |
|     return aElement;
 | |
|   },
 | |
| 
 | |
|   _findVisiblePreviousSiblingNode(aReferenceNode) {
 | |
|     while (
 | |
|       aReferenceNode &&
 | |
|       aReferenceNode.localName == "toolbarpaletteitem" &&
 | |
|       aReferenceNode.firstElementChild.hidden
 | |
|     ) {
 | |
|       aReferenceNode = aReferenceNode.previousElementSibling;
 | |
|     }
 | |
|     return aReferenceNode;
 | |
|   },
 | |
| 
 | |
|   onPaletteContextMenuShowing(event) {
 | |
|     let isFlexibleSpace = event.target.triggerNode.id.includes(
 | |
|       "wrapper-customizableui-special-spring"
 | |
|     );
 | |
|     event.target.querySelector(".customize-context-addToPanel").disabled =
 | |
|       isFlexibleSpace;
 | |
|   },
 | |
| 
 | |
|   onPanelContextMenuShowing(event) {
 | |
|     let inPermanentArea = !!event.target.triggerNode.closest(
 | |
|       "#widget-overflow-fixed-list"
 | |
|     );
 | |
|     let doc = event.target.ownerDocument;
 | |
|     doc.getElementById("customizationPanelItemContextMenuUnpin").hidden =
 | |
|       !inPermanentArea;
 | |
|     doc.getElementById("customizationPanelItemContextMenuPin").hidden =
 | |
|       inPermanentArea;
 | |
| 
 | |
|     doc.ownerGlobal.MozXULElement.insertFTLIfNeeded(
 | |
|       "browser/toolbarContextMenu.ftl"
 | |
|     );
 | |
|     event.target.querySelectorAll("[data-lazy-l10n-id]").forEach(el => {
 | |
|       el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
 | |
|       el.removeAttribute("data-lazy-l10n-id");
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   _checkForDownloadsClick(event) {
 | |
|     if (
 | |
|       event.target.closest("#wrapper-downloads-button") &&
 | |
|       event.button == 0
 | |
|     ) {
 | |
|       event.view.gCustomizeMode._showDownloadsAutoHidePanel();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _setupDownloadAutoHideToggle() {
 | |
|     this.window.addEventListener("click", this._checkForDownloadsClick, true);
 | |
|   },
 | |
| 
 | |
|   _teardownDownloadAutoHideToggle() {
 | |
|     this.window.removeEventListener(
 | |
|       "click",
 | |
|       this._checkForDownloadsClick,
 | |
|       true
 | |
|     );
 | |
|     this.$(kDownloadAutohidePanelId).hidePopup();
 | |
|   },
 | |
| 
 | |
|   _maybeMoveDownloadsButtonToNavBar() {
 | |
|     // If the user toggled the autohide checkbox while the item was in the
 | |
|     // palette, and hasn't moved it since, move the item to the default
 | |
|     // location in the navbar for them.
 | |
|     if (
 | |
|       !CustomizableUI.getPlacementOfWidget("downloads-button") &&
 | |
|       this._moveDownloadsButtonToNavBar &&
 | |
|       this.window.DownloadsButton.autoHideDownloadsButton
 | |
|     ) {
 | |
|       let navbarPlacements = CustomizableUI.getWidgetIdsInArea("nav-bar");
 | |
|       let insertionPoint = navbarPlacements.indexOf("urlbar-container");
 | |
|       while (++insertionPoint < navbarPlacements.length) {
 | |
|         let widget = navbarPlacements[insertionPoint];
 | |
|         // If we find a non-searchbar, non-spacer node, break out of the loop:
 | |
|         if (
 | |
|           widget != "search-container" &&
 | |
|           !(CustomizableUI.isSpecialWidget(widget) && widget.includes("spring"))
 | |
|         ) {
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       CustomizableUI.addWidgetToArea(
 | |
|         "downloads-button",
 | |
|         "nav-bar",
 | |
|         insertionPoint
 | |
|       );
 | |
|       lazy.BrowserUsageTelemetry.recordWidgetChange(
 | |
|         "downloads-button",
 | |
|         "nav-bar",
 | |
|         "move-downloads"
 | |
|       );
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   async _showDownloadsAutoHidePanel() {
 | |
|     let doc = this.document;
 | |
|     let panel = doc.getElementById(kDownloadAutohidePanelId);
 | |
|     panel.hidePopup();
 | |
|     let button = doc.getElementById("downloads-button");
 | |
|     // We don't show the tooltip if the button is in the panel.
 | |
|     if (button.closest("#widget-overflow-fixed-list")) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let offsetX = 0,
 | |
|       offsetY = 0;
 | |
|     let panelOnTheLeft = false;
 | |
|     let toolbarContainer = button.closest("toolbar");
 | |
|     if (toolbarContainer && toolbarContainer.id == "nav-bar") {
 | |
|       let navbarWidgets = CustomizableUI.getWidgetIdsInArea("nav-bar");
 | |
|       if (
 | |
|         navbarWidgets.indexOf("urlbar-container") <=
 | |
|         navbarWidgets.indexOf("downloads-button")
 | |
|       ) {
 | |
|         panelOnTheLeft = true;
 | |
|       }
 | |
|     } else {
 | |
|       await this.window.promiseDocumentFlushed(() => {});
 | |
| 
 | |
|       if (!this._customizing || !this._wantToBeInCustomizeMode) {
 | |
|         return;
 | |
|       }
 | |
|       let buttonBounds = this._getBoundsWithoutFlushing(button);
 | |
|       let windowBounds = this._getBoundsWithoutFlushing(doc.documentElement);
 | |
|       panelOnTheLeft =
 | |
|         buttonBounds.left + buttonBounds.width / 2 > windowBounds.width / 2;
 | |
|     }
 | |
|     let position;
 | |
|     if (panelOnTheLeft) {
 | |
|       // Tested in RTL, these get inverted automatically, so this does the
 | |
|       // right thing without taking RTL into account explicitly.
 | |
|       position = "topleft topright";
 | |
|       if (toolbarContainer) {
 | |
|         offsetX = 8;
 | |
|       }
 | |
|     } else {
 | |
|       position = "topright topleft";
 | |
|       if (toolbarContainer) {
 | |
|         offsetX = -8;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let checkbox = doc.getElementById(kDownloadAutohideCheckboxId);
 | |
|     if (this.window.DownloadsButton.autoHideDownloadsButton) {
 | |
|       checkbox.setAttribute("checked", "true");
 | |
|     } else {
 | |
|       checkbox.removeAttribute("checked");
 | |
|     }
 | |
| 
 | |
|     // We don't use the icon to anchor because it might be resizing because of
 | |
|     // the animations for drag/drop. Hence the use of offsets.
 | |
|     panel.openPopup(button, position, offsetX, offsetY);
 | |
|   },
 | |
| 
 | |
|   onDownloadsAutoHideChange(event) {
 | |
|     let checkbox = event.target.ownerDocument.getElementById(
 | |
|       kDownloadAutohideCheckboxId
 | |
|     );
 | |
|     Services.prefs.setBoolPref(kDownloadAutoHidePref, checkbox.checked);
 | |
|     // Ensure we move the button (back) after the user leaves customize mode.
 | |
|     event.view.gCustomizeMode._moveDownloadsButtonToNavBar = checkbox.checked;
 | |
|   },
 | |
| 
 | |
|   customizeTouchBar() {
 | |
|     let updater = Cc["@mozilla.org/widget/touchbarupdater;1"].getService(
 | |
|       Ci.nsITouchBarUpdater
 | |
|     );
 | |
|     updater.enterCustomizeMode();
 | |
|   },
 | |
| 
 | |
|   togglePong(enabled) {
 | |
|     // It's possible we're toggling for a reason other than hitting
 | |
|     // the button (we might be exiting, for example), so make sure that
 | |
|     // the state and checkbox are in sync.
 | |
|     let whimsyButton = this.$("whimsy-button");
 | |
|     whimsyButton.checked = enabled;
 | |
| 
 | |
|     if (enabled) {
 | |
|       this.visiblePalette.setAttribute("whimsypong", "true");
 | |
|       this.pongArena.hidden = false;
 | |
|       if (!this.uninitWhimsy) {
 | |
|         this.uninitWhimsy = this.whimsypong();
 | |
|       }
 | |
|     } else {
 | |
|       this.visiblePalette.removeAttribute("whimsypong");
 | |
|       if (this.uninitWhimsy) {
 | |
|         this.uninitWhimsy();
 | |
|         this.uninitWhimsy = null;
 | |
|       }
 | |
|       this.pongArena.hidden = true;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   whimsypong() {
 | |
|     function update() {
 | |
|       updateBall();
 | |
|       updatePlayers();
 | |
|     }
 | |
| 
 | |
|     function updateBall() {
 | |
|       if (ball[1] <= 0 || ball[1] >= gameSide) {
 | |
|         if (
 | |
|           (ball[1] <= 0 && (ball[0] < p1 || ball[0] > p1 + paddleWidth)) ||
 | |
|           (ball[1] >= gameSide && (ball[0] < p2 || ball[0] > p2 + paddleWidth))
 | |
|         ) {
 | |
|           updateScore(ball[1] <= 0 ? 0 : 1);
 | |
|         } else {
 | |
|           if (
 | |
|             (ball[1] <= 0 &&
 | |
|               (ball[0] - p1 < paddleEdge ||
 | |
|                 p1 + paddleWidth - ball[0] < paddleEdge)) ||
 | |
|             (ball[1] >= gameSide &&
 | |
|               (ball[0] - p2 < paddleEdge ||
 | |
|                 p2 + paddleWidth - ball[0] < paddleEdge))
 | |
|           ) {
 | |
|             ballDxDy[0] *= Math.random() + 1.3;
 | |
|             ballDxDy[0] = Math.max(Math.min(ballDxDy[0], 6), -6);
 | |
|             if (Math.abs(ballDxDy[0]) == 6) {
 | |
|               ballDxDy[0] += Math.sign(ballDxDy[0]) * Math.random();
 | |
|             }
 | |
|           } else {
 | |
|             ballDxDy[0] /= 1.1;
 | |
|           }
 | |
|           ballDxDy[1] *= -1;
 | |
|           ball[1] = ball[1] <= 0 ? 0 : gameSide;
 | |
|         }
 | |
|       }
 | |
|       ball = [
 | |
|         Math.max(Math.min(ball[0] + ballDxDy[0], gameSide), 0),
 | |
|         Math.max(Math.min(ball[1] + ballDxDy[1], gameSide), 0),
 | |
|       ];
 | |
|       if (ball[0] <= 0 || ball[0] >= gameSide) {
 | |
|         ballDxDy[0] *= -1;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     function updatePlayers() {
 | |
|       if (keydown) {
 | |
|         let p1Adj = 1;
 | |
|         if (
 | |
|           (keydown == 37 && !window.RTL_UI) ||
 | |
|           (keydown == 39 && window.RTL_UI)
 | |
|         ) {
 | |
|           p1Adj = -1;
 | |
|         }
 | |
|         p1 += p1Adj * 10 * keydownAdj;
 | |
|       }
 | |
| 
 | |
|       let sign = Math.sign(ballDxDy[0]);
 | |
|       if (
 | |
|         (sign > 0 && ball[0] > p2 + paddleWidth / 2) ||
 | |
|         (sign < 0 && ball[0] < p2 + paddleWidth / 2)
 | |
|       ) {
 | |
|         p2 += sign * 3;
 | |
|       } else if (
 | |
|         (sign > 0 && ball[0] > p2 + paddleWidth / 1.1) ||
 | |
|         (sign < 0 && ball[0] < p2 + paddleWidth / 1.1)
 | |
|       ) {
 | |
|         p2 += sign * 9;
 | |
|       }
 | |
| 
 | |
|       if (score >= winScore) {
 | |
|         p1 = ball[0];
 | |
|         p2 = ball[0];
 | |
|       }
 | |
|       p1 = Math.max(Math.min(p1, gameSide - paddleWidth), 0);
 | |
|       p2 = Math.max(Math.min(p2, gameSide - paddleWidth), 0);
 | |
|     }
 | |
| 
 | |
|     function updateScore(adj) {
 | |
|       if (adj) {
 | |
|         score += adj;
 | |
|       } else if (--lives == 0) {
 | |
|         quit = true;
 | |
|       }
 | |
|       ball = ballDef.slice();
 | |
|       ballDxDy = ballDxDyDef.slice();
 | |
|       ballDxDy[1] *= score / winScore + 1;
 | |
|     }
 | |
| 
 | |
|     function draw() {
 | |
|       let xAdj = window.RTL_UI ? -1 : 1;
 | |
|       elements["wp-player1"].style.transform =
 | |
|         "translate(" + xAdj * p1 + "px, -37px)";
 | |
|       elements["wp-player2"].style.transform =
 | |
|         "translate(" + xAdj * p2 + "px, " + gameSide + "px)";
 | |
|       elements["wp-ball"].style.transform =
 | |
|         "translate(" + xAdj * ball[0] + "px, " + ball[1] + "px)";
 | |
|       elements["wp-score"].textContent = score;
 | |
|       elements["wp-lives"].setAttribute("lives", lives);
 | |
|       if (score >= winScore) {
 | |
|         let arena = elements.arena;
 | |
|         let image = "url(chrome://browser/skin/customizableui/whimsy.png)";
 | |
|         let position = `${
 | |
|           (window.RTL_UI ? gameSide : 0) + xAdj * ball[0] - 10
 | |
|         }px ${ball[1] - 10}px`;
 | |
|         let repeat = "no-repeat";
 | |
|         let size = "20px";
 | |
|         if (arena.style.backgroundImage) {
 | |
|           if (arena.style.backgroundImage.split(",").length >= 160) {
 | |
|             quit = true;
 | |
|           }
 | |
| 
 | |
|           image += ", " + arena.style.backgroundImage;
 | |
|           position += ", " + arena.style.backgroundPosition;
 | |
|           repeat += ", " + arena.style.backgroundRepeat;
 | |
|           size += ", " + arena.style.backgroundSize;
 | |
|         }
 | |
|         arena.style.backgroundImage = image;
 | |
|         arena.style.backgroundPosition = position;
 | |
|         arena.style.backgroundRepeat = repeat;
 | |
|         arena.style.backgroundSize = size;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     function onkeydown(event) {
 | |
|       keys.push(event.which);
 | |
|       if (keys.length > 10) {
 | |
|         keys.shift();
 | |
|         let codeEntered = true;
 | |
|         for (let i = 0; i < keys.length; i++) {
 | |
|           if (keys[i] != keysCode[i]) {
 | |
|             codeEntered = false;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|         if (codeEntered) {
 | |
|           elements.arena.setAttribute("kcode", "true");
 | |
|           let spacer = document.querySelector(
 | |
|             "#customization-palette > toolbarpaletteitem"
 | |
|           );
 | |
|           spacer.setAttribute("kcode", "true");
 | |
|         }
 | |
|       }
 | |
|       if (event.which == 37 /* left */ || event.which == 39 /* right */) {
 | |
|         keydown = event.which;
 | |
|         keydownAdj *= 1.05;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     function onkeyup(event) {
 | |
|       if (event.which == 37 || event.which == 39) {
 | |
|         keydownAdj = 1;
 | |
|         keydown = 0;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     function uninit() {
 | |
|       document.removeEventListener("keydown", onkeydown);
 | |
|       document.removeEventListener("keyup", onkeyup);
 | |
|       if (rAFHandle) {
 | |
|         window.cancelAnimationFrame(rAFHandle);
 | |
|       }
 | |
|       let arena = elements.arena;
 | |
|       while (arena.firstChild) {
 | |
|         arena.firstChild.remove();
 | |
|       }
 | |
|       arena.removeAttribute("score");
 | |
|       arena.removeAttribute("lives");
 | |
|       arena.removeAttribute("kcode");
 | |
|       arena.style.removeProperty("background-image");
 | |
|       arena.style.removeProperty("background-position");
 | |
|       arena.style.removeProperty("background-repeat");
 | |
|       arena.style.removeProperty("background-size");
 | |
|       let spacer = document.querySelector(
 | |
|         "#customization-palette > toolbarpaletteitem"
 | |
|       );
 | |
|       spacer.removeAttribute("kcode");
 | |
|       elements = null;
 | |
|       document = null;
 | |
|       quit = true;
 | |
|     }
 | |
| 
 | |
|     if (this.uninitWhimsy) {
 | |
|       return this.uninitWhimsy;
 | |
|     }
 | |
| 
 | |
|     let ballDef = [10, 10];
 | |
|     let ball = [10, 10];
 | |
|     let ballDxDyDef = [2, 2];
 | |
|     let ballDxDy = [2, 2];
 | |
|     let score = 0;
 | |
|     let p1 = 0;
 | |
|     let p2 = 10;
 | |
|     let gameSide = 300;
 | |
|     let paddleEdge = 30;
 | |
|     let paddleWidth = 84;
 | |
|     let keydownAdj = 1;
 | |
|     let keydown = 0;
 | |
|     let keys = [];
 | |
|     let keysCode = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
 | |
|     let lives = 5;
 | |
|     let winScore = 11;
 | |
|     let quit = false;
 | |
|     let document = this.document;
 | |
|     let rAFHandle = 0;
 | |
|     let elements = {
 | |
|       arena: document.getElementById("customization-pong-arena"),
 | |
|     };
 | |
| 
 | |
|     document.addEventListener("keydown", onkeydown);
 | |
|     document.addEventListener("keyup", onkeyup);
 | |
| 
 | |
|     for (let id of ["player1", "player2", "ball", "score", "lives"]) {
 | |
|       let el = document.createXULElement("box");
 | |
|       el.id = "wp-" + id;
 | |
|       elements[el.id] = elements.arena.appendChild(el);
 | |
|     }
 | |
| 
 | |
|     let spacer = this.visiblePalette.querySelector("toolbarpaletteitem");
 | |
|     for (let player of ["#wp-player1", "#wp-player2"]) {
 | |
|       let val = "-moz-element(#" + spacer.id + ") no-repeat";
 | |
|       elements.arena.querySelector(player).style.background = val;
 | |
|     }
 | |
| 
 | |
|     let window = this.window;
 | |
|     rAFHandle = window.requestAnimationFrame(function animate() {
 | |
|       update();
 | |
|       draw();
 | |
|       if (quit) {
 | |
|         elements["wp-score"].textContent = score;
 | |
|         elements["wp-lives"] &&
 | |
|           elements["wp-lives"].setAttribute("lives", lives);
 | |
|         elements.arena.setAttribute("score", score);
 | |
|         elements.arena.setAttribute("lives", lives);
 | |
|       } else {
 | |
|         rAFHandle = window.requestAnimationFrame(animate);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     return uninit;
 | |
|   },
 | |
| };
 | |
| 
 | |
| function __dumpDragData(aEvent, caller) {
 | |
|   if (!gDebug) {
 | |
|     return;
 | |
|   }
 | |
|   let str =
 | |
|     "Dumping drag data (" +
 | |
|     (caller ? caller + " in " : "") +
 | |
|     "CustomizeMode.sys.mjs) {\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 += "}";
 | |
|   lazy.log.debug(str);
 | |
| }
 | |
| 
 | |
| function dispatchFunction(aFunc) {
 | |
|   Services.tm.dispatchToMainThread(aFunc);
 | |
| }
 | 
