From 22f2b1f29b85287e6a3011e3005408fce7f85685 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Wed, 3 Dec 2025 17:57:46 +0100 Subject: [PATCH] feat: Early support for unsynced windoiws, b=no-bug, c=workspaces --- .../tabbrowser/content/tabbrowser-js.patch | 40 ++++++++++++------- .../base/BrowsingContextWebProgress-cpp.patch | 22 ++++++++++ .../sessionstore/ZenSessionManager.sys.mjs | 8 +++- src/zen/sessionstore/ZenWindowSync.sys.mjs | 33 +++++++++++++-- src/zen/workspaces/ZenWorkspaces.mjs | 29 +++++++++----- 5 files changed, 101 insertions(+), 31 deletions(-) create mode 100644 src/docshell/base/BrowsingContextWebProgress-cpp.patch diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch index a38f00130..69592a03b 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 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186f0d2bce5 100644 +index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..a932324e780e3c86c504c8b7aa8d433b04ef78d6 100644 --- a/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js @@ -386,6 +386,7 @@ @@ -715,7 +715,17 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 if (this.tabs.length == 1) { return null; } -@@ -6319,7 +6470,7 @@ +@@ -6213,7 +6364,8 @@ + AppConstants.BROWSER_CHROME_URL, + "_blank", + options, +- aTab ++ aTab, ++ "zen-unsynced" + ); + } + +@@ -6319,7 +6471,7 @@ * `true` if element is a `` */ isTabGroup(element) { @@ -724,7 +734,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 } /** -@@ -6404,8 +6555,8 @@ +@@ -6404,8 +6556,8 @@ } // Don't allow mixing pinned and unpinned tabs. @@ -735,7 +745,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 } else { tabIndex = Math.max(tabIndex, this.pinnedTabCount); } -@@ -6431,10 +6582,16 @@ +@@ -6431,10 +6583,16 @@ this.#handleTabMove( element, () => { @@ -754,7 +764,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 if (neighbor && this.isTab(element) && tabIndex > element._tPos) { neighbor.after(element); } else { -@@ -6492,23 +6649,28 @@ +@@ -6492,23 +6650,28 @@ #moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) { if (this.isTabGroupLabel(targetElement)) { targetElement = targetElement.group; @@ -789,7 +799,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 } 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 -@@ -6521,14 +6683,34 @@ +@@ -6521,14 +6684,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. @@ -825,7 +835,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 element.pinned ? this.tabContainer.pinnedTabsContainer : this.tabContainer; -@@ -6537,7 +6719,7 @@ +@@ -6537,7 +6720,7 @@ element, () => { if (moveBefore) { @@ -834,7 +844,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 } else if (targetElement) { targetElement.after(element); } else { -@@ -6607,10 +6789,10 @@ +@@ -6607,10 +6790,10 @@ * @param {TabMetricsContext} [metricsContext] */ moveTabToGroup(aTab, aGroup, metricsContext) { @@ -847,7 +857,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 return; } if (aTab.group && aTab.group.id === aGroup.id) { -@@ -6656,6 +6838,7 @@ +@@ -6656,6 +6839,7 @@ let state = { tabIndex: tab._tPos, @@ -855,7 +865,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 }; if (tab.visible) { state.elementIndex = tab.elementIndex; -@@ -6682,7 +6865,7 @@ +@@ -6682,7 +6866,7 @@ let changedTabGroup = previousTabState.tabGroupId != currentTabState.tabGroupId; @@ -864,7 +874,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 tab.dispatchEvent( new CustomEvent("TabMove", { bubbles: true, -@@ -6723,6 +6906,10 @@ +@@ -6723,6 +6907,10 @@ moveActionCallback(); @@ -875,7 +885,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 // Clear tabs cache after moving nodes because the order of tabs may have // changed. this.tabContainer._invalidateCachedTabs(); -@@ -7623,7 +7810,7 @@ +@@ -7623,7 +7811,7 @@ // preventDefault(). It will still raise the window if appropriate. break; } @@ -884,7 +894,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 window.focus(); aEvent.preventDefault(); break; -@@ -7640,7 +7827,6 @@ +@@ -7640,7 +7828,6 @@ } case "TabGroupCollapse": aEvent.target.tabs.forEach(tab => { @@ -892,7 +902,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 }); break; case "TabGroupCreateByUser": -@@ -8589,6 +8775,7 @@ +@@ -8589,6 +8776,7 @@ aWebProgress.isTopLevel ) { this.mTab.setAttribute("busy", "true"); @@ -900,7 +910,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..3acfbc6d434340f0a629eec7edd9e186 gBrowser._tabAttrModified(this.mTab, ["busy"]); this.mTab._notselectedsinceload = !this.mTab.selected; } -@@ -9623,7 +9810,7 @@ var TabContextMenu = { +@@ -9623,7 +9811,7 @@ var TabContextMenu = { ); contextUnpinSelectedTabs.hidden = !this.contextTab.pinned || !this.multiselected; diff --git a/src/docshell/base/BrowsingContextWebProgress-cpp.patch b/src/docshell/base/BrowsingContextWebProgress-cpp.patch new file mode 100644 index 000000000..9ec052d94 --- /dev/null +++ b/src/docshell/base/BrowsingContextWebProgress-cpp.patch @@ -0,0 +1,22 @@ +diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp +index cc5bf6de7bf4875acf0c572549dc30ff06ac7f6f..4e0f3648f551fe9507a654d19f0a99e455418aa9 100644 +--- a/docshell/base/BrowsingContextWebProgress.cpp ++++ b/docshell/base/BrowsingContextWebProgress.cpp +@@ -54,7 +54,7 @@ NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener( + + if (mListenerInfoList.Contains(listener)) { + // The listener is already registered! +- return NS_ERROR_FAILURE; ++ return NS_OK; + } + + mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask)); +@@ -68,7 +68,7 @@ NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener( + return NS_ERROR_INVALID_ARG; + } + +- return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; ++ return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_OK; + } + + NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM( diff --git a/src/zen/sessionstore/ZenSessionManager.sys.mjs b/src/zen/sessionstore/ZenSessionManager.sys.mjs index 2a61ebb72..84570e465 100644 --- a/src/zen/sessionstore/ZenSessionManager.sys.mjs +++ b/src/zen/sessionstore/ZenSessionManager.sys.mjs @@ -145,13 +145,19 @@ class nsZenSessionManager { } restoreNewWindow(aWindow, SessionStoreInternal) { + if (aWindow.gZenWorkspaces?.privateWindowOrDisabled) { + return; + } lazy.SessionSaver.run().then(() => { const state = lazy.SessionStore.getCurrentState(true); const windows = state.windows || {}; let newWindow = Cu.cloneInto(windows[0], {}); delete newWindow.selected; const newState = { windows: [newWindow] }; - //SessionStoreInternal.restoreWindows(aWindow, newState, {}); + aWindow._zenRestorePromise = new Promise((resolve) => { + SessionStoreInternal.restoreWindows(aWindow, newState, {}); + resolve(); + }); }); } } diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index bfe9f384a..fff488df0 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -80,7 +80,12 @@ class nsZenWindowSync { #browserWindows = { *[Symbol.iterator]() { for (let window of lazy.BrowserWindowTracker.orderedWindows) { - if (window.__SSi && !window.closed && window.gZenStartup.isReady) { + if ( + window.__SSi && + !window.closed && + window.gZenStartup.isReady && + !window.gZenWorkspaces?.privateWindowOrDisabled + ) { yield window; } } @@ -109,9 +114,23 @@ class nsZenWindowSync { * @param {Window} aWindow - The browser window that is about to be shown. */ #onWindowBeforeShow(aWindow) { + // There are 2 possibilities to know if we are trying to open + // a new *unsynced* window: + // 1. We are passing `zen-unsynced` in the window arguments. + // 2. We are trying to open a link in a new window where other synced + // windows already exist + if ( + aWindow.arguments.some((arg) => arg === 'zen-unsynced') || + (typeof aWindow.arguments[0] === 'string' && + aWindow.arguments.length > 1 && + [...this.#browserWindows].length > 0) + ) { + aWindow.document.documentElement.setAttribute('zen-unsynced-window', 'true'); + return; + } aWindow.gZenWindowSync = this; for (let eventName of EVENTS) { - aWindow.addEventListener(eventName, this); + aWindow.addEventListener(eventName, this, true); } } @@ -174,7 +193,7 @@ class nsZenWindowSync { handleEvent(aEvent) { const window = aEvent.currentTarget.ownerGlobal; - if (!window.gZenStartup.isReady) { + if (!window.gZenStartup.isReady || window.gZenWorkspaces?.privateWindowOrDisabled) { return; } if (this.#eventHandlingContext.window && this.#eventHandlingContext.window !== window) { @@ -619,7 +638,13 @@ class nsZenWindowSync { this.onTabSwitchOrWindowFocus(aEvent.target.ownerGlobal, previousTab); } - on_unload() {} + on_unload(aEvent) { + const window = aEvent.target.ownerGlobal; + for (let eventName of EVENTS) { + window.removeEventListener(eventName, this); + } + delete window.gZenWindowSync; + } on_TabGroupCreate(aEvent) { const tabGroup = aEvent.target; diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 83507f6da..a66d4c30a 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -55,6 +55,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { this.promiseDBInitialized, this.promisePinnedInitialized, SessionStore.promiseAllWindowsRestored, + window._zenRestorePromise, ]); } @@ -835,8 +836,12 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { return PrivateBrowsingUtils.isWindowPrivate(window); } + get currentWindowIsSyncing() { + return !document.documentElement.hasAttribute('zen-unsynced-window') && !this.isPrivateWindow; + } + get privateWindowOrDisabled() { - return this.isPrivateWindow || !this.shouldHaveWorkspaces; + return !this.shouldHaveWorkspaces || !this.currentWindowIsSyncing; } get workspaceEnabled() { @@ -865,12 +870,12 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { return this._workspaceCache; } - if (this.isPrivateWindow) { + if (!this.currentWindowIsSyncing) { this._workspaceCache = { - workspaces: this._privateWorkspace ? [this._privateWorkspace] : [], + workspaces: this._tempWorkspace ? [this._tempWorkspace] : [], lastChangeTimestamp: 0, }; - this._activeWorkspace = this._privateWorkspace?.uuid; + this._activeWorkspace = this._tempWorkspace?.uuid; return this._workspaceCache; } @@ -1111,7 +1116,9 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { shouldCloseWindow() { return ( - !window.toolbar.visible || Services.prefs.getBoolPref('browser.tabs.closeWindowWithLastTab') + !window.toolbar.visible || + Services.prefs.getBoolPref('browser.tabs.closeWindowWithLastTab') || + this.privateWindowOrDisabled ); } @@ -1388,7 +1395,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { } async _propagateWorkspaceData({ ignoreStrip = false, clearCache = true, onInit = false } = {}) { - const currentWindowIsPrivate = this.isPrivateWindow; + const currentWindowIsPrivate = !this.currentWindowIsSyncing; if (onInit) { if (currentWindowIsPrivate) return; return await this._propagateWorkspaceDataForWindow(this.ownerWindow, { @@ -1401,7 +1408,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { // For example, when the window is in private browsing mode. if ( !browser.gZenWorkspaces.workspaceEnabled || - browser.gZenWorkspaces.isPrivateWindow !== currentWindowIsPrivate + !browser.gZenWorkspaces.currentWindowIsSyncing !== currentWindowIsPrivate ) { return; } @@ -2450,8 +2457,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { if (!this.workspaceEnabled) { return; } - if (this.isPrivateWindow) { - name = 'Private ' + name; + if (!this.currentWindowIsSyncing) { + name = this.isPrivateWindow ? 'Private ' + name : gZenUIManager.generateUuidv4(); } // get extra tabs remaning (e.g. on new profiles) and just move them to the new workspace const extraTabs = Array.from(gBrowser.tabContainer.arrowScrollbox.children).filter( @@ -2468,8 +2475,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature { !dontChange, containerTabId ); - if (this.isPrivateWindow) { - this._privateWorkspace = workspaceData; + if (!this.currentWindowIsSyncing) { + this._tempWorkspace = workspaceData; } else { await this.saveWorkspace(workspaceData, dontChange); }