feat: Allow pinned tabs to be collapsible, p=#11753, c=tabs, folders, workspaces

* fix: Fixed restoring the previous pinned state clearing up the custom icon, b=no-bug, c=tabs

* feat: Allow pinned tabs to be collapsible, b=no-bug, c=tabs, folders, workspaces

* fix: Fixed new folder context menu item not working, b=no-bug, c=common, folders
This commit is contained in:
mr. m
2025-12-28 17:58:18 +01:00
committed by GitHub
parent 1338b43e10
commit b66b05dcf6
20 changed files with 299 additions and 99 deletions

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..4439fe5fb3c7002b173415b615892ef356b22959 100644
index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..697dde4378c43ae6db46a6b7eb2997982201ec27 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -127,6 +127,8 @@ const TAB_EVENTS = [
@@ -177,7 +177,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..4439fe5fb3c7002b173415b615892ef3
+ winData.folders = aWindow.gZenFolders?.storeDataForSessionStore() || [];
+ winData.activeZenSpace = aWindow.gZenWorkspaces?.activeWorkspace || null;
+ winData.spaces = aWindow.gZenWorkspaces?.getWorkspaces();
+ winData.spaces = aWindow.gZenWorkspaces?.getWorkspacesForSessionStore();
// update tab group state for this window
winData.groups = [];
for (let tabGroup of aWindow.gBrowser.tabGroups) {

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7600f564a 100644
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..160e277d64eaac8408aed90eaf62606479424001 100644
--- a/browser/components/tabbrowser/content/tab.js
+++ b/browser/components/tabbrowser/content/tab.js
@@ -21,6 +21,7 @@
@@ -87,7 +87,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
}
get lastAccessed() {
@@ -382,7 +395,12 @@
@@ -382,7 +395,18 @@
}
get group() {
@@ -97,11 +97,17 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
+ }
+ if (gBrowser.isTabGroup(this.parentElement?.parentElement)) {
+ return this.parentElement.parentElement;
+ }
+ if (this.pinned) {
+ let collapsiblePins = gZenWorkspaces.workspaceElement(this.getAttribute('zen-workspace-id'))?.collapsiblePins;
+ if (collapsiblePins?.collapsed) {
+ return collapsiblePins;
+ }
+ }
}
get splitview() {
@@ -473,6 +491,8 @@
@@ -473,6 +497,8 @@
this.style.MozUserFocus = "ignore";
} else if (
event.target.classList.contains("tab-close-button") ||
@@ -110,7 +116,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
event.target.classList.contains("tab-icon-overlay") ||
event.target.classList.contains("tab-audio-button")
) {
@@ -527,6 +547,10 @@
@@ -527,6 +553,10 @@
this.style.MozUserFocus = "";
}
@@ -121,7 +127,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
on_click(event) {
if (event.button != 0) {
return;
@@ -587,6 +611,14 @@
@@ -587,6 +617,14 @@
// (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true;
}
@@ -136,7 +142,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
}
on_dblclick(event) {
@@ -610,6 +642,8 @@
@@ -610,6 +648,8 @@
animate: true,
triggeringEvent: event,
});

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js
index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057ebc71c2f5f 100644
index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..e92927612abf12631c384a8f54b6a607fb699424 100644
--- a/browser/components/tabbrowser/content/tabgroup.js
+++ b/browser/components/tabbrowser/content/tabgroup.js
@@ -14,11 +14,11 @@
@@ -68,10 +68,10 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
- return false;
- });
+ this.appendChild = function (child) {
+ this.querySelector(".tab-group-container").appendChild(child);
+ this.groupContainer.appendChild(child);
+ for (let tab of this.tabs) {
+ if (tab.hasAttribute("zen-empty-tab") && tab.group === this) {
+ this.querySelector(".zen-tab-group-start").after(tab);
+ this.groupStartElement.after(tab);
+ }
+ }
+ };
@@ -92,7 +92,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
});
}
- this.#tabChangeObserver.observe(this, { childList: true });
+ const container = this.querySelector(".tab-group-container");
+ const container = this.groupContainer;
+ if (container) {
+ this.#tabChangeObserver.observe(container, { childList: true });
+ }
@@ -117,7 +117,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
});
}
@@ -466,13 +492,57 @@
@@ -466,13 +492,65 @@
* @returns {MozTabbrowserTab[]}
*/
get tabs() {
@@ -126,20 +126,29 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
- if (childrenArray[i].tagName == "tab-split-view-wrapper") {
- childrenArray.splice(i, 1, ...childrenArray[i].tabs);
+ // add other group tabs if they are under this group
+ let childs = Array.from(this.querySelector(".tab-group-container")?.children ?? []);
+ let childs = Array.from(this.groupContainer?.children ?? []);
+ const tabsCollect = [];
+ for (let item of childs) {
+ tabsCollect.push(item);
+ if (gBrowser.isTabGroup(item)) {
+ tabsCollect.push(...item.tabs);
+ }
+ }
}
}
- return childrenArray.filter(node => node.matches("tab"));
+ return tabsCollect.filter(node => node.matches("tab"));
+ }
+
+ get groupContainer() {
+ return this.querySelector(".tab-group-container");
+ }
+
+ get groupStartElement() {
+ return this.querySelector(".zen-tab-group-start");
+ }
+
+ get childGroupsAndTabs() {
+ const result = [];
+ const container = this.querySelector(".tab-group-container");
+ const container = this.groupContainer;
+
+ for (const item of Array.from(container.children)) {
+ if (gBrowser.isTab(item)) {
@@ -169,9 +178,8 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
+ currentGroup = currentGroup?.group;
+ if (currentGroup.collapsed) {
+ return false;
}
}
- return childrenArray.filter(node => node.matches("tab"));
+ }
+ }
+ return true;
+ }
+
@@ -180,7 +188,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
}
/**
@@ -553,7 +623,6 @@
@@ -553,7 +631,6 @@
addTabs(tabs, metricsContext) {
for (let tab of tabs) {
if (tab.pinned) {
@@ -188,7 +196,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
}
let tabToMove =
this.ownerGlobal === tab.ownerGlobal
@@ -616,7 +685,7 @@
@@ -616,7 +693,7 @@
*/
on_click(event) {
let isToggleElement =
@@ -197,7 +205,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
event.target === this.#overflowCountLabel;
if (isToggleElement && event.button === 0) {
event.preventDefault();
@@ -687,5 +756,6 @@
@@ -687,5 +764,6 @@
}
}

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac8793422474052f476573 100644
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..04144081560f1678dc9673736ef2bd9d9ca3f478 100644
--- a/browser/components/tabbrowser/content/tabs.js
+++ b/browser/components/tabbrowser/content/tabs.js
@@ -436,7 +436,7 @@
@@ -54,7 +54,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
return this.hasAttribute("overflow");
}
@@ -837,29 +839,54 @@
@@ -837,29 +839,56 @@
if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") {
pinnedChildren.pop();
}
@@ -81,6 +81,8 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
+ tabs.splice(i, 1);
+ // add the tabs in the group to the list
+ tabs.splice(i, 0, ...tab.tabs);
+ } else if (tab.classList.contains("zen-tab-group-start")) {
+ tabs.splice(i, 1);
+ }
+ }
+ };
@@ -119,7 +121,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
}
/**
@@ -926,17 +953,10 @@
@@ -926,17 +955,10 @@
let elementIndex = 0;
@@ -139,7 +141,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
if (isTab(child) && child.visible) {
child.elementIndex = elementIndex++;
focusableItems.push(child);
@@ -944,11 +964,13 @@
@@ -944,11 +966,13 @@
child.labelElement.elementIndex = elementIndex++;
focusableItems.push(child.labelElement);
@@ -154,7 +156,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
} else if (child.tagName == "tab-split-view-wrapper") {
let visibleTabsInSplitView = child.tabs.filter(tab => tab.visible);
visibleTabsInSplitView.forEach(tab => {
@@ -992,6 +1014,7 @@
@@ -992,6 +1016,7 @@
_invalidateCachedTabs() {
this.#allTabs = null;
this._invalidateCachedVisibleTabs();
@@ -162,7 +164,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
}
_invalidateCachedVisibleTabs() {
@@ -1095,7 +1118,7 @@
@@ -1095,7 +1120,7 @@
if (node == null) {
// We have a container for non-tab elements at the end of the scrollbox.
@@ -171,7 +173,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
}
node.before(tab);
@@ -1193,7 +1216,7 @@
@@ -1193,7 +1218,7 @@
// 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.
@@ -180,7 +182,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
const newTab2 = this.newTabButton;
const newTabVertical = document.getElementById(
"vertical-tabs-newtab-button"
@@ -1294,8 +1317,10 @@
@@ -1294,8 +1319,10 @@
*/
_handleTabSelect(aInstant) {
let selectedTab = this.selectedItem;
@@ -191,7 +193,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
selectedTab._notselectedsinceload = false;
}
@@ -1304,7 +1329,7 @@
@@ -1304,7 +1331,7 @@
* @param {boolean} [shouldScrollInstantly=false]
*/
#ensureTabIsVisible(tab, shouldScrollInstantly = false) {
@@ -200,7 +202,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
if (arrowScrollbox?.overflowing) {
arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly);
}
@@ -1437,7 +1462,7 @@
@@ -1437,7 +1464,7 @@
}
_notifyBackgroundTab(aTab) {

View File

@@ -7,6 +7,7 @@
.subviewbutton,
#zen-welcome-start-button,
.zen-toast button,
.zen-current-workspace-indicator-chevron,
.pinned-tabs-container-separator toolbarbutton {
-moz-context-properties: fill, fill-opacity !important;
fill: currentColor !important;
@@ -116,7 +117,7 @@
}
}
#zen-rice-share-options .options-header,
.zen-current-workspace-indicator-chevron,
#PanelUI-zen-gradient-generator-color-page-right {
list-style-image: url('arrow-right.svg');
}

