Skip to content

[web][a11y] Semantic text field erases input typed between browser focus and SemanticsTextEditingStrategy.activate() #194186

Closed

Description

@Vamsi-Annabathula

Steps to reproduce

With accessibility (semantics) enabled, Flutter web's semantic <input> is in the DOM and typeable before the framework knows the field is focused. Everything typed in that window is then erased, not merely dropped.

  1. flutter create fieldprobe
  2. Replace lib/main.dart with:
import 'package:flutter/material.dart';

void main() => runApp(const ProbeApp());

class ProbeApp extends StatelessWidget {
  const ProbeApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: Scaffold(
          body: Padding(
            padding: const EdgeInsets.all(24),
            child: TextField(controller: TextEditingController()),
          ),
        ),
      );
}
  1. flutter build web and serve build/web over HTTP.
  2. Open the page, then enable semantics by activating the "Enable accessibility" placeholder:
document.querySelector('flt-semantics-placeholder').click();
  1. Focus the field and deliver input in the same task — this is the focus-to-input handoff, and it is what any fast input source (automation, locator.fill(), a password manager, an injected paste) does:
const i = document.querySelector('input');
i.focus();
for (const ch of 'ABCDEFGHIJ') {
  i.value += ch;
  i.dispatchEvent(new InputEvent('input', {bubbles: true, data: ch, inputType: 'insertText'}));
}
setTimeout(() => console.log(JSON.stringify(i.value)), 1200);

Expected results

The field holds "ABCDEFGHIJ".

Actual results

The field holds "". Every character typed before the framework activated the field is erased.

With input paced more slowly, the leading characters alone are lost — one character at 60ms/char, more the faster input arrives — because the erase happens once, at a fixed moment after focus, and destroys whatever arrived before it. Typing 9123456783 into a freshly focused field yields 123456783.

Without semantics enabled, nothing is lost at any speed: the engine creates and focuses its own hidden input within a microtask of the pointer event, so there is no window.

Cause

In lib/web_ui/lib/src/engine/semantics/text_field.dart:

  • SemanticsTextEditingStrategy.enable() only stores the config; it installs no DOM listeners.
  • The DOM element is hooked up later, from SemanticTextField.update(), which calls SemanticsTextEditingStrategy.activate() on the next semantics update after hasFocus becomes true.
  • activate() calls DefaultTextEditingStrategy.enable(), which runs
    if (lastEditingState != null) { setEditingState(lastEditingState); }
    and writes the framework's TextEditingValue (empty, for an untouched field) onto the DOM input.

So the browser accepts input into the element from the moment it is focused, the framework never sees that input because no input listener is attached yet, and the framework's stale value is then applied over it.

Re-entering the same field loses nothing, because activate() early-returns when activeTextField == textField.

Impact

Any input delivered faster than the activation frame loses its leading characters silently, and the user sees a plausible-looking but wrong value. In our case three address fields were each stored missing their leading characters — a home address a nurse is dispatched to.

Flutter Doctor output

Reproduced on Flutter 3.41.9 (stable), Dart 3.11.5, Chromium, macOS. lib/web_ui/lib/src/engine/semantics/text_field.dart has no related change between 3.41.9 and 3.47.7, so current stable behaves the same.

Related

Possibly adjacent, both closed: #129324 (unable to type with ensureSemantics enabled) and #192792 (focused field loses DOM focus on an ancestor rebuild). This report is specifically about input accepted by the DOM element between browser focus and activate() being overwritten by the framework's stale editing state.

