Skip to content

[windows] Match Chromium TSF HWND association for the SIP - #3

Draft
KevMCarp wants to merge 8 commits into
kevin/windows-tsf-052cfrom
kevin/cloud-agent-1787868473698-cf7in
Draft

KevMCarp wants to merge 8 commits into
kevin/windows-tsf-052cfrom
kevin/cloud-agent-1787868473698-cf7in

Conversation

@KevMCarp

Copy link
Copy Markdown
Member

Windows SIP heuristics follow the TSF document associated with the HWND, not thread focus. After tap-outside the keyboard was closable but pop / later UI still reopened it because we AssociateFocus’d the editable store onto the Flutter window.

Chromium (ui/base/ime/win)

Layer Chromium This change
Editor ITfThreadMgr::SetFocus(editable) only FocusEditable no longer AssociateFocuses the HWND
NONE AssociateFocus(hwnd, empty_doc) only (internally SetFocuses; they never call both) FocusNonEditable matches that
Win11 empty store Dummy ITextStoreACP + KEYBOARD_DISABLED/EMPTYCONTEXT, TS_SD_READONLY, deny RequestLock Unchanged
InputPane 300 ms debounce TryShow/TryHide; Showing/Hiding only notify observers Unchanged; hide still does not touch TSF
Type NONE trigger Renderer blur / OnTextInputTypeChanged(NONE) Flutter tap-outside does not clearClient, so a pointer that misses the field maps to NONE

TS_SD_INPUTPANEMANUALDISPLAYENABLE is still always set. Chromium still calls TryShow (TODO to drop it once TSF input-pane policy is “serviced”).

Flutter mapping

  • setClient / show after a pointer on the field → SetFocus(editable) + TryShow
  • clearClient or pointer that misses the field → AssociateFocus(empty)
  • InputPane hide → suppress TryShow only (no TSF change)

Fixes the “closable but reopens on pop / other UI” path without the sticky loop from SetFocus-on-hide.

Related: flutter#97269, flutter#99050, flutter#119807, flutter#135492, flutter#187722.

Rebuild the demo against host_debug. Filtered tests: --gtest_filter=*OnScreenKeyboard*:*TextInputPlugin*:*TsfTextStore* (not run on this Linux host).

Open in Web Open in Cursor 

cursoragent and others added 5 commits August 26, 2026 11:58
Replace the Windows TextInput.show/hide no-ops with Chromium-shaped
touch-keyboard control: TSF owns the IME session (ITextStoreACP with
TS_SD_INPUTPANEMANUALDISPLAYENABLE) so the OS does not auto-invoke the
SIP, and IInputPane2 TryShow/TryHide with a 300ms debounce owns
visibility. OccludedRect is converted from root-window client DIPs to
physical pixels before viewInsets.bottom is sent.

Fixes flutter#97269
Fixes flutter#99050
Fixes flutter#119807
Fixes flutter#135492
Fixes flutter#187722

Co-authored-by: Kevin McDermott-Carpenter <kevin.mcdermottcarpenter@ou.ac.uk>
The geometry source_set is not a dep of flutter_windows_unittests, so gn check rejects a direct include. Rect/Size already come in through tsf_text_store.h.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Windows SIP heuristics follow the TSF document associated with the HWND,
not thread focus. Chromium never AssociateFocuses an editable store onto
the window: editors use SetFocus only, and TEXT_INPUT_TYPE_NONE uses
AssociateFocus of the empty document only.

Flutter tap-outside does not clearClient, so a pointer that misses the
field now maps to NONE. InputPane hide still does not change TSF.

Co-authored-by: Kevin McDermott-Carpenter <kevin.mcdermottcarpenter@ou.ac.uk>
KevMCarp pushed a commit that referenced this pull request Sep 9, 2026
…termination (flutter#192294)

## Description

When a target application process exits prematurely (e.g. exit code 255)
before the Debug Adapter Protocol (DAP) initialization handshake
completes, `flutter_tools` crashed with an unhandled
`DebugAdapterException` in the Dart Zone.

In Flutter 3.47.2 telemetry, this was the #3 top crasher with 203
crashes (`DebugAdapterException-34d39b25`).

### Root Cause
1. `FlutterBaseDebugAdapter.handleSessionTerminate` completed
`debuggerInitializationFailedCompleter.completeError(...)` before
calling `super.handleSessionTerminate(...)`.
2. In `package:dap_adapters` (`DartDebugAdapter`),
`handleSessionTerminate` is asynchronous and awaits pending output
events before setting `isTerminating = true`.
3. In `FlutterDebugAdapter._handleAppStarted`, the unawaited
asynchronous message handler caught the error while `isTerminating` was
still `false`, rethrowing `DebugAdapterException` into the Zone.

### Fix
1. Synchronously set `isTerminating = true` in
`FlutterBaseDebugAdapter.handleSessionTerminate`.
2. In `FlutterDebugAdapter._handleAppStarted`, catch
`DebugAdapterException` and send the message to console output rather
than rethrowing into the Zone.
3. Clean up early termination message formatting when `exitSuffix` is
provided.

## Related Issues
* Fixes crasher `DebugAdapterException-34d39b25` in Flutter 3.47.1 and
3.47.2 telemetry.

## Tests
* Added regression tests in
`packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart`
and `flutter_test_adapter_test.dart` verifying that early process exit
outputs cleanly to console and does not produce unhandled asynchronous
exceptions.
cursoragent and others added 3 commits September 9, 2026 22:36
AssociateFocus does not AddRef the document manager, so the bridge now
unassociates the HWND before releasing TSF documents. Null FocusNonEditable
reuses the last HWND instead of forgetting it. The Win11 dummy NONE store
reports the HWND from GetWnd, matching Chromium SetFocusedTextInputClient.

Co-authored-by: Kevin McDermott-Carpenter <kevin.mcdermottcarpenter@ou.ac.uk>
InputPane is MANUALDISPLAYENABLE, so AssociateFocus alone does not hide
the pane. A miss (or a tap with no text client) now TryHides as well as
switching TSF to NONE. A hit on the field unsuppresses and Displays
without waiting for a second TextInput.show.

Requested TryHide no longer clears the pointer latch, so the same tap's
setClient/show is not ignored. User dismiss still suppresses until a
later field tap.

Co-authored-by: Kevin McDermott-Carpenter <kevin.mcdermottcarpenter@ou.ac.uk>
Chromium applies TEXT_INPUT_TYPE_NONE at the type change (clearClient /
AssociateFocus empty), not on later pointers. TryHide or AssociateFocus
on those taps races the OS auto-show and pops the keyboard.

Tap-outside while a client is attached still maps to NONE plus TryHide,
because Flutter does not clearClient on blur.

Co-authored-by: Kevin McDermott-Carpenter <kevin.mcdermottcarpenter@ou.ac.uk>
@KevMCarp
KevMCarp force-pushed the kevin/windows-tsf-052c branch 2 times, most recently from c26a8b7 to 7caa463 Compare September 18, 2026 20:58
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.

2 participants