diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch index ced7c7b49..3790f9846 100644 --- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch +++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch @@ -1,5 +1,6 @@ diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4cd44d980e 100644 +index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4cd44d980e 100644 --- a/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js @@ -432,15 +432,64 @@ @@ -479,6 +480,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c TabBarVisibility.update(); } +@@ -4896,6 +5024,7 @@ @@ -4896,6 +5024,7 @@ telemetrySource, } = {} @@ -487,6 +489,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c // When 'closeWindowWithLastTab' pref is enabled, closing all tabs // can be considered equivalent to closing the window. if ( +@@ -4985,6 +5114,7 @@ @@ -4985,6 +5114,7 @@ if (lastToClose) { this.removeTab(lastToClose, aParams); @@ -495,6 +498,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } catch (e) { console.error(e); } +@@ -5023,6 +5153,12 @@ @@ -5023,6 +5153,12 @@ aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start(); } @@ -508,6 +512,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c // Handle requests for synchronously removing an already // asynchronously closing tab. if (!animate && aTab.closing) { +@@ -5037,6 +5173,9 @@ @@ -5037,6 +5173,9 @@ // state). let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width; @@ -518,6 +523,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c if ( !this._beginRemoveTab(aTab, { closeWindowFastpath: true, +@@ -5085,7 +5224,13 @@ @@ -5085,7 +5224,13 @@ // We're not animating, so we can cancel the animation stopwatch. Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId); @@ -533,6 +539,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c return; } +@@ -5219,7 +5364,7 @@ @@ -5219,7 +5364,7 @@ closeWindowWithLastTab != null ? closeWindowWithLastTab @@ -542,6 +549,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c if (closeWindow) { // We've already called beforeunload on all the relevant tabs if we get here, +@@ -5243,6 +5388,7 @@ @@ -5243,6 +5388,7 @@ newTab = true; @@ -550,6 +558,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c aTab._endRemoveArgs = [closeWindow, newTab]; // swapBrowsersAndCloseOther will take care of closing the window without animation. +@@ -5283,13 +5429,7 @@ @@ -5283,13 +5429,7 @@ aTab._mouseleave(); @@ -565,6 +574,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } else { TabBarVisibility.update(); } +@@ -5422,6 +5562,7 @@ @@ -5422,6 +5562,7 @@ this.tabs[i]._tPos = i; } @@ -573,6 +583,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c if (!this._windowIsClosing) { // update tab close buttons state this.tabContainer._updateCloseButtons(); +@@ -5643,6 +5784,7 @@ @@ -5643,6 +5784,7 @@ } @@ -581,6 +592,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c // If this tab has a successor, it should be selectable, since // hiding or closing a tab removes that tab as a successor. +@@ -5655,13 +5797,13 @@ @@ -5655,13 +5797,13 @@ !excludeTabs.has(aTab.owner) && Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose") @@ -597,6 +609,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c ); let tab = this.tabContainer.findNextTab(aTab, { +@@ -5677,7 +5819,7 @@ @@ -5677,7 +5819,7 @@ } @@ -606,6 +619,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } // If no qualifying visible tab was found, see if there is a tab in +@@ -5698,7 +5840,7 @@ @@ -5698,7 +5840,7 @@ }); } @@ -615,6 +629,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } _blurTab(aTab) { +@@ -6104,10 +6246,10 @@ @@ -6104,10 +6246,10 @@ SessionStore.deleteCustomTabValue(aTab, "hiddenBy"); } @@ -628,6 +643,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c aTab.selected || aTab.closing || // Tabs that are sharing the screen, microphone or camera cannot be hidden. +@@ -6166,6 +6308,7 @@ @@ -6166,6 +6308,7 @@ * @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab */ @@ -636,6 +652,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c if (this.tabs.length == 1) { return null; } +@@ -6299,7 +6442,7 @@ @@ -6299,7 +6442,7 @@ * `true` if element is a `` */ @@ -645,6 +662,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } /** +@@ -6375,8 +6518,8 @@ @@ -6375,8 +6518,8 @@ } @@ -656,6 +674,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } else { tabIndex = Math.max(tabIndex, this.pinnedTabCount); } +@@ -6402,10 +6545,16 @@ @@ -6402,10 +6545,16 @@ this.#handleTabMove( element, @@ -675,6 +694,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c if (neighbor && this.isTab(element) && tabIndex > element._tPos) { neighbor.after(element); } else { +@@ -6463,23 +6612,28 @@ @@ -6463,23 +6612,28 @@ #moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) { if (this.isTabGroupLabel(targetElement)) { @@ -710,6 +730,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } else if (!element.pinned && targetElement && targetElement.pinned) { // If the caller asks to move an unpinned element next to a pinned // tab, move the unpinned element to be the first unpinned element +@@ -6492,14 +6646,34 @@ @@ -6492,14 +6646,34 @@ // move the tab group right before the first unpinned tab. // 4. Moving a tab group and the first unpinned tab is grouped: @@ -746,6 +767,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c element.pinned ? this.tabContainer.pinnedTabsContainer : this.tabContainer; +@@ -6508,7 +6682,7 @@ @@ -6508,7 +6682,7 @@ element, () => { @@ -755,6 +777,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c } else if (targetElement) { targetElement.after(element); } else { +@@ -6580,10 +6754,10 @@ @@ -6580,10 +6754,10 @@ * @param {TabMetricsContext} [metricsContext] */ @@ -768,6 +791,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c return; } if (aTab.group && aTab.group.id === aGroup.id) { +@@ -6613,6 +6787,7 @@ @@ -6613,6 +6787,7 @@ let state = { @@ -776,6 +800,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c }; if (tab.visible) { state.elementIndex = tab.elementIndex; +@@ -6639,7 +6814,7 @@ @@ -6639,7 +6814,7 @@ let changedTabGroup = previousTabState.tabGroupId != currentTabState.tabGroupId; @@ -785,6 +810,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c tab.dispatchEvent( new CustomEvent("TabMove", { bubbles: true, +@@ -6676,6 +6851,10 @@ @@ -6676,6 +6851,10 @@ moveActionCallback(); @@ -796,6 +822,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c // Clear tabs cache after moving nodes because the order of tabs may have // changed. this.tabContainer._invalidateCachedTabs(); +@@ -7576,7 +7755,7 @@ @@ -7576,7 +7755,7 @@ // preventDefault(). It will still raise the window if appropriate. break; @@ -805,6 +832,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c window.focus(); aEvent.preventDefault(); break; +@@ -7593,7 +7772,6 @@ @@ -7593,7 +7772,6 @@ } case "TabGroupCollapse": @@ -813,6 +841,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c }); break; case "TabGroupCreateByUser": +@@ -8542,6 +8720,7 @@ @@ -8542,6 +8720,7 @@ aWebProgress.isTopLevel ) { @@ -821,6 +850,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..30c8fd7e978eb3036f35b17ae3f6ea4c gBrowser._tabAttrModified(this.mTab, ["busy"]); this.mTab._notselectedsinceload = !this.mTab.selected; } +@@ -9543,7 +9722,7 @@ var TabContextMenu = { @@ -9543,7 +9722,7 @@ var TabContextMenu = { ); contextUnpinSelectedTabs.hidden = diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs index 8ced9364c..9d7bc5b36 100644 --- a/src/zen/tabs/ZenPinnedTabManager.mjs +++ b/src/zen/tabs/ZenPinnedTabManager.mjs @@ -87,8 +87,11 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { } onTabIconChanged(tab, url = null) { + tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } })); tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } })); const iconUrl = url ?? tab.iconImage.src; + if (tab.hasAttribute('zen-essential')) { + tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`); if (tab.hasAttribute('zen-essential')) { tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`); } @@ -156,6 +159,17 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { } } + async _onTabClick(e) { + const tab = e.target?.closest('tab'); + if (e.button === 1 && tab) { + await this.onCloseTabShortcut(e, tab, { + closeIfPending: Services.prefs.getBoolPref( + 'zen.pinned-tab-manager.wheel-close-if-pending' + ), + }); + } + } + async resetPinnedTab(tab) { if (!tab) { tab = TabContextMenu.contextTab; @@ -181,6 +195,9 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { _initClosePinnedTabShortcut() { let cmdClose = document.getElementById('cmd_close'); + _initClosePinnedTabShortcut() { + let cmdClose = document.getElementById('cmd_close'); + if (cmdClose) { cmdClose.addEventListener('command', this.onCloseTabShortcut.bind(this)); } @@ -212,6 +229,32 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { ), ]; + async onCloseTabShortcut( + event, + selectedTab = gBrowser.selectedTab, + { + behavior = lazy.zenPinnedTabCloseShortcutBehavior, + noClose = false, + closeIfPending = false, + alwaysUnload = false, + folderToUnload = null, + } = {} + ) { + try { + const tabs = Array.isArray(selectedTab) ? selectedTab : [selectedTab]; + const pinnedTabs = [ + ...new Set( + tabs + .flatMap((tab) => { + if (tab.group?.hasAttribute('split-view-group')) { + return tab.group.tabs; + } + return tab; + }) + .filter((tab) => tab?.pinned) + ), + ]; + if (!pinnedTabs.length) { return; } @@ -944,16 +987,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { } } - async onTabLabelChanged(tab) { - tab.dispatchEvent(new CustomEvent('ZenTabLabelChanged', { detail: { tab } })); - if (!this._pinsCache) { - return; - } - // If our current pin in the cache point to about:blank, we need to update the entry - const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id')); - if (!pin) { - return; - } + async onTabLabelChanged(tab) { + tab.dispatchEvent(new CustomEvent('ZenTabLabelChanged', { detail: { tab } })); + if (!this._pinsCache) { + return; + } + // If our current pin in the cache point to about:blank, we need to update the entry + const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id')); + if (!pin) { + return; + } if (pin.url === 'about:blank' && tab.linkedBrowser.currentURI.spec !== 'about:blank') { await this.replacePinnedUrlWithCurrent(tab);