Skip to content

macOS: Set static text accessibility role for read-only text fields - #190330

Merged
auto-submit[bot] merged 1 commit into
flutter:masterfrom
cbracken:a11y-static-text-readonly-fields
Jul 31, 2026
Merged

auto-submit[bot] merged 1 commit into
flutter:masterfrom
cbracken:a11y-static-text-readonly-fields

Conversation

@cbracken

Copy link
Copy Markdown
Member

#184501 updated accessibility role mapping such that read-only text fields are mapped to ax::mojom::Role::kTextField. On macOS, read-only text fields are intentionally treated as static text. Because of the role change, the macOS embedder incorrectly instantiated FlutterTextPlatformNode for read-only text fields, leading to detached accessibility elements and test crashes.

This updates the macOS platform node delegate to only instantiate a FlutterTextPlatformNode when the target text field is actually editable and not read-only or disabled. We now explicitly return read-only text fields as standard static text elements. This matches current behaviour but makes it explicit in the platform node delegate for macOS.

We're also now calling loadView on the viewController in tests. In a running app, the view controller will always be loaded, so this better approximates reality in the tests... and avoids bugs such as those caught in #184501.

This should allow us to reland #184501.

Issue: #184559

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@cbracken
cbracken requested a review from a team as a code owner July 31, 2026 01:21
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Jul 31, 2026
@cbracken
cbracken requested a review from chunhtai July 31, 2026 01:22
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-macos Building on or for macOS specifically a: desktop Running on desktop team-macos Owned by the macOS platform team labels Jul 31, 2026
@cbracken
cbracken requested a review from hannah-hyj July 31, 2026 01:22

@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 FlutterPlatformNodeDelegateMac to treat read-only or disabled text fields as static text by overriding GetData and adjusting the logic in Init and NodeDataChanged. Additionally, the corresponding tests are updated to load the view controller, assert non-nil accessibility elements, and use string comparison for accessibility roles. The review feedback points out a redundant check in Init, suggesting a simplification since GetData already overrides the role of read-only or disabled text fields.

