Skip to content

Make UIA gate popup activation deterministic and attribute wait timeouts - #631

Merged
coneilen merged 1 commit into
mainfrom
coneilen-microsoft-uia-gate-local-reliability
Oct 5, 2026
Merged

coneilen merged 1 commit into
mainfrom
coneilen-microsoft-uia-gate-local-reliability

Conversation

@coneilen

@coneilen coneilen commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Windows UIA live gate failed locally at a different step each run (Edit edge, Promote ... to Goal/Turn, multi-project convergence) while passing on hosted CI. On the interactive desktop, synthesized SendInput popup-menu clicks are intermittently not delivered. When that happens, the gate's posted-Enter fallback can end TrackPopupMenu with command 0 (no item hilited), which closes the popup exactly like a successful selection. Its WM_COMMAND fallbacks can't recover either, because the product never routes the menu IDs through WM_COMMAND. This PR makes popup activation deterministic and attributes every UIA wait timeout. It is a gate-only change; no product code changes.

Root cause (what was proven, and what was not)

Local reproduction on main 8ffa38e with the standalone gate (pinned bootstrap, scratch USERPROFILE/LOCALAPPDATA/TEMP, Pester 5.7.1):

  • Early batch: 5/6 passes, then 1 failure (output lost to an inherited stdout handle). Instrumented batches: a1–a8 failed 3 of 8 (Edit edge twice, ElementSelected once); b1–b5 passed. b6–b8 also failed, but overlapped my own input probe, so I've excluded them.
  • Not focus theft. A 100 ms foreground monitor (process + class only) shows the shell held foreground continuously from the previous modal's close to the failure. The attribution confirms the shell owned foreground at timeout, was enabled (no modal up), and had no popup open.
  • Not a detection gap or a short timeout. No top-level window of the shell PID had the modal title. The shell's own status text was unchanged from the prior action, so the command never executed.
  • Injected input is not reliably delivered on this desktop. A standalone probe ran the gate's exact SetCursorPos + hover + SendInput sequence against a real TrackPopupMenu(TPM_RETURNCMD) popup. Hit-test was the popup and SendInput returned 2/2. Our WH_MOUSE_LL hook saw both injected events within 6–20 ms, yet no click reached the menu or even a plain foreground window (0 of about 60). Two hours later the same probe delivered 5/5, so the condition is intermittent and environmental. Mouse/input-hooking utilities run on this desktop (Synergy, MagicMouseUtilities, PowerToys); which one is responsible is not proven.
  • Locally, 20/20 gate menu activations in a passing batch needed the posted-Enter fallback; on CI run 37351425076, keyboardFallback was false 8/8.
  • Mechanism:
    • Enter posted to a menu with no hilited item returns 0. Probe: 20/20 returned 0 without hilite, and 20/20 returned 5110 after MN_SELECTITEM re-selected the item.
    • In run c1 the fixed gate logged hiliteAtEnterFallback=False hiliteBeforeEnter=True for 5120 and then opened "Create or edit edge". Without re-selection, that state is the b6 failure.
    • MainWindow.Command has no 51xx IDs. The context-menu IDs only exist as TrackPopupMenu return values, so SendCommand(shell, 5110/5116-5120) is a no-op. In c2, when the Turn submenu hover did not reveal the submenu, that no-op fallback ran and the modal wait failed.
  • Not a shell product bug as far as observed: the shell never received the command. Unexplained: one c1 failure where edit-form combo 9103 ignored both injected and posted arrow keys. This PR does not address it; the new attribution captures shell-owned modal windows so the next occurrence is attributable.

