From ebd1910bb8d7ed31e9dccdfcae17504221d790fd Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sat, 7 Jun 2025 20:35:08 +0200 Subject: [PATCH] fix: Fixed restoring tabs after restoring windows, b=bug #8699, c=workspaces, tests --- .../sessionstore/SessionStore-sys-mjs.patch | 11 +- .../tabbrowser/content/tabbrowser-js.patch | 102 ++++++++++-------- src/zen/tests/workspaces/browser.toml | 2 + .../tests/workspaces/browser_issue_8699.js | 47 ++++++++ .../browser_private_mode_startup.js | 2 +- 5 files changed, 119 insertions(+), 45 deletions(-) create mode 100644 src/zen/tests/workspaces/browser_issue_8699.js diff --git a/src/browser/components/sessionstore/SessionStore-sys-mjs.patch b/src/browser/components/sessionstore/SessionStore-sys-mjs.patch index 4dc63dcca..0e0064ed3 100644 --- a/src/browser/components/sessionstore/SessionStore-sys-mjs.patch +++ b/src/browser/components/sessionstore/SessionStore-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs -index 8c6047e1ada5a22e57e1e665965237c9e22641d7..16b171c56081759e81d3efa6c0c7840fbd7902ff 100644 +index 8c6047e1ada5a22e57e1e665965237c9e22641d7..d3472a36fc99c488f0fd0fa8cb9c6927c24bdc6d 100644 --- a/browser/components/sessionstore/SessionStore.sys.mjs +++ b/browser/components/sessionstore/SessionStore.sys.mjs @@ -2088,7 +2088,6 @@ var SessionStoreInternal = { @@ -51,6 +51,15 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..16b171c56081759e81d3efa6c0c7840f userContextId: state.userContextId, skipLoad: true, preferredRemoteType, +@@ -5179,7 +5182,7 @@ var SessionStoreInternal = { + + for (let i = tabbrowser.pinnedTabCount; i < tabbrowser.tabs.length; i++) { + let tab = tabbrowser.tabs[i]; +- if (homePages.includes(tab.linkedBrowser.currentURI.spec)) { ++ if (homePages.includes(tab.linkedBrowser.currentURI.spec) && !tab.hasAttribute("zen-empty-tab")) { + removableTabs.push(tab); + } + } @@ -5239,7 +5242,7 @@ var SessionStoreInternal = { } diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch index f8a009de8..3d850deaa 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 d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91bf07fdb93 100644 +index d5aa64842a35c6697263c63fd3a0571b64b01344..478050fd5202b70ee3191471a017e091a3a87e92 100644 --- a/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js @@ -413,11 +413,41 @@ @@ -301,7 +301,18 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b }); } -@@ -3560,7 +3642,7 @@ +@@ -3517,7 +3599,9 @@ + let shouldUpdateForPinnedTabs = false; + /** @type {Map} */ + let tabGroupWorkingData = new Map(); +- ++ if (this._hasAlreadyInitializedZenSessionStore) { ++ selectTab += 1; // SessionStoreInternal.restoreTabs expects a 1-based index. ++ } + for (const tabGroupData of tabGroupDataList) { + tabGroupWorkingData.set(tabGroupData.id, { + stateData: tabGroupData, +@@ -3560,7 +3644,7 @@ // Add a new tab if needed. if (!tab) { let createLazyBrowser = @@ -310,7 +321,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b let url = "about:blank"; if (tabData.entries?.length) { -@@ -3598,7 +3680,8 @@ +@@ -3598,7 +3682,8 @@ skipLoad: true, preferredRemoteType, }); @@ -320,7 +331,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if (select) { tabToSelect = tab; } -@@ -3622,7 +3705,8 @@ +@@ -3622,7 +3707,8 @@ // needs calling: shouldUpdateForPinnedTabs = true; } @@ -330,7 +341,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b let { groupId } = tabData; const tabGroup = tabGroupWorkingData.get(groupId); // if a tab refers to a tab group we don't know, skip any group -@@ -3636,7 +3720,10 @@ +@@ -3636,7 +3722,10 @@ tabGroup.stateData.id, tabGroup.stateData.color, tabGroup.stateData.collapsed, @@ -342,26 +353,31 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b ); tabsFragment.appendChild(tabGroup.node); } -@@ -3684,8 +3771,16 @@ +@@ -3684,9 +3773,23 @@ // to remove the old selected tab. if (tabToSelect) { let leftoverTab = this.selectedTab; -- this.selectedTab = tabToSelect; -- this.removeTab(leftoverTab); -+ gZenWorkspaces._tabToRemoveForEmpty = leftoverTab; -+ if (Services.prefs.getBoolPref("zen.workspaces.continue-where-left-off")) { -+ gZenWorkspaces._tabToSelect = selectTab - 1; -+ } -+ if (gZenWorkspaces._initialTab && !gZenVerticalTabsManager._canReplaceNewTab) { -+ gZenWorkspaces._initialTab._shouldRemove = true; ++ if (this._hasAlreadyInitializedZenSessionStore) { + this.selectedTab = tabToSelect; + this.removeTab(leftoverTab); ++ } else { ++ gZenWorkspaces._tabToRemoveForEmpty = leftoverTab; ++ if (Services.prefs.getBoolPref("zen.workspaces.continue-where-left-off")) { ++ gZenWorkspaces._tabToSelect = selectTab - 1; ++ } ++ if (gZenWorkspaces._initialTab && !gZenVerticalTabsManager._canReplaceNewTab) { ++ gZenWorkspaces._initialTab._shouldRemove = true; ++ } + } + } + else { + gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab; } ++ this._hasAlreadyInitializedZenSessionStore = true; if (tabs.length > 1 || !tabs[0].selected) { -@@ -3881,7 +3976,7 @@ + this._updateTabsAfterInsert(); +@@ -3881,7 +3984,7 @@ // Ensure we have an index if one was not provided. if (typeof elementIndex != "number" && typeof tabIndex != "number") { // Move the new tab after another tab if needed, to the end otherwise. @@ -370,7 +386,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if ( !bulkOrderedOpen && ((openerTab && -@@ -3904,7 +3999,7 @@ +@@ -3904,7 +4007,7 @@ ) { elementIndex = Infinity; } else if (previousTab.visible) { @@ -379,7 +395,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } else if (previousTab == FirefoxViewHandler.tab) { elementIndex = 0; } -@@ -3932,14 +4027,14 @@ +@@ -3932,14 +4035,14 @@ } // Ensure index is within bounds. if (tab.pinned) { @@ -398,7 +414,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b // Prevent a flash of unstyled content by setting up the tab content // and inherited attributes before appending it (see Bug 1592054): -@@ -3947,7 +4042,7 @@ +@@ -3947,7 +4050,7 @@ this.tabContainer._invalidateCachedTabs(); @@ -407,7 +423,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if (this.isTab(itemAfter) && itemAfter.group == tabGroup) { // Place at the front of, or between tabs in, the same tab group this.tabContainer.insertBefore(tab, itemAfter); -@@ -3980,6 +4075,7 @@ +@@ -3980,6 +4083,7 @@ if (pinned) { this._updateTabBarForPinnedTabs(); } @@ -415,7 +431,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b TabBarVisibility.update(); } -@@ -4268,6 +4364,9 @@ +@@ -4268,6 +4372,9 @@ return; } @@ -425,7 +441,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b this.removeTabs(selectedTabs, { telemetrySource }); } -@@ -4520,6 +4619,7 @@ +@@ -4520,6 +4627,7 @@ telemetrySource, } = {} ) { @@ -433,7 +449,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b // When 'closeWindowWithLastTab' pref is enabled, closing all tabs // can be considered equivalent to closing the window. if ( -@@ -4604,6 +4704,7 @@ +@@ -4604,6 +4712,7 @@ if (lastToClose) { this.removeTab(lastToClose, aParams); } @@ -441,7 +457,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } catch (e) { console.error(e); } -@@ -4641,6 +4742,12 @@ +@@ -4641,6 +4750,12 @@ aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start(); } @@ -454,7 +470,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b // Handle requests for synchronously removing an already // asynchronously closing tab. if (!animate && aTab.closing) { -@@ -4655,7 +4762,9 @@ +@@ -4655,7 +4770,9 @@ // frame created for it (for example, by updating the visually selected // state). let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width; @@ -465,7 +481,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if ( !this._beginRemoveTab(aTab, { closeWindowFastpath: true, -@@ -4821,7 +4930,7 @@ +@@ -4821,7 +4938,7 @@ closeWindowWithLastTab != null ? closeWindowWithLastTab : !window.toolbar.visible || @@ -474,7 +490,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if (closeWindow) { // We've already called beforeunload on all the relevant tabs if we get here, -@@ -4845,6 +4954,7 @@ +@@ -4845,6 +4962,7 @@ newTab = true; } @@ -482,7 +498,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b aTab._endRemoveArgs = [closeWindow, newTab]; // swapBrowsersAndCloseOther will take care of closing the window without animation. -@@ -4885,9 +4995,7 @@ +@@ -4885,9 +5003,7 @@ aTab._mouseleave(); if (newTab) { @@ -493,7 +509,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } else { TabBarVisibility.update(); } -@@ -5016,6 +5124,7 @@ +@@ -5016,6 +5132,7 @@ this.tabs[i]._tPos = i; } @@ -501,7 +517,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if (!this._windowIsClosing) { if (wasPinned) { this.tabContainer._positionPinnedTabs(); -@@ -5230,6 +5339,7 @@ +@@ -5230,6 +5347,7 @@ } let excludeTabs = new Set(aExcludeTabs); @@ -509,7 +525,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b // If this tab has a successor, it should be selectable, since // hiding or closing a tab removes that tab as a successor. -@@ -5242,13 +5352,13 @@ +@@ -5242,13 +5360,13 @@ !excludeTabs.has(aTab.owner) && Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose") ) { @@ -525,7 +541,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b ); let tab = this.tabContainer.findNextTab(aTab, { -@@ -5264,7 +5374,7 @@ +@@ -5264,7 +5382,7 @@ } if (tab) { @@ -534,7 +550,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } // If no qualifying visible tab was found, see if there is a tab in -@@ -5285,7 +5395,7 @@ +@@ -5285,7 +5403,7 @@ }); } @@ -543,7 +559,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } _blurTab(aTab) { -@@ -5686,10 +5796,10 @@ +@@ -5686,10 +5804,10 @@ SessionStore.deleteCustomTabValue(aTab, "hiddenBy"); } @@ -556,7 +572,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b aTab.selected || aTab.closing || // Tabs that are sharing the screen, microphone or camera cannot be hidden. -@@ -5909,7 +6019,7 @@ +@@ -5909,7 +6027,7 @@ * `true` if element is a `` */ isTabGroup(element) { @@ -565,7 +581,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } /** -@@ -5986,7 +6096,7 @@ +@@ -5986,7 +6104,7 @@ // Don't allow mixing pinned and unpinned tabs. if (this.isTab(element) && element.pinned) { @@ -574,7 +590,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b } else { tabIndex = Math.max(tabIndex, this.pinnedTabCount); } -@@ -6012,10 +6122,16 @@ +@@ -6012,10 +6130,16 @@ this.#handleTabMove( element, () => { @@ -593,7 +609,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if (neighbor && this.isTab(element) && tabIndex > element._tPos) { neighbor.after(element); } else { -@@ -6084,17 +6200,29 @@ +@@ -6084,17 +6208,29 @@ targetElement = targetElement.group; } } @@ -627,7 +643,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b if (element.pinned && this.tabContainer.verticalMode) { return this.tabContainer.verticalPinnedTabsContainer; } -@@ -6154,7 +6282,7 @@ +@@ -6154,7 +6290,7 @@ if (!this.isTab(aTab)) { throw new Error("Can only move a tab into a tab group"); } @@ -636,7 +652,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b return; } if (aTab.group && aTab.group.id === aGroup.id) { -@@ -6248,6 +6376,10 @@ +@@ -6248,6 +6384,10 @@ moveActionCallback(); @@ -647,7 +663,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b // Clear tabs cache after moving nodes because the order of tabs may have // changed. this.tabContainer._invalidateCachedTabs(); -@@ -7145,7 +7277,7 @@ +@@ -7145,7 +7285,7 @@ // preventDefault(). It will still raise the window if appropriate. break; } @@ -656,7 +672,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b window.focus(); aEvent.preventDefault(); break; -@@ -8044,6 +8176,7 @@ +@@ -8044,6 +8184,7 @@ aWebProgress.isTopLevel ) { this.mTab.setAttribute("busy", "true"); @@ -664,7 +680,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b gBrowser._tabAttrModified(this.mTab, ["busy"]); this.mTab._notselectedsinceload = !this.mTab.selected; } -@@ -9009,7 +9142,7 @@ var TabContextMenu = { +@@ -9009,7 +9150,7 @@ var TabContextMenu = { ); contextUnpinSelectedTabs.hidden = !this.contextTab.pinned || !this.multiselected; @@ -673,7 +689,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..adf3d7c318196a8a02158dfaf9d4d91b // Move Tab items let contextMoveTabOptions = document.getElementById( "context_moveTabOptions" -@@ -9278,6 +9411,7 @@ var TabContextMenu = { +@@ -9278,6 +9419,7 @@ var TabContextMenu = { telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP, }); } else { diff --git a/src/zen/tests/workspaces/browser.toml b/src/zen/tests/workspaces/browser.toml index 1cc57677b..c68d45ddc 100644 --- a/src/zen/tests/workspaces/browser.toml +++ b/src/zen/tests/workspaces/browser.toml @@ -8,5 +8,7 @@ support-files = [ ["browser_double_click_newtab.js"] ["browser_overflow_scrollbox.js"] +["browser_issue_8699.js"] + ["browser_private_mode.js"] ["browser_private_mode_startup.js"] diff --git a/src/zen/tests/workspaces/browser_issue_8699.js b/src/zen/tests/workspaces/browser_issue_8699.js new file mode 100644 index 000000000..087b1aebc --- /dev/null +++ b/src/zen/tests/workspaces/browser_issue_8699.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +add_task(async function test_Restore_Closed_Tabs() { + const currentTab = gBrowser.selectedTab; + const tabsToClose = []; + for (let i = 0; i < 3; i++) { + const tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + `https://example.com/${i}`, + true, + { skipAnimation: true } + ); + tabsToClose.push(tab); + } + gBrowser.selectedTab = tabsToClose[0]; + await TabStateFlusher.flushWindow(window); + Assert.equal( + gBrowser.tabs.length, + 5, // 1 initial tab + 3 new tabs + 'There should be four tabs after opening three new tabs' + ); + gBrowser.removeTabs(tabsToClose); + await TabStateFlusher.flushWindow(window); + await new Promise((resolve) => { + Assert.equal( + gBrowser.selectedTab, + currentTab, + 'Current tab should still be selected after closing tabs' + ); + Assert.equal(gBrowser.tabs.length, 2, 'There should be one tab left after closing all tabs'); + restoreLastClosedTabOrWindowOrSession(); + ok(!currentTab.selected, 'Current tab should not be selected after restore'); + Assert.equal( + gBrowser.tabs.length, + 5, // 1 initial tab + 3 restored tabs + 'There should be four tabs after restoring closed tabs' + ); + gBrowser.selectedTab = currentTab; + resolve(); + }); + for (const tab of gBrowser.tabs.filter((t) => t !== currentTab)) { + await BrowserTestUtils.removeTab(tab); + } +}); diff --git a/src/zen/tests/workspaces/browser_private_mode_startup.js b/src/zen/tests/workspaces/browser_private_mode_startup.js index c964207a8..58ead2d83 100644 --- a/src/zen/tests/workspaces/browser_private_mode_startup.js +++ b/src/zen/tests/workspaces/browser_private_mode_startup.js @@ -12,7 +12,7 @@ add_task(async function test_Private_Mode_Startup() { setTimeout(() => { Assert.equal( privateWindow.gBrowser.tabs.length, - 1, + 2, 'Private window should start with one tab' ); resolve();