Repository navigation
cmux NEXT browser: a page under a hidden ancestor takes no focus (focus loop hang) - #27873
Conversation
…cus loop beachball) Red on the base: the grant runs while an ancestor hides the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing a focus loop that hung the app A workspace switch hid an ancestor of the focused Chromium page's view. Chromium ordered the page window out inside AppKit's hide walk, before the page's own viewDidHide concealed it. The cmux window took the keys, ChildPageFocus.keysReturned re-sent the target, and CEFTab.setFocused still saw the page as shown (visibleTab, not occluded), so it granted focus, which showed the page window again. The cycle repeated inside one FocusCoordinator drain forever (NIGHTLY 1.0.0-nightly.4093446036501, ~57 key changes a second, beachball). CEFTab's shown check now also requires its view in a window under no hidden ancestor. The ask stays pending and the existing contentDidAppear -> grantPendingFocusIfShown path grants it when the view shows again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le an ancestor hides Review note: pins the real topology (Chromium's view under the page container). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Correctness review (independent subagent, read-only) — head 3c9a7b7 Verdict: no blocking findings.
|
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
…-walk flag The fleet run showed isHiddenOrHasHiddenAncestor still false for the page's container while AppKit's hide walk had not reached it (a view hidden earlier in the walk asked for focus): the sibling test stayed red. Each ancestor's isHidden is set before the walk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Correctness re-review (independent subagent, read-only) — head 831f713 Verdict: SHIP. No blocking findings.
Tests at this head (side branch carrying #27862 compile fix): CEFDeferredFocusTests ran 5 tests and ChildPageFocusKeyChangeTests ran 11, all passing (cmux-ci step |
|
Shipped in:
|
Fixes #27869
What
cmux NEXT nightly
1.0.0-nightly.4093446036501hung forever after a workspace switch while a Chromium page had the keyboard. The main thread ran at ~95% CPU inside a single display-link callback and never recovered.The loop:
CEFTabContentView.viewDidHideconceals the page.ChildPageFocus.keysReturnedre-sends the focus target.CEFTab.setFocused(true)still sees the page as shown (visibleTab === self && !isOccluded). It callscmux_shim_set_focus, which orders the page window front again.FocusCoordinator.senddrain. AppKit logged about 57 page-window key changes a second.Fix
CEFTabnow has oneisShowncheck, used bysetFocused,grantPendingFocusIfShownandattach. It also requires the page's container to be in a window with no hidden ancestor. That check reads each ancestor's ownisHidden, notisHiddenOrHasHiddenAncestor, because AppKit updates that flag view by view during the hide walk. A focus request made in the middle of a hide becomespendingFocus, and the existingviewDidUnhide→contentDidAppear→present→grantPendingFocusIfShownpath grants it when the page shows again. Outside a hide walk,visibleTab === selfalready implies both new conditions, so behavior elsewhere doesn't change.Tests
CEFDeferredFocusTestsgains two behavior tests at theCEFTabboundary, using the existing stand-in shim:Each test expects no grant while hidden and exactly one grant on unhide.
Command:
cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=CEFDeferredFocusTests,ChildPageFocusKeyChangeTestsThe base has a test-target compile red (
RemoteViewVNCTests.swift:130, fixed by #27862). The fleet runs therefore use side branches that carry that one-file fix:side/focusloop-red(the test commit) andside/focusloop-green(the head).Observed / Not run
sampleof the hung process, AppKit unified logs, and the onset time.side/focusloop-red@4c657921, step8605113ebede759c818778d8):focusAskedWhileAnAncestorHidesIsGrantedWhenItShowsAgainfailed,(FocusSpyShim.granted → [10]).isEmpty → false.isHiddenOrHasHiddenAncestor, step637312fcef3be5b699603669): the from-inside test passed but the sibling test stayed red. That led to the ancestor walk.831f7135(side/focusloop-green@e582a896, step46d97b373ffe4f6f779938a7): CEFDeferredFocusTests ran 5 tests and ChildPageFocusKeyChangeTests ran 11, all passing.Follow-up (not in this PR)
FocusCoordinator.send's drain has no bound, andkeysReturnedcan re-send for the same page more than once in one drain. Another cause of "Chromium hides a page window we think is shown" would loop the same way.Changelog
Fixed: cmux NEXT no longer hangs when you switch workspaces while a browser page has the keyboard.
🤖 Generated with Claude Code