Repository navigation
[VPAT] Expose a semantics text field value while it is not being edited - #195445
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the _updateValue method to SemanticTextField to copy the framework's value into the DOM element when the field is not being edited, along with comprehensive unit tests. However, using isA checks for DomHTMLInputElement and DomHTMLTextAreaElement is fragile because Dart's JS interop extension types erase to the same runtime check, making the else if block dead code. It is recommended to use the tagName property to safely distinguish between these elements.
| if (editableElement.isA<DomHTMLInputElement>()) { | ||
| final input = editableElement as DomHTMLInputElement; | ||
| if (input.value != value) { | ||
| input.value = value; | ||
| } | ||
| } else if (editableElement.isA<DomHTMLTextAreaElement>()) { | ||
| final textArea = editableElement as DomHTMLTextAreaElement; | ||
| if (textArea.value != value) { | ||
| textArea.value = value; | ||
| } | ||
| } |
There was a problem hiding this comment.
In Dart's modern JS interop (using extension types), extension types are completely erased to their representation type (such as JSObject or HTMLElement) at runtime. As a result, isA<DomHTMLInputElement>() and isA<DomHTMLTextAreaElement>() both erase to the same runtime check (e.g., isA<JSObject>()).
This means editableElement.isA<DomHTMLInputElement>() will evaluate to true even when editableElement is a <textarea>, making the else if block dead code. While the tests currently pass because both <input> and <textarea> share a value property in JavaScript (and the cast is a no-op at runtime), this is highly fragile and conceptually incorrect.
To reliably distinguish between <input> and <textarea> elements at runtime, use the tagName property (which is always uppercase for HTML elements).
| if (editableElement.isA<DomHTMLInputElement>()) { | |
| final input = editableElement as DomHTMLInputElement; | |
| if (input.value != value) { | |
| input.value = value; | |
| } | |
| } else if (editableElement.isA<DomHTMLTextAreaElement>()) { | |
| final textArea = editableElement as DomHTMLTextAreaElement; | |
| if (textArea.value != value) { | |
| textArea.value = value; | |
| } | |
| } | |
| if (editableElement.tagName == 'INPUT') { | |
| final input = editableElement as DomHTMLInputElement; | |
| if (input.value != value) { | |
| input.value = value; | |
| } | |
| } else if (editableElement.tagName == 'TEXTAREA') { | |
| final textArea = editableElement as DomHTMLTextAreaElement; | |
| if (textArea.value != value) { | |
| textArea.value = value; | |
| } | |
| } |
References
- Optimize for readability and avoid fragile runtime type checks with erased extension types. (link)
There was a problem hiding this comment.
isA isn't a runtime is check on the erased type. It is lowered at compile time to an instanceof check against the JS name of the extension type, which here comes from @js('HTMLInputElement') and @js('HTMLTextAreaElement') in dom.dart. A <textarea> therefore fails isA() and takes the second branch. This is the same pattern text_editing.dart uses to tell the two elements apart, so I'll keep it for consistency.
|
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Roll Flutter from fcfb4f203eae to 934720261be0 (38 revisions) flutter/flutter@fcfb4f2...9347202 2026-11-03 [email protected] Roll Skia from 5bceaa033999 to c733ad3e37cb (5 revisions) (flutter/flutter#195872) 2026-11-03 [email protected] Roll Skia from 34d2bf479385 to 5bceaa033999 (9 revisions) (flutter/flutter#195864) 2026-11-03 [email protected] fix: prefer pointer events over semantics tap for mouse clicks on web (flutter/flutter#191529) 2026-11-03 [email protected] [VPAT] Expose a semantics text field value while it is not being edited (flutter/flutter#195445) 2026-11-03 [email protected] [Impeller] Make a GL context current before constructing an AiksContext without a surface (flutter/flutter#195629) 2026-11-03 [email protected] [flutter_tools] Eliminate globals.dart from project.dart (flutter/flutter#194066) 2026-11-03 [email protected] [tool] Complete context runner elimination and migrate AppContext usages to ToolContext (flutter/flutter#190797) 2026-11-03 [email protected] [web] Unify font collections between CanvasKit and Skwasm (flutter/flutter#195539) 2026-11-03 [email protected] [Android] Replay predictive back state for retained engines (flutter/flutter#192286) 2026-11-03 [email protected] [vulkan]: Adds depth/stencil synchronization for subpasses (flutter/flutter#195689) 2026-11-03 [email protected] [web] Honor requested upscaling in instantiateImageCodecWithSize and ImageDescriptor.instantiateCodec (flutter/flutter#195836) 2026-11-03 [email protected] Roll Skia from 866b61ca92c9 to 34d2bf479385 (9 revisions) (flutter/flutter#195848) 2026-11-03 [email protected] Roll Fuchsia Test Scripts from xl1LWBuppzu-BnOzV... to B6zarykZp6PKWRueg... (flutter/flutter#195847) 2026-11-03 [email protected] Remove unused ffi (flutter/flutter#195710) 2026-11-02 [email protected] Revert: [Android] Let the raster thread use every core of a chip without little cores (flutter/flutter#195838) 2026-11-02 [email protected] Run 10 impeller iOS benchmarks as one devicelab task (flutter/flutter#195661) 2026-11-02 [email protected] Use named uniforms instead of setFloat in StretchEffect (flutter/flutter#195372) 2026-11-02 [email protected] [flutter_tools] Report when the browser disconnects during a web test run (flutter/flutter#192198) 2026-11-02 [email protected] fix: Resolve memory leak in image_list example (flutter/flutter#189526) 2026-11-02 [email protected] Only run the common local engine configurations in presubmit (flutter/flutter#195688) 2026-11-02 [email protected] Update gcloud_version to version [email protected] (flutter/flutter#195769) 2026-11-02 [email protected] [Impeller] Order the Vulkan copy-texture-to-texture layout transition after the copy (flutter/flutter#195814) 2026-11-02 [email protected] [Impeller] Order the Vulkan ResizeTexture layout transition after its blit (flutter/flutter#195721) 2026-11-02 [email protected] [web] Fix CanvasKit fragment shader uniforms being freed or mutated after recording (flutter/flutter#195718) 2026-11-02 [email protected] Roll Fuchsia Linux SDK from MqypI6RNyVZ_FLUE-... to T_-6-6X1lDMghhAvX... (flutter/flutter#195809) 2026-11-02 [email protected] [Android] Log why Impeller falls back to OpenGLES (flutter/flutter#194764) 2026-11-02 [email protected] Roll Skia from 3f35fb6e591b to 866b61ca92c9 (11 revisions) (flutter/flutter#195802) 2026-11-02 [email protected] Roll Skia from 3f35fb6e591b to 866b61ca92c9 (11 revisions) (flutter/flutter#195802) 2026-11-02 [email protected] Only run the common local engine configurations in presubmit (flutter/flutter#195688) 2026-11-02 [email protected] Balance tool_integration_tests subshards by expected duration (flutter/flutter#195628) 2026-11-02 [email protected] [web_paragraph] Map code units without a cluster to a cluster (flutter/flutter#195384) 2026-11-02 [email protected] [web] Hide synthetic autofill inputs from assistive technology (flutter/flutter#195618) 2026-11-02 [email protected] Roll Packages from 3742a4e to 3f34a6f (8 revisions) (flutter/flutter#195795) 2026-11-02 [email protected] [web] Unskip web tests that pass on CanvasKit and Skwasm: platform_view_test, route_notification_messages_test (flutter/flutter#195605) 2026-11-02 [email protected] Roll Skia from e5ad3868b8db to 3f35fb6e591b (1 revision) (flutter/flutter#195789) 2026-11-02 [email protected] Roll Skia from f5215e74aa16 to e5ad3868b8db (3 revisions) (flutter/flutter#195779) 2026-11-02 [email protected] Roll Skia from e43478a2f9f8 to f5215e74aa16 (1 revision) (flutter/flutter#195776) 2026-11-01 [email protected] Roll Fuchsia Linux SDK from kW0A0xxNkqf38ljm4... to MqypI6RNyVZ_FLUE-... (flutter/flutter#195768) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose ...
Fixes #147200
Problem
With semantics enabled, a text field's
<input>only gets a value while it is focused, because only the text editing strategy writes it. A field that was never focused, such as a disabled field showing prefilled text, exposes an empty value, so screen readers do not read its content. Two tests pinned this with a TODO pointing at #147200.Fix
SemanticTextFieldnow copies the semantics value into its element when the value changes and the field is not being edited. The active field is left to the text editing strategy, obscured fields are skipped because their semantics value is masked, and fields owned by an autofill group are skipped. The two tests now check the real value, and new tests cover disabled, multi-line, obscured, autofill-owned and updated values.Demo
Open the a11y_assessments text field page with semantics enabled and move a screen reader to the second, disabled "Email" field.
Before: VoiceOver says "Email, dimmed, edit text" and never says "xyz". The element's
valueis empty.After: VoiceOver also reads "xyz", and the element has
value="xyz".Console check:
[...document.querySelectorAll('[data-semantics-role="text-field"]')].map(e => e.value)returns["", ""]before and["", "xyz"]after.