Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/browser/components/tabbrowser/content/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 14 kB image not shown  

Quelle  tabsplitview.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 chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  ChromeUtils.defineESModuleGetters(this, {
    DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  });

  /**
   * A shared task which updates the urlbar indicator whenever:
   * - A split view is activated or deactivated.
   * - The active tab of a split view changes.
   * - The order of tabs in a split view changes.
   *
   * @type {DeferredTask}
   */

  const updateUrlbarButton = new DeferredTask(() => {
    const { activeSplitView, selectedTab } = gBrowser;
    const button = document.getElementById("split-view-button");
    if (activeSplitView) {
      const activeIndex = activeSplitView.tabs.indexOf(selectedTab);
      button.hidden = false;
      button.setAttribute("data-active-index", activeIndex);
    } else {
      button.hidden = true;
      button.removeAttribute("data-active-index");
    }
  }, 0);

  class MozTabSplitViewWrapper extends MozXULElement {
    /** @type {MutationObserver} */
    #tabChangeObserver;

    /** @type {MozTabbrowserTab[]} */
    #tabs = [];

    #isClosing = false;

    #isUnsplitting = false;

    #shouldMoveAllTabsAtOnce = true;

    #storedPanelWidths = new WeakMap();

    /**
     * @returns {boolean}
     */

    get hasActiveTab() {
      return this.hasAttribute("hasactivetab");
    }

    get shouldMoveAllTabsAtOnce() {
      return this.#shouldMoveAllTabsAtOnce;
    }

    /**
     * @returns {MozTabbrowserGroup}
     */

    get group() {
      return gBrowser.isTabGroup(this.parentElement)
        ? this.parentElement
        : null;
    }

    /**
     * @typedef {object} TabSplitViewStateData
     *   State of a tab group inside of an open window.
     * @property {number} id
     *   Unique ID of the tab splitview.
     * @property {number} numberOfTabs
     *   Number of expected tabs in the splitview.
     *
     * Collect data related to a single tab splitview, synchronously.
     *
     * @returns {TabSplitViewStateData}
     *   Serialized splitview data
     */

    get state() {
      return {
        id: this.splitViewId,
        numberOfTabs: this.tabs.length,
      };
    }

    /**
     * @param {boolean} val
     */

    set hasActiveTab(val) {
      this.toggleAttribute("hasactivetab", val);
    }

    get multiselected() {
      return this.hasAttribute("multiselected");
    }

    constructor() {
      super();
      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "_hasUsedSplitView",
        "browser.tabs.splitview.hasUsed",
        false
      );
    }

    connectedCallback() {
      // Set up TabSelect listener, as this gets
      // removed in disconnectedCallback
      this.documentGlobal.addEventListener("TabSelect"this);

      this.#observeTabChanges();
      this.#restorePanelWidths();

      if (this.hasActiveTab) {
        this.#activate();
      }

      if (this._initialized) {
        return;
      }

      if (!this._hasUsedSplitView) {
        Services.prefs.setBoolPref("browser.tabs.splitview.hasUsed"true);
      }

      this._initialized = true;

      this.textContent = "";

      // Mirroring MozTabbrowserTab
      this.container = gBrowser.tabContainer;
    }

    disconnectedCallback() {
      this.#tabChangeObserver?.disconnect();
      this.documentGlobal.removeEventListener("TabSelect"this);
      this.#deactivate();
      this.#resetPanelWidths();
      this.container.dispatchEvent(
        new CustomEvent("SplitViewRemoved", {
          bubbles: true,
          composed: true,
        })
      );
    }

    #observeTabChanges() {
      if (!this.#tabChangeObserver) {
        this.#tabChangeObserver = new window.MutationObserver(mutations => {
          if (this.tabs.length) {
            this.hasActiveTab = this.tabs.some(tab => tab.selected);
            this.tabs.forEach((tab, index) => {
              // Renumber tabs so that a11y tools can tell users that a given
              // tab is "1 of 2" in the split view, for example.
              tab.setAttribute("aria-posinset", index + 1);
              tab.setAttribute("aria-setsize"this.tabs.length);
              tab.updateSplitViewAriaLabel(index);
            });
            this.dispatchEvent(
              new CustomEvent("SplitViewTabChange", {
                bubbles: true,
              })
            );
          } else {
            this.remove();
          }

          if (
            this.tabs.length == 1 &&
            mutations.length &&
            mutations[0].removedNodes.length == 1
          ) {
            // We assume you end up with only one tab in a splitview when the other tab is closed,
            // in which case, move the remaining tab out via this.unsplitTabs.
            this.unsplitTabs("tab_close");
          }
        });
      }
      this.#tabChangeObserver.observe(this, {
        childList: true,
      });
    }

    get splitViewId() {
      return parseInt(this.getAttribute("splitViewId"));
    }

    set splitViewId(val) {
      this.setAttribute("splitViewId", val);
    }

    /**
     * @returns {MozTabbrowserTab[]}
     */

    get tabs() {
      return Array.from(this.children).filter(node => node.matches("tab"));
    }

    get visible() {
      return this.tabs.every(tab => tab.visible);
    }

    get pinned() {
      return false;
    }

    /**
     * Get the list of tab panels from this split view.
     *
     * @returns {XULElement[]}
     */

    get panels() {
      const panels = [];
      for (const { linkedPanel } of this.#tabs) {
        const el = document.getElementById(linkedPanel);
        if (el) {
          panels.push(el);
        }
      }
      return panels;
    }

    /**
     * Show all Split View tabs in the content area.
     */

    #activate() {
      updateUrlbarButton.arm();
      gBrowser.showSplitViewPanels(this.#tabs);
      this.container.dispatchEvent(
        new CustomEvent("TabSplitViewActivate", {
          detail: { tabs: this.#tabs, splitview: this },
          bubbles: true,
        })
      );
    }

    /**
     * Remove Split View tabs from the content area, which in turn resets
     * various attributes on tabpanels.
     */

    #deactivate() {
      gBrowser.tabpanels.removeTabsFromSplitview(
        this.#tabs.filter(tab => !tab.splitview || tab.splitview === this)
      );
      updateUrlbarButton.arm();
      this.container.dispatchEvent(
        new CustomEvent("TabSplitViewDeactivate", {
          detail: { tabs: this.#tabs, splitview: this },
          bubbles: true,
        })
      );
    }

    /**
     * Temporarily hide Split View panels when switching to a non-split-view tab,
     * preserving split-view panel attributes so panels re-enter the flex layout at
     * their correct size on reactivation, avoiding content reflow inside split-view
     * browser elements.
     */

    #suspend() {
      gBrowser.tabpanels.suspendSplitViewPanels(
        this.#tabs.filter(tab => !tab.splitview || tab.splitview === this)
      );
      updateUrlbarButton.arm();
      this.container.dispatchEvent(
        new CustomEvent("TabSplitViewDeactivate", {
          detail: { tabs: this.#tabs, splitview: this },
          bubbles: true,
        })
      );
    }

    /**
     * Remove customized panel widths. Cache width values so that they can be
     * restored if this Split View is later reactivated.
     */

    #resetPanelWidths() {
      for (const panel of this.panels) {
        const width = panel.getAttribute("width");
        if (width) {
          this.#storedPanelWidths.set(panel, width);
          panel.removeAttribute("width");
          panel.style.removeProperty("width");
        }
      }
    }

    /**
     * Resize panel widths back to cached values.
     */

    #restorePanelWidths() {
      for (const panel of this.panels) {
        const width = this.#storedPanelWidths.get(panel);
        if (width) {
          panel.setAttribute("width", width);
          panel.style.setProperty("width", width + "px");
        }
      }
    }

    /**
     * Reset custom width on the right panel, allowing it to fill the rest of
     * the available space.
     */

    resetRightPanelWidth() {
      const panel = this.panels[1];
      this.#storedPanelWidths.delete(panel);
      panel.removeAttribute("width");
      panel.style.removeProperty("width");
    }

    /**
     * add tabs to the split view wrapper
     *
     * @param {MozTabbrowserTab[]} tabs
     * @param {object} [options]
     * @param {boolean} [options.isSessionRestore]
     * @param {int} [options.indexOfReplacedTab] [optional] Used if replacing a tab in the split view
     */

    addTabs(tabs, { isSessionRestore = false, indexOfReplacedTab = -1 } = {}) {
      for (let tab of tabs) {
        if (tab.pinned) {
          return;
        }
        let tabToMove =
          this.documentGlobal === tab.documentGlobal
            ? tab
            : gBrowser.adoptTab(tab, {
                tabIndex: gBrowser.tabs.at(-1)._tPos + 1,
                selectTab: tab.selected,
              });
        if (indexOfReplacedTab > -1 && indexOfReplacedTab < this.#tabs.length) {
          this.#tabs[indexOfReplacedTab] = tabToMove;
        } else {
          this.#tabs.push(tabToMove);
        }
        isSessionRestore
          ? this.appendChild(tab)
          : gBrowser.moveTabToSplitView(tabToMove, this, indexOfReplacedTab);
        if (tab === gBrowser.selectedTab) {
          this.hasActiveTab = true;
        }
      }

      if (this.hasActiveTab) {
        this.#activate();
      }
      // Attempt to update uriCount metric using the resulting tabs collection,
      // as tabs may not be added to the splitview if they are pinned etc.
      for (let tab of this.tabs) {
        let tabURI = tab.linkedBrowser.currentURI.spec;
        if (!isBlankPageURL(tabURI) && tabURI !== "about:opentabs") {
          // Add to the counter which tracks the number of URIs loaded into splitview tabs
          const index = tabs.indexOf(tab);
          const label = String(index + 1); // 0 -> "1" (LTR left), 1 -> "2" (LTR right)
          Glean.splitview.uriCount[label].add(1);
        }
      }
    }

    /**
     * Close about:openTabs and move all tabs out of the split view wrapper;
     * splitview removal is handled by #observeTabChanges.
     *
     * @param {string} [trigger]
     *   The trigger method for ending the split view. Used for telemetry.
     *   Valid values: "menu_separate", "icon_separate", "icon_close", "tab_close", "footer_separate".
     */

    unsplitTabs(trigger = null) {
      if (this.#isUnsplitting) {
        return;
      }
      this.#isUnsplitting = true;
      let telemetryTrigger = this.#isClosing ? null : trigger;
      // Record telemetry for split view end
      if (telemetryTrigger) {
        const tab_layout = gBrowser.tabContainer.verticalMode
          ? "vertical"
          : "horizontal";

        Glean.splitview.end.record({
          tab_layout,
          trigger: telemetryTrigger,
        });
      }

      // If the split view has about:opentabs open, remove that tab
      let aboutOpenTabs = this.#tabs.filter(
        tab => tab?.linkedBrowser?.currentURI?.spec === "about:opentabs"
      );
      aboutOpenTabs.forEach(aboutOpenTab => {
        gBrowser.removeTab(aboutOpenTab);
      });

      for (let i = this.tabs.length - 1; i >= 0; i--) {
        gBrowser.handleTabMove(this.tabs[i], () =>
          gBrowser.tabContainer.insertBefore(
            this.tabs[i],
            this.nextElementSibling
          )
        );
      }
    }

    /**
     * Replace a tab in the split view with another tab
     */

    replaceTab(tabToReplace, newTab) {
      let indexOfReplacedTab = this.tabs.indexOf(tabToReplace);

      // Select the adopted tab BEFORE removing the old one to prevent Firefox
      // from auto-selecting the wrong tab when the old selected tab is removed.
      if (tabToReplace.selected) {
        gBrowser.selectedTab = newTab;
      }
      gBrowser.removeTab(tabToReplace);
      this.addTabs([newTab], { isSessionRestore: false, indexOfReplacedTab });

      // We need to re-activate after removing one of the split view tabs
      this.#activate();
    }

    /**
     * Reverse order of the tabs in the split view wrapper.
     *
     * @param {string} [trigger]
     *   The trigger method for reversing tabs. Used for telemetry.
     */

    reverseTabs(trigger = null) {
      const [firstTab, secondTab] = this.#tabs;
      this.#shouldMoveAllTabsAtOnce = false;
      gBrowser.moveTabBefore(secondTab, firstTab);
      this.#shouldMoveAllTabsAtOnce = true;
      this.#tabs = [secondTab, firstTab];
      if (this.hasActiveTab) {
        gBrowser.showSplitViewPanels(this.#tabs);
        updateUrlbarButton.arm();
      }

      // Record telemetry
      if (trigger) {
        Glean.splitview.reverse.record({ trigger });
      }
    }

    /**
     * Close all tabs in the split view wrapper and delete the split view.
     *
     * @param {string} [trigger]
     *   The trigger method for ending the split view. Used for telemetry.
     */

    close(trigger = null) {
      // Record telemetry before closing
      if (trigger) {
        const tab_layout = gBrowser.tabContainer.verticalMode
          ? "vertical"
          : "horizontal";
        Glean.splitview.end.record({
          tab_layout,
          trigger,
        });
      }
      this.#isClosing = true;
      gBrowser.removeTabs(this.#tabs);
    }

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

    on_TabSelect(event) {
      const wasActive = this.hasActiveTab;
      this.hasActiveTab = event.target.splitview === this;
      if (this.hasActiveTab) {
        this.#activate();
        // This check ensures we don't call suspend for every tab selection
        // or for a selected tab in a splitview that is being dragged to another window,
        // as this event fires as part of updateCurrentBrowser; we
        // utilize this temporary property - removedByAdoption -
        // that is added in adoptSplitView.
      } else if (wasActive && !event.detail.previousTab?.removedByAdoption) {
        this.#suspend();
      }
    }
  }

  customElements.define("tab-split-view-wrapper", MozTabSplitViewWrapper);
}

Messung V0.5 in Prozent
C=91 H=95 G=92

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© 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.