From e7ddfb52dbfbc382c297cfe1c78544e062019c92 Mon Sep 17 00:00:00 2001 From: Nikola Hristov <139394063+iexavl@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:16:13 +0300 Subject: [PATCH] gh-15115: Fix creating empty split view with url bar open (gh-15116) --- src/zen/common/modules/ZenUIManager.mjs | 139 ++++++++++++++++-------- src/zen/split-view/ZenViewSplitter.mjs | 40 +++++-- 2 files changed, 122 insertions(+), 57 deletions(-) diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index d5e605771..dbb1bd2ca 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -493,6 +493,13 @@ window.gZenUIManager = { _clearTimeout: null, _lastTab: null, + _urlbarSessionCounter: 0, + _urlbarOwner: null, + + _isOwner(closeSeq) { + return this._urlbarOwner === closeSeq; + }, + // Check if browser elements are in a valid state for tab operations _validateBrowserState() { // Check if browser window is still open @@ -517,8 +524,15 @@ window.gZenUIManager = { werePassedURL, searchClipboard, where, - overridePreferance = false + overridePreferance = false, + closeToken = {} ) { + // Keep this here. It's better to make sure every + // call has a different token rather then forgetting + // to increment it in one of the early returns. + const closeSeq = ++this._urlbarSessionCounter; + closeToken.id = closeSeq; + // Validate browser state first if (!this._validateBrowserState()) { console.warn("Browser state invalid for new tab operation"); @@ -542,7 +556,13 @@ window.gZenUIManager = { // Close the new tab popup on cmd/ctrl + t if (!overridePreferance && gURLBar.hasAttribute("zen-newtab")) { - this.handleUrlbarClose(); + // The _zenHandleUrlbarClose holds a token in it's closure. + // Call that instead. + if (gURLBar._zenHandleUrlbarClose) { + gURLBar._zenHandleUrlbarClose(); + } else { + this.handleUrlbarClose(closeSeq); + } return true; } @@ -568,7 +588,9 @@ window.gZenUIManager = { this._prevUrlbarLabel = gURLBar._untrimmedValue || ""; // Set up URL bar for new tab - gURLBar._zenHandleUrlbarClose = this.handleUrlbarClose.bind(this); + gURLBar._zenHandleUrlbarClose = (onSwitch, onElementPicked) => + this.handleUrlbarClose(closeSeq, onSwitch, onElementPicked); + this._urlbarOwner = closeSeq; gURLBar.setAttribute("zen-newtab", true); // Update newtab buttons @@ -582,18 +604,33 @@ window.gZenUIManager = { document.getElementById("Browser:OpenLocation").doCommand(); } catch (e) { console.error("Error opening location in new tab:", e); - this.handleUrlbarClose(false); + this.handleUrlbarClose(closeSeq, false, false); return false; } return true; }, + /** + * Check whether the closeToken given by handleNewTab + * corresponds to a close event. + * + * @param {object} closeToken - Token previously passed to handleNewTab. + * @param {CustomEvent} event - A ZenURLBarClosed event. + * @returns {boolean} + */ + matchesCloseToken(closeToken, event) { + return ( + typeof closeToken?.id === "number" && + event?.detail?.closeSeq === closeToken.id + ); + }, + clearUrlbarData() { this._prevUrlbarLabel = null; this._lastSearch = ""; }, - handleUrlbarClose(onSwitch = false, onElementPicked = false) { + handleUrlbarClose(closeSeq, onSwitch = false, onElementPicked = false) { // Validate browser state first if (!this._validateBrowserState()) { console.warn("Browser state invalid for URL bar close operation"); @@ -607,59 +644,69 @@ window.gZenUIManager = { const isFocusedBefore = gURLBar.focused; setTimeout(() => { - // We use this attribute on Tabbrowser::addTab - gURLBar.removeAttribute("zen-newtab"); + /* If someone else opened the url bar in the meantime don't be + * stingy and leave it open for them.*/ + if (this._isOwner(closeSeq)) { + // We use this attribute on Tabbrowser::addTab + gURLBar.removeAttribute("zen-newtab"); - // Safely restore tab visual state with proper validation - if ( - this._lastTab && - !this._lastTab.closing && - this._lastTab.documentGlobal && - !this._lastTab.documentGlobal.closed && - gBrowser.selectedTab === this._lastTab - ) { - this._lastTab._visuallySelected = true; - this._lastTab = null; - } - - // Reset newtab buttons - for (const button of this.newtabButtons) { - button.removeAttribute("in-urlbar"); - } - - // Handle search data - if (onSwitch) { - this.clearUrlbarData(); - } else { - this._lastSearch = gURLBar._untrimmedValue || ""; - - if (this._clearTimeout) { - clearTimeout(this._clearTimeout); + // Safely restore tab visual state with proper validation + if ( + this._lastTab && + !this._lastTab.closing && + this._lastTab.documentGlobal && + !this._lastTab.documentGlobal.closed && + gBrowser.selectedTab === this._lastTab + ) { + this._lastTab._visuallySelected = true; + this._lastTab = null; } - this._clearTimeout = setTimeout(() => { + // Reset newtab buttons + for (const button of this.newtabButtons) { + button.removeAttribute("in-urlbar"); + } + + // Handle search data + if (onSwitch) { this.clearUrlbarData(); - }, this.urlbarWaitToClear); - } + } else { + this._lastSearch = gURLBar._untrimmedValue || ""; - // Safely restore URL bar state with proper validation - if (this._prevUrlbarLabel) { - gURLBar.setURI({ - uri: this._prevUrlbarLabel, - dueToTabSwitch: onSwitch, - isSameDocument: !onSwitch, - }); - } + if (this._clearTimeout) { + clearTimeout(this._clearTimeout); + } - gURLBar.handleRevert(); + this._clearTimeout = setTimeout(() => { + this.clearUrlbarData(); + }, this.urlbarWaitToClear); + } + + // Safely restore URL bar state with proper validation + if (this._prevUrlbarLabel) { + gURLBar.setURI({ + uri: this._prevUrlbarLabel, + dueToTabSwitch: onSwitch, + isSameDocument: !onSwitch, + }); + } + + gURLBar.handleRevert(); + } if (isFocusedBefore) { setTimeout(() => { window.dispatchEvent( new CustomEvent("ZenURLBarClosed", { - detail: { onSwitch, onElementPicked }, + detail: { onSwitch, onElementPicked, closeSeq }, }) ); + + if (!this._isOwner(closeSeq)) { + return; + } + this._urlbarOwner = null; + gURLBar.view.close({ elementPicked: onElementPicked }); gURLBar.updateTextOverflow(); @@ -683,6 +730,8 @@ window.gZenUIManager = { } } }, 0); + } else if (this._isOwner(closeSeq)) { + this._urlbarOwner = null; } }, 0); }, diff --git a/src/zen/split-view/ZenViewSplitter.mjs b/src/zen/split-view/ZenViewSplitter.mjs index c2cd59e49..e988bb5d2 100644 --- a/src/zen/split-view/ZenViewSplitter.mjs +++ b/src/zen/split-view/ZenViewSplitter.mjs @@ -2530,22 +2530,33 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { this.activateSplitView(data); gBrowser.selectedTab = emptyTab; setTimeout(() => { + const closeToken = {}; + const controller = new AbortController(); + const cleanup = (onSwitch = false, groupIndex = null) => { + if (groupIndex === null) { + groupIndex = this._data.findIndex(group => + group.tabs.includes(emptyTab) + ); + } + this.removeTabFromGroup(emptyTab, groupIndex, { + changeTab: !onSwitch, + forUnsplit: true, + }); + const command = document.getElementById("cmd_zenNewEmptySplit"); + command.removeAttribute("disabled"); + }; window.addEventListener( "ZenURLBarClosed", event => { + if (!gZenUIManager.matchesCloseToken(closeToken, event)) { + return; + } + controller.abort(); const { onElementPicked, onSwitch } = event.detail; const groupIndex = this._data.findIndex(group => group.tabs.includes(emptyTab) ); const newSelectedTab = gBrowser.selectedTab; - const cleanup = () => { - this.removeTabFromGroup(emptyTab, groupIndex, { - changeTab: !onSwitch, - forUnsplit: true, - }); - const command = document.getElementById("cmd_zenNewEmptySplit"); - command.removeAttribute("disabled"); - }; if (onElementPicked) { if ( newSelectedTab === emptyTab || @@ -2553,7 +2564,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { selectedTab.getAttribute("zen-workspace-id") !== newSelectedTab.getAttribute("zen-workspace-id") ) { - cleanup(); + cleanup(onSwitch, groupIndex); return; } this.removeTabFromGroup(emptyTab, groupIndex, { @@ -2569,12 +2580,17 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { topOrLeft ? 0 : 1 ); } else { - cleanup(); + cleanup(onSwitch, groupIndex); } }, - { once: true } + { signal: controller.signal } ); - gZenUIManager.handleNewTab(false, false, "tab", true); + if ( + !gZenUIManager.handleNewTab(false, false, "tab", true, closeToken) + ) { + controller.abort(); + cleanup(); + } }); }); }