Fix mismatch between hit-test tree and traversal tree#186826
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces logic to filter SemanticsNode children in hit-test order, ensuring that nodes with missing traversal parents are excluded to maintain consistency with the traversal tree. The changes include refactoring _addToUpdate and debug methods to use the new filtering logic, along with a regression test. Feedback highlights a potential null pointer exception on detached nodes and suggests a performance optimization to avoid redundant list allocations when filtering is not necessary.
| List<SemanticsNode> _childrenInHitTestOrder() { | ||
| if (_children == null) { | ||
| return const <SemanticsNode>[]; | ||
| } | ||
| // If the child node is a traversal child, but the current node is | ||
| // not a traversal parent, it means the child node should be | ||
| // grafted to be a child of a traversal parent node that has the | ||
| // same identifier as the child. However, if the traversal parent is not | ||
| // in the tree, after grafting, the child will not be in the tree as well. | ||
| // To keep the hit-test tree match the traversal tree, we also skip this | ||
| // node in childrenInHitTestOrder. Otherwise, the number of nodes in two | ||
| // trees will be different and user might accidentally hit test a node | ||
| // that will never be traversed. | ||
| bool shouldSkipInHitTest(SemanticsNode child) { | ||
| if (kIsWeb) { | ||
| return false; | ||
| } | ||
| if (child._isTraversalChild && !_isTraversalParent) { | ||
| final SemanticsNode? traversalParent = | ||
| owner!._traversalParentNodes[child.getSemanticsData().traversalChildIdentifier]; | ||
| return traversalParent == null; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| final filtered = <SemanticsNode>[]; | ||
| for (final SemanticsNode child in _children!) { | ||
| if (!shouldSkipInHitTest(child)) { | ||
| filtered.add(child); | ||
| } | ||
| } | ||
| return filtered; | ||
| } |
There was a problem hiding this comment.
The current implementation of _childrenInHitTestOrder has two main issues:
- Potential Crash: The use of
owner!(line 4070) will throw a null pointer exception if theSemanticsNodeis detached from the semantics owner. This can happen during debugging (e.g., when callingtoStringDeep) or when generating diagnostic information for a detached node. - Performance Regression: It always allocates a new
Listand iterates through all children, even when no filtering is needed (which is the common case, especially when_isTraversalParentis true).
The suggested implementation below addresses both issues by adding a null check for owner, returning _children! directly when no filtering is possible, and using a lazy allocation pattern for the filtered list.
List<SemanticsNode> _childrenInHitTestOrder() {
if (_children == null) {
return const <SemanticsNode>[];
}
if (kIsWeb || _isTraversalParent) {
return _children!;
}
bool shouldSkipInHitTest(SemanticsNode child) {
if (child._isTraversalChild) {
final SemanticsOwner? owner = this.owner;
if (owner == null) {
return false;
}
final SemanticsNode? traversalParent =
owner._traversalParentNodes[child.getSemanticsData().traversalChildIdentifier];
return traversalParent == null;
}
return false;
}
List<SemanticsNode>? filtered;
for (int i = 0; i < _children!.length; i += 1) {
final SemanticsNode child = _children![i];
if (shouldSkipInHitTest(child)) {
filtered ??= _children!.take(i).toList();
} else {
filtered?.add(child);
}
}
return filtered ?? _children!;
}| return false; | ||
| } | ||
|
|
||
| final filtered = <SemanticsNode>[]; |
There was a problem hiding this comment.
Drive-by nit: use a for literal?
return [
for (final SemanticsNode child in _children ?? const <SemanticsNode>[])
if (!shouldSkipInHitTest(child))
child,
];
There was a problem hiding this comment.
If we want to clean this up, I would inverse the
shouldSkipInHitTest to shouldNotSkipInHitTest
and then
_children!.where(shouldNotSkipInHitTest).toList();
There was a problem hiding this comment.
may as well make the return type a iterable so you don't need a toList at the end
There was a problem hiding this comment.
I still kept it as List to keep consistent with _childrenInTraversalOrder. But let me know if Iterable is prefered.
| return false; | ||
| } | ||
|
|
||
| final filtered = <SemanticsNode>[]; |
There was a problem hiding this comment.
If we want to clean this up, I would inverse the
shouldSkipInHitTest to shouldNotSkipInHitTest
and then
_children!.where(shouldNotSkipInHitTest).toList();
0e23b89 to
4117678
Compare
4117678 to
5a5d194
Compare
chunhtai
left a comment
There was a problem hiding this comment.
LGTM, except one comment
|
Thanks for your reviews and suggestions! Addressing comments made the approvals be dismissed. Just requested your stamps again:)! |
|
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. |
Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
74d78df to
153111a
Compare
flutter/flutter@b05a9d7...54e199a 2026-06-01 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from jMR_VXQi07kAk8vbR... to q27k7_um1GvVrySZS... (flutter/flutter#187338) 2026-06-01 rmacnak@google.com Remove use of deprecated API related to removal of the VM isolate. (flutter/flutter#187013) 2026-06-01 116356835+AbdeMohlbi@users.noreply.github.com Improve `dependOnInheritedWidgetOfExactType` documentation to explain why it is bad to use it in initState (flutter/flutter#186216) 2026-06-01 chris@bracken.jp Revert "Move dart-lang/ai to a top level third party dependency in en… (flutter/flutter#187370) 2026-05-30 jakemac@google.com Move dart-lang/ai to a top level third party dependency in engine (flutter/flutter#187268) 2026-05-30 evanwall@buffalo.edu add sdf golden variants for OpenGL (flutter/flutter#187246) 2026-05-30 engine-flutter-autoroll@skia.org Roll Skia from dc01525ac468 to 0aee4675e0ad (6 revisions) (flutter/flutter#187334) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from c480ba2eb2eb to dc01525ac468 (4 revisions) (flutter/flutter#187317) 2026-05-29 jason-simmons@users.noreply.github.com Remove the Y coordinate flip workaround in the Material stretch effect shader now that it is no longer required by the Impeller GLES back end (flutter/flutter#187247) 2026-05-29 bkonyi@google.com [flutter_tools, devicelab] Fix filesystem safety guard for symlinked temp directories (flutter/flutter#187320) 2026-05-29 30870216+gaaclarke@users.noreply.github.com Brings linux tests out of bringup. (flutter/flutter#187271) 2026-05-29 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#187321) 2026-05-29 engine-flutter-autoroll@skia.org Roll Fuchsia GN SDK from SEfYx3xgueX3aFAY3... to oOAcFhkoE2_-Sy67z... (flutter/flutter#187310) 2026-05-29 36861262+QuncCccccc@users.noreply.github.com Fix mismatch between hit-test tree and traversal tree (flutter/flutter#186826) 2026-05-29 jason-simmons@users.noreply.github.com [Impeller] Ensure that the TextureGLES destructor cleans up all objects that it holds including the sync fence (flutter/flutter#187216) 2026-05-29 engine-flutter-autoroll@skia.org Roll Packages from 10cbdc5 to e930ced (3 revisions) (flutter/flutter#187306) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from d9d6b440c4e7 to c480ba2eb2eb (1 revision) (flutter/flutter#187305) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC stuartmorgan@google.com,tarrinneal@google.com 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 To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…r#11816) flutter/flutter@b05a9d7...54e199a 2026-06-01 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from jMR_VXQi07kAk8vbR... to q27k7_um1GvVrySZS... (flutter/flutter#187338) 2026-06-01 rmacnak@google.com Remove use of deprecated API related to removal of the VM isolate. (flutter/flutter#187013) 2026-06-01 116356835+AbdeMohlbi@users.noreply.github.com Improve `dependOnInheritedWidgetOfExactType` documentation to explain why it is bad to use it in initState (flutter/flutter#186216) 2026-06-01 chris@bracken.jp Revert "Move dart-lang/ai to a top level third party dependency in en… (flutter/flutter#187370) 2026-05-30 jakemac@google.com Move dart-lang/ai to a top level third party dependency in engine (flutter/flutter#187268) 2026-05-30 evanwall@buffalo.edu add sdf golden variants for OpenGL (flutter/flutter#187246) 2026-05-30 engine-flutter-autoroll@skia.org Roll Skia from dc01525ac468 to 0aee4675e0ad (6 revisions) (flutter/flutter#187334) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from c480ba2eb2eb to dc01525ac468 (4 revisions) (flutter/flutter#187317) 2026-05-29 jason-simmons@users.noreply.github.com Remove the Y coordinate flip workaround in the Material stretch effect shader now that it is no longer required by the Impeller GLES back end (flutter/flutter#187247) 2026-05-29 bkonyi@google.com [flutter_tools, devicelab] Fix filesystem safety guard for symlinked temp directories (flutter/flutter#187320) 2026-05-29 30870216+gaaclarke@users.noreply.github.com Brings linux tests out of bringup. (flutter/flutter#187271) 2026-05-29 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#187321) 2026-05-29 engine-flutter-autoroll@skia.org Roll Fuchsia GN SDK from SEfYx3xgueX3aFAY3... to oOAcFhkoE2_-Sy67z... (flutter/flutter#187310) 2026-05-29 36861262+QuncCccccc@users.noreply.github.com Fix mismatch between hit-test tree and traversal tree (flutter/flutter#186826) 2026-05-29 jason-simmons@users.noreply.github.com [Impeller] Ensure that the TextureGLES destructor cleans up all objects that it holds including the sync fence (flutter/flutter#187216) 2026-05-29 engine-flutter-autoroll@skia.org Roll Packages from 10cbdc5 to e930ced (3 revisions) (flutter/flutter#187306) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from d9d6b440c4e7 to c480ba2eb2eb (1 revision) (flutter/flutter#187305) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC stuartmorgan@google.com,tarrinneal@google.com 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 To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
This PR is to update hit-test tree to make sure traversal tree and hit-test tree have the same number of nodes, even though they can have different shapes. In OverlayPortal, if the child node is a traversal child, but the current node is not a traversal parent, it means the child node should be grafted to be a child of a traversal parent node that has the same identifier as the child. However, if the traversal parent is not in the tree, after grafting, the child will not be in the tree as well. To keep the hit-test tree match the traversal tree, we also skip this node in childrenInHitTestOrder. Otherwise, the number of nodes in two trees will be different and user might accidentally hit test a node that will never be traversed. Fixes flutter#184898 ## 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. --------- Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
This PR is to update hit-test tree to make sure traversal tree and hit-test tree have the same number of nodes, even though they can have different shapes.
In OverlayPortal, if the child node is a traversal child, but the current node is not a traversal parent, it means the child node should be grafted to be a child of a traversal parent node that has the same identifier as the child. However, if the traversal parent is not in the tree, after grafting, the child will not be in the tree as well. To keep the hit-test tree match the traversal tree, we also skip this node in childrenInHitTestOrder. Otherwise, the number of nodes in two trees will be different and user might accidentally hit test a node that will never be traversed.
Fixes #184898
Pre-launch Checklist
///).