View File

@@ -2,4 +2,4 @@
# 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/.
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="1.5" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><polyline points="6.5 2.75 12.75 9 6.5 15.25"></polyline></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="2" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><polyline points="6.5 2.75 12.75 9 6.5 15.25"></polyline></g></svg>

View File

@@ -125,8 +125,6 @@ window.gZenUIManager = {
}
menu.setAttribute('hidden', 'true');
}
// The first separator in the tab context menu is now useless.
document.getElementById('tabContextMenu').querySelector('menuseparator').remove();
},
_initCreateNewPopup() {

View File

@@ -2,7 +2,7 @@
// 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/.
class ZenFolder extends MozTabbrowserTabGroup {
export class nsZenFolder extends MozTabbrowserTabGroup {
#initialized = false;
static markup = `
@@ -68,7 +68,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
}
this.#initialized = true;
this._activeTabs = [];
this.icon.appendChild(ZenFolder.rawIcon.cloneNode(true));
this.icon.appendChild(nsZenFolder.rawIcon.cloneNode(true));
this.labelElement.parentElement.setAttribute('context', 'zenFolderActions');
@@ -81,7 +81,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
};
if (this.collapsed) {
this.querySelector('.tab-group-container').setAttribute('hidden', true);
this.groupContainer.setAttribute('hidden', true);
}
}
@@ -141,7 +141,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
gZenFolders.createFolder([], {
renameFolder: !gZenUIManager.testingEnabled,
label: 'Subfolder',
insertAfter: this.querySelector('.tab-group-container').lastElementChild,
insertAfter: this.groupContainer.lastElementChild,
});
}
@@ -181,8 +181,12 @@ class ZenFolder extends MozTabbrowserTabGroup {
}
get allItems() {
return [...this.querySelector('.tab-group-container').children].filter(
(child) => !child.classList.contains('zen-tab-group-start')
return [...this.groupContainer.children].filter(
(child) =>
!(
child.classList.contains('zen-tab-group-start') ||
child.classList.contains('pinned-tabs-container-separator')
)
);
}
@@ -274,4 +278,4 @@ class ZenFolder extends MozTabbrowserTabGroup {
}
}
customElements.define('zen-folder', ZenFolder);
customElements.define('zen-folder', nsZenFolder);

View File

@@ -189,6 +189,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
window.addEventListener('TabSelect', this);
window.addEventListener('TabOpen', this);
const onNewFolder = this.#onNewFolder.bind(this);
document.getElementById('zen-context-menu-new-folder').addEventListener('command', onNewFolder);
document
.getElementById('zen-context-menu-new-folder-toolbar')
.addEventListener('command', onNewFolder);
@@ -803,6 +804,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (!isTab && !groupElem?.hasAttribute('selected') && !forCollapse) {
groupElem = null; // Don't indent if the group is not selected
}
if (groupElem?.tagName.toLowerCase() === 'zen-workspace-collapsible-pins') {
groupElem = null; // Don't indent if it's inside the collapsible pinned tabs
}
let level = groupElem?.level + 1 || 0;
if (gBrowser.isTabGroupLabel(groupElem)) {
// If it is a group label, we should not increase its level by one.
@@ -1036,8 +1040,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
default: {
// Should insert after zen-empty-tab
const start =
parentWorkingData.node.querySelector('.zen-tab-group-start').nextElementSibling;
const start = parentWorkingData.node.groupStartElement.nextElementSibling;
start.after(node);
}
}
@@ -1128,8 +1131,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const dropElementGroup = dropElement?.isZenFolder ? dropElement : dropElement?.group;
const isSplitGroup = dropElement?.group?.hasAttribute('split-view-group');
let firstGroupElem =
dropElementGroup?.querySelector('.zen-tab-group-start')?.nextElementSibling;
let firstGroupElem = dropElementGroup?.groupStartElement.nextElementSibling;
if (gBrowser.isTabGroup(firstGroupElem)) firstGroupElem = firstGroupElem.labelElement;
const isInMiddleZone =
@@ -1212,6 +1214,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
return heightShift;
} else {
heightShift += window.windowUtils.getBoundsWithoutFlushing(tabsContainer).height;
if (tabsContainer.separatorElement) {
heightShift -= window.windowUtils.getBoundsWithoutFlushing(
tabsContainer.separatorElement
).height;
}
}
return heightShift;
}
@@ -1225,8 +1232,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const activeFoldersIds = new Set();
const itemsToHide = [];
const tabsContainer = group.querySelector('.tab-group-container');
const groupStart = group.querySelector('.zen-tab-group-start');
const tabsContainer = group.groupContainer;
const groupStart = group.groupStartElement;
const groupItems = this.#collectGroupItems(group, {
selectedTabs,
@@ -1304,11 +1311,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const animations = [];
const itemsToHide = [];
const tabsContainer = group.querySelector('.tab-group-container');
const tabsContainer = group.groupContainer;
tabsContainer.removeAttribute('hidden');
tabsContainer.style.overflow = 'hidden';
const groupStart = group.querySelector('.zen-tab-group-start');
const groupStart = group.groupStartElement;
const itemsToShow = this.#normalizeGroupItems(group.childGroupsAndTabs);
const activeFolders = group.childActiveGroups;
@@ -1422,7 +1429,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
folder.removeAttribute('has-active');
folder.activeTabs = [];
const groupItems = this.#normalizeGroupItems(folder.allItems);
const tabsContainer = folder.querySelector('.tab-group-container');
const tabsContainer = folder.groupContainer;
// Set correct margin-top after animation
const afterAnimate = () => {
@@ -1436,7 +1443,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
groupStart.style.marginTop = `${-(collapsedHeight + 4)}px`;
};
const groupStart = folder.querySelector('.zen-tab-group-start');
const groupStart = folder.groupStartElement;
const collapsedHeight = this.#calculateHeightShift(tabsContainer, []);
// Collect animations for this specific folder becoming inactive
@@ -1474,7 +1481,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
animations.push(async () => {
folder.removeAttribute('has-active');
const groupItems = this.#normalizeGroupItems(folder.allItems);
const tabsContainer = folder.querySelector('.tab-group-container');
const tabsContainer = folder.groupContainer;
// Set correct margin-top after animation
const afterAnimate = () => {
@@ -1488,7 +1495,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
groupStart.style.marginTop = `${-(collapsedHeight + 4)}px`;
};
const groupStart = folder.querySelector('.zen-tab-group-start');
const groupStart = folder.groupStartElement;
const collapsedHeight = this.#calculateHeightShift(tabsContainer, []);
// Collect animations for this specific folder becoming inactive
@@ -1573,8 +1580,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
currentGroup.activeTabs = activeTabs;
}
const tabsContainer = currentGroup.querySelector('.tab-group-container');
const groupStart = currentGroup.querySelector('.zen-tab-group-start');
const tabsContainer = currentGroup.groupContainer;
const groupStart = currentGroup.groupStartElement;
tabsContainer.style.overflow = 'clip';
if (tabsContainer.hasAttribute('hidden')) tabsContainer.removeAttribute('hidden');
@@ -1673,8 +1680,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
animateGroupMove(group, expand = false) {
if (!group?.isZenFolder) return;
const groupStart = group.querySelector('.zen-tab-group-start');
const tabsContainer = group.querySelector('.tab-group-container');
const groupStart = group.groupStartElement;
const tabsContainer = group.groupContainer;
const heightContainer = expand ? 0 : this.#calculateHeightShift(tabsContainer, []);
tabsContainer.style.overflow = 'clip';

View File

@@ -202,16 +202,6 @@ zen-folder {
}
}
&[collapsed] {
& > .tabbrowser-tab:not([hidden]) {
display: flex;
}
&:not([has-active]) > .tab-group-container {
overflow-y: clip;
}
}
:root[zen-sidebar-expanded] &[has-active] > .tab-group-label-container {
& .tab-reset-button {
display: flex;
@@ -224,6 +214,11 @@ zen-folder {
}
}
zen-workspace[collapsedpinnedtabs] .zen-workspace-pinned-tabs-section,
zen-folder[collapsed]:not([has-active]) > .tab-group-container {
overflow-y: clip;
}
/* Tabs popup */
#zen-folder-tabs-popup {
--arrowpanel-padding: 0;

View File

@@ -224,6 +224,7 @@ export class nsZenSessionManager {
// If there's no initial state, nothing to restore. This would
// happen if the file is empty or corrupted.
if (!initialState) {
this.log('No initial state to restore!');
return;
}
// If there are no windows, we create an empty one. By default,
@@ -249,7 +250,7 @@ export class nsZenSessionManager {
// guarantee that all tabs, groups, folders and split view data
// are properly synced across all windows.
this.log(`Restoring Zen session data into ${initialState.windows?.length || 0} windows`);
for (const winData of initialState.windows || []) {
for (const winData of initialState.windows) {
this.#restoreWindowData(winData);
}
}

View File

@@ -176,8 +176,8 @@ class nsZenWindowSync {
// This should only happen really when updating from an older version
// that didn't have this feature.
this.#runOnAllWindows(null, (aWindow) => {
const { gBrowser } = aWindow;
for (let tab of gBrowser.tabs) {
const { gZenWorkspaces } = aWindow;
for (let tab of gZenWorkspaces.allStoredTabs) {
if (!tab.id) {
tab.id = this.#newTabSyncId;
lazy.TabStateFlusher.flush(tab.linkedBrowser);

View File

@@ -322,11 +322,14 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
const state = this.#getTabState(tab);
const initialState = tab._zenPinnedInitialState;
if (!initialState?.entry) {
return;
}
// Remove everything except the entry we want to keep
state.entries = [initialState.entry];
state.image = initialState.image;
state.image = tab.zenStaticIcon || initialState.image;
state.index = 0;
SessionStore.setTabState(tab, state);

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
import { nsZenMultiWindowFeature } from 'chrome://browser/content/zen-components/ZenCommonUtils.mjs';

View File

@@ -1,17 +1,59 @@
// 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/.
/* 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/. */
import { nsZenFolder } from 'chrome://browser/content/zen-components/ZenFolder.mjs';
// A helper class to manage collapsible pinned tabs in a workspace.
class nsZenCollapsiblePins extends nsZenFolder {
#spaceElement;
connectedCallback() {
this.setAttribute('hidden', 'true');
this.#spaceElement = this.parentElement;
super.connectedCallback();
}
get groupContainer() {
return this.#spaceElement.pinnedTabsContainer;
}
get groupStartElement() {
// Fetch this instead of the tab-group-start since it is not guaranteed this
// element will be the first child of the pinned tabs container.
return this.#spaceElement.pinnedTabsContainer.querySelector('.space-fake-collapsible-start');
}
get collapsed() {
return super.collapsed;
}
set collapsed(value) {
if (value) {
this.#spaceElement.setAttribute('collapsedpinnedtabs', 'true');
} else {
this.#spaceElement.removeAttribute('collapsedpinnedtabs');
}
super.collapsed = value;
}
}
export class nsZenWorkspace extends MozXULElement {
#initialPinnedElementChildrenCount;
class nsZenWorkspace extends MozXULElement {
static get markup() {
return `
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1" context="zenWorkspaceMoreActions">
<hbox class="zen-current-workspace-indicator-icon" />
<stack class="zen-current-workspace-indicator-stack">
<image class="zen-current-workspace-indicator-chevron" />
<hbox class="zen-current-workspace-indicator-icon" />
</stack>
<label class="zen-current-workspace-indicator-name" flex="1" />
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions" />
</vbox>
<arrowscrollbox orient="vertical" class="workspace-arrowscrollbox">
<vbox class="zen-workspace-tabs-section zen-workspace-pinned-tabs-section" hide-separator="true">
<html:div class="zen-tab-group-start space-fake-collapsible-start" style="order: -9999;" />
<hbox class="pinned-tabs-container-separator">
<toolbarseparator flex="1" />
<toolbarbutton command="cmd_zenCloseUnpinnedTabs"
@@ -68,6 +110,9 @@ class nsZenWorkspace extends MozXULElement {
this.tabsContainer = this.querySelector('.zen-workspace-normal-tabs-section');
this.indicator = this.querySelector('.zen-current-workspace-indicator');
this.pinnedTabsContainer = this.querySelector('.zen-workspace-pinned-tabs-section');
this.pinnedTabsContainer.separatorElement = this.pinnedTabsContainer.querySelector(
'.pinned-tabs-container-separator'
);
this.initializeAttributeInheritance();
this.scrollbox = this.querySelector('arrowscrollbox');
@@ -80,10 +125,17 @@ class nsZenWorkspace extends MozXULElement {
this.scrollbox.addEventListener('underflow', this);
this.scrollbox.addEventListener('overflow', this);
this.indicator.querySelector('.zen-current-workspace-indicator-name').onRenameFinished =
this.onIndicatorRenameFinished.bind(this);
const indicatorName = this.indicator.querySelector('.zen-current-workspace-indicator-name');
indicatorName.onRenameFinished = this.onIndicatorRenameFinished.bind(this);
indicatorName.addEventListener('dblclick', (event) => {
if (this.hasPinnedTabs) {
// Prevent renaming when there are pinned tabs
event.stopPropagation();
}
});
this.pinnedTabsContainer.scrollbox = this.scrollbox;
this.#initialPinnedElementChildrenCount = this.pinnedTabsContainer.children.length;
this.indicator
.querySelector('.zen-workspaces-actions')
@@ -92,10 +144,20 @@ class nsZenWorkspace extends MozXULElement {
this.indicator
.querySelector('.zen-current-workspace-indicator-icon')
.addEventListener('dblclick', (event) => {
if (this.hasPinnedTabs) {
return;
}
event.stopPropagation();
gZenWorkspaces.changeWorkspaceIcon();
});
this.indicator.addEventListener('click', (event) => {
if (this.hasPinnedTabs) {
event.stopPropagation();
this.collapsiblePins.collapsed = !this.collapsiblePins.collapsed;
}
});
if (!gZenWorkspaces.currentWindowIsSyncing) {
let actionsButton = this.indicator.querySelector('.zen-workspaces-actions');
const moveTabToFragment = window.MozXULElement.parseXULToFragment(
@@ -169,11 +231,26 @@ class nsZenWorkspace extends MozXULElement {
this.tabsContainer.setAttribute('zen-workspace-id', this.id);
this.pinnedTabsContainer.setAttribute('zen-workspace-id', this.id);
this.collapsiblePins = document.createXULElement('zen-workspace-collapsible-pins');
this.prepend(this.collapsiblePins);
this.#updateOverflow();
this.onGradientCacheChanged = this.#onGradientCacheChanged.bind(this);
window.addEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged);
const tabPinCallback = () => {
this.checkPinsExistence();
};
this.addEventListener('TabPinned', tabPinCallback);
this.addEventListener('TabUnpinned', tabPinCallback);
this.addEventListener('TabClose', (event) => {
if (event.target.pinned) {
tabPinCallback();
}
});
this.dispatchEvent(
new CustomEvent('ZenWorkspaceAttached', {
bubbles: true,
@@ -185,6 +262,7 @@ class nsZenWorkspace extends MozXULElement {
disconnectedCallback() {
window.removeEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged);
super.disconnectedCallback();
}
get active() {
@@ -200,6 +278,14 @@ class nsZenWorkspace extends MozXULElement {
this.#updateOverflow();
}
get hasPinnedTabs() {
return this.hasAttribute('haspinnedtabs');
}
get hasCollapsedPinnedTabs() {
return this.hasAttribute('collapsedpinnedtabs');
}
#updateOverflow() {
if (!this.scrollbox) return;
if (this.overflows) {
@@ -273,6 +359,15 @@ class nsZenWorkspace extends MozXULElement {
this.style.setProperty('--zen-primary-color', primaryColor);
}
checkPinsExistence() {
if (this.pinnedTabsContainer.children.length > this.#initialPinnedElementChildrenCount) {
this.setAttribute('haspinnedtabs', 'true');
} else {
this.removeAttribute('haspinnedtabs');
this.collapsiblePins.collapsed = false;
}
}
clearThemeStyles() {
this.style.colorScheme = '';
this.style.removeProperty('--toolbox-textcolor');
@@ -311,3 +406,4 @@ class nsZenWorkspace extends MozXULElement {
}
customElements.define('zen-workspace', nsZenWorkspace);
customElements.define('zen-workspace-collapsible-pins', nsZenCollapsiblePins);

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
class nsZenWorkspaceCreation extends MozXULElement {
#wasInCollapsedMode = false;

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
class nsZenWorkspaceIcons extends MozXULElement {
constructor() {

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
import { nsZenThemePicker } from 'chrome://browser/content/zen-components/ZenGradientGenerator.mjs';
@@ -465,6 +465,7 @@ class nsZenWorkspaces {
workspaceWrapper.pinnedTabsContainer,
tabs
);
workspaceWrapper.checkPinsExistence();
resolve();
},
{ once: true }
@@ -827,6 +828,20 @@ class nsZenWorkspaces {
return [...this._workspaceCache];
}
getWorkspacesForSessionStore() {
const spaces = this.getWorkspaces();
let spacesForSS = [];
for (const space of spaces) {
let newSpace = { ...space };
const element = this.workspaceElement(space.uuid);
if (element) {
newSpace.hasCollapsedPinnedTabs = element.hasCollapsedPinnedTabs;
}
spacesForSS.push(newSpace);
}
return spacesForSS;
}
async workspaceBookmarks() {
if (this.privateWindowOrDisabled) {
this._workspaceBookmarksCache = {
@@ -854,11 +869,22 @@ class nsZenWorkspaces {
if (this.#hasInitialized) {
return;
}
this._workspaceCache = aWinData.spaces?.length
? aWinData.spaces
const spacesFromStore = aWinData.spaces || [];
this._workspaceCache = spacesFromStore.length
? [...spacesFromStore]
: [await this.createAndSaveWorkspace('Space', undefined, true)];
for (const workspace of this._workspaceCache) {
// We don't want to depend on this by mistake
delete workspace.hasCollapsedPinnedTabs;
}
this.activeWorkspace = aWinData.activeZenSpace || this._workspaceCache[0].uuid;
await this.initializeWorkspaces();
for (const workspace of spacesFromStore) {
const element = this.workspaceElement(workspace.uuid);
if (element) {
element.collapsiblePins.collapsed = workspace.hasCollapsedPinnedTabs || false;
}
}
this.#hasInitialized = true;
}
@@ -1788,11 +1814,14 @@ class nsZenWorkspaces {
}
const indicatorName = workspaceIndicator.querySelector('.zen-current-workspace-indicator-name');
const indicatorIcon = workspaceIndicator.querySelector('.zen-current-workspace-indicator-icon');
const iconStack = workspaceIndicator.querySelector('.zen-current-workspace-indicator-stack');
if (this.workspaceHasIcon(currentWorkspace)) {
indicatorIcon.removeAttribute('no-icon');
iconStack.removeAttribute('no-icon');
} else {
indicatorIcon.setAttribute('no-icon', 'true');
iconStack.setAttribute('no-icon', 'true');
}
const icon = this.getWorkspaceIcon(currentWorkspace);
indicatorIcon.innerHTML = '';

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
// Integration of workspace-specific bookmarks into Places
window.ZenWorkspaceBookmarksStorage = {

View File

@@ -155,13 +155,14 @@
/* Mark workspaces indicator */
.zen-current-workspace-indicator {
--indicator-gap: 10px;
margin-top: 1px;
padding: calc(2px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
font-weight: 500;
position: relative;
max-height: var(--zen-workspace-indicator-height);
min-height: var(--zen-workspace-indicator-height);
gap: 10px;
gap: var(--indicator-gap);
align-items: center;
flex-direction: row !important;
max-width: 100%;
@@ -368,3 +369,52 @@ zen-workspace {
}
%include create-workspace-form.css
/* Pinned tabs collapse styles */
.zen-current-workspace-indicator-chevron {
display: none;
}
:root[zen-sidebar-expanded] {
.zen-current-workspace-indicator-stack {
transition: margin-inline-end 0.1s;
&[no-icon='true'] {
margin-inline-end: calc(-1 * (var(--indicator-gap) + 16px));
}
}
.zen-current-workspace-indicator-chevron {
width: 16px;
height: 16px;
transition: transform 0.2s, opacity 0.2s;
transform: rotate(90deg);
padding: 2px;
.zen-current-workspace-indicator-stack[no-icon='true'] & {
display: flex;
opacity: 0;
}
}
& zen-workspace[haspinnedtabs] .zen-current-workspace-indicator:hover,
& zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator {
.zen-current-workspace-indicator-chevron {
display: flex;
opacity: 1;
}
.zen-current-workspace-indicator-stack {
margin-inline-end: 0;
}
.zen-current-workspace-indicator-icon {
display: none;
}
}
zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator-chevron {
transform: rotate(0deg);
}
}