Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  tabs.js

  Sprache: JAVA
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


"use strict";

// This is loaded into all browser windows. Wrap in a block to prevent
// leaking to window scope.
{
  const DIRECTION_BACKWARD = -1;
  const DIRECTION_FORWARD = 1;

  const isTab = element => gBrowser.isTab(element);
  const isTabGroup = element => gBrowser.isTabGroup(element);
  const isTabGroupLabel = element => gBrowser.isTabGroupLabel(element);
  const isSplitViewWrapper = element => gBrowser.isSplitViewWrapper(element);

  class MozTabbrowserTabs extends MozElements.TabsBase {
    static observedAttributes = ["orient"];

    #mustUpdateTabMinHeight = false;
    #tabMinHeight = 36;
    #animatingGroups = new Set();

    constructor() {
      super();

      this.addEventListener("TabSelect"this);
      this.addEventListener("TabClose"this);
      this.addEventListener("TabAttrModified"this);
      this.addEventListener("TabHide"this);
      this.addEventListener("TabShow"this);
      this.addEventListener("TabHoverStart"this);
      this.addEventListener("TabHoverEnd"this);
      this.addEventListener("TabNoteIconHoverStart"this);
      this.addEventListener("TabNoteIconHoverEnd"this);
      this.addEventListener("TabGroupLabelHoverStart"this);
      this.addEventListener("TabGroupLabelHoverEnd"this);
      // Capture collapse/expand early so we mark animating groups before
      // overflow/underflow handlers run.
      this.addEventListener("TabGroupExpand"thistrue);
      this.addEventListener("TabGroupCollapse"thistrue);
      this.addEventListener("TabGroupAnimationComplete"this);
      this.addEventListener("TabGroupCreate"this);
      this.addEventListener("TabGroupRemoved"this);
      this.addEventListener("SplitViewCreated"this);
      this.addEventListener("SplitViewRemoved"this);
      this.addEventListener("transitionend"this);
      this.addEventListener("dblclick"this);
      this.addEventListener("click"this);
      this.addEventListener("click"thistrue);
      this.addEventListener("keydown"this, { mozSystemGroup: true });
      this.addEventListener("mouseleave"this);
      this.addEventListener("focusin"this);
      this.addEventListener("focusout"this);
      this.addEventListener("contextmenu"this);
      this.addEventListener("dragstart"this);
      this.addEventListener("dragover"this);
      this.addEventListener("drop"this);
      this.addEventListener("dragend"this);
      this.addEventListener("dragleave"this);
    }

    init() {
      this.startupTime = Services.startup.getStartupInfo().start.getTime();

      this.arrowScrollbox = document.getElementById(
        "tabbrowser-arrowscrollbox"
      );
      this.arrowScrollbox.addEventListener("wheel"thistrue);
      this.arrowScrollbox.addEventListener("underflow"this);
      this.arrowScrollbox.addEventListener("overflow"this);
      this.pinnedTabsContainer = document.getElementById(
        "pinned-tabs-container"
      );
      this.pinnedTabsContainer.setAttribute(
        "orient",
        this.getAttribute("orient")
      );

      // Override arrowscrollbox.js method, since our scrollbox's children are
      // inherited from the scrollbox binding parent (this).
      this.arrowScrollbox._getScrollableElements = () => {
        return this.ariaFocusableItems.reduce((elements, item) => {
          if (this.arrowScrollbox._canScrollToElement(item)) {
            elements.push(item);
            if (
              isTab(item) &&
              item.group &&
              item.group.collapsed &&
              item.selected
            ) {
              // overflow container is scrollable, but not in focus order
              elements.push(item.group.overflowContainer);
            }
          }
          return elements;
        }, []);
      };
      this.arrowScrollbox._canScrollToElement = element => {
        if (isTab(element)) {
          return !element.pinned;
        }
        return true;
      };

      // Override for performance reasons. This is the size of a single element
      // that can be scrolled when using mouse wheel scrolling. If we don't do
      // this then arrowscrollbox computes this value by calling
      // _getScrollableElements and dividing the box size by that number.
      // However in the tabstrip case we already know the answer to this as,
      // when we're overflowing, it is always the same as the tab min width or
      // height. For tab group labels, the number won't exactly match, but
      // that shouldn't be a problem in practice since the arrowscrollbox
      // stops at element bounds when finishing scrolling.
      Object.defineProperty(this.arrowScrollbox, "lineScrollAmount", {
        get: () =>
          this.verticalMode ? this.#tabMinHeight : this._tabMinWidthPref,
      });

      this.baseConnect();

      this._blockDblClick = false;
      this._closeButtonsUpdatePending = false;
      this._closingTabsSpacer = this.querySelector(".closing-tabs-spacer");
      this._tabDefaultMaxWidth = NaN;
      this._lastTabClosedByMouse = false;
      this._hasTabTempMaxWidth = false;
      this._scrollButtonWidth = 0;
      this._animateElement = this.arrowScrollbox;
      this._tabClipWidth = Services.prefs.getIntPref(
        "browser.tabs.tabClipWidth"
      );
      this._hiddenSoundPlayingTabs = new Set();
      this.previewPanel = null;

      this.allTabs[0].label = this.emptyTabTitle;

      this.newTabButton.setAttribute(
        "aria-label",
        DynamicShortcutTooltip.getText("tabs-newtab-button")
      );

      let handleResize = () => {
        this._updateCloseButtons();
        this._handleTabSelect(true);
      };
      window.addEventListener("resize", handleResize);
      this._fullscreenMutationObserver = new MutationObserver(handleResize);
      this._fullscreenMutationObserver.observe(document.documentElement, {
        attributeFilter: ["inFullscreen""inDOMFullscreen"],
      });
      window.addEventListener("uidensitychanged"this);

      this.boundObserve = (...args) => this.observe(...args);
      Services.prefs.addObserver("privacy.userContext"this.boundObserve);
      this.observe(null"nsPref:changed""privacy.userContext.enabled");

      document
        .getElementById("vertical-tabs-newtab-button")
        .addEventListener("keypress"this);
      document
        .getElementById("tabs-newtab-button")
        .addEventListener("keypress"this);

      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_tabMinWidthPref",
        "browser.tabs.tabMinWidth",
        null,
        (pref, prevValue, newValue) => this.#updateTabMinWidth(newValue),
        newValue => {
          const LIMIT = 50;
          return Math.max(newValue, LIMIT);
        }
      );
      this.#updateTabMinWidth(this._tabMinWidthPref);
      this.#updateTabMinHeight();

      CustomizableUI.addListener(this);
      this._updateNewTabVisibility();

      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_closeTabByDblclick",
        "browser.tabs.closeTabByDblclick",
        false
      );

      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_sidebarVisibility",
        "sidebar.visibility",
        "always-show"
      );

      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_sidebarPositionStart",
        "sidebar.position_start",
        true
      );

      if (gMultiProcessBrowser) {
        this.tabbox.tabpanels.setAttribute("async""true");
      }

      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_showTabHoverPreview",
        "browser.tabs.hoverPreview.enabled",
        false
      );
      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_showTabGroupHoverPreview",
        "browser.tabs.groups.hoverPreview.enabled",
        false
      );

      this.tooltip = "tabbrowser-tab-tooltip";

      this.tabDragAndDrop = new window.TabDragAndDrop(this);
      this.tabDragAndDrop.init();
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (name == "orient") {
        // reset this attribute so we don't have incorrect styling for vertical tabs
        this.removeAttribute("overflow");
        this.#updateTabMinWidth();
        this.#updateTabMinHeight();
        this.pinnedTabsContainer?.setAttribute("orient", newValue);
      }
      super.attributeChangedCallback(name, oldValue, newValue);
    }

    // Event handlers

    handleEvent(aEvent) {
      switch (aEvent.type) {
        case "mouseout": {
          // If the "related target" (the node to which the pointer went) is not
          // a child of the current document, the mouse just left the window.
          let relatedTarget = aEvent.relatedTarget;
          if (relatedTarget && relatedTarget.ownerDocument == document) {
            break;
          }
        }
        // fall through
        case "mousemove":
          if (
            document.getElementById("tabContextMenu").state != "open" &&
            !this.#isMovingTab()
          ) {
            this._unlockTabSizing();
          }
          break;
        case "mouseleave":
          this.previewPanel?.deactivate();
          break;
        default: {
          let methodName = `on_${aEvent.type}`;
          if (methodName in this) {
            this[methodName](aEvent);
          } else {
            throw new Error(`Unexpected event ${aEvent.type}`);
          }
        }
      }
    }

    /**
     * @param {CustomEvent} event
     */

    on_TabSelect(event) {
      const {
        target: newTab,
        detail: { previousTab },
      } = event;

      // In some cases (e.g. by selecting a tab in a collapsed tab group),
      // changing the selected tab may cause a tab to appear/disappear.
      if (previousTab.group?.collapsed || newTab.group?.collapsed) {
        this._invalidateCachedVisibleTabs();
      }
      this._handleTabSelect();
    }

    on_TabClose(event) {
      this._hiddenSoundPlayingStatusChanged(event.target, { closed: true });
    }

    on_TabAttrModified(event) {
      if (
        event.detail.changed.includes("soundplaying") &&
        !event.target.visible
      ) {
        this._hiddenSoundPlayingStatusChanged(event.target);
      }
      if (
        event.detail.changed.includes("soundplaying") ||
        event.detail.changed.includes("muted") ||
        event.detail.changed.includes("activemedia-blocked")
      ) {
        this.updateTabSoundLabel(event.target);
      }
    }

    on_TabHide(event) {
      if (event.target.soundPlaying) {
        this._hiddenSoundPlayingStatusChanged(event.target);
      }
    }

    on_TabShow(event) {
      if (event.target.soundPlaying) {
        this._hiddenSoundPlayingStatusChanged(event.target);
      }
    }

    on_TabHoverStart(event) {
      if (!this._showTabHoverPreview) {
        return;
      }
      this.ensureTabPreviewPanelLoaded();
      this.previewPanel.activate(event.target);
    }

    on_TabHoverEnd(event) {
      this.previewPanel?.deactivate(event.target);
    }

    on_TabNoteIconHoverStart(event) {
      if (!this._showTabHoverPreview) {
        return;
      }
      this.ensureTabPreviewPanelLoaded();
      this.previewPanel.activateNotePanel(
        event.target,
        event.detail.noteIconElement
      );
    }

    on_TabNoteIconHoverEnd(event) {
      this.previewPanel?.deactivateNotePanel(event.target);
      if (event.detail.returningToTab) {
        this.previewPanel?.activate(event.target);
      }
    }

    cancelTabGroupPreview() {
      this.previewPanel?.panelOpener.clear();
    }

    showTabGroupPreview(group) {
      if (!this._showTabGroupHoverPreview) {
        return;
      }
      this.ensureTabPreviewPanelLoaded();
      this.previewPanel.activate(group);
    }

    on_TabGroupLabelHoverStart(event) {
      this.showTabGroupPreview(event.target.group);
    }

    on_TabGroupLabelHoverEnd(event) {
      this.previewPanel?.deactivate(event.target.group);
    }

    on_TabGroupExpand(event) {
      this._invalidateCachedVisibleTabs();
      this.#animatingGroups.add(event.target.id);
    }

    on_TabGroupCollapse(event) {
      this._invalidateCachedVisibleTabs();
      this._unlockTabSizing();
      this.#animatingGroups.add(event.target.id);
    }

    on_TabGroupAnimationComplete(event) {
      // Delay clearing the animating flag so overflow/underflow handlers
      // triggered by the size change can observe it and skip auto-scroll.
      window.requestAnimationFrame(() => {
        this.#animatingGroups.delete(event.target.id);
      });
    }

    on_TabGroupCreate() {
      this._invalidateCachedTabs();
    }

    on_TabGroupRemoved() {
      this._invalidateCachedTabs();
    }

    on_SplitViewCreated() {
      this._invalidateCachedTabs();
    }

    on_SplitViewRemoved() {
      this._invalidateCachedTabs();
    }

    /**
     * @param {TransitionEvent} event
     */

    on_transitionend(event) {
      if (event.propertyName != "max-width") {
        return;
      }

      let tab = event.target?.closest("tab");

      if (!tab) {
        return;
      }

      if (tab.hasAttribute("fadein")) {
        if (tab._fullyOpen) {
          this._updateCloseButtons();
        } else {
          this._handleNewTab(tab);
        }
      } else if (tab.closing) {
        gBrowser._endRemoveTab(tab);
      }

      let evt = new CustomEvent("TabAnimationEnd", { bubbles: true });
      tab.dispatchEvent(evt);
    }

    on_dblclick(event) {
      // When the tabbar has an unified appearance with the titlebar
      // and menubar, a double-click in it should have the same behavior
      // as double-clicking the titlebar
      if (CustomTitlebar.enabled && !this.verticalMode) {
        return;
      }

      // Make sure it is the primary button, we are hitting our arrowscrollbox,
      // and we're not hitting the scroll buttons.
      if (
        event.button != 0 ||
        event.target != this.arrowScrollbox ||
        event.composedTarget.localName == "toolbarbutton"
      ) {
        return;
      }

      if (!this._blockDblClick) {
        BrowserCommands.openTab();
      }

      event.preventDefault();
    }

    on_click(event) {
      if (event.eventPhase == Event.CAPTURING_PHASE && event.button == 0) {
        /* Catches extra clicks meant for the in-tab close button.
         * Placed here to avoid leaking (a temporary handler added from the
         * in-tab close button binding would close over the tab and leak it
         * until the handler itself was removed). (bug 897751)
         *
         * The only sequence in which a second click event (i.e. dblclik)
         * can be dispatched on an in-tab close button is when it is shown
         * after the first click (i.e. the first click event was dispatched
         * on the tab). This happens when we show the close button only on
         * the active tab. (bug 352021)
         * The only sequence in which a third click event can be dispatched
         * on an in-tab close button is when the tab was opened with a
         * double click on the tabbar. (bug 378344)
         * In both cases, it is most likely that the close button area has
         * been accidentally clicked, therefore we do not close the tab.
         *
         * We don't want to ignore processing of more than one click event,
         * though, since the user might actually be repeatedly clicking to
         * close many tabs at once.
         */

        let target = event.originalTarget;
        if (target.classList.contains("tab-close-button")) {
          // We preemptively set this to allow the closing-multiple-tabs-
          // in-a-row case.
          if (this._blockDblClick) {
            target._ignoredCloseButtonClicks = true;
          } else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
            target._ignoredCloseButtonClicks = true;
            event.stopPropagation();
            return;
          } else {
            // Reset the "ignored click" flag
            target._ignoredCloseButtonClicks = false;
          }
        }

        /* Protects from close-tab-button errant doubleclick:
         * Since we're removing the event target, if the user
         * double-clicks the button, the dblclick event will be dispatched
         * with the tabbar as its event target (and explicit/originalTarget),
         * which treats that as a mouse gesture for opening a new tab.
         * In this context, we're manually blocking the dblclick event.
         */

        if (this._blockDblClick) {
          if (!("_clickedTabBarOnce" in this)) {
            this._clickedTabBarOnce = true;
            return;
          }
          delete this._clickedTabBarOnce;
          this._blockDblClick = false;
        }
      } else if (
        event.eventPhase == Event.BUBBLING_PHASE &&
        event.button == 1
      ) {
        let tab = event.target?.closest("tab");
        if (tab) {
          if (tab.multiselected) {
            gBrowser.removeMultiSelectedTabs();
          } else {
            gBrowser.removeTab(tab, {
              animate: true,
              triggeringEvent: event,
            });
          }
        } else if (isTabGroupLabel(event.target)) {
          event.target.group.saveAndClose();
        } else if (
          event.originalTarget.closest("scrollbox") &&
          !Services.prefs.getBoolPref(
            "widget.gtk.titlebar-action-middle-click-enabled"
          )
        ) {
          // Check whether the click
          // was dispatched on the open space of it.
          let visibleTabs = this.visibleTabs;
          let lastTab = visibleTabs.at(-1);
          let winUtils = window.windowUtils;
          let endOfTab =
            winUtils.getBoundsWithoutFlushing(lastTab)[
              (this.verticalMode && "bottom") ||
                (this.#rtlMode ? "left" : "right")
            ];
          if (
            (this.verticalMode && event.clientY > endOfTab) ||
            (!this.verticalMode &&
              (this.#rtlMode
                ? event.clientX < endOfTab
                : event.clientX > endOfTab))
          ) {
            BrowserCommands.openTab();
          }
        } else {
          return;
        }

        event.preventDefault();
        event.stopPropagation();
      }
    }

    on_keydown(event) {
      let { altKey, shiftKey } = event;
      let [accel, nonAccel] =
        AppConstants.platform == "macosx"
          ? [event.metaKey, event.ctrlKey]
          : [event.ctrlKey, event.metaKey];

      let keyComboForFocusedElement =
        !accel && !shiftKey && !altKey && !nonAccel;
      let keyComboForMove = accel && shiftKey && !altKey && !nonAccel;
      let keyComboForFocus = accel && !shiftKey && !altKey && !nonAccel;

      if (!keyComboForFocusedElement && !keyComboForMove && !keyComboForFocus) {
        return;
      }

      if (keyComboForFocusedElement) {
        let ariaFocusedItem = this.ariaFocusedItem;
        if (isTabGroupLabel(ariaFocusedItem)) {
          switch (event.keyCode) {
            case KeyEvent.DOM_VK_SPACE:
            case KeyEvent.DOM_VK_RETURN: {
              ariaFocusedItem.click();
              event.preventDefault();
            }
          }
        }
      } else if (keyComboForMove) {
        switch (event.keyCode) {
          case KeyEvent.DOM_VK_UP:
            gBrowser.moveTabBackward();
            break;
          case KeyEvent.DOM_VK_DOWN:
            gBrowser.moveTabForward();
            break;
          case KeyEvent.DOM_VK_RIGHT:
            if (RTL_UI) {
              gBrowser.moveTabBackward();
            } else {
              gBrowser.moveTabForward();
            }
            break;
          case KeyEvent.DOM_VK_LEFT:
            if (RTL_UI) {
              gBrowser.moveTabForward();
            } else {
              gBrowser.moveTabBackward();
            }
            break;
          case KeyEvent.DOM_VK_HOME:
            gBrowser.moveTabToStart();
            break;
          case KeyEvent.DOM_VK_END:
            gBrowser.moveTabToEnd();
            break;
          default:
            // Consume the keydown event for the above keyboard
            // shortcuts only.
            return;
        }

        event.preventDefault();
      } else if (keyComboForFocus) {
        switch (event.keyCode) {
          case KeyEvent.DOM_VK_UP:
            this.#advanceFocus(DIRECTION_BACKWARD);
            break;
          case KeyEvent.DOM_VK_DOWN:
            this.#advanceFocus(DIRECTION_FORWARD);
            break;
          case KeyEvent.DOM_VK_RIGHT:
            if (RTL_UI) {
              this.#advanceFocus(DIRECTION_BACKWARD);
            } else {
              this.#advanceFocus(DIRECTION_FORWARD);
            }
            break;
          case KeyEvent.DOM_VK_LEFT:
            if (RTL_UI) {
              this.#advanceFocus(DIRECTION_FORWARD);
            } else {
              this.#advanceFocus(DIRECTION_BACKWARD);
            }
            break;
          case KeyEvent.DOM_VK_HOME:
            this.ariaFocusedItem = this.ariaFocusableItems.at(0);
            break;
          case KeyEvent.DOM_VK_END:
            this.ariaFocusedItem = this.ariaFocusableItems.at(-1);
            break;
          case KeyEvent.DOM_VK_SPACE: {
            let ariaFocusedItem = this.ariaFocusedItem;
            if (isTab(ariaFocusedItem)) {
              if (ariaFocusedItem.multiselected) {
                gBrowser.removeFromMultiSelectedTabs(ariaFocusedItem);
              } else {
                gBrowser.addToMultiSelectedTabs(ariaFocusedItem);
              }
            }
            break;
          }
          default:
            // Consume the keydown event for the above keyboard
            // shortcuts only.
            return;
        }

        event.preventDefault();
      }
    }

    /**
     * @param {FocusEvent} event
     */

    on_focusin(event) {
      if (event.target == this.selectedItem) {
        this.tablistHasFocus = true;
        if (!this.ariaFocusedItem) {
          // If the active tab is receiving focus and there isn't a keyboard
          // focus target yet, set the keyboard focus target to the active
          // tab. Do not override the keyboard-focused item if the user
          // already set a keyboard focus.
          this.ariaFocusedItem = this.selectedItem;
        }
      }
      let focusReturnedFromGroupPanel = event.relatedTarget?.classList.contains(
        "group-preview-button"
      );
      if (
        !focusReturnedFromGroupPanel &&
        this.tablistHasFocus &&
        isTabGroupLabel(this.ariaFocusedItem)
      ) {
        this.showTabGroupPreview(this.ariaFocusedItem.group);
      }
    }

    /**
     * @param {FocusEvent} event
     */

    on_focusout(event) {
      this.cancelTabGroupPreview();
      if (event.target == this.selectedItem) {
        this.tablistHasFocus = false;
      }
    }

    on_keypress(event) {
      if (event.defaultPrevented) {
        return;
      }
      if (event.key == " " || event.key == "Enter") {
        event.preventDefault();
        event.target.click();
      }
    }

    on_dragstart(event) {
      this.tabDragAndDrop.handle_dragstart(event);
    }

    on_dragover(event) {
      this.tabDragAndDrop.handle_dragover(event);
    }

    on_drop(event) {
      this.tabDragAndDrop.handle_drop(event);
    }

    on_dragend(event) {
      this.tabDragAndDrop.handle_dragend(event);
    }

    on_dragleave(event) {
      this.tabDragAndDrop.handle_dragleave(event);
    }

    on_wheel(event) {
      if (
        Services.prefs.getBoolPref("toolkit.tabbox.switchByScrolling"false)
      ) {
        event.stopImmediatePropagation();
      }
    }

    on_overflow(event) {
      // Ignore overflow events from nested scrollable elements
      if (event.target != this.arrowScrollbox) {
        return;
      }

      this.toggleAttribute("overflow"true);
      this._updateCloseButtons();

      if (!this.#animatingGroups.size) {
        this._handleTabSelect(true);
      }

      document
        .getElementById("tab-preview-panel")
        ?.setAttribute("rolluponmousewheel"true);
    }

    on_underflow(event) {
      // Ignore underflow events:
      // - from nested scrollable elements
      // - corresponding to an overflow event that we ignored
      if (event.target != this.arrowScrollbox || !this.overflowing) {
        return;
      }

      this.removeAttribute("overflow");

      if (this._lastTabClosedByMouse) {
        this._expandSpacerBy(this._scrollButtonWidth);
      }

      for (let tab of gBrowser._removingTabs) {
        gBrowser.removeTab(tab);
      }

      this._updateCloseButtons();

      document
        .getElementById("tab-preview-panel")
        ?.removeAttribute("rolluponmousewheel");
    }

    on_contextmenu(event) {
      // When pressing the context menu key (as opposed to right-clicking)
      // while a tab group label has aria focus (as opposed to DOM focus),
      // open the tab group context menu as if the label had DOM focus.
      // The button property is used to differentiate between key and mouse.
      if (event.button == 0 && isTabGroupLabel(this.ariaFocusedItem)) {
        gBrowser.tabGroupMenu.openEditModal(this.ariaFocusedItem.group);
        event.preventDefault();
      }
    }

    on_uidensitychanged() {
      this._updateCloseButtons();
      this.#updateTabMinHeight();
      this._handleTabSelect(true);
    }

    // Utilities

    get emptyTabTitle() {
      // Normal tab title is used also in the permanent private browsing mode.
      const l10nId =
        PrivateBrowsingUtils.isWindowPrivate(window) &&
        !Services.prefs.getBoolPref("browser.privatebrowsing.autostart")
          ? "tabbrowser-empty-private-tab-title"
          : "tabbrowser-empty-tab-title";
      return gBrowser.tabLocalization.formatValueSync(l10nId);
    }

    get tabbox() {
      return document.getElementById("tabbrowser-tabbox");
    }

    get newTabButton() {
      return this.querySelector("#tabs-newtab-button");
    }

    get verticalMode() {
      return this.getAttribute("orient") == "vertical";
    }

    get expandOnHover() {
      return this._sidebarVisibility == "expand-on-hover";
    }

    get #rtlMode() {
      return !this.verticalMode && RTL_UI;
    }

    get overflowing() {
      return this.hasAttribute("overflow");
    }

    #allTabs;
    get allTabs() {
      if (this.#allTabs) {
        return this.#allTabs;
      }
      // Remove temporary periphery element added at drag start.
      let pinnedChildren = Array.from(this.pinnedTabsContainer.children);
      if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") {
        pinnedChildren.pop();
      }
      let unpinnedChildren = Array.from(this.arrowScrollbox.children);
      // remove arrowScrollbox periphery element.
      unpinnedChildren.pop();

      // explode tab groups and split view wrappers
      // Iterate backwards over the array to preserve indices while we modify
      // things in place
      for (let i = unpinnedChildren.length - 1; i >= 0; i--) {
        if (
          unpinnedChildren[i].tagName == "tab-group" ||
          unpinnedChildren[i].tagName == "tab-split-view-wrapper"
        ) {
          unpinnedChildren.splice(i, 1, ...unpinnedChildren[i].tabs);
        }
      }

      this.#allTabs = [...pinnedChildren, ...unpinnedChildren];
      return this.#allTabs;
    }

    get allGroups() {
      let children = Array.from(this.arrowScrollbox.children);
      return children.filter(node => node.tagName == "tab-group");
    }

    get allSplitViews() {
      let children = Array.from(this.arrowScrollbox.children);
      let splitViews = [];
      for (let node of children) {
        if (node.tagName == "tab-split-view-wrapper") {
          splitViews.push(node);
        } else if (node.tagName == "tab-group") {
          splitViews.push(
            ...Array.from(node.children).filter(
              child => child.tagName == "tab-split-view-wrapper"
            )
          );
        }
      }
      return splitViews;
    }

    /**
     * Returns all tabs in the current window, including hidden tabs and tabs
     * in collapsed groups, but excluding closing tabs and the Firefox View tab.
     */

    get openTabs() {
      if (!this.#openTabs) {
        this.#openTabs = this.allTabs.filter(tab => tab.isOpen);
      }
      return this.#openTabs;
    }
    #openTabs;

    /**
     * Same as `openTabs` but excluding hidden tabs.
     */

    get nonHiddenTabs() {
      if (!this.#nonHiddenTabs) {
        this.#nonHiddenTabs = this.openTabs.filter(tab => !tab.hidden);
      }
      return this.#nonHiddenTabs;
    }
    #nonHiddenTabs;

    /**
     * Same as `openTabs` but excluding hidden tabs and tabs in collapsed groups.
     */

    get visibleTabs() {
      if (!this.#visibleTabs) {
        this.#visibleTabs = this.openTabs.filter(tab => tab.visible);
      }
      return this.#visibleTabs;
    }
    #visibleTabs;

    /**
     * @returns {boolean} true if the keyboard focus is on the active tab
     */

    get tablistHasFocus() {
      return this.hasAttribute("tablist-has-focus");
    }

    /**
     * @param {boolean} hasFocus true if the keyboard focus is on the active tab
     */

    set tablistHasFocus(hasFocus) {
      this.toggleAttribute("tablist-has-focus", hasFocus);
    }

    /** @typedef {MozTabbrowserTab|MozTextLabel} FocusableItem */

    /** @type {FocusableItem[]} */
    #focusableItems;

    /** @type {dragAndDropElements[]} */
    #dragAndDropElements;

    /**
     * @returns {FocusableItem[]}
     * @override
     */

    get ariaFocusableItems() {
      if (this.#focusableItems) {
        return this.#focusableItems;
      }

      let unpinnedChildren = Array.from(this.arrowScrollbox.children);
      let pinnedChildren = Array.from(this.pinnedTabsContainer.children);

      let focusableItems = [];
      for (let child of pinnedChildren) {
        if (isTab(child)) {
          focusableItems.push(child);
        }
      }
      for (let child of unpinnedChildren) {
        if (isTab(child) && child.visible) {
          focusableItems.push(child);
        } else if (isTabGroup(child)) {
          focusableItems.push(child.labelElement);

          let visibleTabsInGroup = child.tabs.filter(tab => tab.visible);
          focusableItems.push(...visibleTabsInGroup);
        } else if (child.tagName == "tab-split-view-wrapper") {
          let visibleTabsInSplitView = child.tabs.filter(tab => tab.visible);
          focusableItems.push(...visibleTabsInSplitView);
        }
      }

      this.#focusableItems = focusableItems;

      return this.#focusableItems;
    }

    /**
     * @returns {dragAndDropElements[]}
     * Representation of every drag and drop element including tabs, tab group labels and split view wrapper.
     * We keep this separate from ariaFocusableItems because not every element for drag n'drop also needs to be
     * focusable (ex, we don't want the splitview container to be focusable, only its children).
     */

    get dragAndDropElements() {
      if (this.#dragAndDropElements) {
        return this.#dragAndDropElements;
      }

      let elementIndex = 0;
      let dragAndDropElements = [];
      let unpinnedChildren = Array.from(this.arrowScrollbox.children);
      let pinnedChildren = Array.from(this.pinnedTabsContainer.children);

      for (let child of [...pinnedChildren, ...unpinnedChildren]) {
        if (
          !(
            (isTab(child) && child.visible) ||
            isTabGroup(child) ||
            isSplitViewWrapper(child)
          )
        ) {
          continue;
        }

        if (isTabGroup(child)) {
          child.labelElement.elementIndex = elementIndex++;
          dragAndDropElements.push(child.labelElement);

          let tabsAndSplitViews = child.tabsAndSplitViews.filter(
            node => node.visible
          );
          tabsAndSplitViews.forEach(ele => {
            ele.elementIndex = elementIndex++;
          });
          dragAndDropElements.push(...tabsAndSplitViews);
        } else {
          child.elementIndex = elementIndex++;
          dragAndDropElements.push(child);
        }
      }

      this.#dragAndDropElements = dragAndDropElements;
      return this.#dragAndDropElements;
    }

    /**
     * Moves the ARIA focus in the tab strip left or right, as appropriate, to
     * the next tab or tab group label.
     *
     * @param {-1|1} direction
     */

    #advanceFocus(direction) {
      let currentIndex = this.ariaFocusableItems.indexOf(this.ariaFocusedItem);
      let newIndex = currentIndex + direction;

      // Clamp the index so that the focus stops at the edges of the tab strip
      newIndex = Math.min(
        this.ariaFocusableItems.length - 1,
        Math.max(0, newIndex)
      );

      let itemToFocus = this.ariaFocusableItems[newIndex];
      this.ariaFocusedItem = itemToFocus;

      // If the newly-focused item is a tab group label and the group is collapsed,
      // proactively show the tab group preview
      if (isTabGroupLabel(this.ariaFocusedItem)) {
        this.showTabGroupPreview(this.ariaFocusedItem.group);
      }
    }

    _invalidateCachedTabs() {
      this.#allTabs = null;
      this._invalidateCachedVisibleTabs();
    }

    _invalidateCachedVisibleTabs() {
      this.#openTabs = null;
      this.#nonHiddenTabs = null;
      this.#visibleTabs = null;
      // Focusable items must also be visible, but they do not depend on
      // this.#visibleTabs, so changes to visible tabs need to also invalidate
      // the focusable items and dragAndDropElements cache.
      this.#focusableItems = null;
      this.#dragAndDropElements = null;
    }

    #isMovingTab() {
      return this.hasAttribute("movingtab");
    }

    isContainerVerticalPinnedGrid(tab) {
      return (
        tab.pinned &&
        this.verticalMode &&
        this.hasAttribute("expanded") &&
        !this.expandOnHover
      );
    }

    /**
     * Changes the selected tab or tab group label on the tab strip
     * relative to the ARIA-focused tab strip element or the active tab. This
     * is intended for traversing the tab strip visually, e.g by using keyboard
     * arrows. For cases where keyboard shortcuts or other logic should only
     * select tabs (and never tab group labels), see `advanceSelectedTab`.
     *
     * @override
     * @param {-1|1} direction
     * @param {boolean} shouldWrap
     */

    advanceSelectedItem(aDir, aWrap) {
      let groupPanel = this.previewPanel?.tabGroupPanel;
      if (groupPanel && groupPanel.isActive) {
        // if the group panel is open, it should receive keyboard focus here
        // instead of moving to the next item in the tabstrip.
        groupPanel.focusPanel(aDir);
        return;
      }

      // cancel any pending group popup since we expect to deselect the label
      this.cancelTabGroupPreview();

      let { ariaFocusableItems, ariaFocusedIndex } = this;

      // Advance relative to the ARIA-focused item if set, otherwise advance
      // relative to the active tab.
      let currentItemIndex =
        ariaFocusedIndex >= 0
          ? ariaFocusedIndex
          : ariaFocusableItems.indexOf(this.selectedItem);

      let newItemIndex = currentItemIndex + aDir;

      if (aWrap) {
        if (newItemIndex >= ariaFocusableItems.length) {
          newItemIndex = 0;
        } else if (newItemIndex < 0) {
          newItemIndex = ariaFocusableItems.length - 1;
        }
      } else {
        newItemIndex = Math.min(
          ariaFocusableItems.length - 1,
          Math.max(0, newItemIndex)
        );
      }

      if (currentItemIndex == newItemIndex) {
        return;
      }

      // If the next item is a tab, select it. If the next item is a tab group
      // label, keep the active tab selected and just set ARIA focus on the tab
      // group label.
      let newItem = ariaFocusableItems[newItemIndex];
      if (isTab(newItem)) {
        this._selectNewTab(newItem, aDir, aWrap);
      }
      this.ariaFocusedItem = newItem;

      // If the newly-focused item is a tab group label and the group is collapsed,
      // proactively show the tab group preview
      if (isTabGroupLabel(this.ariaFocusedItem)) {
        this.showTabGroupPreview(this.ariaFocusedItem.group);
      }
    }

    ensureTabPreviewPanelLoaded() {
      if (!this.previewPanel) {
        const TabHoverPanelSet = ChromeUtils.importESModule(
          "chrome://browser/content/tabbrowser/tab-hover-preview.mjs"
        ).default;
        this.previewPanel = new TabHoverPanelSet(window);
      }
    }

    appendChild(tab) {
      return this.insertBefore(tab, null);
    }

    insertBefore(tab, node) {
      if (!this.arrowScrollbox) {
        throw new Error("Shouldn't call this without arrowscrollbox");
      }

      if (node == null) {
        // We have a container for non-tab elements at the end of the scrollbox.
        node = this.arrowScrollbox.lastChild;
      }

      node.before(tab);

      if (this.#mustUpdateTabMinHeight) {
        this.#updateTabMinHeight();
      }
    }

    #updateTabMinWidth(val) {
      this.style.setProperty(
        "--tab-min-width-pref",
        (val ?? this._tabMinWidthPref) + "px"
      );
    }

    #updateTabMinHeight() {
      if (!this.verticalMode || !window.toolbar.visible) {
        this.#mustUpdateTabMinHeight = false;
        return;
      }

      // Find at least one tab we can scroll to.
      let firstScrollableTab = this.visibleTabs.find(
        this.arrowScrollbox._canScrollToElement
      );

      if (!firstScrollableTab) {
        // If not, we're in a pickle. We should never get here except if we
        // also don't use the outcome of this work (because there's nothing to
        // scroll so we don't care about the scrollbox size).
        // So just set a flag so we re-run once we do have a new tab.
        this.#mustUpdateTabMinHeight = true;
        return;
      }

      let { height } =
        window.windowUtils.getBoundsWithoutFlushing(firstScrollableTab);

      // Use the current known height or a sane default.
      this.#tabMinHeight = height || 36;

      // The height we got may be incorrect if a flush is pending so re-check it after
      // a flush completes.
      window
        .promiseDocumentFlushed(() => {})
        .then(
          () => {
            height =
              window.windowUtils.getBoundsWithoutFlushing(
                firstScrollableTab
              ).height;

            if (height) {
              this.#tabMinHeight = height;
            }
          },
          () => {
            /* ignore errors */
          }
        );
    }

    get _isCustomizing() {
      return document.documentElement.hasAttribute("customizing");
    }

    // This overrides the TabsBase _selectNewTab method so that we can
    // potentially interrupt keyboard tab switching when sharing the
    // window or screen.
    _selectNewTab(aNewTab, aFallbackDir, aWrap) {
      if (!gSharedTabWarning.willShowSharedTabWarning(aNewTab)) {
        super._selectNewTab(aNewTab, aFallbackDir, aWrap);
      }
    }

    observe(aSubject, aTopic) {
      switch (aTopic) {
        case "nsPref:changed": {
          // This is has to deal with changes in
          // privacy.userContext.enabled and
          // privacy.userContext.newTabContainerOnLeftClick.enabled.
          let containersEnabled =
            Services.prefs.getBoolPref("privacy.userContext.enabled") &&
            !PrivateBrowsingUtils.isWindowPrivate(window);

          // This pref won't change so often, so just recreate the menu.
          const newTabLeftClickOpensContainersMenu = Services.prefs.getBoolPref(
            "privacy.userContext.newTabContainerOnLeftClick.enabled"
          );

          // There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and
          // for when the tab strip is overflowed (which is shared by vertical and horizontal tabs);
          // Attach the long click popup to all of them.
          const newTab = document.getElementById("new-tab-button");
          const newTab2 = this.newTabButton;
          const newTabVertical = document.getElementById(
            "vertical-tabs-newtab-button"
          );

          for (let parent of [newTab, newTab2, newTabVertical]) {
            if (!parent) {
              continue;
            }

            parent.removeAttribute("type");
            if (parent.menupopup) {
              parent.menupopup.remove();
            }

            if (containersEnabled) {
              parent.setAttribute("context""new-tab-button-popup");

              let popup = document
                .getElementById("new-tab-button-popup")
                .cloneNode(true);
              popup.removeAttribute("id");
              popup.className = "new-tab-popup";
              popup.setAttribute("position""after_end");
              popup.addEventListener("popupshowing", CreateContainerTabMenu);
              parent.prepend(popup);
              parent.setAttribute("type""menu");
              // Update tooltip text
              DynamicShortcutTooltip.nodeToTooltipMap[parent.id] =
                newTabLeftClickOpensContainersMenu
                  ? "newTabAlwaysContainer.tooltip"
                  : "newTabContainer.tooltip";
            } else {
              DynamicShortcutTooltip.nodeToTooltipMap[parent.id] =
                "newTabButton.tooltip";
              parent.removeAttribute("context""new-tab-button-popup");
            }
            // evict from tooltip cache
            DynamicShortcutTooltip.cache.delete(parent.id);

            // If containers and press-hold container menu are both used,
            // add to gClickAndHoldListenersOnElement; otherwise, remove.
            if (containersEnabled && !newTabLeftClickOpensContainersMenu) {
              gClickAndHoldListenersOnElement.add(parent);
            } else {
              gClickAndHoldListenersOnElement.remove(parent);
            }
          }

          break;
        }
      }
    }

    _updateCloseButtons() {
      if (this.overflowing) {
        // Tabs are at their minimum widths.
        this.setAttribute("closebuttons""activetab");
        return;
      }

      if (this._closeButtonsUpdatePending) {
        return;
      }
      this._closeButtonsUpdatePending = true;

      // Wait until after the next paint to get current layout data from
      // getBoundsWithoutFlushing.
      window.requestAnimationFrame(() => {
        window.requestAnimationFrame(() => {
          this._closeButtonsUpdatePending = false;

          // The scrollbox may have started overflowing since we checked
          // overflow earlier, so check again.
          if (this.overflowing) {
            this.setAttribute("closebuttons""activetab");
            return;
          }

          // Check if tab widths are below the threshold where we want to
          // remove close buttons from background tabs so that people don't
          // accidentally close tabs by selecting them.
          let rect = ele => {
            return window.windowUtils.getBoundsWithoutFlushing(ele);
          };
          // See bug 2007766, we need to find the first tab that isn't
          // inside a split view, because those can be narrower than the threshold.
          let tab = this.visibleTabs
            .slice(gBrowser.pinnedTabCount)
            .find(t => !t.splitview);
          if (tab && rect(tab).width <= this._tabClipWidth) {
            this.setAttribute("closebuttons""activetab");
          } else {
            this.removeAttribute("closebuttons");
          }
        });
      });
    }

    /**
     * @param {boolean} [aInstant]
     */

    _handleTabSelect(aInstant) {
      let selectedTab = this.selectedItem;
      this.#ensureTabIsVisible(selectedTab, aInstant);

      selectedTab._notselectedsinceload = false;
    }

    /**
     * @param {MozTabbrowserTab} tab
     * @param {boolean} [shouldScrollInstantly=false]
     */

    #ensureTabIsVisible(tab, shouldScrollInstantly = false) {
      let arrowScrollbox = tab.closest("arrowscrollbox");
      if (arrowScrollbox?.overflowing) {
        arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly);
      }
    }

    /**
     * Try to keep the active tab's close button under the mouse cursor
     */

    _lockTabSizing(aClosingTab, aTabWidth) {
      if (this.verticalMode) {
        return;
      }

      let tabs = this.visibleTabs;
      let numPinned = gBrowser.pinnedTabCount;

      if (tabs.length <= numPinned) {
        // There are no unpinned tabs left.
        return;
      }

      let isEndTab = aClosingTab && aClosingTab._tPos > tabs.at(-1)._tPos;

      if (!this._tabDefaultMaxWidth) {
        this._tabDefaultMaxWidth = parseFloat(
          window.getComputedStyle(tabs[numPinned]).maxWidth
        );
      }
      this._lastTabClosedByMouse = true;
      this._scrollButtonWidth = window.windowUtils.getBoundsWithoutFlushing(
        this.arrowScrollbox._scrollButtonDown
      ).width;
      if (aTabWidth === undefined) {
        aTabWidth = window.windowUtils.getBoundsWithoutFlushing(
          tabs[numPinned]
        ).width;
      }

      if (this.overflowing) {
        // Don't need to do anything if we're in overflow mode and aren't scrolled
        // all the way to the right, or if we're closing the last tab.
        if (isEndTab || !this.arrowScrollbox.hasAttribute("scrolledtoend")) {
          return;
        }
        // If the tab has an owner that will become the active tab, the owner will
        // be to the left of it, so we actually want the left tab to slide over.
        // This can't be done as easily in non-overflow mode, so we don't bother.
        if (aClosingTab?.owner) {
          return;
        }
        this._expandSpacerBy(aTabWidth);
      } /* non-overflow mode */ else {
        if (isEndTab && !this._hasTabTempMaxWidth) {
          // Locking is neither in effect nor needed, so let tabs expand normally.
          return;
        }
        // Force tabs to stay the same width, unless we're closing the last tab,
        // which case we need to let them expand just enough so that the overall
        // tabbar width is the same.
        if (isEndTab) {
          let numNormalTabs = tabs.length - numPinned;
          aTabWidth = (aTabWidth * (numNormalTabs + 1)) / numNormalTabs;
          if (aTabWidth > this._tabDefaultMaxWidth) {
            aTabWidth = this._tabDefaultMaxWidth;
          }
        }
        aTabWidth += "px";
        let tabsToReset = [];
        for (let i = numPinned; i < tabs.length; i++) {
          let tab = tabs[i];
          tab.style.setProperty("max-width", aTabWidth, "important");
          if (!isEndTab) {
            // keep tabs the same width
            tab.animationsEnabled = false;
            tabsToReset.push(tab);
          }
        }

        if (tabsToReset.length) {
          window
            .promiseDocumentFlushed(() => {})
            .then(() => {
              window.requestAnimationFrame(() => {
                for (let tab of tabsToReset) {
                  tab.animationsEnabled = true;
                }
              });
            });
        }

        this._hasTabTempMaxWidth = true;
        gBrowser.addEventListener("mousemove"this);
        window.addEventListener("mouseout"this);
      }
    }

    _expandSpacerBy(pixels) {
      let spacer = this._closingTabsSpacer;
      spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
      this.toggleAttribute("using-closing-tabs-spacer"true);
      gBrowser.addEventListener("mousemove"this);
      window.addEventListener("mouseout"this);
    }

    _unlockTabSizing() {
      gBrowser.removeEventListener("mousemove"this);
      window.removeEventListener("mouseout"this);

      if (this._hasTabTempMaxWidth) {
        this._hasTabTempMaxWidth = false;
        // Only visible tabs have their sizes locked, but those visible tabs
        // could become invisible before being unlocked (e.g. by being inside
        // of a collapsing tab group), so it's better to reset all tabs.
        let tabs = this.allTabs;
        for (let i = 0; i < tabs.length; i++) {
          tabs[i].style.maxWidth = "";
        }
      }

      if (this.hasAttribute("using-closing-tabs-spacer")) {
        this.removeAttribute("using-closing-tabs-spacer");
        this._closingTabsSpacer.style.width = 0;
      }
    }

    _notifyBackgroundTab(aTab) {
      if (aTab.pinned || !aTab.visible || !this.overflowing) {
        return;
      }

      this._lastTabToScrollIntoView = aTab;
      if (!this._backgroundTabScrollPromise) {
        this._backgroundTabScrollPromise = window
          .promiseDocumentFlushed(() => {
            let lastTabRect =
              this._lastTabToScrollIntoView.getBoundingClientRect();
            let selectedTab = this.selectedItem;
            if (selectedTab.pinned) {
              selectedTab = null;
            } else {
              selectedTab = selectedTab.getBoundingClientRect();
              selectedTab = {
                left: selectedTab.left,
                right: selectedTab.right,
                top: selectedTab.top,
                bottom: selectedTab.bottom,
              };
            }
            return [
              this._lastTabToScrollIntoView,
              this.arrowScrollbox.scrollClientRect,
              lastTabRect,
              selectedTab,
            ];
          })
          .then(([tabToScrollIntoView, scrollRect, tabRect, selectedRect]) => {
            // First off, remove the promise so we can re-enter if necessary.
            delete this._backgroundTabScrollPromise;
            // Then, if the layout info isn't for the last-scrolled-to-tab, re-run
            // the code above to get layout info for *that* tab, and don't do
            // anything here, as we really just want to run this for the last-opened tab.
            if (this._lastTabToScrollIntoView != tabToScrollIntoView) {
              this._notifyBackgroundTab(this._lastTabToScrollIntoView);
              return;
            }
            delete this._lastTabToScrollIntoView;
            // Is the new tab already completely visible?
            if (
              this.verticalMode
                ? scrollRect.top <= tabRect.top &&
                  tabRect.bottom <= scrollRect.bottom
                : scrollRect.left <= tabRect.left &&
                  tabRect.right <= scrollRect.right
            ) {
              return;
            }

            if (this.arrowScrollbox.smoothScroll) {
              // Can we make both the new tab and the selected tab completely visible?
              if (
                !selectedRect ||
                (this.verticalMode
                  ? Math.max(
                      tabRect.bottom - selectedRect.top,
                      selectedRect.bottom - tabRect.top
                    ) <= scrollRect.height
                  : Math.max(
                      tabRect.right - selectedRect.left,
                      selectedRect.right - tabRect.left
                    ) <= scrollRect.width)
              ) {
                this.#ensureTabIsVisible(tabToScrollIntoView);
                return;
              }

              let scrollPixels;
              if (this.verticalMode) {
                scrollPixels = tabRect.top - selectedRect.top;
              } else if (this.#rtlMode) {
                scrollPixels = selectedRect.right - scrollRect.right;
              } else {
                scrollPixels = selectedRect.left - scrollRect.left;
              }
              this.arrowScrollbox.scrollByPixels(scrollPixels);
            }

            if (!this._animateElement.hasAttribute("highlight")) {
              this._animateElement.toggleAttribute("highlight"true);
              setTimeout(
                function (ele) {
                  ele.removeAttribute("highlight");
                },
                150,
                this._animateElement
              );
            }
          });
      }
    }

    _handleNewTab(tab) {
      if (tab.container != this) {
        return;
      }
      tab._fullyOpen = true;
      gBrowser.tabAnimationsInProgress--;

      this._updateCloseButtons();

      if (tab.hasAttribute("selected")) {
        this._handleTabSelect();
      } else if (!tab.hasAttribute("skipbackgroundnotify")) {
        this._notifyBackgroundTab(tab);
      }

      // If this browser isn't lazy (indicating it's probably created by
      // session restore), preload the next about:newtab if we don't
      // already have a preloaded browser.
      if (tab.linkedPanel) {
        NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
      }

      if (UserInteraction.running("browser.tabs.opening", window)) {
        UserInteraction.finish("browser.tabs.opening", window);
      }
    }

    _canAdvanceToTab(aTab) {
      return !aTab.closing;
    }

    /**
     * Returns the panel associated with a tab if it has a connected browser
     * and/or it is the selected tab.
     * For background lazy browsers, this will return null.
     */

    getRelatedElement(aTab) {
      if (!aTab) {
        return null;
      }

      // Cannot access gBrowser before it's initialized.
      if (!gBrowser._initialized) {
        return this.tabbox.tabpanels.firstElementChild;
      }

      // If the tab's browser is lazy, we need to `_insertBrowser` in order
      // to have a linkedPanel.  This will also serve to bind the browser
      // and make it ready to use. We only do this if the tab is selected
      // because otherwise, callers might end up unintentionally binding the
      // browser for lazy background tabs.
      if (!aTab.linkedPanel) {
        if (!aTab.selected) {
          return null;
        }
        gBrowser._insertBrowser(aTab);
      }
      return document.getElementById(aTab.linkedPanel);
    }

    _updateNewTabVisibility() {
      // Helper functions to help deal with customize mode wrapping some items
      let wrap = n =>
        n.parentNode.localName == "toolbarpaletteitem" ? n.parentNode : n;
      let unwrap = n =>
        n && n.localName == "toolbarpaletteitem" ? n.firstElementChild : n;

      // Starting from the tabs element, find the next sibling that:
      // - isn't hidden; and
      // - isn't the all-tabs button.
      // If it's the new tab button, consider the new tab button adjacent to the tabs.
      // If the new tab button is marked as adjacent and the tabstrip doesn't
      // overflow, we'll display the 'new tab' button inline in the tabstrip.
      // In all other cases, the separate new tab button is displayed in its
      // customized location.
      let sib = this;
      do {
        sib = unwrap(wrap(sib).nextElementSibling);
      } while (sib && (sib.hidden || sib.id == "alltabs-button"));

      this.toggleAttribute(
        "hasadjacentnewtabbutton",
        sib && sib.id == "new-tab-button"
      );
    }

    onWidgetAfterDOMChange(aNode, aNextNode, aContainer) {
      if (
        aContainer.ownerDocument == document &&
        aContainer.id == "TabsToolbar-customization-target"
      ) {
        this._updateNewTabVisibility();
      }
    }

    onAreaNodeRegistered(aArea, aContainer) {
      if (aContainer.ownerDocument == document && aArea == "TabsToolbar") {
        this._updateNewTabVisibility();
      }
    }

    onAreaReset(aArea, aContainer) {
      this.onAreaNodeRegistered(aArea, aContainer);
    }

    _hiddenSoundPlayingStatusChanged(tab, opts) {
      let closed = opts && opts.closed;
      if (!closed && tab.soundPlaying && !tab.visible) {
        this._hiddenSoundPlayingTabs.add(tab);
        this.toggleAttribute("hiddensoundplaying"true);
      } else {
        this._hiddenSoundPlayingTabs.delete(tab);
        if (this._hiddenSoundPlayingTabs.size == 0) {
          this.removeAttribute("hiddensoundplaying");
        }
      }
    }

    destroy() {
      if (this.boundObserve) {
        Services.prefs.removeObserver("privacy.userContext"this.boundObserve);
      }
      CustomizableUI.removeListener(this);
    }

    updateTabSoundLabel(tab) {
      // Add aria-label for inline audio button
      const [unmute, mute, unblock] =
        gBrowser.tabLocalization.formatMessagesSync([
          "tabbrowser-unmute-tab-audio-aria-label",
          "tabbrowser-mute-tab-audio-aria-label",
          "tabbrowser-unblock-tab-audio-aria-label",
        ]);
      if (tab.audioButton) {
        if (tab.hasAttribute("muted") || tab.hasAttribute("soundplaying")) {
          let ariaLabel;
          tab.linkedBrowser.audioMuted
            ? (ariaLabel = unmute.attributes[0].value)
            : (ariaLabel = mute.attributes[0].value);
          tab.audioButton.setAttribute("aria-label", ariaLabel);
        } else if (tab.hasAttribute("activemedia-blocked")) {
          tab.audioButton.setAttribute(
            "aria-label",
            unblock.attributes[0].value
          );
        }
      }
    }
  }

  customElements.define("tabbrowser-tabs", MozTabbrowserTabs, {
    extends"tabs",
  });
}

Messung V0.5 in Prozent
C=84 H=89 G=86

¤ Dauer der Verarbeitung: 0.32 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002