void FlutterPlatformNodeDelegateMac::Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) {
FlutterPlatformNodeDelegate::Init(bridge, node);
if (GetData().IsTextField()) {
if (GetData().IsTextField() && !GetData().IsReadOnlyOrDisabled()) {

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

Since GetData() overrides the role of read-only or disabled text fields to ax::mojom::Role::kStaticText, GetData().IsTextField() will already return false for those nodes. Therefore, the additional check !GetData().IsReadOnlyOrDisabled() is redundant and can be removed to simplify the condition.

  if (GetData().IsTextField()) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Bad bot. Not redundant.

GetData() only rewrites the role to kStaticText but leaves kEditableRoot untouched. IsTextField() returns true whenever kEditableRoot is set, completely independently of the role, so the role rewrite only makese GetData().IsTextField() false for read-only fields where kEditableRoot is unset.

@cbracken cbracken changed the title [macOS] Set static text accessibility role for read-only text fields macOS: Set static text accessibility role for read-only text fields Jul 31, 2026

@chunhtai chunhtai 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.

The code LGTM, but can you double check whether the voice over still work when you use mouse to select text? it should announce what gets selected or unselected with a lower pitch

flutter#184501 updated accessibility
role mapping such that read-only text fields are mapped to
`ax::mojom::Role::kTextField`. On macOS, read-only text fields are
intentionally treated as static text. Because of the role change, the
macOS embedder incorrectly instantiated `FlutterTextPlatformNode` for
read-only text fields, leading to detached accessibility elements and
test crashes.

This updates the macOS platform node delegate to only instantiate a
FlutterTextPlatformNode when the target text field is actually editable
and not read-only or disabled. We now explicitly return read-only text
fields as standard static text elements. This matches current behaviour
but makes it explicit in the platform node delegate for macOS.

We're also now calling loadView on the viewController in tests. In a
running app, the view controller will always be loaded, so this better
approximates reality in the tests... and avoids bugs such as those
caught in flutter#184501.

Issue: flutter#184559
@cbracken
cbracken force-pushed the a11y-static-text-readonly-fields branch from 5bd94be to e0385ee Compare July 31, 2026 03:07
@cbracken

Copy link
Copy Markdown
Member Author

The code LGTM, but can you double check whether the voice over still work when you use mouse to select text? it should announce what gets selected or unselected with a lower pitch

Manually verified this and can confirm selection still works correctly.

@chunhtai chunhtai 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.

LGTM

@cbracken cbracken 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 866407a Jul 31, 2026
22 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2026
@cbracken
cbracken deleted the a11y-static-text-readonly-fields branch July 31, 2026 10:03
cbracken added a commit to cbracken/flutter that referenced this pull request Jul 31, 2026
This is a partial re-land of
flutter#184501, which just lands the
common bridge code and tests as well as a macOS test for behaviour
landed in landed in flutter#190330.

This sets AXNodeData::SetRestriction() from the Flutter semantics flags:
kDisabled when is_enabled is false, and kReadOnly for read-only text
fields. Previously the restriction was never populated, so disabled and
read-only nodes were indistinguishable from editable/enabled ones at the
platform layer.

We also add a macOS regression test that checks that a disabled,
editable text field is exposed as static text rather than a
FlutterTextField.

This guards the !GetData().IsReadOnlyOrDisabled() check in
FlutterPlatformNodeDelegateMac::Init. The GetData() override rewrites a
disabled field's role to kStaticText but leaves kEditableRoot set, so
IsTextField() stays true and the restriction check is what prevents an
editable FlutterTextField from being created. I've added this in
response to a bad comment by the Gemini review bot to ensure that this
doesn't regress.

Issue: flutter#184559
cbracken added a commit to cbracken/flutter that referenced this pull request Jul 31, 2026
This is a partial re-land of
flutter#184501, which just lands the
common bridge code and tests as well as a macOS test for behaviour
landed in landed in flutter#190330.

This sets AXNodeData::SetRestriction() from the Flutter semantics flags:
kDisabled when is_enabled is false, and kReadOnly for read-only text
fields. Previously the restriction was never populated, so disabled and
read-only nodes were indistinguishable from editable/enabled ones at the
platform layer.

We also add a macOS regression test that checks that a disabled,
editable text field is exposed as static text rather than a
FlutterTextField.

This guards the !GetData().IsReadOnlyOrDisabled() check in
FlutterPlatformNodeDelegateMac::Init. The GetData() override rewrites a
disabled field's role to kStaticText but leaves kEditableRoot set, so
IsTextField() stays true and the restriction check is what prevents an
editable FlutterTextField from being created. I've added this in
response to a bad comment by the Gemini review bot to ensure that this
doesn't regress.

Issue: flutter#184559
cbracken added a commit to cbracken/flutter that referenced this pull request Aug 2, 2026
This is a partial re-land of
flutter#184501, which just lands the
common bridge code and tests as well as a macOS test for behaviour
landed in landed in flutter#190330.

This sets AXNodeData::SetRestriction() from the Flutter semantics flags:
kDisabled when is_enabled is false, and kReadOnly for read-only text
fields. Previously the restriction was never populated, so disabled and
read-only nodes were indistinguishable from editable/enabled ones at the
platform layer.

We also add a macOS regression test that checks that a disabled,
editable text field is exposed as static text rather than a
FlutterTextField.

This guards the !GetData().IsReadOnlyOrDisabled() check in
FlutterPlatformNodeDelegateMac::Init. The GetData() override rewrites a
disabled field's role to kStaticText but leaves kEditableRoot set, so
IsTextField() stays true and the restriction check is what prevents an
editable FlutterTextField from being created. I've added this in
response to a bad comment by the Gemini review bot to ensure that this
doesn't regress.

Issue: flutter#184559
pull Bot pushed a commit to androiddevnotesforks/flutter that referenced this pull request Aug 3, 2026
…r#190353)

This is a partial re-land of
flutter#184501, which just lands the
common bridge code and tests as well as a macOS test for behaviour
landed in landed in flutter#190330.

This sets AXNodeData::SetRestriction() from the Flutter semantics flags:
kDisabled when is_enabled is false, and kReadOnly for read-only text
fields. Previously the restriction was never populated, so disabled and
read-only nodes were indistinguishable from editable/enabled ones at the
platform layer.

We also add a macOS regression test that checks that a disabled,
editable text field is exposed as static text rather than a
FlutterTextField.

This guards the !GetData().IsReadOnlyOrDisabled() check in
FlutterPlatformNodeDelegateMac::Init. The GetData() override rewrites a
disabled field's role to kStaticText but leaves kEditableRoot set, so
IsTextField() stays true and the restriction check is what prevents an
editable FlutterTextField from being created. I've added this in
response to a bad comment by the Gemini review bot to ensure that this
doesn't regress.

Issue: flutter#184559

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->


## Pre-launch Checklist

- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
cbracken added a commit to cbracken/flutter that referenced this pull request Aug 4, 2026
Relands flutter#184501, which was reverted in flutter#186492 because mapping read-only
text fields to Role::kTextField caused the macOS embedder to instantiate
a FlutterTextPlatformNode for them, producing detached accessibility
elements and test crashes.

The underlying macOS issue is now fixed by flutter#190330 and flutter#190353. This
relands the remaining bits of flutter#184501. In the common bits of the bridge,
read-only text fields now map to `Role::kTextField`. In the Windows
implementation `ENABLED_CHANGED` and `READONLY_CHANGED` are dispatched
as MSAA `kStateChanged` events and `AXPlatformNodeWin` now raises the
corresponding `UIA_IsEnabledPropertyId` change.

Issue: flutter#184559
cbracken added a commit to cbracken/flutter that referenced this pull request Aug 4, 2026
Relands flutter#184501, which was reverted in flutter#186492 because mapping read-only
text fields to Role::kTextField caused the macOS embedder to instantiate
a FlutterTextPlatformNode for them, producing detached accessibility
elements and test crashes.

The underlying macOS issue is now fixed by flutter#190330 and flutter#190353. This
relands the remaining bits of flutter#184501. In the common bits of the bridge,
read-only text fields now map to `Role::kTextField`. In the Windows
implementation `ENABLED_CHANGED` and `READONLY_CHANGED` are dispatched
as MSAA `kStateChanged` events and `AXPlatformNodeWin` now raises the
corresponding `UIA_IsEnabledPropertyId` change.

Issue: flutter#184559
pull Bot pushed a commit to AbhiShake1/flutter that referenced this pull request Aug 5, 2026
Relands flutter#184501, which was reverted in flutter#186492 because mapping read-only
text fields to Role::kTextField caused the macOS embedder to instantiate
a FlutterTextPlatformNode for them, producing detached accessibility
elements and test crashes.

The underlying macOS issue is now fixed by flutter#190330 and flutter#190353. This
relands the remaining bits of flutter#184501. In the common bits of the bridge,
read-only text fields now map to `Role::kTextField`. In the Windows
implementation `ENABLED_CHANGED` and `READONLY_CHANGED` are dispatched
as MSAA `kStateChanged` events and `AXPlatformNodeWin` now raises the
corresponding `UIA_IsEnabledPropertyId` change.

Fixes: flutter#184559


## Pre-launch Checklist

- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
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: desktop Running on desktop CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-macos Building on or for macOS specifically team-macos Owned by the macOS platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants