From f77249e8e9e241e34ffb53f215b63a5d689d485a Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Fri, 9 Jan 2026 16:52:22 +0100 Subject: [PATCH] fix: Make sure to update cache when swaping docshells, b=bug #11831, c=no-component --- .../sessionstore/TabState-sys-mjs.patch | 11 +--- .../tabbrowser/content/tabgroup-js.patch | 31 +++++++--- src/zen/drag-and-drop/ZenDragAndDrop.js | 26 ++++++--- .../sessionstore/ZenSessionManager.sys.mjs | 6 +- src/zen/sessionstore/ZenWindowSync.sys.mjs | 58 +++++++++---------- 5 files changed, 70 insertions(+), 62 deletions(-) diff --git a/src/browser/components/sessionstore/TabState-sys-mjs.patch b/src/browser/components/sessionstore/TabState-sys-mjs.patch index bc19f5a93..f54808ee4 100644 --- a/src/browser/components/sessionstore/TabState-sys-mjs.patch +++ b/src/browser/components/sessionstore/TabState-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs -index 82721356d191055bec0d4b0ca49e481221988801..ffa95005b96ea384433f18dace63faa35d2d21bf 100644 +index 82721356d191055bec0d4b0ca49e481221988801..238d6ae1a4261e098d1e986e3c3df813d9d625f3 100644 --- a/browser/components/sessionstore/TabState.sys.mjs +++ b/browser/components/sessionstore/TabState.sys.mjs @@ -85,7 +85,24 @@ class _TabState { @@ -27,12 +27,3 @@ index 82721356d191055bec0d4b0ca49e481221988801..ffa95005b96ea384433f18dace63faa3 tabData.userContextId = tab.userContextId || 0; -@@ -98,7 +115,7 @@ class _TabState { - - // Copy data from the tab state cache only if the tab has fully finished - // restoring. We don't want to overwrite data contained in __SS_data. -- this.copyFromCache(browser.permanentKey, tabData, options); -+ this.copyFromCache(tab.permanentKey, tabData, options); - - // After copyFromCache() was called we check for properties that are kept - // in the cache only while the tab is pending or restoring. Once that diff --git a/src/browser/components/tabbrowser/content/tabgroup-js.patch b/src/browser/components/tabbrowser/content/tabgroup-js.patch index ad0d2ccba..22fb849a4 100644 --- a/src/browser/components/tabbrowser/content/tabgroup-js.patch +++ b/src/browser/components/tabbrowser/content/tabgroup-js.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js -index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee3c507b39 100644 +index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..9a38d7dc10403c1ee721b6abf33d525a4aa1f821 100644 --- a/browser/components/tabbrowser/content/tabgroup.js +++ b/browser/components/tabbrowser/content/tabgroup.js @@ -14,11 +14,11 @@ @@ -78,7 +78,20 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee this.#updateLabelAriaAttributes(); -@@ -143,6 +162,8 @@ +@@ -132,17 +151,21 @@ + let tabGroupCreateDetail = this.#wasCreatedByAdoption + ? { isAdoptingGroup: true } + : {}; ++ if (!this.hasAttribute('drag-image')) { + this.dispatchEvent( + new CustomEvent("TabGroupCreate", { + bubbles: true, + detail: tabGroupCreateDetail, + }) + ); ++ } + // Reset `wasCreatedByAdoption` to default of false so that we only + // claim that a tab group was created by adoption the first time it // mounts after getting created by `Tabbrowser.adoptTabGroup`. this.#wasCreatedByAdoption = false; } @@ -87,7 +100,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee resetDefaultGroupName = () => { this.#defaultGroupName = ""; -@@ -211,7 +232,10 @@ +@@ -211,7 +234,10 @@ } }); } @@ -99,7 +112,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee } get color() { -@@ -305,6 +329,9 @@ +@@ -305,6 +331,9 @@ } set collapsed(val) { @@ -109,7 +122,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee if (!!val == this.collapsed) { return; } -@@ -391,7 +418,6 @@ +@@ -391,7 +420,6 @@ tabGroupName, }) .then(result => { @@ -117,7 +130,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee }); } -@@ -466,13 +492,65 @@ +@@ -466,13 +494,65 @@ * @returns {MozTabbrowserTab[]} */ get tabs() { @@ -188,7 +201,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee } /** -@@ -560,7 +638,6 @@ +@@ -560,7 +640,6 @@ ); } else { if (tabOrSplitView.pinned) { @@ -196,7 +209,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee } let tabToMove = this.ownerGlobal === tabOrSplitView.ownerGlobal -@@ -625,7 +702,7 @@ +@@ -625,7 +704,7 @@ */ on_click(event) { let isToggleElement = @@ -205,7 +218,7 @@ index 9abf115062e7c86196d7cb6b8ac82a9bb63b5a65..150b0266c979bcfec84e0c1975f91bee event.target === this.#overflowCountLabel; if (isToggleElement && event.button === 0) { event.preventDefault(); -@@ -696,5 +773,6 @@ +@@ -696,5 +775,6 @@ } } diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js index d16273392..8a4809f19 100644 --- a/src/zen/drag-and-drop/ZenDragAndDrop.js +++ b/src/zen/drag-and-drop/ZenDragAndDrop.js @@ -105,8 +105,14 @@ '#tabbrowser-arrowscrollbox-periphery' ); const dragData = draggedTab._dragData; - const wrapper = document.createElement('div'); const tabRect = window.windowUtils.getBoundsWithoutFlushing(movingTabs[0]); + const wrapper = document.createElement('div'); + wrapper.style.width = tabRect.width + 'px'; + wrapper.style.height = tabRect.height * movingTabs.length + 'px'; + wrapper.style.overflow = 'clip'; + wrapper.style.position = 'fixed'; + wrapper.style.top = '-9999px'; + periphery.appendChild(wrapper); for (let i = 0; i < movingTabs.length; i++) { const tab = movingTabs[i]; const tabClone = tab.cloneNode(true); @@ -125,15 +131,16 @@ if (!movingTabs.length > 1) { tabClone.style.transform = `translate(${(tabRect.width - dragData.offsetX) / 2}px, ${(tabRect.height - dragData.offsetY) / 2}px)`; } + tabClone.setAttribute('drag-image', 'true'); wrapper.appendChild(tabClone); + if (isTab(tabClone) && !tabClone.hasAttribute('zen-essential')) { + // We need to limit the label content so the drag image doesn't grow too big. + const label = tabClone.textLabel; + const tabLabelParentWidth = label.parentElement.getBoundingClientRect().width; + label.textContent = label.textContent.slice(0, Math.floor(tabLabelParentWidth / 6)); + } } this.#maybeCreateDragImageDot(movingTabs, wrapper); - wrapper.style.width = tabRect.width + 'px'; - wrapper.style.height = tabRect.height * movingTabs.length + 'px'; - wrapper.style.overflow = 'clip'; - wrapper.style.position = 'fixed'; - wrapper.style.top = '-9999px'; - periphery.appendChild(wrapper); this._tempDragImageParent = wrapper; return wrapper; } @@ -707,7 +714,9 @@ !dropElement || dropElement.hasAttribute('zen-essential') || draggedTab.hasAttribute('zen-essential') || - draggedTab.getAttribute('zen-workspace-id') != gZenWorkspaces.activeWorkspace + draggedTab.getAttribute('zen-workspace-id') != gZenWorkspaces.activeWorkspace || + !dropElement.visible || + !draggedTab.visible ) { return; } @@ -847,7 +856,6 @@ const numPinned = gBrowser.pinnedTabCount - numEssentials; const tabToUse = event.target.closest(dropZoneSelector); if (!tabToUse) { - this.clearDragOverVisuals(); return; } const isPinned = tabToUse.pinned; diff --git a/src/zen/sessionstore/ZenSessionManager.sys.mjs b/src/zen/sessionstore/ZenSessionManager.sys.mjs index b865555f5..7576ec7a9 100644 --- a/src/zen/sessionstore/ZenSessionManager.sys.mjs +++ b/src/zen/sessionstore/ZenSessionManager.sys.mjs @@ -293,10 +293,8 @@ export class nsZenSessionManager { /** * Collects session data for all tabs in a given window. * - * @param sidebarData - * The sidebar data object to populate. - * @param state - * The current session state. + * @param sidebarData The sidebar data object to populate. + * @param state The current session state. */ #collectTabsData(sidebarData, state) { const tabIdRelationMap = new Map(); diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index 91a624a4a..bf0c20cfa 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -11,6 +11,7 @@ ChromeUtils.defineESModuleGetters(lazy, { SessionStore: 'resource:///modules/sessionstore/SessionStore.sys.mjs', TabStateFlusher: 'resource:///modules/sessionstore/TabStateFlusher.sys.mjs', ZenSessionStore: 'resource:///modules/zen/ZenSessionManager.sys.mjs', + TabStateCache: 'resource:///modules/sessionstore/TabStateCache.sys.mjs', }); XPCOMUtils.defineLazyPreferenceGetter(lazy, 'gWindowSyncEnabled', 'zen.window-sync.enabled'); @@ -328,24 +329,6 @@ class nsZenWindowSync { } } - /** - * Ensures that all synced tabs with a given ID has the same permanentKey. - * @param {Object} aTab - The tab to ensure sync for. - */ - #makeSureTabSyncsPermanentKey(aTab) { - if (!aTab.id) { - return; - } - let permanentKey = aTab.linkedBrowser.permanentKey; - this.#runOnAllWindows(null, (win) => { - const tab = this.getItemFromWindow(win, aTab.id); - if (tab) { - tab.linkedBrowser.permanentKey = permanentKey; - tab.permanentKey = permanentKey; - } - }); - } - /** * Retrieves a item element from a window by its ID. * @@ -543,6 +526,7 @@ class nsZenWindowSync { * @param {Object} aOtherTab - The tab in the other window. */ async #swapBrowserDocShellsAsync(aOurTab, aOtherTab) { + lazy.TabStateFlusher.flush(aOtherTab.linkedBrowser); await this.#styleSwapedBrowsers(aOurTab, aOtherTab, () => { this.#swapBrowserDocSheellsInner(aOurTab, aOtherTab); }); @@ -591,14 +575,18 @@ class nsZenWindowSync { * * @param {Object} aOurTab - The tab in the current window. * @param {Object} aOtherTab - The tab in the other window. - * @param {boolean} focus - Indicates if the tab should be focused after the swap. - * @param {boolean} onClose - Indicates if the swap is done during a tab close operation. + * @param {boolean} options.focus - Indicates if the tab should be focused after the swap. + * @param {boolean} options.onClose - Indicates if the swap is done during a tab close operation. */ - #swapBrowserDocSheellsInner(aOurTab, aOtherTab, focus = true, onClose = false) { + #swapBrowserDocSheellsInner(aOurTab, aOtherTab, { focus = true, onClose = false } = {}) { // Can't swap between chrome and content processes. if (aOurTab.linkedBrowser.isRemoteBrowser != aOtherTab.linkedBrowser.isRemoteBrowser) { return false; } + // See https://github.com/zen-browser/desktop/issues/11851, swapping the browsers + // don't seem to update the state's cache properly, leading to issues when restoring + // the session later on. + let tabState = this.#getTabState(aOtherTab); // Running `swapBrowsersAndCloseOther` doesn't expect us to use the tab after // the operation, so it doesn't really care about cleaning up the other tab. // We need to make a new tab progress listener for the other tab after the swap. @@ -607,10 +595,6 @@ class nsZenWindowSync { () => { this.log(`Swapping docshells between windows for tab ${aOurTab.id}`); aOurTab.ownerGlobal.gBrowser.swapBrowsersAndCloseOther(aOurTab, aOtherTab, false); - // Sometimes, when closing a window for example, when we swap the browsers, - // there's a chance that the tab does not have the entries state moved over properly. - // To avoid losing history entries, we have to keep the permanentKey in sync. - this.#makeSureTabSyncsPermanentKey(aOurTab); // Since we are moving progress listeners around, there's a chance that we // trigger a load while making the switch, and since we remove the previous // tab's listeners, the other browser window will never get the 'finish load' event @@ -626,7 +610,11 @@ class nsZenWindowSync { // We do need to do this though instead of just unloading the browser because // firefox doesn't expect an unloaded + selected tab, so we need to get // around this limitation somehow. - if (!onClose && aOtherTab.linkedBrowser?.currentURI.spec !== 'about:blank') { + if ( + !onClose && + (aOtherTab.linkedBrowser?.currentURI.spec !== 'about:blank' || + aOtherTab.hasAttribute('busy')) + ) { this.log(`Loading about:blank in our tab ${aOtherTab.id} before swap`); aOtherTab.linkedBrowser.loadURI(Services.io.newURI('about:blank'), { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), @@ -653,7 +641,15 @@ class nsZenWindowSync { // It's also important to note that if we don't flush the state here, // we would start receiving invalid history changes from the the incorrect // browser view that was just swapped out. - lazy.TabStateFlusher.flush(aOurTab.linkedBrowser); + lazy.TabStateFlusher.flush(aOurTab.linkedBrowser).finally(() => { + if (!tabState.entries?.length) { + this.log(`Error: No tab state entries found for tab ${aOtherTab.id} during swap`); + return; + } + lazy.TabStateCache.update(aOurTab.linkedBrowser.permanentKey, { + entries: tabState.entries, + }); + }); return true; } @@ -767,9 +763,12 @@ class nsZenWindowSync { for (let tab of activeTabsOnClosedWindow) { const targetTab = this.getItemFromWindow(mostRecentWindow, tab.id); if (targetTab) { - targetTab._zenContentsVisible = true; this.log(`Moving active tab ${tab.id} to most recent window on close`); - this.#swapBrowserDocSheellsInner(targetTab, tab, targetTab.selected, /* onClose =*/ true); + this.#swapBrowserDocSheellsInner(targetTab, tab, { + focus: targetTab.selected, + onClose: true, + }); + targetTab._zenContentsVisible = true; // We can animate later, whats important is to always stay on the same // process and avoid async operations here to avoid the closed window // being unloaded before the swap is done. @@ -950,7 +949,6 @@ class nsZenWindowSync { SYNC_FLAG_ICON | SYNC_FLAG_LABEL | SYNC_FLAG_MOVE ); }); - this.#makeSureTabSyncsPermanentKey(tab); lazy.TabStateFlusher.flush(tab.linkedBrowser); }