Repository navigation
Do not merge semantics configs anchoring different traversal parents - #190344
Nael-Sayegh wants to merge 2 commits into
Conversation
Fixes flutter#182444 `SemanticsConfiguration.isCompatibleWith` guarded `_traversalChildIdentifier` but not `_traversalParentIdentifier`, so two sibling anchors -- for example two adjacent `Tooltip`s, each backed by its own `OverlayPortal` -- were considered mergeable. `absorb` then kept only the first identifier, since it assigns with `??=`, and silently dropped the second. The dropped identifier is the only handle its traversal children have to find their parent, so the second portal's overlay child resolved no traversal parent and was serialized as an orphan. On Windows `ui::AXTree::Unserialize` rejects the whole update with "N will not be in the tree and is not the new root", after which the accessibility tree stays frozen for the life of the window and NVDA can no longer traverse the app. A `ListView` is what forces the merge in practice: each list item is wrapped in an `IndexedSemantics`, which absorbs everything it contains into a single node. The same two anchors in a bare `Row` stay separate, which is why the issue only reproduces inside a list. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request modifies SemanticsConfiguration to prevent the merging of two configurations that anchor different traversal parents by ensuring they are not considered compatible if they have different non-null _traversalParentIdentifier values. A corresponding regression test has been added to verify this behavior. I have no feedback to provide.
|
@Nael-Sayegh before we can move forward with this PR you will need to sign the CLA. Thank you! |
| // two sibling OverlayPortals) must not be merged: [absorb] can only keep | ||
| // one `traversalParentIdentifier`, so the other one would be dropped and | ||
| // its traversal children would end up grafted to nothing. | ||
| if (_traversalParentIdentifier != null && |
There was a problem hiding this comment.
We should follow the same _traversalChildIdentifier that just check for inequilty, this is to ensure they don't merge up at all to create weird situation
|
Hi @Nael-Sayegh, friendly reminder that you need to sign the CLA before moving forward with the PR. |
|
This pull request is being closed because it has not been updated in the last 21 days after a request for more information. If you are still working on this, please feel free to reopen it or file a new pull request. Thanks for your contribution. |
Fixes #182444
The bug
SemanticsConfiguration.isCompatibleWithguards_traversalChildIdentifierbut not_traversalParentIdentifier:So two sibling anchors — for example two adjacent
Tooltips, each backed by its ownOverlayPortal— are considered mergeable.absorbthen keeps only the first identifier, because it assigns with??=:The second one is silently dropped. Since that identifier is the only handle its traversal children have to find their parent, the second portal's overlay child resolves no traversal parent (
traversalParent = -1) and is serialized as an orphan — no node claims it as a child.On Windows
ui::AXTree::Unserializethen rejects the entire update withN will not be in the tree and is not the new root(ax_tree.cc:1346). After that the accessibility tree stays frozen for the life of the window, so NVDA keeps presenting a stale screen and the app becomes untraversable.Note the engine cannot defend against this:
accessibility_bridge.cccopieschildren_in_traversal_orderstraight intoAXNodeData::child_idswithout validating or pruning, so an inconsistentSemanticsUpdateis passed through as-is.Why a
ListViewis needed to reproduceEach
ListViewitem is wrapped in anIndexedSemantics, which absorbs everything it contains into a single node — that is what forces the two anchors together. The same two anchors in a bareRowstay separate, which is why the issue only reproduces inside a list. NeitherTooltipnor hovering is actually required: two bareSemantics(traversalParentIdentifier: ...)widgets in a list item are enough.This also corrects the root-cause analysis in the issue itself, which attributes the bug to detachment ordering during
sendSemanticsUpdate. That hypothesis is not what happens here — the identifier never reaches a node in the first place. The two related fixes that landed in May (#186118, #186826) do not cover this case; I verified the issue still reproduces onmaster.The fix
Refuse to merge two configurations that each anchor a different, non-null traversal parent. The guard is deliberately narrower than the
traversalChildIdentifierone above it: a plain!=would also stop an anchor from merging with ordinary unannotated content, which would change tree shape broadly for no benefit.Tests
Added
sibling traversal parents are not merged into a single nodeinpackages/flutter/test/widgets/semantics_test.dart, next to the existing grafting tests. It fails without the fix:and passes with it.
Also verified against the issue's own reproduction, using a harness that replays the engine's bridge contract in Dart (every serialized node must end up reachable from the root): fails on
stable3.44.8 and on unpatchedmasterwith the exact production error text, passes with the fix, including a 5-tooltip variant.No regressions:
test/widgets(7174 passed),test/material(7973 passed), plustest/semantics,test/widgets/semantics_test.dart,test/widgets/overlay_portal_test.dartandtest/material/tooltip_test.dart.Pre-launch Checklist
Co-Authored-Byon the commit); the analysis, the reproduction and the test results above were verified by running them.///).