Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/browser/components/customizableui/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 97 kB image not shown  

Quellcode-Bibliothek CustomizeMode.sys.mjs   Sprache: unbekannt

 
/* 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",
  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.BrowserCommands.openTab();
  }
  win.gBrowser.removeTab(gTab, { animate: true });
  gTab = null;
}

var gTabsProgressListener = {
  onLocationChange(aBrowser, aWebProgress, aRequest, aLocation) {
    // 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
    );
  }

  this._attachEventListeners();

  // 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_minimizeWindow",
    "cmd_quitApplication",
    "View:FullScreen",
    "Browser:NextTab",
    "Browser:PrevTab",
    "Browser:NewUserContextTab",
    "Tools:PrivateBrowsing",
    "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");

    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.toggleAttribute("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.toggleAttribute("customizing", true);

      let customizableToolbars = document.querySelectorAll(
        "toolbar[customizable=true]:not([autohide=true], [collapsed=true])"
      );
      for (let toolbar of customizableToolbars) {
        toolbar.toggleAttribute("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) {
    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;
    toolbar.toggleAttribute(
      "customizing",
      aEvent.detail.visible && toolbar.getAttribute("customizable") == "true"
    );
    this._onUIChange();
  },

  onWidgetMoved() {
    this._onUIChange();
  },

  onWidgetAdded() {
    this._onUIChange();
  },

  onWidgetRemoved() {
    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.BrowserAddonUI.openAddonsMgr("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) {
    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.CustomTitlebar.systemSupported;
  },

  _ensureCustomizationPanels() {
    let template = this.$("customizationPanel");
    template.replaceWith(template.content);

    let wrapper = this.$("customModeWrapper");
    wrapper.replaceWith(wrapper.content);
  },

  _attachEventListeners() {
    let container = this.$("customization-container");

    container.addEventListener("command", event => {
      switch (event.target.id) {
        case "customization-titlebar-visibility-checkbox":
          // NB: because command fires after click, by the time we've fired, the checkbox binding
          //     will already have switched the button's state, so this is correct:
          this.toggleTitlebar(event.target.checked);
          break;
        case "customization-uidensity-menuitem-compact":
        case "customization-uidensity-menuitem-normal":
        case "customization-uidensity-menuitem-touch":
          this.setUIDensity(event.target.mode);
          break;
        case "customization-uidensity-autotouchmode-checkbox":
          this.updateAutoTouchMode(event.target.checked);
          break;
        case "whimsy-button":
          this.togglePong(event.target.checked);
          break;
        case "customization-touchbar-button":
          this.customizeTouchBar();
          break;
        case "customization-undo-reset-button":
          this.undoReset();
          break;
        case "customization-reset-button":
          this.reset();
          break;
        case "customization-done-button":
          this.exit();
          break;
      }
    });

    container.addEventListener("popupshowing", event => {
      switch (event.target.id) {
        case "customization-toolbar-menu":
          this.window.ToolbarContextMenu.onViewToolbarsPopupShowing(event);
          break;
        case "customization-uidensity-menu":
          this.onUIDensityMenuShowing();
          break;
      }
    });

    let updateDensity = event => {
      switch (event.target.id) {
        case "customization-uidensity-menuitem-compact":
        case "customization-uidensity-menuitem-normal":
        case "customization-uidensity-menuitem-touch":
          this.updateUIDensity(event.target.mode);
      }
    };
    let densityMenu = this.document.getElementById(
      "customization-uidensity-menu"
    );
    densityMenu.addEventListener("focus", updateDensity);
    densityMenu.addEventListener("mouseover", updateDensity);

    let resetDensity = event => {
      switch (event.target.id) {
        case "customization-uidensity-menuitem-compact":
        case "customization-uidensity-menuitem-normal":
        case "customization-uidensity-menuitem-touch":
          this.resetUIDensity();
      }
    };
    densityMenu.addEventListener("blur", resetDensity);
    densityMenu.addEventListener("mouseout", resetDensity);

    this.$("customization-lwtheme-link").addEventListener("click", () => {
      this.openAddonsManagerThemes();
    });

    this.$(kPaletteItemContextMenu).addEventListener("popupshowing", event => {
      this.onPaletteContextMenuShowing(event);
    });

    this.$(kPaletteItemContextMenu).addEventListener("command", event => {
      switch (event.target.id) {
        case "customizationPaletteItemContextMenuAddToToolbar":
          this.addToToolbar(
            event.target.parentNode.triggerNode,
            "palette-context"
          );
          break;
        case "customizationPaletteItemContextMenuAddToPanel":
          this.addToPanel(
            event.target.parentNode.triggerNode,
            "palette-context"
          );
          break;
      }
    });

    let autohidePanel = this.$(kDownloadAutohidePanelId);
    autohidePanel.addEventListener("popupshown", event => {
      this._downloadPanelAutoHideTimeout = this.window.setTimeout(
        () => event.target.hidePopup(),
        4000
      );
    });
    autohidePanel.addEventListener("mouseover", () => {
      this.window.clearTimeout(this._downloadPanelAutoHideTimeout);
    });
    autohidePanel.addEventListener("mouseout", event => {
      this._downloadPanelAutoHideTimeout = this.window.setTimeout(
        () => event.target.hidePopup(),
        2000
      );
    });
    autohidePanel.addEventListener("popuphidden", () => {
      this.window.clearTimeout(this._downloadPanelAutoHideTimeout);
    });

    this.$(kDownloadAutohideCheckboxId).addEventListener("command", event => {
      this.onDownloadsAutoHideChange(event);
    });
  },

  _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,
--> --------------------

--> maximum size reached

--> --------------------

[ 0.64Quellennavigators  Projekt   ]