diff --git a/src/zen/common/sys/ui/ZenSpaceRoutingNavigation.sys.mjs b/src/zen/common/sys/ui/ZenSpaceRoutingNavigation.sys.mjs index ce0fef437..8715d07c4 100644 --- a/src/zen/common/sys/ui/ZenSpaceRoutingNavigation.sys.mjs +++ b/src/zen/common/sys/ui/ZenSpaceRoutingNavigation.sys.mjs @@ -35,13 +35,6 @@ export class ZenSpaceRoutingNavigation extends ZenUIComponent { return; } - // The tab we spawn for a route must be allowed to load once without being - // redirected again, regardless of when its workspace attribute lands. - if (aBrowser._zenSkipNavRouteOnce) { - aBrowser._zenSkipNavRouteOnce = false; - return; - } - let uri; try { uri = aRequest.QueryInterface(Ci.nsIChannel).URI; @@ -80,16 +73,49 @@ export class ZenSpaceRoutingNavigation extends ZenUIComponent { } const currentWorkspaceId = tab.getAttribute("zen-workspace-id"); - if ( - !win.gZenSpaceRoutingManager.shouldRedirectNavigation( + const targetWorkspaceId = + win.gZenSpaceRoutingManager.getRedirectTargetWorkspaceId( uri.spec, currentWorkspaceId, win - ) - ) { + ); + if (!targetWorkspaceId) { return; } + // A brand-new tab whose very first real navigation this is (a + // target="_blank" link, window.open(), or a freshly opened tab) is still + // showing its initial about:blank document. There is nothing to preserve, + // so rather than cancelling the load and spawning a duplicate tab - which + // would leave this one behind empty - just move this very tab into the + // destination space and let the in-flight load finish in place. + const isInitialDocument = + aBrowser.browsingContext?.currentWindowGlobal?.isInitialDocument ?? false; + if (isInitialDocument) { + const wasSelected = tab.selected; + // Defer so we don't mutate the tab strip from inside a progress notification. + win.setTimeout(() => { + if (!tab.isConnected) { + return; + } + gBrowser.selectedTab = tab.owner; + win.gZenWorkspaces.moveTabToWorkspace(tab, targetWorkspaceId); + if (wasSelected) { + const targetWorkspace = + win.gZenWorkspaces.getWorkspaceFromId(targetWorkspaceId); + if (targetWorkspace) { + win.gZenWorkspaces.lastSelectedWorkspaceTabs[targetWorkspaceId] = + tab; + win.gZenWorkspaces.changeWorkspace(targetWorkspace); + } + } + }, 0); + return; + } + + // An already-loaded page is navigating in place. Preserve it in its current + // tab and re-open the destination in a new routed tab instead. + // // Under Fission the parent-side aRequest is a RemoteWebProgress stand-in // whose cancel()/loadInfo throw NS_ERROR_NOT_IMPLEMENTED (the real channel // lives in the content process). Stop the in-place load through the browser, @@ -111,13 +137,14 @@ export class ZenSpaceRoutingNavigation extends ZenUIComponent { // Defer so we don't mutate the tab strip from inside a progress notification. win.setTimeout(() => { - const newTab = gBrowser.addTab(urlToOpen, { + gBrowser.addTab(urlToOpen, { triggeringPrincipal: principal, ownerTab: tab.isConnected ? tab : null, + // The user was actively navigating this tab, so follow the navigation + // into the routed tab instead of opening it in the background (addTab + // defaults inBackground to true). + inBackground: false, }); - if (newTab?.linkedBrowser) { - newTab.linkedBrowser._zenSkipNavRouteOnce = true; - } }, 0); } } diff --git a/src/zen/space-routing/ZenSpaceRoutingManager.sys.mjs b/src/zen/space-routing/ZenSpaceRoutingManager.sys.mjs index b7b40387f..41185019b 100644 --- a/src/zen/space-routing/ZenSpaceRoutingManager.sys.mjs +++ b/src/zen/space-routing/ZenSpaceRoutingManager.sys.mjs @@ -104,7 +104,7 @@ class nsZenSpaceRoutingManager { break; default: { const targetWorkspace = - win?.gZenWorkspaces?.getWorkspaceFromId(targetRoute); + win.gZenWorkspaces.getWorkspaceFromId(targetRoute); if (targetWorkspace) { userContextId = targetWorkspace.containerTabId; @@ -159,8 +159,26 @@ class nsZenSpaceRoutingManager { * @returns {boolean} True when the navigation should open in a new routed tab */ shouldRedirectNavigation(uriString, currentWorkspaceId, win) { + return !!this.getRedirectTargetWorkspaceId( + uriString, + currentWorkspaceId, + win + ); + } + + /** + * Resolves the destination space for an in-place top-level navigation, or + * null when the navigation should be left alone (no rule, the destination is + * "most-recent-space", the tab already lives there, or the space is gone). + * + * @param {string} uriString - The destination URI + * @param {string|null} currentWorkspaceId - The zen-workspace-id of the navigating tab + * @param {Window} win - The owning browser window + * @returns {string|null} The target workspace id, or null to leave the navigation in place + */ + getRedirectTargetWorkspaceId(uriString, currentWorkspaceId, win) { if (!win?.gZenWorkspaces?.workspaceEnabled) { - return false; + return null; } const targetRoute = this.routeUri(uriString, { fromExternal: false }); @@ -170,11 +188,13 @@ class nsZenSpaceRoutingManager { targetRoute === "most-recent-space" || targetRoute === currentWorkspaceId ) { - return false; + return null; } // Only redirect when the destination space actually exists. - return !!win.gZenWorkspaces.getWorkspaceFromId(targetRoute); + return win.gZenWorkspaces.getWorkspaceFromId(targetRoute) + ? targetRoute + : null; } /** diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs index cf7043809..51cfb89ba 100644 --- a/src/zen/spaces/ZenSpaceManager.mjs +++ b/src/zen/spaces/ZenSpaceManager.mjs @@ -2298,12 +2298,12 @@ class nsZenWorkspaces { } onBeforeTabSelect(aTab) { - if (this.#inChangingWorkspace) { + if (this.#inChangingWorkspace || !aTab) { // Just in case, Let's not do these checks while we are // in the middle of changing workspace, return false; } - const tabSpace = aTab?.getAttribute("zen-workspace-id"); + const tabSpace = aTab.getAttribute("zen-workspace-id"); if ( tabSpace && tabSpace !== this.activeWorkspace &&