fix(focus): a creation takes the keys from the Chromium page the person left, in the key's turn (P0 INVERSE-KEY)

Merged
LA
5d ago
+111-21

Summary

After Cmd-N (a workspace with Home's agent page, a Chromium page in its own child window) and then Ctrl-` for a new terminal, the Home page's Chromium window could keep the key window while the terminal was being made. Under memory pressure that took long enough for the VIDEO EXTRA dogfood smoke gate to fail (dogfood 5b50f8bb57ac, round 5 step 12: "the keys would go to a window that is not a main window: CefNSWindow ..."). The line typed for the new terminal went to the page window instead of the cmux window's creation hold.

Root cause: while the new tab was not in the window's topology yet, the focus waited for it (an expectation) and still resolved the agent page. The wait moved no responder, and every later re-apply (the page window taking the keys on its own, a windowKey change) went through FocusEffectApplier.focusPage, which kept or re-granted the page. A terminal target whose surface was not shown yet only resigned the pane responder and left a Chromium page window key too.

Now, as in Chrome (Cmd-T moves the focus off the page in the key's turn):

  • FocusMachine.send: when the focus starts or stops waiting for another tab (FocusState.awaitsOtherTab) and the reduction moved no responder, it adds .moveResponder, so the target applies again in that reduction. (The rule is in Swift: the app links cmux-focus-core from the pinned CCmuxAppFFI release, so a reducer change would need a new FFI release and pin first.)
  • FocusEffectApplier: while the person's creation is on its way to another tab and its hold owns the window's keys (CreationInputBuffer.keepsKeysFromLeftPage), the page they left blurs and the cmux window takes the keys back: one key change (#24913). A page the creation itself makes (a new browser tab's stand-in) still takes its keys, before and after the reply. A terminal target whose surface is not shown yet blurs a Chromium page too.
  • CreationInputCoordinator: a hold that opens while the focus waits re-applies it (a creation that asks for its focus before it opens its hold), and a hold that ends while the focus still waits re-applies it before held keys run again, so a failed creation gives the page its keys back.
  • The Swift world invariants treat a window whose focus waits for another tab as unsettled, so the page giving up its keys files no W1/chromium desync.

ChildPageFocus.ownerTakesMain moves out of FocusEffectApplier unchanged to pay back the App lines.

Relation to #32537 / #32579 (CEFTab.grantFocus makes the page window key when CEF SetFocus moved none): they make this defect more likely, because every re-apply of the page target during the wait then reliably makes the page window key. With this PR those re-applies blur the left page instead.

Testing

FocusWaitLeavesPageTests (drives FocusMachine: Home's agent page focused, Ctrl-`'s intent and an expectation for a tab not shown yet; then a newer intent):

Observed / Not run

  • The VIDEO EXTRA smoke gate on reopen-f4's dogfood proof build d5ca497a (dogfood + #32579 + #32684 + #32654, without this fix), 10 rounds, +15 GB memory hog, cmux20: 8 of 8 debug.key rounds passed; the 2 real-key rounds stopped at the known New Column gap. So the defect did not show in 8 rounds there; it failed 1 of 10 before.
  • Smoke journey (Cmd-N, Ctrl-, type, 3 rounds, +15 GB hog, cmux20) on a build of b3c127a64c3 (build 0aa995f9; that head's Rust reducer change was not in the app, the Swift parts were): round 1 real-key known gap, round 2 real-key FAIL at step 74 (after the relaunch: Cmd-N, Ctrl-, type; the check saw the CefNSWindow key 7 ms after the real-key Ctrl- was posted; the round's final focus state had the cmux window key and the new terminal focused), round 3 debug.key pass. The real-key check may run before the app handled the posted Ctrl-; not proven either way.
  • Smoke journey on build b3c438d4 of eca6b2212ca, cmux20, +15 GB hog, 7 rounds over two runs (gate 0b9e8ba4aa1, then af9821fa31a, which waits until each real key is in the app's input journal): debug.key rounds 3 of 3 PASS; real-key rounds 4 of 4 passed every Cmd-N, Ctrl-`, type step (8, 12, 74; keys landed 89-132 ms after the post) and then stop NOT RUN at the known New Column real-key gap (step 43/79). No round failed.
  • The b3c127a real-key FAIL above: its check ran 7 ms after the post, while on af9821fa31a a real key lands 89-132 ms after the post, so that check almost certainly ran before the app had the Ctrl-`. Not proven (that round kept no journal); the harness now waits for the key and keeps the journal on a failed check.
  • Not run: a real Chromium page in a unit test (the applier's page path is covered only by the smoke journey), latency benches of split and New Tab (the reviewer's cost note: an optimistic split now applies the responder up to 3 times; a page-to-page creation that waits makes 2 key changes instead of 1).

Changelog

Fixed: Typing right after Ctrl-` on a Home or browser page no longer goes to the page while the new terminal opens

Proof

No capture: a key-window change with no visible UI difference; the evidence is the smoke journey.