Skip to content

[web] Autofill all AutofillGroup fields in semantics mode - #187108

Merged
auto-submit[bot] merged 26 commits into
flutter:masterfrom
flutter-zl:issue_180652_autofill_semantics
Jul 31, 2026
Merged

auto-submit[bot] merged 26 commits into
flutter:masterfrom
flutter-zl:issue_180652_autofill_semantics

Conversation

@flutter-zl

@flutter-zl flutter-zl commented May 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #180652

Problem

With a screen reader on, picking a saved login only filled the field you tapped. The other fields stayed empty.

Browsers autofill a login by putting all its fields in one hidden <form>. But in screen-reader mode each field has to live in its own spot for accessibility, so it never went into that form. The browser saw a half-built login and filled only one field.

Fix

Still build the hidden form, but instead of moving the field into it, just link them with the HTML form="<id>" attribute. The field stays where the screen reader needs it, and the browser now sees a complete login and fills every field.

Demo

Semantics ON:

Semantics OFF (regression check, both behave the same):

Known limitation

Chrome on iOS still fills only the username, not the password. That is a pre-existing WebKit limitation unrelated to this change (it also happens without a screen reader) and is tracked separately in #185327.

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 26, 2026
@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-web Web applications specifically and removed CICD Run CI/CD labels May 26, 2026
@flutter-zl
flutter-zl force-pushed the issue_180652_autofill_semantics branch from 56b1674 to 85930ea Compare May 28, 2026 18:15
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 28, 2026
@flutter-zl
flutter-zl marked this pull request as ready for review May 28, 2026 18:26

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the web text editing semantics to support grouped autofill without regressing accessibility tab traversal. It links the focused input element to the autofill form using the HTML form attribute instead of moving it into the form, and demotes it to a synthetic placeholder on blur. Review feedback suggests using WebKit engine checks instead of Safari-only checks to support other WebKit-based browsers on iOS, avoiding force-unwraps on potentially null configurations, and retrieving the element's name attribute dynamically to prevent runtime casting errors with multiline text areas.

Comment thread engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart Outdated
/// the Safari/iOS strategy), so the strategy type alone misses Safari there.
/// Fall back to a browser check so synthetic fields are not zero-sized on
/// Safari/WebKit, which would defeat autofill.
bool get _treatAsSafariForAutofill => _isSafariStrategy || ui_web.browser.isSafari;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the WebKit check in text_field.dart, using ui_web.browser.isSafari here misses other WebKit-based browsers (like Chrome/Firefox on iOS) when semantics mode is active. Consider checking if the browser engine is WebKit to ensure synthetic fields are not zero-sized on any WebKit-based browser, which would defeat autofill.

Suggested change
bool get _treatAsSafariForAutofill => _isSafariStrategy || ui_web.browser.isSafari;
bool get _treatAsSafariForAutofill =>
_isSafariStrategy || ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/// reliable, order-independent signal that [_updateInputType] must not
/// overwrite `autocomplete`, otherwise grouped autofill silently breaks on
/// the next semantics update (flutter/flutter#180652).
bool get _isAutofillOwned => (editableElement as DomHTMLInputElement).name?.isNotEmpty ?? false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Casting editableElement directly to DomHTMLInputElement can lead to runtime type errors if the field is a multiline text field (which uses DomHTMLTextAreaElement). Since both input and textarea elements support the name attribute, you can safely use editableElement.getAttribute('name') to avoid any unsafe casting.

Suggested change
bool get _isAutofillOwned => (editableElement as DomHTMLInputElement).name?.isNotEmpty ?? false;
bool get _isAutofillOwned => editableElement.getAttribute('name')?.isNotEmpty ?? false;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@github-actions github-actions Bot removed the CICD Run CI/CD label May 28, 2026
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 29, 2026
@github-actions github-actions Bot removed the CICD Run CI/CD label May 29, 2026
@flutter-zl flutter-zl added the CICD Run CI/CD label May 29, 2026
@flutter-zl
flutter-zl requested review from chunhtai and mdebbar May 29, 2026 22:35
@mdebbar

mdebbar commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

We already have a semantics form: https://github.com/flutter/flutter/blob/master/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/form.dart I'm curious why that's not working as expected?