Activity

  1. suryaKommana2662 commented on Oct 12, 2026

    @suryaKommana2662
    Contributor

    @Vamsi-Annabathula Thanks for the report. I tested the provided reproduction steps on Flutter 3.47.7 stable using Chrome on macOS. After enabling semantics and running the JavaScript input script, the field retained the full value (ABCDEFGHIJ). I could not reproduce the reported input loss in this test. Could you please confirm whether the issue still occurs on the latest stable version?

    Image

    Flutter Doctor Output

    [✓] Flutter (Channel stable, 3.47.7, on macOS 27.0 26A428 darwin-arm64, locale en-IN) [310ms]
        • Flutter version 3.47.7 on channel stable at /Users/suryakommana/develop/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision abaf9c5237 (4 days ago), 2026-10-08 09:51:21 -0700
        • Engine revision deb287481e
        • Dart version 3.13.5
        • DevTools version 2.60.0
        • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets,
          enable-record-use, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [1,534ms]
        • Android SDK at /Users/suryakommana/Library/Android/sdk
        • Emulator version 37.1.11.0 (build_id 15917651) (CL:N/A)
        • Platform android-37.0, build-tools 36.0.0
        • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
          This is the JDK bundled with the latest Android Studio installation on this machine.
          To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
        • Java version OpenJDK Runtime Environment (build 25.0.2+-15348964-b329.117)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 27.0) [782ms]
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 27A266a
        • CocoaPods version 1.17.0
    
    [✓] Chrome - develop for the web [4ms]
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Connected device (4 available) [6.1s]
        • sdk gphone64 arm64 (mobile) • emulator-5554                        • android-arm64  • Android 14 (API 34) (emulator)
        • iPhone 18 Pro (mobile)      • D80F2104-E911-4251-BEDD-E67D9B7EBF58 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-27-0 (simulator)
        • macOS (desktop)             • macos                                • darwin-arm64   • macOS 27.0 26A428 darwin-arm64
        • Chrome (web)                • chrome                               • web-javascript • Google Chrome 155.0.8059.39
    
    [✓] Network resources [709ms]
        • All expected network resources are available.
    
    • No issues found!
  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    and removed
    in triagePresently being triaged by the triage team
    on Oct 12, 2026
  3. Street-PuNk commented on Oct 26, 2026

    @Street-PuNk

    I can reproduce this on 3.47.7 stable (engine deb287481e, Dart 3.13.5), Chromium 152 on Windows 11, flutter build web --release.

    I think the original snippet can look like it passes for two reasons, because input.value is not what the framework sees:

    1. The page has no system focus (document.hasFocus() === false, e.g. the snippet is run from the DevTools console while DevTools has focus). The browser then does not dispatch focus to the input, the framework is never told about the focus, activate() never runs and nothing overwrites the DOM. input.value reads "ABCDEFGHIJ" while controller.text is "". The input is still lost, just invisibly.
    2. The field is already focused and active when the snippet runs (in my run document.activeElement was already the input right after semantics was enabled), so i.focus() is a no-op and activate() returns early.

    A variant that avoids both: the same app with one extra line that shows the framework value:

    final controller = TextEditingController();
    // body:
    Column(children: [
      TextField(controller: controller),
      ListenableBuilder(
        listenable: controller,
        builder: (context, _) => Text('framework value: "${controller.text}"'),
      ),
    ])

    Click once anywhere on the page (so the document has focus), enable semantics, then run:

    const sleep = ms => new Promise(r => setTimeout(r, ms));
    const i = document.querySelector('input');
    i.blur(); await sleep(800);            // make sure the field is not active
    i.focus();
    for (const ch of 'KLMNOPQRST') {
      i.value += ch;
      i.dispatchEvent(new InputEvent('input', {bubbles: true, data: ch, inputType: 'insertText'}));
    }
    console.log('right after:', i.value);
    await sleep(1200);
    console.log('1.2 s later:', i.value);

    Result (every round I tried): right after, the DOM holds the typed text. 1.2 s later it is reverted to the previous framework value, and the framework value line never shows the typed characters.

    We hit the same thing with real, trusted input from CDP: Input.dispatchMouseEvent click on the field followed immediately by Input.insertText. The text is visible in the field, but the framework value is empty and the form is submitted empty. With a ~2 s pause after the click it works. As a workaround in our test tooling we wait until the semantics strategy is active (a probe mousedown on the input gets defaultPrevented by preventDefaultForMouseEvents()) before inserting text.

  4. github-actions commented on Nov 2, 2026

    @github-actions

    Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now.

    If you find this problem please file a new issue with the same description, what happens, logs and the output of 'flutter doctor -v'. All system setups can be slightly different so it's always better to open new issues and reference the related ones.

    Thanks for your contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions