Skip to content

gh-12740: remove deleted workspace tabs from switch-to-tab#12794

Merged
mr-cheffy merged 8 commits intozen-browser:devfrom
Vedaant-Rajoo:fix-issue-12740
Mar 22, 2026
Merged

gh-12740: remove deleted workspace tabs from switch-to-tab#12794
mr-cheffy merged 8 commits intozen-browser:devfrom
Vedaant-Rajoo:fix-issue-12740

Conversation

@Vedaant-Rajoo
Copy link
Contributor

Summary

Fixes #12740 by removing workspace-owned tabs through the normal tab close path when a
workspace is deleted.

Previously, deleting a workspace only removed the workspace metadata/UI. Tabs that
still belonged to that workspace could remain alive in the browser tab model, which
allowed stale Switch Tab results to continue appearing in the urlbar.

What changed

  • close workspace-owned tabs before removing workspace metadata
  • include both regular and pinned workspace tabs
  • exclude essentials and empty tabs from workspace deletion
  • clear Zen's stored tab cache after deletion
  • add/update workspace tests around deletion and unload behavior

Why this approach

The stale urlbar result was a symptom of the tabs not actually being closed. The safest
fix is to delete those tabs through the normal gBrowser.removeTabs(...) path so the
usual browser and Zen cleanup runs, instead of trying to manually refresh urlbar state
afterward.

Notes

  • essentials are intentionally not deleted because they are not workspace-owned
  • the delete regression test passes
  • while working on this, some older unload tests also needed cleanup because they were
    creating unrealistic workspace state by mutating attributes directly

Short maintainer note:
The core reasoning here is that Switch Tab was not primarily a urlbar caching bug. The
deleted workspace tabs were surviving the workspace deletion flow, so the correct fix
point was the workspace delete path itself. Closing those tabs normally is lower risk
than adding special-case invalidation around urlbar providers.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 17, 2026
@dosubot dosubot bot added the Bug label Mar 17, 2026
workspace => workspace.uuid !== windowID
);
this.#propagateWorkspaceData(workspacesData);
this._allStoredTabs = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 1271 is not redundant in all cases
the redundant one is more likely the clear inside #deleteWorkspaceOwnedTabs(), not the one in removeWorkspace()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are some cases where i found it useful:

  • The workspace is deleted but it has no workspace-owned tabs left.
    In that case #deleteWorkspaceOwnedTabs() may do nothing, so no tab-close-driven cache invalidation happens. But allStoredTabs is still stale because the workspace container set changed.

  • The workspace only had tabs that are excluded from deletion logic.
    Example: no normal workspace-owned tabs, only essentials/empty-tab edge cases. The workspace still disappears from the workspace UI/container model, so the cached tab walk can still be stale.

  • You move tab deletion out of removeWorkspace() or it happens later/asynchronously.
    Then line 1271 becomes the only immediate invalidation at the time workspace data is changed.

Why this matters:

  • allStoredTabs is built by walking workspace containers, not just gBrowser.tabs.
  • removeWorkspace() changes workspace data and leads to workspace container removal.
  • If no lower-level tab invalidation fires, the memoized cache can point at the old container-derived state.

So:

  • if real tab removal definitely happens first, line 1271 may be redundant
  • if workspace metadata/UI can change without guaranteed tab-removal invalidation, line 1271 is still useful

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use delete then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, better to use gBrowser.tabContainer._invalidateCachedVisibleTabs();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tracked down why this._allStoredTabs = null originally looked like the fix.

What made it confusing is that there are actually two delete paths with different behavior:

  • deleting the currently active workspace
  • deleting a different workspace while staying in the current one

Deleting the active workspace worked because the removal code was using gBrowser.tabs, and in Zen that effectively reflects the active workspace strip. So the tabs from the workspace being deleted were present in that enumeration and got removed.

Deleting a different workspace failed because its tabs were not part of that same gBrowser.tabs view, so #deleteWorkspaceOwnedTabs() could miss them entirely. That is why stale Switch to Tab entries remained.

I initially thought this._allStoredTabs = null was the right fix because clearing that cache made the stale results disappear in practice. After tracing it further, the real issue was not the open-tabs/urlbar unregister path, and not moz_openpages_temp either; those were being cleaned up correctly on workspace deletion. The actual problem was that the delete path was enumerating tabs from an active-workspace-scoped source.

So the fix here is to use allStoredTabs for workspace-owned tab deletion, since that walks tabs across workspace containers instead of only what gBrowser.tabs currently exposes for the active space. I also updated the test to cover deleting a workspace from another workspace, since that was the failing branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #12862

workspace => workspace.uuid !== windowID
);
this.#propagateWorkspaceData(workspacesData);
this._allStoredTabs = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use delete then

removeNonWorkspaceTabs();
});

add_task(async function test_DeleteWorkspace_RemovesWorkspaceOwnedTabs() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is a bit useless, i'd just check if TabRemove event is being fired for every tab and TabGroupRemoved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the test to cover the actual removal lifecycle instead of only checking the final tab state. It now waits for the relevant TabClose events and TabGroupRemoved, then asserts the workspace-owned tabs are gone. The updated coverage is in browser_workspace_unload.js.

tab =>
tab.getAttribute("zen-workspace-id") === workspaceID &&
!tab.hasAttribute("zen-essential") &&
!tab.hasAttribute("zen-empty-tab")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!tab.hasAttribute("zen-empty-tab") not needed here AFAIK. At least i'd do (!tab.hasAttribute("zen-empty-tab") && !tab.group) so it also removes empty tabs of the folders and therefor, remove the folder its self

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this by handling workspace folders explicitly during workspace deletion instead of trying to fold zen-empty-tab handling into the normal tab filter. removeWorkspace() now deletes workspace folders first, which also clears the folder empty-tab path, and then removes the remaining workspace-owned tabs. I also extended the test to cover a folder case in browser_workspace_unload.js. The deletion change is in ZenSpaceManager.mjs.

Copy link
Contributor Author

@Vedaant-Rajoo Vedaant-Rajoo Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

I verified the workspace-deletion change in isolation and the updated deletion test passes.

I still couldn’t get a clean full-file run, but this does not look introduced by this PR: the same file already fails on a full origin/dev checkout, and it fails more broadly there. The main issue is unload tests not fully unloading tabs (pending stays false / linkedPanel still exists), which then leaves unexpected tabs behind at the end of the run.

Because of that, I used a temporary local .only() only to confirm the new deletion behavior independently, then removed it afterward.

workspace => workspace.uuid !== windowID
);
this.#propagateWorkspaceData(workspacesData);
this._allStoredTabs = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, better to use gBrowser.tabContainer._invalidateCachedVisibleTabs();

…kspace folders and improve workspace switching tests
@mr-cheffy mr-cheffy linked an issue Mar 21, 2026 that may be closed by this pull request
5 tasks
@mr-cheffy mr-cheffy changed the title 12740: remove deleted workspace tabs from switch-to-tab gh-12740: remove deleted workspace tabs from switch-to-tab Mar 21, 2026
@mr-cheffy mr-cheffy requested review from Copilot and mr-cheffy March 21, 2026 17:00

This comment was marked as resolved.

Vedaant-Rajoo and others added 2 commits March 21, 2026 15:36
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Vedaant Rajoo <vedaant12345@gmail.com>
@mr-cheffy
Copy link
Member

You haven't ran the tests you created, have you?

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Mar 22, 2026

This comment was marked as resolved.

@mr-cheffy mr-cheffy merged commit e80c16a into zen-browser:dev Mar 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Switch Tab" persists after deleting a space Tabs are lost then a workspace deletion is synced

3 participants