Skip to content

fix: don't rebuild tab model on EDITORS_SELECTION event#317227

Open
yogeshwaran-c wants to merge 1 commit into
microsoft:mainfrom
yogeshwaran-c:fix/tab-list-webview-corruption-228270
Open

fix: don't rebuild tab model on EDITORS_SELECTION event#317227
yogeshwaran-c wants to merge 1 commit into
microsoft:mainfrom
yogeshwaran-c:fix/tab-list-webview-corruption-228270

Conversation

@yogeshwaran-c
Copy link
Copy Markdown
Contributor

Summary

Fixes #228270vscode.window.tabGroups.close(tab) throws Tab close: Invalid tab not found! after an extension opens a webview in the same view column as the stored tab.

Root cause

bpasero's bisect on the issue points at 88bc75f (Tabs Multi Select, #211712), which introduced a new GroupModelChangeKind.EDITORS_SELECTION event that the editor group model fires whenever the selected editors change (e.g. as a side effect of setSelection on EDITOR_OPEN and EDITOR_ACTIVE).

The switch in MainThreadEditorTabs._updateTabsModel doesn't have a case for EDITORS_SELECTION, so it falls through to default, which calls _createTabsModel() and sends a full $acceptEditorTabModel to the extension host. ExtHostEditorTabs.$acceptEditorTabModel rebuilds every ExtHostEditorTabGroup / ExtHostEditorTab from scratch, so any vscode.Tab API object an extension is still holding becomes a stale reference.

_findExtHostTabFromApi then can't match the extension's tab via tab.apiObject === apiTab and _closeTabs throws Tab close: Invalid tab not found!.

The issue's repro is:

  1. Extension stores tabGroups.activeTabGroup.activeTab.
  2. Extension calls createWebviewPanel(..., tab.group.viewColumn)EDITOR_OPEN (handled) followed by EDITORS_SELECTION (falls into default → full rebuild → tab reference invalidated).
  3. Extension awaits any async hop (e.g. webview.postMessage), then calls tabGroups.close(tab) → throws.

Fix

Handle EDITORS_SELECTION as a no-op in the switch. Multi-select is a workbench-internal concept and isn't exposed via the tabs API — the same treatment we already give EDITOR_TRANSIENT. The handled events (EDITOR_OPEN, EDITOR_CLOSE, EDITOR_ACTIVE, etc.) already deliver the surface that's actually exposed through vscode.window.tabGroups, so suppressing the rebuild has no observable effect on the API other than preserving reference identity.

Test plan

  • Build with the change against main.
  • Manual repro from the issue:
    • Install a dev extension whose command runs:
      const tab = vscode.window.tabGroups.activeTabGroup.activeTab;
      const panel = vscode.window.createWebviewPanel('TestPanel', 'Test', tab.group.viewColumn);
      panel.webview.html = '<body>Hello</body>';
      await panel.webview.postMessage('hello');
      vscode.window.tabGroups.close(tab);
    • Before fix: throws Tab close: Invalid tab not found! in the debug console.
    • After fix: original tab closes cleanly, no onDidChangeTabs event lost.
  • Existing ExtHostEditorTabs suite still passes (no changes to extHost side, model rebuild path is unchanged for events that should rebuild).

Regression introduced by 88bc75f.

When the multi-select selection state of editors changes, the editor
group model fires an EDITORS_SELECTION event. The switch in
MainThreadEditorTabs._updateTabsModel did not handle this kind, so it
fell through to the default branch and called _createTabsModel(),
rebuilding the entire tab model.

A full rebuild sends $acceptEditorTabModel to the extension host,
which recreates every ExtHostEditorTabGroup and ExtHostEditorTab
instance. Any vscode.Tab reference an extension is still holding
becomes stale: _findExtHostTabFromApi compares apiObject by reference,
so subsequent calls like tabGroups.close(tab) throw
'Tab close: Invalid tab not found!'.

Reproduction (from issue): an extension stores the currently active
tab, opens a webview in the same view column, awaits an async hop
(e.g. webview.postMessage), then calls tabGroups.close on the stored
tab. The webview open triggers EDITOR_OPEN followed by
EDITORS_SELECTION; the latter caused the rebuild between the
extension capturing the tab reference and using it.

Multi-select state is workbench-internal and not exposed in the tabs
API, so this event can be treated as a no-op (same as EDITOR_TRANSIENT
which is also not surfaced through the API). Regression introduced by
88bc75f (Tabs Multi Select, microsoft#211712).

Fixes microsoft#228270
Copilot AI review requested due to automatic review settings May 19, 2026 01:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a tabs API regression where workbench-internal editor multi-selection changes caused the extension-host tab model to be rebuilt, invalidating existing vscode.Tab references held by extensions.

Changes:

  • Treats GroupModelChangeKind.EDITORS_SELECTION as a no-op in MainThreadEditorTabs.
  • Prevents unnecessary full tab model rebuilds for selection state that is not exposed through the tabs API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tab list gets corrupted when a new webview is opened (extension API)

3 participants