gecko-dev/browser/components/tabbrowser/content/tabgroup.js
Jeremy Swinarton a5e3795c2b Bug 1908422: Add closedGroups to SessionRestore for tab groups r=dao,sthompson,sessionstore-reviewers,tabbrowser-reviewers
This patch adds a `closedGroups` array to the SessionRestore state, and
adds functionality that ensures closed tab groups end up in the closed
groups array and that closed tab counts respect closed groups.

This does not update `undoClosedTab` or any related methods. Attempting
to restore a closed tab group will result in an error.

Differential Revision: https://phabricator.services.mozilla.com/D226397
2024-11-12 16:34:19 +00:00

220 lines
5.4 KiB
JavaScript

/* 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`.
{
class MozTabbrowserTabGroup extends MozXULElement {
static markup = `
<vbox class="tab-group-label-container" pack="center">
<label class="tab-group-label" role="button"/>
</vbox>
<html:slot/>
`;
/** @type {MozTextLabel} */
#labelElement;
#colorCode;
constructor() {
super();
}
static get inheritedAttributes() {
return {
".tab-group-label": "text=label,tooltiptext=label",
};
}
connectedCallback() {
if (this._initialized) {
return;
}
this.textContent = "";
this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance();
this._initialized = true;
this.#labelElement = this.querySelector(".tab-group-label");
this.#labelElement.addEventListener("click", this);
this.#updateLabelAriaAttributes(this.label);
this.#updateCollapsedAriaAttributes(this.collapsed);
this.createdDate = Date.now();
this.addEventListener("TabSelect", this);
this._tabsChangedObserver = new window.MutationObserver(mutationList => {
for (let mutation of mutationList) {
mutation.addedNodes.forEach(node => {
node.tagName === "tab" &&
node.dispatchEvent(
new CustomEvent("TabGrouped", {
bubbles: true,
detail: this,
})
);
});
mutation.removedNodes.forEach(node => {
node.tagName === "tab" &&
node.dispatchEvent(
new CustomEvent("TabUngrouped", {
bubbles: true,
detail: this,
})
);
});
}
if (!this.tabs.length) {
this.dispatchEvent(
new CustomEvent("TabGroupRemoved", { bubbles: true })
);
this.remove();
}
});
this._tabsChangedObserver.observe(this, { childList: true });
this.#labelElement.addEventListener("contextmenu", e => {
e.preventDefault();
gBrowser.tabGroupMenu.openEditModal(this);
return false;
});
}
disconnectedCallback() {
this._tabsChangedObserver.disconnect();
}
get color() {
return this.#colorCode;
}
set color(code) {
this.#colorCode = code;
this.style.setProperty(
"--tab-group-color",
`var(--tab-group-color-${code})`
);
this.style.setProperty(
"--tab-group-color-invert",
`var(--tab-group-color-${code}-invert)`
);
this.style.setProperty(
"--tab-group-color-pale",
`var(--tab-group-color-${code}-pale)`
);
}
get id() {
return this.getAttribute("id");
}
set id(val) {
this.setAttribute("id", val);
}
get label() {
return this.getAttribute("label");
}
set label(val) {
this.setAttribute("label", val);
this.#updateLabelAriaAttributes(val);
}
get collapsed() {
return this.hasAttribute("collapsed");
}
set collapsed(val) {
if (!!val == this.collapsed) {
return;
}
this.toggleAttribute("collapsed", val);
this.#updateCollapsedAriaAttributes(val);
const eventName = val ? "TabGroupCollapse" : "TabGroupExpand";
this.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
}
/**
* @param {string} label
*/
#updateLabelAriaAttributes(label) {
const ariaLabel = label == "" ? "unnamed" : label;
const ariaDescription = `${ariaLabel} tab group`;
this.#labelElement?.setAttribute("aria-label", ariaLabel);
this.#labelElement?.setAttribute("aria-description", ariaDescription);
}
/**
* @param {boolean} collapsed
*/
#updateCollapsedAriaAttributes(collapsed) {
const ariaExpanded = collapsed ? "false" : "true";
this.#labelElement?.setAttribute("aria-expanded", ariaExpanded);
}
get tabs() {
return Array.from(this.children).filter(node => node.matches("tab"));
}
/**
* @returns {MozTextLabel}
*/
get labelElement() {
return this.#labelElement;
}
/**
* add tabs to the group
*
* @param tabs array of tabs to add
*/
addTabs(tabs) {
for (let tab of tabs) {
let tabToMove =
this.ownerGlobal === tab.ownerGlobal
? tab
: gBrowser.adoptTab(
tab,
gBrowser.tabs.at(-1)._tPos + 1,
tab.selected
);
gBrowser.moveTabToGroup(tabToMove, this);
}
}
/**
* remove all tabs from the group and delete the group
*
*/
ungroupTabs() {
for (let tab of this.tabs) {
gBrowser.ungroupTab(tab);
}
}
/**
* @param {PointerEvent} event
*/
on_click(event) {
if (event.target === this.#labelElement && event.button === 0) {
event.preventDefault();
this.collapsed = !this.collapsed;
}
}
on_TabSelect() {
this.collapsed = false;
}
}
customElements.define("tab-group", MozTabbrowserTabGroup);
}