tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ToolbarContextMenu.sys.mjs (22228B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 const lazy = {};
      8 ChromeUtils.defineESModuleGetters(lazy, {
      9  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     10  CustomizableUI:
     11    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     12  ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
     13  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
     14 });
     15 
     16 XPCOMUtils.defineLazyPreferenceGetter(
     17  lazy,
     18  "gAlwaysOpenPanel",
     19  "browser.download.alwaysOpenPanel",
     20  true
     21 );
     22 
     23 XPCOMUtils.defineLazyPreferenceGetter(
     24  lazy,
     25  "gAddonAbuseReportEnabled",
     26  "extensions.abuseReport.enabled",
     27  false
     28 );
     29 
     30 /**
     31 * Various events handlers to set the state of the toolbar-context-menu popup,
     32 * as well as to handle some commands from that popup.
     33 */
     34 export var ToolbarContextMenu = {
     35  /**
     36   * Makes visible the "autohide the downloads button" checkbox in the popup
     37   * in the event that the downloads button was context clicked. Otherwise,
     38   * hides that checkbox.
     39   *
     40   * This method also sets the checkbox state depending on the current user
     41   * configuration for hiding the downloads button.
     42   *
     43   * @param {Element} popup
     44   *   The toolbar-context-menu element for a window.
     45   */
     46  updateDownloadsAutoHide(popup) {
     47    let { document, DownloadsButton } = popup.ownerGlobal;
     48    let checkbox = document.getElementById(
     49      "toolbar-context-autohide-downloads-button"
     50    );
     51    let isDownloads =
     52      popup.triggerNode &&
     53      ["downloads-button", "wrapper-downloads-button"].includes(
     54        popup.triggerNode.id
     55      );
     56    checkbox.hidden = !isDownloads;
     57    checkbox.toggleAttribute(
     58      "checked",
     59      DownloadsButton.autoHideDownloadsButton
     60    );
     61  },
     62 
     63  /**
     64   * Handler for the toolbar-context-autohide-downloads-button command event
     65   * that is fired when the checkbox for autohiding the downloads button is
     66   * changed. This method does the work of updating the internal preference
     67   * state for auto-hiding the downloads button.
     68   *
     69   * @param {CommandEvent} event
     70   */
     71  onDownloadsAutoHideChange(event) {
     72    let autoHide = event.target.hasAttribute("checked");
     73    Services.prefs.setBoolPref("browser.download.autohideButton", autoHide);
     74  },
     75 
     76  /**
     77   * Makes visible the "always open downloads panel" checkbox in the popup
     78   * in the event that the downloads button was context clicked. Otherwise,
     79   * hides that checkbox.
     80   *
     81   * This method also sets the checkbox state depending on the current user
     82   * configuration for always showing the panel.
     83   *
     84   * @param {Element} popup
     85   *   The toolbar-context-menu element for a window.
     86   */
     87  updateDownloadsAlwaysOpenPanel(popup) {
     88    let { document } = popup.ownerGlobal;
     89    let separator = document.getElementById(
     90      "toolbarDownloadsAnchorMenuSeparator"
     91    );
     92    let checkbox = document.getElementById(
     93      "toolbar-context-always-open-downloads-panel"
     94    );
     95    let isDownloads =
     96      popup.triggerNode &&
     97      ["downloads-button", "wrapper-downloads-button"].includes(
     98        popup.triggerNode.id
     99      );
    100    separator.hidden = checkbox.hidden = !isDownloads;
    101    checkbox.toggleAttribute("checked", lazy.gAlwaysOpenPanel);
    102  },
    103 
    104  /**
    105   * Handler for the toolbar-context-always-open-downloads-panel command event
    106   * that is fired when the checkbox for always showing the downloads panel is
    107   * changed. This method does the work of updating the internal preference
    108   * state for always showing the downloads panel.
    109   *
    110   * @param {CommandEvent} event
    111   */
    112  onDownloadsAlwaysOpenPanelChange(event) {
    113    let alwaysOpen = event.target.hasAttribute("checked");
    114    Services.prefs.setBoolPref("browser.download.alwaysOpenPanel", alwaysOpen);
    115  },
    116 
    117  /**
    118   * This is called when a menupopup for configuring toolbars fires its
    119   * popupshowing event. There are multiple such menupopups, and this logic
    120   * tries to work for all of them. This method will insert menuitems into
    121   * the popup to allow for controlling the toolbars within the browser
    122   * toolbox.
    123   *
    124   * @param {Event} aEvent
    125   *   The popupshowing event for the menupopup.
    126   * @param {DOMNode} aInsertPoint
    127   *   The point within the menupopup to insert the controls for each toolbar.
    128   */
    129  // eslint-disable-next-line complexity
    130  onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
    131    var popup = aEvent.target;
    132    let window = popup.ownerGlobal;
    133    let {
    134      document,
    135      BookmarkingUI,
    136      MozXULElement,
    137      onViewToolbarCommand,
    138      showFullScreenViewContextMenuItems,
    139      gBrowser,
    140      CustomizationHandler,
    141      gNavToolbox,
    142    } = window;
    143 
    144    // triggerNode can be a nested child element of a toolbaritem.
    145    let toolbarItem = popup.triggerNode;
    146    while (toolbarItem) {
    147      let localName = toolbarItem.localName;
    148      if (localName == "toolbar") {
    149        toolbarItem = null;
    150        break;
    151      }
    152      if (localName == "toolbarpaletteitem") {
    153        toolbarItem = toolbarItem.firstElementChild;
    154        break;
    155      }
    156      if (localName == "menupopup") {
    157        aEvent.preventDefault();
    158        aEvent.stopPropagation();
    159        return;
    160      }
    161      let parent = toolbarItem.parentElement;
    162      if (parent) {
    163        if (
    164          parent.classList.contains("customization-target") ||
    165          parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
    166          parent.localName == "toolbarpaletteitem" ||
    167          parent.localName == "toolbar" ||
    168          parent.id == "vertical-tabs"
    169        ) {
    170          break;
    171        }
    172      }
    173      toolbarItem = parent;
    174    }
    175 
    176    // Empty the menu
    177    for (var i = popup.children.length - 1; i >= 0; --i) {
    178      var deadItem = popup.children[i];
    179      if (deadItem.hasAttribute("toolbarId")) {
    180        popup.removeChild(deadItem);
    181      }
    182    }
    183 
    184    let showTabStripItems = toolbarItem?.id == "tabbrowser-tabs";
    185    let isVerticalTabStripMenu =
    186      showTabStripItems && toolbarItem.parentElement.id == "vertical-tabs";
    187 
    188    if (aInsertPoint) {
    189      aInsertPoint.hidden = isVerticalTabStripMenu;
    190    }
    191    document.getElementById("toolbar-context-customize").hidden =
    192      isVerticalTabStripMenu;
    193 
    194    if (!isVerticalTabStripMenu) {
    195      MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
    196      let firstMenuItem = aInsertPoint || popup.firstElementChild;
    197      let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
    198      for (let toolbar of toolbarNodes) {
    199        if (!toolbar.hasAttribute("toolbarname")) {
    200          continue;
    201        }
    202 
    203        if (toolbar.id == "PersonalToolbar") {
    204          let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
    205          popup.insertBefore(menu, firstMenuItem);
    206        } else {
    207          let menuItem = document.createXULElement("menuitem");
    208          menuItem.setAttribute("id", "toggle_" + toolbar.id);
    209          menuItem.setAttribute("toolbarId", toolbar.id);
    210          menuItem.setAttribute("type", "checkbox");
    211          menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
    212          let hidingAttribute =
    213            toolbar.getAttribute("type") == "menubar"
    214              ? "autohide"
    215              : "collapsed";
    216          menuItem.toggleAttribute(
    217            "checked",
    218            !toolbar.hasAttribute(hidingAttribute)
    219          );
    220          menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
    221 
    222          popup.insertBefore(menuItem, firstMenuItem);
    223          menuItem.addEventListener("command", onViewToolbarCommand);
    224        }
    225      }
    226    }
    227 
    228    let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
    229    let removeFromToolbar = popup.querySelector(
    230      ".customize-context-removeFromToolbar"
    231    );
    232 
    233    let isTitlebarSpacer = toolbarItem?.classList.contains("titlebar-spacer");
    234 
    235    let isMenuBarSpacer =
    236      toolbarItem?.localName == "spacer" &&
    237      toolbarItem?.parentElement?.id == "toolbar-menubar";
    238 
    239    // Show/hide fullscreen context menu items and set the
    240    // autohide item's checked state to mirror the autohide pref.
    241    showFullScreenViewContextMenuItems(popup);
    242 
    243    // Show/hide sidebar and vertical tabs menu items
    244    let sidebarRevampEnabled = Services.prefs.getBoolPref("sidebar.revamp");
    245    let showSidebarActions =
    246      ["tabbrowser-tabs", "sidebar-button"].includes(toolbarItem?.id) ||
    247      toolbarItem?.localName == "toolbarspring" ||
    248      isTitlebarSpacer ||
    249      isMenuBarSpacer;
    250 
    251    let toggleVerticalTabsItem = document.getElementById(
    252      "toolbar-context-toggle-vertical-tabs"
    253    );
    254    toggleVerticalTabsItem.hidden = !showSidebarActions;
    255    document.l10n.setAttributes(
    256      toggleVerticalTabsItem,
    257      gBrowser.tabContainer?.verticalMode
    258        ? "toolbar-context-turn-off-vertical-tabs"
    259        : "toolbar-context-turn-on-vertical-tabs"
    260    );
    261    document.getElementById("toolbar-context-customize-sidebar").hidden =
    262      !sidebarRevampEnabled ||
    263      (toolbarItem?.id != "sidebar-button" &&
    264        !gBrowser.tabContainer?.verticalMode) ||
    265      (!["tabbrowser-tabs", "sidebar-button"].includes(toolbarItem?.id) &&
    266        gBrowser.tabContainer?.verticalMode);
    267    document.getElementById("sidebarRevampSeparator").hidden =
    268      !showSidebarActions || isVerticalTabStripMenu;
    269    document.getElementById("customizationMenuSeparator").hidden =
    270      toolbarItem?.id == "tabbrowser-tabs" ||
    271      (toolbarItem?.localName == "toolbarspring" &&
    272        !CustomizationHandler.isCustomizing()) ||
    273      isMenuBarSpacer ||
    274      isTitlebarSpacer;
    275 
    276    // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
    277    if (!moveToPanel || !removeFromToolbar) {
    278      return;
    279    }
    280 
    281    for (let node of popup.querySelectorAll(
    282      'menuitem[contexttype="toolbaritem"]'
    283    )) {
    284      node.hidden = showTabStripItems;
    285    }
    286 
    287    for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
    288      node.hidden = !showTabStripItems;
    289    }
    290 
    291    document
    292      .getElementById("toolbar-context-menu")
    293      .querySelectorAll("[data-lazy-l10n-id]")
    294      .forEach(el => {
    295        el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
    296        el.removeAttribute("data-lazy-l10n-id");
    297      });
    298 
    299    // The "normal" toolbar items menu separator is hidden because it's unused
    300    // when hiding the "moveToPanel" and "removeFromToolbar" items on flexible
    301    // space items. But we need to ensure its hidden state is reset in the case
    302    // the context menu is subsequently opened on a non-flexible space item.
    303    let menuSeparator = document.getElementById("tabbarItemsMenuSeparator");
    304    menuSeparator.hidden = false;
    305 
    306    document.getElementById("toolbarNavigatorItemsMenuSeparator").hidden =
    307      !showTabStripItems;
    308 
    309    let isSpacerItem =
    310      toolbarItem?.localName.includes("separator") ||
    311      toolbarItem?.localName.includes("spring") ||
    312      toolbarItem?.localName.includes("spacer") ||
    313      toolbarItem?.id.startsWith("customizableui-special");
    314 
    315    // For spacer items, customization items should only appear
    316    // when the user is actively customizing the toolbar.
    317    let shouldHideCustomizationItems =
    318      isSpacerItem && !CustomizationHandler.isCustomizing();
    319 
    320    if (shouldHideCustomizationItems) {
    321      moveToPanel.hidden = true;
    322      removeFromToolbar.hidden = true;
    323      menuSeparator.hidden = !showTabStripItems;
    324    }
    325 
    326    if (toolbarItem?.id != "tabbrowser-tabs") {
    327      menuSeparator.hidden = true;
    328    }
    329 
    330    if (showTabStripItems) {
    331      let multipleTabsSelected = !!gBrowser.multiSelectedTabsCount;
    332      document.getElementById("toolbar-context-bookmarkSelectedTabs").hidden =
    333        !multipleTabsSelected;
    334      document.getElementById("toolbar-context-bookmarkSelectedTab").hidden =
    335        multipleTabsSelected;
    336      document.getElementById("toolbar-context-reloadSelectedTabs").hidden =
    337        !multipleTabsSelected;
    338      document.getElementById("toolbar-context-reloadSelectedTab").hidden =
    339        multipleTabsSelected;
    340      document.getElementById("toolbar-context-selectAllTabs").disabled =
    341        gBrowser.allTabsSelected();
    342      let closedCount = lazy.SessionStore.getLastClosedTabCount(window);
    343      document
    344        .getElementById("History:UndoCloseTab")
    345        .setAttribute("disabled", closedCount == 0);
    346      document.l10n.setArgs(
    347        document.getElementById("toolbar-context-undoCloseTab"),
    348        { tabCount: closedCount }
    349      );
    350      return;
    351    }
    352 
    353    let movable =
    354      toolbarItem?.id && lazy.CustomizableUI.isWidgetRemovable(toolbarItem);
    355    if (movable) {
    356      if (lazy.CustomizableUI.isSpecialWidget(toolbarItem.id)) {
    357        moveToPanel.setAttribute("disabled", true);
    358      } else {
    359        moveToPanel.removeAttribute("disabled");
    360      }
    361      if (shouldHideCustomizationItems) {
    362        removeFromToolbar.setAttribute("disabled", true);
    363      } else {
    364        removeFromToolbar.removeAttribute("disabled");
    365      }
    366    } else {
    367      removeFromToolbar.setAttribute("disabled", true);
    368      moveToPanel.setAttribute("disabled", true);
    369    }
    370  },
    371 
    372  /**
    373   * Given an opened menupopup, returns the triggerNode that opened that
    374   * menupopup. If customize mode is enabled, this will return the unwrapped
    375   * underlying triggerNode, rather than the customize mode wrapper around it.
    376   *
    377   * @param {DOMNode} popup
    378   *   The menupopup to get the unwrapped trigger node for.
    379   * @returns {DOMNode}
    380   *   The underlying trigger node that opened the menupopup.
    381   */
    382  _getUnwrappedTriggerNode(popup) {
    383    // Toolbar buttons are wrapped in customize mode. Unwrap if necessary.
    384    let { triggerNode } = popup;
    385    let { gCustomizeMode } = popup.ownerGlobal;
    386    if (triggerNode && gCustomizeMode.isWrappedToolbarItem(triggerNode)) {
    387      return triggerNode.firstElementChild;
    388    }
    389    return triggerNode;
    390  },
    391 
    392  /**
    393   * For an opened menupopup, if the triggerNode was provided by an extension,
    394   * returns the extension ID. Otherwise, return the empty string.
    395   *
    396   * @param {DOMNode} popup
    397   *   The menupopup that was opened.
    398   * @returns {string}
    399   *   The ID of the extension that provided the triggerNode, or the empty
    400   *   string if the triggerNode was not provided by an extension.
    401   */
    402  _getExtensionId(popup) {
    403    let node = this._getUnwrappedTriggerNode(popup);
    404    return node && node.getAttribute("data-extensionid");
    405  },
    406 
    407  /**
    408   * For an opened menupopup, if the triggerNode was provided by an extension,
    409   * returns the widget ID of the triggerNode. Otherwise, return the empty
    410   * string.
    411   *
    412   * @param {DOMNode} popup
    413   *   The menupopup that was opened.
    414   * @returns {string}
    415   *   The ID of the extension-provided widget that was the triggerNode, or the
    416   *   empty string if the trigger node was not provided by an extension
    417   *   widget.
    418   */
    419  _getWidgetId(popup) {
    420    let node = this._getUnwrappedTriggerNode(popup);
    421    return node?.closest(".unified-extensions-item")?.id;
    422  },
    423 
    424  /**
    425   * Updates the toolbar context menu items unique to gUnifiedExtensions.button.
    426   *
    427   * @param {Element} popup
    428   *   The toolbar-context-menu element for a window.
    429   */
    430  updateExtensionsButtonContextMenu(popup) {
    431    const isExtsButton = popup.triggerNode?.id === "unified-extensions-button";
    432    const isCustomizingExtsButton =
    433      popup.triggerNode?.id === "wrapper-unified-extensions-button";
    434    const { gUnifiedExtensions } = popup.ownerGlobal;
    435 
    436    const checkbox = popup.querySelector(
    437      "#toolbar-context-always-show-extensions-button"
    438    );
    439    if (isCustomizingExtsButton) {
    440      checkbox.hidden = false;
    441      checkbox.toggleAttribute(
    442        "checked",
    443        gUnifiedExtensions.buttonAlwaysVisible
    444      );
    445    } else if (isExtsButton && !gUnifiedExtensions.buttonAlwaysVisible) {
    446      // The button may be visible despite the user's preference, which could
    447      // remind the user of the button's existence. Offer an option to unhide
    448      // the button, in case the user is looking for a way to do so.
    449      checkbox.hidden = false;
    450      checkbox.removeAttribute("checked");
    451    } else {
    452      checkbox.hidden = true;
    453    }
    454 
    455    // removeFromToolbar is shown but disabled by default, via an earlier call
    456    // to ToolbarContextMenu.onViewToolbarsPopupShowing. Enable/hide if needed.
    457    if (isExtsButton) {
    458      const removeFromToolbar = popup.querySelector(
    459        ".customize-context-removeFromToolbar"
    460      );
    461      if (gUnifiedExtensions.buttonAlwaysVisible) {
    462        removeFromToolbar.removeAttribute("disabled");
    463      } else {
    464        // No need to show "Remove from Toolbar" even if disabled, because the
    465        // "Always Show in Toolbar" checkbox is already shown above.
    466        removeFromToolbar.hidden = true;
    467      }
    468    }
    469  },
    470 
    471  /**
    472   * Updates the toolbar context menu to show the right state if an
    473   * extension-provided widget acted as the triggerNode. This will, for example,
    474   * show or hide items for managing the underlying addon.
    475   *
    476   * @param {DOMNode} popup
    477   *   The menupopup for the toolbar context menu.
    478   * @returns {Promise<undefined>}
    479   *   Resolves once the menupopup state has been set.
    480   */
    481  async updateExtension(popup) {
    482    let removeExtension = popup.querySelector(
    483      ".customize-context-removeExtension"
    484    );
    485    let manageExtension = popup.querySelector(
    486      ".customize-context-manageExtension"
    487    );
    488    let reportExtension = popup.querySelector(
    489      ".customize-context-reportExtension"
    490    );
    491    let pinToToolbar = popup.querySelector(".customize-context-pinToToolbar");
    492    let separator = reportExtension.nextElementSibling;
    493    let id = this._getExtensionId(popup);
    494    let addon = id && (await lazy.AddonManager.getAddonByID(id));
    495 
    496    for (let element of [removeExtension, manageExtension, separator]) {
    497      element.hidden = !addon;
    498    }
    499 
    500    if (pinToToolbar) {
    501      pinToToolbar.hidden = !addon;
    502    }
    503 
    504    reportExtension.hidden = !addon || !lazy.gAddonAbuseReportEnabled;
    505 
    506    if (addon) {
    507      popup.querySelector(".customize-context-moveToPanel").hidden = true;
    508      popup.querySelector(".customize-context-removeFromToolbar").hidden = true;
    509 
    510      if (pinToToolbar) {
    511        let widgetId = this._getWidgetId(popup);
    512        if (widgetId) {
    513          let area = lazy.CustomizableUI.getPlacementOfWidget(widgetId).area;
    514          let inToolbar = area != lazy.CustomizableUI.AREA_ADDONS;
    515          pinToToolbar.toggleAttribute("checked", inToolbar);
    516        }
    517      }
    518 
    519      removeExtension.disabled = !(
    520        addon.permissions & lazy.AddonManager.PERM_CAN_UNINSTALL
    521      );
    522 
    523      if (popup.id === "toolbar-context-menu") {
    524        lazy.ExtensionsUI.originControlsMenu(popup, id);
    525      }
    526    }
    527  },
    528 
    529  /**
    530   * Handler for the context menu item for removing an extension.
    531   *
    532   * @param {DOMNode} popup
    533   *   The menupopup that triggered the extension removal.
    534   * @returns {Promise<undefined>}
    535   *   Resolves when the extension has been removed.
    536   */
    537  async removeExtensionForContextAction(popup) {
    538    let { BrowserAddonUI } = popup.ownerGlobal;
    539 
    540    let id = this._getExtensionId(popup);
    541    await BrowserAddonUI.removeAddon(id, "browserAction");
    542  },
    543 
    544  /**
    545   * Handler for the context menu item for issuing a report on an extension.
    546   *
    547   * @param {DOMNode} popup
    548   *   The menupopup that triggered the extension report.
    549   * @param {string} reportEntryPoint
    550   *   A string describing the UI entrypoint for the report.
    551   * @returns {Promise<undefined>}
    552   *   Resolves when the extension has been removed.
    553   */
    554  async reportExtensionForContextAction(popup, reportEntryPoint) {
    555    let { BrowserAddonUI } = popup.ownerGlobal;
    556    let id = this._getExtensionId(popup);
    557    await BrowserAddonUI.reportAddon(id, reportEntryPoint);
    558  },
    559 
    560  /**
    561   * Handler for the context menu item for managing an extension.
    562   *
    563   * @param {DOMNode} popup
    564   *   The menupopup that triggered extension management.
    565   * @returns {Promise<undefined>}
    566   *   Resolves when the extension's about:addons management page has been
    567   *   opened.
    568   */
    569  async openAboutAddonsForContextAction(popup) {
    570    let { BrowserAddonUI } = popup.ownerGlobal;
    571    let id = this._getExtensionId(popup);
    572    await BrowserAddonUI.manageAddon(id, "browserAction");
    573  },
    574 
    575  /**
    576   * Hides the first visible menu separator if it would appear at the top of the
    577   * toolbar context menu (i.e., when all preceding menu items are hidden). This
    578   * prevents a separator from appearing at the top of the menu with no items above it.
    579   *
    580   * Fix for Bug 1955241.
    581   *
    582   * @param {Element} popup
    583   *   The toolbar-context-menu element for a window.
    584   */
    585  hideLeadingSeparatorIfNeeded(popup) {
    586    // Find the first non-hidden element in the menu
    587    let firstVisibleElement = popup.firstElementChild;
    588    while (firstVisibleElement && firstVisibleElement.hidden) {
    589      firstVisibleElement = firstVisibleElement.nextElementSibling;
    590    }
    591 
    592    // If the first visible element is a separator, hide it
    593    if (
    594      firstVisibleElement &&
    595      firstVisibleElement.localName === "menuseparator"
    596    ) {
    597      firstVisibleElement.hidden = true;
    598    }
    599  },
    600 
    601  /**
    602   * Hides the "Move to Panel" and "Remove from Toolbar" items if both are
    603   * disabled. This prevents showing a menu with no useful items. If at least
    604   * one of the items is enabled, both items are shown for consistency.
    605   *
    606   * This is its own method to allow it to be called after other methods
    607   * that may change the disabled state of either menu item.
    608   *
    609   * @param {Element} popup
    610   *   The toolbar-context-menu element for a window.
    611   */
    612  updateCustomizationItemsVisibility(popup) {
    613    let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
    614    let removeFromToolbar = popup.querySelector(
    615      ".customize-context-removeFromToolbar"
    616    );
    617 
    618    if (
    619      removeFromToolbar?.getAttribute("disabled") &&
    620      moveToPanel.getAttribute("disabled")
    621    ) {
    622      removeFromToolbar.hidden = true;
    623      moveToPanel.hidden = true;
    624    }
    625  },
    626 };