@flutter-zl

Copy link
Copy Markdown
Contributor Author

We already have a semantics form: https://github.com/flutter/flutter/blob/master/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/form.dart I'm curious why that's not working as expected?

That's the a11y form, only created for the framework's Form widget for screen-reader announcement; AutofillGroup doesn't emit it and its inputs have no name/autocomplete, so the password manager won't fill it. Browser autofill goes through EngineAutofillForm in the text-editing layer (the shared path that also works with semantics off), and since the semantics-mode case was never implemented (per yjbanov's comment), this PR wires it up by linking the focused semantics to that form via the form= attribute.

@mdebbar

mdebbar commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

AutofillGroup doesn't emit it

That might be exactly where the problem lies.

The autofill setup in text_editing.dart is admittedly a giant mess (mostly my doing), but it's required for semantics-off mode. For semantics-on mode, we have a much cleaner path via standard forms and inputs. Since semantics already support forms, we should focus on making that work correctly.

I’d much prefer we make the simpler semantics forms work rather than dragging the text-editing autofill complexity into semantics mode.

@chunhtai, is it reasonable to have AutofillGroup emit a semantics form? We essentially treat it as a virtual form for a group of autofillable inputs anyway.

@chunhtai

Copy link
Copy Markdown
Contributor

is it reasonable to have AutofillGroup emit a semantics form? We essentially treat it as a virtual form for a group of autofillable inputs anyway.

I felt this is a web only thing that it needs to use a form to enable the autofill. so I am more toward keeping the logic private to web engine.

@mdebbar mdebbar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chunhtai do you think it makes sense (in the future, not now) to have all autofill information available in the semantics tree? I'm thinking about a world where semantics is enabled by default, having autofill info in the semantics tree will simplify a lot of things.

formElement = _createFormElementAndFields(
focusedElement,
focusedAutofill,
associateFocusedByAttribute: associateFocusedByAttribute,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to check if my understanding is correct: in semantics mode, we create the <form> and all text fields inside it, and then we delete the "focused" one because we want to use the semantics field.

If my understanding is correct, imagine a simple form of username and password. In the semantics tree, we create a username field and a password field. Then the user focuses on the username field, so we create the form, and we create an extra password field inside it. Now we have one username field and two password fields. Is this the case?

I think in semantics mode, the _createFormElementAndFields() method should only create the form, no fields, because the fields are already created in the semantics tree.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It's "skip," not "create then delete", the focused field is never synthesized:
   if (field... == focusedAutofill.uniqueIdentifier) {
     if (associateFocusedByAttribute) {
       continue; // focused field stays in <flt-semantics>, linked via form=
    }
     htmlElement = focusedElement;
   } else {
    // synthetic for non-focused fields
   }
  1. So with username focused: username = one element (the real semantics input, linked by form=, not
    duplicated); password = the idle semantics input + one synthetic in the form, but only the synthetic is in
    the form.

  2. The form can't be empty, though: the semantics password isn't inside the form, so the browser won't group it. The synthetic is its in-form stand-in and value relay (browser fills it → listener forwards to the framework → AutofillScope writes it to the real field). Same path semantics-off uses. And the focused field stays in its semantics node because moving it into the form broke a11y tab order (Fix a11y tab traversal flutter-team-archive/engine#25797).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's "skip," not "create then delete", the focused field is never synthesized

Ah you are right. I was confused by the synthetic.remove(); line. Can we delete it? Maybe replace it with an assert to make sure no synthetic element was created for the focused ID.

The form can't be empty, though

The form is never empty. There's always at least a submitBtn in it.

the semantics password isn't inside the form, so the browser won't group it.

Can we do the same form= attribute for non-focused fields too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. synthetic.remove() is only for waking a dormant form where a previous blur left a placeholder, not for deleting something created during the initial form build. The first-build case is already enforced by the continue in _createFormElementAndFields: the focused field is not synthesized when the form is first created in semantics mode.

  2. Agreed that “empty form” was imprecise. The form always has submitBtn.

  3. For non-focused fields, this code path cannot use form= today. EngineAutofillForm only receives the focused semantics DOM element. For the other fields it only has AutofillInfo, not the real SemanticTextField.editableElement, so it cannot attach form= to them. The synthetic controls are also the relay path for browser autofill values via TextInputClient.updateEditingStateWithTag.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-focused fields can use SemanticsTextEditingStrategy.instance.inputConfiguration.autofillGroup!.formDomId to get the ID for form=. I don't know what's the best place to do this, but maybe in the update() method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.formDomId is reachable from update().

The blocker is that form= alone only associates the element with the form. It does not tell the browser or Flutter which autofill field this semantics input represents.

For example, if we only add form= to the password input, we get:

<input form="login-form">

But for browser autofill and framework value propagation, the real field would also need the matching AutofillInfo metadata:

<input
  form="login-form"
  name="current-password"
  autocomplete="current-password">

Today that DOM-element-to-AutofillInfo mapping only exists for the focused text input client. For non-focused fields, the synthetic inputs are created directly from the group's AutofillInfo, so they have the correct name, autocomplete, and tag for TextInputClient.updateEditingStateWithTag.

So I think keeping the synthetic non-focused fields is safer for this PR.

@chunhtai

Copy link
Copy Markdown
Contributor

do you think it makes sense (in the future, not now) to have all autofill information available in the semantics tree? I'm thinking about a world where semantics is enabled by default, having autofill info in the semantics tree will simplify a lot of things.

If I understand correctly, the pr grabs the autofill info from textinput client and use that to add form to the semantics dom. You suggest that we should have this autofill info in the semantics tree directly right? yeah i think that makes sense. It also inline with my suggestion to separate out coupling of textfield semantics and textinputclient so that it makes things easier to implement a custom textfield. cc @Renzo-Olivares

@flutter-zl
flutter-zl requested a review from mdebbar June 12, 2026 21:43
elements.addAll(existingForm.elements);
} else {
formElement = _createFormElementAndFields(focusedElement, focusedAutofill);
formElement = _createFormElementAndFields(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if someone call wakeup with associateFocusedByAttribute: false and then call it with associateFocusedByAttribute: true?

It feels like associateFocusedByAttribute should be a constructor parameter of EngineAutofillForm so that we don't need to worry about the mismatch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion. Updated.

@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 25, 2026
@auto-submit

auto-submit Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/187108, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 29, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 29, 2026
@auto-submit

auto-submit Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/187108, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2026
@auto-submit

auto-submit Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/187108, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Jul 31, 2026
Merged via the queue into flutter:master with commit 031c6b8 Jul 31, 2026
28 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Aug 9, 2026
…12401)

Manual roll Flutter from 2757a77a73df to e52f01c920ad (47 revisions)

Manual roll requested by [email protected]

flutter/flutter@2757a77...e52f01c

2026-07-31 [email protected] [Impeller] Execute PipelineCompileQueue jobs in the order that they were added (flutter/flutter#190262)
2026-07-31 [email protected] Roll Packages from 7d17fc8 to 5351d8c (5 revisions) (flutter/flutter#190351)
2026-07-31 [email protected] Roll Skia from dcbd015c9646 to 7ef86a5b0eb9 (4 revisions) (flutter/flutter#190347)
2026-07-31 [email protected] [Impeller] Do not acquire an unneeded image in KHRSwapchainVK if the swapchain must be recreated due to a size change (flutter/flutter#190116)
2026-07-31 [email protected] Remove the --enable-serial-gc engine flag (flutter/flutter#190310)
2026-07-31 [email protected] Migrate platform_channel_swift example to material_ui (flutter/flutter#190308)
2026-07-31 [email protected] macOS: Set static text accessibility role for read-only text fields (flutter/flutter#190330)
2026-07-31 [email protected] Roll Dart SDK from 96f49fc43f59 to c3acfc2479f6 (1 revision) (flutter/flutter#190336)
2026-07-31 [email protected] Roll Skia from 07f08414dd66 to dcbd015c9646 (1 revision) (flutter/flutter#190333)
2026-07-31 [email protected] [web] Autofill all AutofillGroup fields in semantics mode (flutter/flutter#187108)
2026-07-31 [email protected] iOS: Configure test task runner thread at display QoS (flutter/flutter#190283)
2026-07-31 [email protected] Roll Skia from a1109355de78 to 07f08414dd66 (5 revisions) (flutter/flutter#190328)
2026-07-31 [email protected] Support reduceMotion accessibility feature in MediaQuery (flutter/flutter#190287)
2026-07-30 [email protected] Roll Dart SDK from 43f7de5f4977 to 96f49fc43f59 (4 revisions) (flutter/flutter#190312)
2026-07-30 [email protected] fix: text selection two handles directionality. (flutter/flutter#179928)
2026-07-30 [email protected] Add java/gradle check when gradle crashes (flutter/flutter#189997)
2026-07-30 [email protected] [Impeller] Add filterQuality support to ImageFilter.shader (flutter/flutter#188544)
2026-07-30 [email protected] [flutter/flutter] Migrate multiple_windows example to material_ui (flutter/flutter#190313)
2026-07-30 [email protected] Roll Skia from 50cf67910462 to a1109355de78 (6 revisions) (flutter/flutter#190306)
2026-07-30 [email protected] Migrate swift xctests to Swift testing tests (flutter/flutter#187801)
2026-07-30 [email protected] Migrates the platform_channel example to material_ui (flutter/flutter#190253)
2026-07-30 [email protected] iOS: Drop unnecessary @mainactor from VSyncClientTests (flutter/flutter#190285)
2026-07-30 [email protected] Add nullptr check to isOpaque (flutter/flutter#189796)
2026-07-30 [email protected] [Flutter GPU] Bind uniforms and textures by cached reflection index (flutter/flutter#189820)
2026-07-30 [email protected] Migrate layers example to material_ui (flutter/flutter#190237)
2026-07-30 [email protected] Add `ShapeBorder` hit test fast paths (flutter/flutter#187962)
2026-07-30 [email protected] ci: mac-must-wait part duex (flutter/flutter#190295)
2026-07-30 [email protected] Roll Skia from 491301f393d1 to 50cf67910462 (3 revisions) (flutter/flutter#190294)
2026-07-30 [email protected] Roll Packages from dd11626 to 7d17fc8 (1 revision) (flutter/flutter#190293)
2026-07-30 [email protected] Roll Skia from 177de1940bf2 to 491301f393d1 (1 revision) (flutter/flutter#190279)
2026-07-30 [email protected] Roll Skia from b8bda91f02db to 177de1940bf2 (5 revisions) (flutter/flutter#190272)
2026-07-30 [email protected] Roll Fuchsia Linux SDK from J8rVTlBnjnUpuHYk3... to _J8wM3kyQpLN9wvRD... (flutter/flutter#190273)
2026-07-30 [email protected] Roll Skia from 78afc18c9ba0 to b8bda91f02db (3 revisions) (flutter/flutter#190268)
2026-07-30 [email protected] Revert: Split the platform and cpuArch part of TargetPlatform (flutter/flutter#190258)
2026-07-30 [email protected] Return null from RenderProxyBoxMixin.computeDryBaseline when the child has no baseline (flutter/flutter#189723)
2026-07-30 [email protected] Roll Skia from 6fea713d32ec to 78afc18c9ba0 (4 revisions) (flutter/flutter#190257)
2026-07-30 [email protected] Roll pub packages (flutter/flutter#190259)
2026-07-29 [email protected] [cross imports] Check examples cross imports (flutter/flutter#187662)
2026-07-29 [email protected] ci: mac verify binaries still has to wait on linux_host_engine (flutter/flutter#190239)
2026-07-29 [email protected] Adds analytics to macos impeller opt in/out (flutter/flutter#190220)
2026-07-29 [email protected] Roll Skia from 0f35bba4945c to 6fea713d32ec (6 revisions) (flutter/flutter#190230)
2026-07-29 [email protected] iOS: Fix Swift warning in TracingTests (flutter/flutter#190203)
2026-07-29 [email protected] Convert all gradle-wrapper.properties in /dev to use distribution 'bin' (flutter/flutter#190226)
2026-07-29 [email protected] Roll Packages from 3e63635 to dd11626 (11 revisions) (flutter/flutter#190216)
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team triaged-accessibility Triaged by Framework Accessibility team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AutofillGroup does not autofill all related fields in semantics mode

6 participants