Skip to content

Refactor OverlayPortal semantics #173005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

QuncCccccc
Copy link
Contributor

Fixes #163576

This PR refactored the grafting part on OverlayPortal. Originally, the semantics tree of OverlayPortal was constructed/grafted in render object phase to make sure the correctness of the traversal order. However this resulted wrong hit-test order and the issue surfaced on web. With the fact that on web we are not able to graft/correct hit-test order tree, this PR:

  • Reverts the original grafting of the OverlayPortal so the hit-test order is always correct.
  • Then, we adds the grafting and updates the traversal order when we send childrenInTraversalOrder to engine.
  • Updating childrenInTraversalOrder causes it have different length from the length of childrenInHitTestOrder and wrong hit-test transform of the OverlayPortal children because when the transform is calculated, it assumes a correct traversal order. To fix these issues, this PR also:
    • recalculates the transform for OverlayPortal children.
    • adds hitTestTransform property and pass it to Android engine.
    • skip grafting for web because it assumes the same length of childrenInTraversalOrder and childrenInHitTestOrder.
    • added grafting by using ARIA-owns in web engine to fix the traversal order.

Pre-launch Checklist

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

Copy link
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions github-actions bot added platform-android Android applications specifically framework flutter/packages/flutter repository. See also f: labels. engine flutter/engine repository. See also e: labels. f: material design flutter/packages/flutter/material repository. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-web Web applications specifically team-android Owned by Android platform team labels Jul 30, 2025
@QuncCccccc QuncCccccc force-pushed the remove_grafting_for_web_only_copy branch from c365ca5 to c305e6b Compare July 31, 2025 01:06
@QuncCccccc QuncCccccc requested a review from chunhtai July 31, 2025 06:57
@@ -683,6 +683,7 @@ class SemanticsUpdateBuilder {
required int platformViewId,
required int scrollChildren,
required int scrollIndex,
required int? overlayPortalParent,
Copy link
Contributor

Choose a reason for hiding this comment

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

this needs a better name. something like traversalOwner. overlay is a widget layer concept

@@ -1816,6 +1816,7 @@ abstract class SemanticsUpdateBuilder {
required int platformViewId,
required int scrollChildren,
required int scrollIndex,
required int overlayPortalParent,
Copy link
Contributor

Choose a reason for hiding this comment

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

somewhere in the doc should mention what this paremeter does and why it is here and that is should only be used in web and why

if (overlayPortalParent != -1) {
SemanticsObject? parent = owner._semanticsTree[overlayPortalParent!];
if (parent != null && parent.semanticRole != null) {
parent.element.setAttribute('aria-owns', '$kFlutterSemanticNodePrefix${id}');
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this get cleaned up if the overlayPotalParent change to null?

///
/// If this node indicates an overlay portal child, this is its overlay portal
/// parent node in traversal order. Otherwise, it is the same as [parent].
SemanticsNode? get semanticsParent => _semanticsParent ?? parent;
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs a better name as something along the line of traversalOwner or traversalParent

@@ -3039,6 +3053,9 @@ class SemanticsNode with DiagnosticableTreeMixin {
String get identifier => _identifier;
String _identifier = _kEmptyConfig.identifier;

bool get _isOverlayPortalParent => getSemanticsData().identifier.endsWith('parent');
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems a bit easy to collide with real world usage. I think we should create a new property for setting overlay parent of a given semantics node.

Copy link
Contributor

Choose a reason for hiding this comment

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

also overlayportal is a widget layer concept, the name here should be more generic. imagine if in the future we have other non-overlay use case that also need to leverage this mechanism. The name here will be confusing

@@ -3576,8 +3593,60 @@ class SemanticsNode with DiagnosticableTreeMixin {
static final Int32List _kEmptyCustomSemanticsActionsList = Int32List(0);
static final Float64List _kIdentityTransform = _initIdentityTransform();

static Matrix4 _computeChildGeometry({
Copy link
Contributor

Choose a reason for hiding this comment

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

since now the grafting only happens in semantics node's hittest transofmr, can you simplify the logic in _SemanticsGeomtry to make sure parent is always an ancestor of child?

also this needs a better name.

childrenInHitTestOrder = _kEmptyChildList;
if (_isOverlayPortalParent) {
final List<SemanticsNode> sortedChildren = _childrenInTraversalOrder();
childrenInTraversalOrder = Int32List(sortedChildren.length);
Copy link
Contributor

Choose a reason for hiding this comment

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

consider turn this into a method to be reuse here and below

for (final SemanticsNode node in visitedNodes) {
assert(node.parent?._dirty != true); // could be null (no parent) or false (not dirty)
final String identifier = node.identifier.split(' ')[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit scary given people may have space in their identifier

@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
if (childIdentifier != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

oh nice, so we won't have parentData issue then

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) engine flutter/engine repository. See also e: labels. f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. platform-android Android applications specifically platform-web Web applications specifically team-android Owned by Android platform team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Web] SemanticsBinding.instance.ensureSemantics() is propagating click on overlapping widgets
2 participants