Changes

  • ClickPopupMenuItem: before the Enter fallback, record whether the intended item is still hilited, re-select it with MN_SELECTITEM, and throw if its hilite cannot be proved. New HiliteAtEnterFallback/HiliteBeforeEnter evidence is logged as UIA_EDGE_MENU_CLICK/UIA_SKETCH_MENU_CLICK.

  • Sketch "Promote to" submenu: if hover does not open the real submenu, open it by keyboard navigation of the same native menu (RevealSubmenuByKeyboard: MN_SELECTITEM on the parent, then VK_RIGHT). This replaces the no-op WM_COMMAND; if it still doesn't open, the gate fails with an explicit message.

  • UIA_WAIT_TIMEOUT_ATTRIBUTION is written for:

    • popup open/dismiss, desktop element/gone (with per-search FindFirst timing) and foreground waits
    • edge and sketch modal waits, multi-project convergence, and any gate failure

    Each record contains:

    • step, elapsed vs deadline
    • the foreground window's owning process name and class (no foreign titles)
    • every shell-PID top-level window with class, visibility, enabled and hung state
    • the shell's own status text
    • PrintWindow captures of shell-owned windows only (never the screen), saved in the retained sandbox logs
  • Selection event waits keep their 1 s on-time assertion. On a miss they observe for 10 s more, purely to log late vs absent delivery, and then still fail.

  • Get-FocusDiagnostics no longer logs other applications' window titles or focused element names.

  • ValidationRunner.Tests.ps1: new contracts for the re-select-before-Enter fallback, keyboard submenu reveal (and absence of the no-op submenu command), and the attribution hooks.

Not changed: no timeouts were raised and no retries were added. The other existing no-op WM_COMMAND fallbacks in the edge path are left in place (existing contracts pin them); they cannot mask a failure because the modal assertion still follows them.

Test plan

RED: pwsh -NoProfile -File Tools\windows\Tests\ValidationRunner.Tests.ps1 with the main 8ffa38e gate -> exit 1, "RED: UIA popup Enter fallback can close the menu without choosing the intended item"
GREEN: pwsh -NoProfile -File Tools\windows\Tests\ValidationRunner.Tests.ps1 at 261f7a4 -> exit 0, "ValidationRunner.Tests.ps1: PASS" (multi-project contracts executed=380)
REGRESSION: uia-live-gate.ps1 on the built shell at 261f7a4, 8 consecutive local runs (d1-d8) -> 8/8 EXIT=0, each with the final summary JSON

Additional local live evidence. I used a scoped WH_MOUSE_LL simulator that drops injected button events only on #32768 popups owned by this worktree's graphcode-windows.exe, and clears the menu hilite, reproducing the c1 state:

  • main gate: 2/2 failed, "clicking Edit Edge from the canvas did not open the edge editor" (hilite:true, keyboardFallback:true)
  • fixed gate: 4/4 passed, 10/10 menu activations rescued per run
  • With the drop only (hilite intact), main 3/3 and fixed 4/4 passed. This shows the drop alone is harmless; the losing condition is hilite loss before Enter.

Limits:

  • The d1–d8 runs happened while injected clicks were being delivered normally on this desktop, so they show no regression but do not exercise the fallbacks.
  • The keyboard submenu reveal was never needed in any run and is untested live.
  • The original real-world hilite-loss trigger is unidentified (environmental input stack).
  • Full validate.ps1 -Task windows-shell was not re-run locally after the change; exact-head CI is the regression authority for the unit/integration suites.

Checklist

  • I have read the Contributing Guidelines
  • I have signed off my commits (git commit -s) per the DCO
  • Tests pass locally (make test): macOS targets, not run for this Windows gate change
  • Code follows the existing style (make check): macOS Swift lint, not applicable to the changed PowerShell files
  • I added the test/contract before the implementation and observed the intended RED failure: the contract was written after the fix, and RED was observed by running it against the unmodified main gate

On an interactive desktop, synthesized SendInput mouse clicks reach the
low-level hook chain but are not delivered to the target window, so the
gate's popup-menu activation silently relied on its posted-Enter fallback.
Enter with no hilited item ends TrackPopupMenu with command 0, which closes
the popup exactly like a successful selection, and the gate's WM_COMMAND
fallbacks to the shell window are no-ops because TPM_RETURNCMD menus never
route those IDs. Runs then failed at whichever modal wait came next.

- Re-select the intended item with MN_SELECTITEM and prove its hilite
  immediately before the Enter fallback; fail loudly if it cannot be proved.
- Reveal the Promote to submenu with keyboard navigation of the real menu
  instead of the no-op WM_COMMAND fallback.
- Record UIA_WAIT_TIMEOUT_ATTRIBUTION for wait timeouts and gate failures:
  step, elapsed vs deadline, foreground owner process and class only, every
  top-level shell window, the shell status text, and PrintWindow captures of
  shell-owned windows only. Late UIA selection events are measured without
  changing the on-time assertions.
- Stop logging other applications' window titles and focused element names.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Colin Neilens <coneilen@microsoft.com>
@coneilen
coneilen merged commit e770438 into main Oct 5, 2026
24 checks passed
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.

1 participant