[cherry-pick] Fix tab close button stuck showing "Close Others" after focus changes - #334829
Open
vs-code-engineering[bot] wants to merge 1 commit into
Open
[cherry-pick] Fix tab close button stuck showing "Close Others" after focus changes#334829vs-code-engineering[bot] wants to merge 1 commit into
vs-code-engineering[bot] wants to merge 1 commit into
Conversation
Contributor
Author
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: Benjamin Christopher Simmonds (@benibenj)Matched files:
|
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Test setup must reset existing modifier-key state to prevent order-dependent failures.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes stale Close Others tab actions after focus changes.
Changes:
- Tracks and revalidates local Alt/hover state.
- Clears stale state on focus loss.
- Adds regression tests and singleton lifecycle handling.
File summaries
| File | Description |
|---|---|
src/vs/workbench/test/browser/parts/editor/multiEditorTabsControl.test.ts |
Adds regression coverage, but setup does not reset pre-existing singleton Alt state, making tests order-dependent. |
src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts |
Prevents stale Alt-hover tab actions. |
src/vs/workbench/browser/parts/editor/editorTabsControl.ts |
Exposes the host service to subclasses. |
src/vs/base/browser/dom.ts |
Exempts singleton modifier subscriptions from leak detection. |
Review details
Suppressed comments (2)
src/vs/workbench/test/browser/parts/editor/multiEditorTabsControl.test.ts:151
- Resetting the shared emitter immediately after
setFocus(false)masks whether the new focus handler actually clears its local Alt state. This test still passes ifsetAltPressed(false)is removed: clearing the hovered index redraws the icon, then this reset updates the local state. Re-hover before resetting and assert that the action remains Close, then clear that synthetic hover before testing focus return.
hostService.setFocus(false);
ModifierKeyEmitter.getInstance().resetKeyStatus();
src/vs/workbench/test/browser/parts/editor/multiEditorTabsControl.test.ts:195
- This regression test stops at
mousedown, butBaseActionViewItemexecutes the action from itsclickhandler (src/vs/base/browser/ui/actionbar/actionViewItems.ts:152-158). It therefore verifies the icon swap but not the critical guarantee that the stale Close Others action cannot run. Exercise the complete click path and assert that no other editor was closed (or that only the target closes on the next click).
mouseDownOnTabAction(0, false);
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // The tabs control resolves the shared modifier key emitter on creation, | ||
| // so dispose it again to keep each test independent of the Alt state that | ||
| // other suites may have left behind | ||
| disposables.add(toDisposable(() => ModifierKeyEmitter.disposeInstance())); |
Dmitriy Vasyura (dmitrivMS)
approved these changes
Sep 6, 2026
Ulugbek Abdullaev (ulugbekna)
approved these changes
Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cherry-pick of #334515 from
main.Description
Alt+hover swaps a tab's Close action for Close Others. Both inputs to that swap could go stale across a focus change:
redrawTabAction()readmodifierKeyEmitter.keyStatus.altKeyat paint time (the Altkeyupis delivered to whichever app you Alt+Tab'd into), andhoveredTabIndexwas only cleared by a realmouseleave. Coming back to the window without moving the mouse could therefore leave thexarmed as "Close Others", so a plain click silently closed every other editor in the group.Changes —
MultiEditorTabsControlisAltPressedfield, fed by theModifierKeyEmitterevent, replaces the two on-demand reads ofmodifierKeyEmitter.keyStatus.altKey. It is the single source of truth for the swap and redraws the hovered tab when it flips.IHostService.onDidChangeFocus(false)clears both the Alt state and the hovered tab, so a hover left over from before the focus change cannot be re-armed by Alt state re-established on return. Re-hovering a tab arms it again as usual.mousemove/mousedownon the tabs container carry the real modifier state even when akeyupwas missed entirely — this covers macOS Mission Control, where the window may never see ablur.The mouse listeners are registered in the capture phase because
BaseActionViewItemcallsEventHelper.stop(e, true)onmousedown; a bubble-phase listener never sees a press on the close button itself, which is precisely the reported "click without moving the mouse" case.In the stale-Alt case the capture handler swaps the action item mid-
mousedown, so that first click is swallowed rather than executing "Close Others" — the icon is then correct and the next click closes the single tab.EditorTabsControl.hostServicebecomesprotectedso the subclass can use it.Tests
src/vs/workbench/test/browser/parts/editor/multiEditorTabsControl.test.tscovers the baseline swap plus the focus-loss, mouse-move and action-mousedown paths; each regression test was checked to fail with its corresponding part of the change reverted.Notes for reviewers
menuEntryActionViewItem.tshas the same latent staleness pattern; left untouched to keep this scoped to tabs.