From 5afc6cd7d004d465f588360cfd32b53d51638468 Mon Sep 17 00:00:00 2001 From: octaviusz <50177704+octaviusz@users.noreply.github.com> Date: Sun, 24 Aug 2025 02:21:25 +0300 Subject: [PATCH] fix: improve tab selection for collapsed folders, p=#9977 Co-authored-by: Mr. M Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com> --- .../tabbrowser/content/tabbrowser-js.patch | 40 +++-- src/zen/folders/ZenFolders.mjs | 159 +++++++++--------- src/zen/workspaces/ZenGradientGenerator.mjs | 6 +- 3 files changed, 105 insertions(+), 100 deletions(-) diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch index 39300183e..ae63eeb81 100644 --- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch +++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js -index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c1ec15e92 100644 +index d80a66a01002e78a9c65545d08fe786328ddf124..bb57b1eeb033f602d5014ab23e2cc1389bb9e615 100644 --- a/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js @@ -422,15 +422,60 @@ @@ -337,7 +337,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c if (!tab) { let createLazyBrowser = - restoreTabsLazily && !select && !tabData.pinned; -+ restoreTabsLazily && !tabData.pinned; ++ restoreTabsLazily; let url = "about:blank"; if (tabData.entries?.length) { @@ -391,10 +391,10 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c + gZenWorkspaces._initialTab._shouldRemove = true; + } + } - } ++ } + else { + gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab; -+ } + } + this._hasAlreadyInitializedZenSessionStore = true; if (tabs.length > 1 || !tabs[0].selected) { @@ -606,7 +606,15 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c aTab.selected || aTab.closing || // Tabs that are sharing the screen, microphone or camera cannot be hidden. -@@ -5972,7 +6103,7 @@ +@@ -5839,6 +5970,7 @@ + * @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab + */ + replaceTabWithWindow(aTab, aOptions) { ++ if (!this.isTab(aTab)) return; // TODO: Handle tab groups + if (this.tabs.length == 1) { + return null; + } +@@ -5972,7 +6104,7 @@ * `true` if element is a `` */ isTabGroup(element) { @@ -615,7 +623,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c } /** -@@ -6049,7 +6180,7 @@ +@@ -6049,7 +6181,7 @@ // Don't allow mixing pinned and unpinned tabs. if (this.isTab(element) && element.pinned) { @@ -624,7 +632,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c } else { tabIndex = Math.max(tabIndex, this.pinnedTabCount); } -@@ -6075,10 +6206,16 @@ +@@ -6075,10 +6207,16 @@ this.#handleTabMove( element, () => { @@ -643,7 +651,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c if (neighbor && this.isTab(element) && tabIndex > element._tPos) { neighbor.after(element); } else { -@@ -6136,22 +6273,23 @@ +@@ -6136,22 +6274,23 @@ #moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) { if (this.isTabGroupLabel(targetElement)) { targetElement = targetElement.group; @@ -673,7 +681,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c moveBefore = false; } else if (!element.pinned && targetElement && targetElement.pinned) { // If the caller asks to move an unpinned element next to a pinned -@@ -6165,14 +6303,34 @@ +@@ -6165,14 +6304,34 @@ // move the tab group right before the first unpinned tab. // 4. Moving a tab group and the first unpinned tab is grouped: // move the tab group right before the first unpinned tab's tab group. @@ -709,7 +717,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c element.pinned ? this.tabContainer.pinnedTabsContainer : this.tabContainer; -@@ -6181,7 +6339,7 @@ +@@ -6181,7 +6340,7 @@ element, () => { if (moveBefore) { @@ -718,7 +726,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c } else if (targetElement) { targetElement.after(element); } else { -@@ -6227,10 +6385,10 @@ +@@ -6227,10 +6386,10 @@ * @param {TabMetricsContext} [metricsContext] */ moveTabToGroup(aTab, aGroup, metricsContext) { @@ -731,7 +739,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c return; } if (aTab.group && aTab.group.id === aGroup.id) { -@@ -6324,6 +6482,10 @@ +@@ -6324,6 +6483,10 @@ moveActionCallback(); @@ -742,7 +750,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c // Clear tabs cache after moving nodes because the order of tabs may have // changed. this.tabContainer._invalidateCachedTabs(); -@@ -7221,7 +7383,7 @@ +@@ -7221,7 +7384,7 @@ // preventDefault(). It will still raise the window if appropriate. break; } @@ -751,7 +759,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c window.focus(); aEvent.preventDefault(); break; -@@ -8166,6 +8328,7 @@ +@@ -8166,6 +8329,7 @@ aWebProgress.isTopLevel ) { this.mTab.setAttribute("busy", "true"); @@ -759,7 +767,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c gBrowser._tabAttrModified(this.mTab, ["busy"]); this.mTab._notselectedsinceload = !this.mTab.selected; } -@@ -9157,7 +9320,7 @@ var TabContextMenu = { +@@ -9157,7 +9321,7 @@ var TabContextMenu = { ); contextUnpinSelectedTabs.hidden = !this.contextTab.pinned || !this.multiselected; @@ -768,7 +776,7 @@ index d80a66a01002e78a9c65545d08fe786328ddf124..1c01a1931a2d0b98f2d8f9a3b27e565c // Build Ask Chat items TabContextMenu.GenAI.buildTabMenu( document.getElementById("context_askChat"), -@@ -9476,6 +9639,7 @@ var TabContextMenu = { +@@ -9476,6 +9640,7 @@ var TabContextMenu = { ) ); } else { diff --git a/src/zen/folders/ZenFolders.mjs b/src/zen/folders/ZenFolders.mjs index 080883724..563c4f4b6 100644 --- a/src/zen/folders/ZenFolders.mjs +++ b/src/zen/folders/ZenFolders.mjs @@ -178,17 +178,16 @@ } #initEventListeners() { - window.addEventListener('TabGrouped', this.#onTabGrouped.bind(this)); - window.addEventListener('TabUngrouped', this.#onTabUngrouped.bind(this)); - window.addEventListener('TabGroupRemoved', this.#onTabGroupRemoved.bind(this)); - window.addEventListener('TabGroupCreate', this.#onTabGroupCreate.bind(this)); - window.addEventListener('TabPinned', this.#onTabPinned.bind(this)); - window.addEventListener('TabUnpinned', this.#onTabUnpinned.bind(this)); - window.addEventListener('TabGroupExpand', this.#onTabGroupExpand.bind(this)); - window.addEventListener('TabGroupCollapse', this.#onTabGroupCollapse.bind(this)); - window.addEventListener('FolderGrouped', this.#onFolderGrouped.bind(this)); - window.addEventListener('TabSelect', this.#onTabSelected.bind(this)); - window.addEventListener('TabOpen', this.#onTabOpened.bind(this)); + window.addEventListener('TabGrouped', this); + window.addEventListener('TabUngrouped', this); + window.addEventListener('TabGroupCreate', this); + window.addEventListener('TabPinned', this); + window.addEventListener('TabUnpinned', this); + window.addEventListener('TabGroupExpand', this); + window.addEventListener('TabGroupCollapse', this); + window.addEventListener('FolderGrouped', this); + window.addEventListener('TabSelect', this); + window.addEventListener('TabOpen', this); const onNewFolder = this.#onNewFolder.bind(this); document .getElementById('zen-context-menu-new-folder') @@ -201,7 +200,16 @@ }); } - #onTabGrouped(event) { + handleEvent(aEvent) { + let methodName = `on_${aEvent.type}`; + if (methodName in this) { + this[methodName](aEvent); + } else { + throw new Error(`Unexpected event ${aEvent.type}`); + } + } + + on_TabGrouped(event) { const tab = event.detail; const group = tab.group; group.pinned = tab.pinned; @@ -218,16 +226,19 @@ } } - #onFolderGrouped(event) { + on_FolderGrouped(event) { if (this._sessionRestoring) return; const folder = event.detail; folder.group.collapsed = false; } - async #onTabSelected() { + async on_TabSelect(event) { const tab = event.target; - const group = tab?.group; - if (!group?.isZenFolder) return; + let group = tab?.group; + if (group?.hasAttribute('split-view-group')) group = group?.group; + if (!group?.isZenFolder || tab.hasAttribute('folder-active')) { + return; + } const collapsedRoot = group.rootMostCollapsedFolder; if (!collapsedRoot) { @@ -239,7 +250,7 @@ gBrowser.tabContainer._invalidateCachedTabs(); } - #onTabOpened(event) { + on_TabOpen(event) { const tab = event.target; const group = tab.group; if (!group?.isZenFolder || tab.pinned) return; @@ -256,7 +267,7 @@ } } - #onTabUngrouped(event) { + on_TabUngrouped(event) { const tab = event.detail; const group = event.target; tab.removeAttribute('folder-active'); @@ -267,6 +278,7 @@ const activeGroup = group.activeGroups; if (activeGroup?.length > 0) { for (const folder of activeGroup) { + folder.activeTabs = folder.activeTabs.filter((tab) => tab.hasAttribute('folder-active')); if (!folder.activeTabs.length) { folder.removeAttribute('has-active'); } @@ -276,7 +288,7 @@ } } - #onTabGroupCreate(event) { + on_TabGroupCreate(event) { const group = event.target; const tabs = group.tabs; if (!group.pinned) { @@ -290,9 +302,7 @@ } } - #onTabGroupRemoved() {} - - #onTabPinned(event) { + on_TabPinned(event) { const tab = event.target; const group = tab.group; if (group && group.hasAttribute('split-view-group')) { @@ -300,7 +310,7 @@ } } - #onTabUnpinned(event) { + on_TabUnpinned(event) { const tab = event.target; const group = tab.group; if (group && group.hasAttribute('split-view-group')) { @@ -316,7 +326,7 @@ this.#popup.hidePopup(); } - async #onTabGroupCollapse(event) { + async on_TabGroupCollapse(event) { const group = event.target; if (!group.isZenFolder) return; @@ -444,7 +454,7 @@ } } - async #onTabGroupExpand(event) { + async on_TabGroupExpand(event) { const group = event.target; if (!group.isZenFolder) return; @@ -461,8 +471,11 @@ const folders = new Map(); group.removeAttribute('has-active'); for (let tab of activeTabs) { - if (!folders.has(tab?.group?.id)) { - folders.set(tab?.group?.id, tab?.group?.activeGroups?.at(-1)); + const group = tab?.group?.hasAttribute('split-view-group') + ? tab?.group?.group + : tab?.group; + if (!folders.has(group?.id)) { + folders.set(group?.id, group?.activeGroups?.at(-1)); } let activeGroup = folders.get(tab?.group?.id); // If group has active tabs, we need to update the indentation @@ -507,7 +520,9 @@ const groupItems = normalizeGroupItems(group.childGroupsAndTabs); const itemsToHide = []; + // TODO: It is necessary to correctly set marginTop for groups with has-active for (const activeGroup of activeGroups) { + const activeGroupItems = activeGroup.childGroupsAndTabs; let selectedTabs = activeGroup.activeTabs; let selectedGroupIds = new Set(); @@ -519,7 +534,7 @@ if (selectedTabs.length) { let selectedIdx = -1; - for (let i = 0; i < activeGroup.childGroupsAndTabs.length; i++) { + for (let i = 0; i < activeGroupItems.length; i++) { const item = activeGroup.childGroupsAndTabs[i]; let selectedTab = item; @@ -535,7 +550,7 @@ } if (selectedIdx >= 0) { - for (let i = selectedIdx; i < activeGroup.childGroupsAndTabs.length; i++) { + for (let i = selectedIdx; i < activeGroupItems.length; i++) { const item = activeGroup.childGroupsAndTabs[i]; if (selectedTabs.includes(item)) continue; @@ -1002,7 +1017,7 @@ const secondaryLabel = document.createElement('div'); secondaryLabel.className = 'tab-list-item-secondary-label'; - secondaryLabel.textContent = formatRelativeTime(tab.lastAccessed); + secondaryLabel.textContent = `${formatRelativeTime(tab.lastAccessed)} • ${tab.group.label}`; labelsContainer.append(mainLabel, secondaryLabel); content.append(icon, labelsContainer); @@ -1113,7 +1128,8 @@ } if ( gBrowser.isTab(groupElem) && - !(groupElem.hasAttribute('zen-empty-tab') && groupElem.group === tab.group) + (!(groupElem.hasAttribute('zen-empty-tab') && groupElem.group === tab.group) || + groupElem?.hasAttribute('zen-empty-tab')) ) { groupElem = groupElem.group; isTab = true; @@ -1226,7 +1242,7 @@ { marginTop: newMargin, }, - { duration: 0.15, ease: 'easeInOut' } + { duration: 0.1, ease: 'easeInOut' } ) .then(() => { selectedTab.style.removeProperty('--zen-folder-indent'); @@ -1254,7 +1270,7 @@ { marginTop: [newMargin, oldMargin], }, - { duration: 0.15, ease: 'easeInOut' } + { duration: 0.1, ease: 'easeInOut' } ); groupStart.removeAttribute('old-margin'); groupStart.removeAttribute('new-margin'); @@ -1297,57 +1313,42 @@ } } - // Always new selected item - let current = selectedItems?.at(-1)?.group; - while (current) { - const activeForGroup = selectedItems.filter((t) => current.contains(t)); - if (activeForGroup.length) { - current.activeTabs = activeForGroup; + for (const tab of selectedItems) { + let current = tab?.group?.hasAttribute('split-view-group') ? tab.group.group : tab?.group; + while (current) { + const activeForGroup = selectedItems.filter((t) => current.contains(t)); + if (activeForGroup.length) { + if (current.collapsed) { + current.setAttribute('has-active', 'true'); + current.activeTabs = activeForGroup; + const tabsContainer = current.querySelector('.tab-group-container'); + const groupStart = current.querySelector('.zen-tab-group-start'); + const curMarginTop = parseInt(groupStart.style.marginTop) || 0; - if (current.collapsed) { - const tabsContainer = current.querySelector('.tab-group-container'); - const groupStart = current.querySelector('.zen-tab-group-start'); + if (tabsContainer.hasAttribute('hidden')) tabsContainer.removeAttribute('hidden'); - if (tabsContainer.hasAttribute('hidden')) tabsContainer.removeAttribute('hidden'); - - let heightUntilSelected; - if (activeForGroup.length) { - const selectedItem = activeForGroup[0]; - const isSplitView = selectedItem.group?.hasAttribute('split-view-group'); - const selectedContainer = isSplitView ? selectedItem.group : selectedItem; - heightUntilSelected = - window.windowUtils.getBoundsWithoutFlushing(selectedContainer).top - - window.windowUtils.getBoundsWithoutFlushing(groupStart).bottom; - if (isSplitView) { - heightUntilSelected -= 2; + animations.push(...this.updateFolderIcon(current, 'close', false)); + animations.push( + gZenUIManager.motion.animate( + groupStart, + { + marginTop: [curMarginTop, 0], + }, + { duration: 0.1, ease: 'easeInOut' } + ) + ); + for (const tab of activeForGroup) { + this.setFolderIndentation( + [tab], + current, + /* for collapse = */ true, + /* animate = */ false + ); } - } else { - heightUntilSelected = - window.windowUtils.getBoundsWithoutFlushing(tabsContainer).height; } - - animations.push(...this.updateFolderIcon(current, 'close', false)); - animations.push( - gZenUIManager.motion.animate( - groupStart, - { - marginTop: [0, -(heightUntilSelected + 4 * (selectedItems.length === 0 ? 1 : 0))], - }, - { duration: 0.1, ease: 'easeInOut' } - ) - ); - } - - for (const tab of activeForGroup) { - this.setFolderIndentation( - [tab], - current, - /* for collapse = */ true, - /* animate = */ false - ); } + current = current.group; } - current = current.group; } const selectedItemsSet = new Set(); @@ -1442,7 +1443,7 @@ } if (group.collapsed) { - this.#onTabGroupCollapse({ target: group }); + this.on_TabGroupCollapse({ target: group }); } const labelContainer = group.querySelector('.tab-group-label-container'); diff --git a/src/zen/workspaces/ZenGradientGenerator.mjs b/src/zen/workspaces/ZenGradientGenerator.mjs index ba87edfaf..89b5d720d 100644 --- a/src/zen/workspaces/ZenGradientGenerator.mjs +++ b/src/zen/workspaces/ZenGradientGenerator.mjs @@ -1090,11 +1090,7 @@ } let opacity = this.currentOpacity; if (forToolbar && !this.#allowTransparencyOnSidebar) { - color = this.blendColors( - color.c, - this.getToolbarModifiedBaseRaw().slice(0, 3), - opacity * 100 - ); + color = this.blendColors(color.c, this.getToolbarModifiedBaseRaw().slice(0, 3), 90); opacity = 1; // Toolbar colors should always be fully opaque } else { color = color.c;