tor-browser

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

GroupsList.sys.mjs (6823B)


      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 { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
      6 import { TabMetrics } from "moz-src:///browser/components/tabbrowser/TabMetrics.sys.mjs";
      7 
      8 const MAX_INITIAL_ITEMS = 5;
      9 
     10 export class GroupsPanel {
     11  constructor({ view, containerNode, showAll = false }) {
     12    this.view = view;
     13    this.#showAll = showAll;
     14    this.containerNode = containerNode;
     15    this.win = containerNode.ownerGlobal;
     16    this.doc = containerNode.ownerDocument;
     17    this.panelMultiView = null;
     18    this.view.addEventListener("ViewShowing", this);
     19  }
     20 
     21  handleEvent(event) {
     22    switch (event.type) {
     23      case "ViewShowing":
     24        if (event.target == this.view) {
     25          this.panelMultiView = this.view.panelMultiView;
     26          this.#populate();
     27          this.#addObservers();
     28          this.win.addEventListener("unload", this);
     29        }
     30        break;
     31      case "PanelMultiViewHidden":
     32        if ((this.panelMultiView = event.target)) {
     33          this.#cleanup();
     34          this.#removeObservers();
     35          this.panelMultiView = null;
     36        }
     37        break;
     38      case "unload":
     39        if (this.panelMultiView) {
     40          this.#removeObservers();
     41        }
     42        break;
     43      case "command":
     44        this.#handleCommand(event);
     45        break;
     46    }
     47  }
     48 
     49  #addObservers() {
     50    Services.obs.addObserver(this, "sessionstore-closed-objects-changed");
     51    Services.obs.addObserver(this, "browser-tabgroup-removed-from-dom");
     52  }
     53 
     54  #removeObservers() {
     55    Services.obs.removeObserver(this, "sessionstore-closed-objects-changed");
     56    Services.obs.removeObserver(this, "browser-tabgroup-removed-from-dom");
     57  }
     58 
     59  observe(aSubject, aTopic) {
     60    switch (aTopic) {
     61      case "sessionstore-closed-objects-changed":
     62      case "browser-tabgroup-removed-from-dom":
     63        this.#cleanup();
     64        this.#populate();
     65        break;
     66    }
     67  }
     68 
     69  #handleCommand(event) {
     70    let { tabGroupId, command } = event.target.dataset;
     71 
     72    switch (command) {
     73      case "allTabsGroupView_selectGroup": {
     74        let group = this.win.gBrowser.getTabGroupById(tabGroupId);
     75        group.select();
     76        group.ownerGlobal.focus();
     77        break;
     78      }
     79 
     80      case "allTabsGroupView_restoreGroup":
     81        this.win.SessionStore.openSavedTabGroup(tabGroupId, this.win, {
     82          source: TabMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU,
     83        });
     84        break;
     85    }
     86  }
     87 
     88  #setupListeners() {
     89    this.view.addEventListener("command", this);
     90    this.panelMultiView.addEventListener("PanelMultiViewHidden", this);
     91  }
     92 
     93  #cleanup() {
     94    this.containerNode.innerHTML = "";
     95    this.view.removeEventListener("command", this);
     96  }
     97 
     98  #showAll;
     99  #populate() {
    100    let fragment = this.doc.createDocumentFragment();
    101 
    102    let openGroups = this.win.gBrowser.getAllTabGroups({
    103      sortByLastSeenActive: true,
    104    });
    105    let savedGroups = [];
    106    if (!PrivateBrowsingUtils.isWindowPrivate(this.win)) {
    107      savedGroups = this.win.SessionStore.savedGroups.toSorted(
    108        (group1, group2) => group2.closedAt - group1.closedAt
    109      );
    110    }
    111 
    112    let totalItemCount = savedGroups.length + openGroups.length;
    113    if (totalItemCount && !this.#showAll) {
    114      let header = this.doc.createElement("h2");
    115      header.setAttribute("class", "subview-subheader");
    116      this.doc.l10n.setAttributes(
    117        header,
    118        "all-tabs-menu-recent-tab-groups-header"
    119      );
    120      fragment.appendChild(header);
    121    }
    122 
    123    let addShowAllButton = !this.#showAll && totalItemCount > MAX_INITIAL_ITEMS;
    124    let itemCount = addShowAllButton ? 1 : 0;
    125    for (let groupData of openGroups) {
    126      if (itemCount >= MAX_INITIAL_ITEMS && !this.#showAll) {
    127        break;
    128      }
    129      itemCount++;
    130      let row = this.#createRow(groupData);
    131      fragment.appendChild(row);
    132    }
    133 
    134    for (let groupData of savedGroups) {
    135      if (itemCount >= MAX_INITIAL_ITEMS && !this.#showAll) {
    136        break;
    137      }
    138      itemCount++;
    139      let row = this.#createRow(groupData, { isOpen: false });
    140      fragment.appendChild(row);
    141    }
    142 
    143    if (addShowAllButton) {
    144      let button = this.doc.createXULElement("toolbarbutton");
    145      button.setAttribute("id", "allTabsMenu-groupsViewShowMore");
    146      button.setAttribute("class", "subviewbutton subviewbutton-nav");
    147      button.setAttribute("closemenu", "none");
    148      button.setAttribute("flex", "1");
    149      this.doc.l10n.setAttributes(button, "all-tabs-menu-tab-groups-show-all");
    150      fragment.appendChild(button);
    151    }
    152 
    153    this.containerNode.replaceChildren(fragment);
    154    this.#setupListeners();
    155  }
    156 
    157  /**
    158   * @param {TabGroupStateData} group
    159   * @param {object} [options]
    160   * @param {boolean} [options.isOpen]
    161   *   Set to true if the group is currently open, and false if it's saved
    162   * @returns {XULElement}
    163   */
    164  #createRow(group, { isOpen = true } = {}) {
    165    let { doc } = this;
    166    let row = doc.createXULElement("toolbaritem");
    167    row.setAttribute("class", "all-tabs-item all-tabs-group-item");
    168 
    169    row.style.setProperty(
    170      "--tab-group-color",
    171      `var(--tab-group-color-${group.color})`
    172    );
    173    row.style.setProperty(
    174      "--tab-group-color-invert",
    175      `var(--tab-group-color-${group.color}-invert)`
    176    );
    177    row.style.setProperty(
    178      "--tab-group-color-pale",
    179      `var(--tab-group-color-${group.color}-pale)`
    180    );
    181    let button = doc.createXULElement("toolbarbutton");
    182    button.setAttribute(
    183      "class",
    184      "all-tabs-button subviewbutton subviewbutton-iconic all-tabs-group-action-button tab-group-icon"
    185    );
    186    button.dataset.tabGroupId = group.id;
    187    if (!isOpen) {
    188      button.classList.add(
    189        "all-tabs-group-saved-group",
    190        "tab-group-icon-closed"
    191      );
    192      button.dataset.command = "allTabsGroupView_restoreGroup";
    193      button.setAttribute("context", "saved-tab-group-context-menu");
    194    } else {
    195      button.dataset.command = "allTabsGroupView_selectGroup";
    196      button.setAttribute("context", "open-tab-group-context-menu");
    197    }
    198    button.setAttribute("flex", "1");
    199    button.setAttribute("crop", "end");
    200 
    201    let setName = tabGroupName => {
    202      if (group.saved) {
    203        doc.l10n.setAttributes(button, "tabbrowser-manager-closed-tab-group", {
    204          tabGroupName,
    205        });
    206      } else {
    207        button.setAttribute("label", tabGroupName);
    208        button.setAttribute("tooltiptext", tabGroupName);
    209      }
    210    };
    211 
    212    if (group.name) {
    213      setName(group.name);
    214    } else {
    215      doc.l10n
    216        .formatValues([{ id: "tab-group-name-default" }])
    217        .then(([msg]) => {
    218          setName(msg);
    219        });
    220    }
    221    row.appendChild(button);
    222    return row;
    223  }
    224 }