@@ -1358,6 +1358,33 @@ class SemanticsObject {
13581358 /// The dom element of this semantics object.
13591359 DomElement get element => semanticRole! .element;
13601360
1361+ /// Returns the HTML element that contains the HTML elements of direct
1362+ /// children of this object.
1363+ ///
1364+ /// The element is created lazily. When the child list is empty this element
1365+ /// is not created. This is necessary for "aria-label" to function correctly.
1366+ /// The browser will ignore the [label] of HTML element that contain child
1367+ /// elements.
1368+ DomElement ? getOrCreateChildContainer () {
1369+ if (_childContainerElement == null ) {
1370+ _childContainerElement = createDomElement ('flt-semantics-container' );
1371+ _childContainerElement! .style
1372+ ..position = 'absolute'
1373+ // Ignore pointer events on child container so that platform views
1374+ // behind it can be reached.
1375+ ..pointerEvents = 'none' ;
1376+ element.append (_childContainerElement! );
1377+ }
1378+ return _childContainerElement;
1379+ }
1380+
1381+ /// The element that contains the elements belonging to the child semantics
1382+ /// nodes.
1383+ ///
1384+ /// This element is used to correct for [_rect] offsets. It is only non-`null`
1385+ /// when there are non-zero children (i.e. when [hasChildren] is `true` ).
1386+ DomElement ? _childContainerElement;
1387+
13611388 /// The parent of this semantics object.
13621389 ///
13631390 /// This value is not final until the tree is finalized. It is not safe to
@@ -1655,15 +1682,22 @@ class SemanticsObject {
16551682 // Trivial case: remove all children.
16561683 if (_childrenInHitTestOrder == null || _childrenInHitTestOrder! .isEmpty) {
16571684 if (_currentChildrenInRenderOrder == null || _currentChildrenInRenderOrder! .isEmpty) {
1685+ // A container element must not have been created when child list is empty.
1686+ assert (_childContainerElement == null );
16581687 _currentChildrenInRenderOrder = null ;
16591688 return ;
16601689 }
16611690
1691+ // A container element must have been created when child list is not empty.
1692+ assert (_childContainerElement != null );
1693+
16621694 // Remove all children from this semantics object.
16631695 final int len = _currentChildrenInRenderOrder! .length;
16641696 for (int i = 0 ; i < len; i++ ) {
16651697 owner._detachObject (_currentChildrenInRenderOrder! [i].id);
16661698 }
1699+ _childContainerElement! .remove ();
1700+ _childContainerElement = null ;
16671701 _currentChildrenInRenderOrder = null ;
16681702 return ;
16691703 }
@@ -1672,6 +1706,7 @@ class SemanticsObject {
16721706 final Int32List childrenInTraversalOrder = _childrenInTraversalOrder! ;
16731707 final Int32List childrenInHitTestOrder = _childrenInHitTestOrder! ;
16741708 final int childCount = childrenInHitTestOrder.length;
1709+ final DomElement ? containerElement = getOrCreateChildContainer ();
16751710
16761711 assert (childrenInTraversalOrder.length == childrenInHitTestOrder.length);
16771712
@@ -1703,7 +1738,7 @@ class SemanticsObject {
17031738 // Trivial case: previous list was empty => just populate the container.
17041739 if (_currentChildrenInRenderOrder == null || _currentChildrenInRenderOrder! .isEmpty) {
17051740 for (final SemanticsObject child in childrenInRenderOrder) {
1706- element .append (child.element);
1741+ containerElement ! .append (child.element);
17071742 owner._attachObject (parent: this , child: child);
17081743 }
17091744 _currentChildrenInRenderOrder = childrenInRenderOrder;
@@ -1787,9 +1822,9 @@ class SemanticsObject {
17871822 final SemanticsObject child = childrenInRenderOrder[i];
17881823 if (! stationaryIds.contains (child.id)) {
17891824 if (refNode == null ) {
1790- element .append (child.element);
1825+ containerElement ! .append (child.element);
17911826 } else {
1792- element .insertBefore (child.element, refNode);
1827+ containerElement ! .insertBefore (child.element, refNode);
17931828 }
17941829 owner._attachObject (parent: this , child: child);
17951830 } else {
@@ -1955,6 +1990,10 @@ class SemanticsObject {
19551990
19561991 // Reparent element.
19571992 if (previousElement != element) {
1993+ final DomElement ? container = _childContainerElement;
1994+ if (container != null ) {
1995+ element.append (container);
1996+ }
19581997 final DomElement ? parent = previousElement? .parent;
19591998 if (parent != null ) {
19601999 parent.insertBefore (element, previousElement);
@@ -2040,74 +2079,60 @@ class SemanticsObject {
20402079 /// Indicates whether the node is currently expanded.
20412080 bool get isExpanded => hasFlag (ui.SemanticsFlag .isExpanded);
20422081
2043- /// Role-specific adjustment of the vertical position of the children .
2082+ /// Role-specific adjustment of the vertical position of the child container .
20442083 ///
20452084 /// This is used, for example, by the [SemanticScrollable] to compensate for the
20462085 /// `scrollTop` offset in the DOM.
20472086 ///
20482087 /// This field must not be null.
2049- double verticalScrollAdjustment = 0.0 ;
2088+ double verticalContainerAdjustment = 0.0 ;
20502089
2051- /// Role-specific adjustment of the horizontal position of children.
2090+ /// Role-specific adjustment of the horizontal position of the child
2091+ /// container.
20522092 ///
20532093 /// This is used, for example, by the [SemanticScrollable] to compensate for the
20542094 /// `scrollLeft` offset in the DOM.
20552095 ///
20562096 /// This field must not be null.
2057- double horizontalScrollAdjustment = 0.0 ;
2058-
2059- double verticalAdjustmentFromParent = 0.0 ;
2060- double horizontalAdjustmentFromParent = 0.0 ;
2097+ double horizontalContainerAdjustment = 0.0 ;
20612098
20622099 /// Computes the size and position of [element] and, if this element
2063- /// [hasChildren] , computes the parent adjustment for each child .
2100+ /// [hasChildren] , of [getOrCreateChildContainer] .
20642101 void recomputePositionAndSize () {
20652102 element.style
20662103 ..width = '${_rect !.width }px'
20672104 ..height = '${_rect !.height }px' ;
20682105
2106+ final DomElement ? containerElement = hasChildren ? getOrCreateChildContainer () : null ;
2107+
20692108 final bool hasZeroRectOffset = _rect! .top == 0.0 && _rect! .left == 0.0 ;
20702109 final Float32List ? transform = _transform;
20712110 final bool hasIdentityTransform =
20722111 transform == null || isIdentityFloat32ListTransform (transform);
20732112
2074- // If this node has children, we need to compensate for the parent's rect and
2075- // pass down the scroll adjustments.
2076- if (hasChildren) {
2077- final double translateX = - _rect! .left + horizontalScrollAdjustment;
2078- final double translateY = - _rect! .top + verticalScrollAdjustment;
2079-
2080- for (final childIndex in _childrenInTraversalOrder! ) {
2081- final child = owner._semanticsTree[childIndex];
2082- if (child == null ) {
2083- continue ;
2084- }
2085- child.horizontalAdjustmentFromParent = translateX;
2086- child.verticalAdjustmentFromParent = translateY;
2087- }
2088- }
2089-
20902113 if (hasZeroRectOffset &&
20912114 hasIdentityTransform &&
2092- verticalAdjustmentFromParent == 0.0 &&
2093- horizontalAdjustmentFromParent == 0.0 ) {
2115+ verticalContainerAdjustment == 0.0 &&
2116+ horizontalContainerAdjustment == 0.0 ) {
20942117 _clearSemanticElementTransform (element);
2118+ if (containerElement != null ) {
2119+ _clearSemanticElementTransform (containerElement);
2120+ }
20952121 return ;
20962122 }
20972123
20982124 late Matrix4 effectiveTransform;
20992125 bool effectiveTransformIsIdentity = true ;
2100-
2101- final double left = _rect! .left + horizontalAdjustmentFromParent;
2102- final double top = _rect! .top + verticalAdjustmentFromParent;
2103-
2104- if (left != 0.0 || top != 0.0 ) {
2126+ if (! hasZeroRectOffset) {
21052127 if (transform == null ) {
2128+ final double left = _rect! .left;
2129+ final double top = _rect! .top;
21062130 effectiveTransform = Matrix4 .translationValues (left, top, 0.0 );
2107- effectiveTransformIsIdentity = false ;
2131+ effectiveTransformIsIdentity = left == 0.0 && top == 0.0 ;
21082132 } else {
21092133 // Clone to avoid mutating _transform.
2110- effectiveTransform = Matrix4 .fromFloat32List (transform).clone ()..translate (left, top);
2134+ effectiveTransform =
2135+ Matrix4 .fromFloat32List (transform).clone ()..translate (_rect! .left, _rect! .top);
21112136 effectiveTransformIsIdentity = effectiveTransform.isIdentity ();
21122137 }
21132138 } else if (! hasIdentityTransform) {
@@ -2122,16 +2147,19 @@ class SemanticsObject {
21222147 } else {
21232148 _clearSemanticElementTransform (element);
21242149 }
2125- }
21262150
2127- /// Computes the size and position of children.
2128- void updateChildrenPositionAndSize () {
2129- for (final childIndex in _childrenInTraversalOrder! ) {
2130- final child = owner._semanticsTree[childIndex];
2131- if (child == null ) {
2132- continue ;
2151+ if (containerElement != null ) {
2152+ if (! hasZeroRectOffset ||
2153+ verticalContainerAdjustment != 0.0 ||
2154+ horizontalContainerAdjustment != 0.0 ) {
2155+ final double translateX = - _rect! .left + horizontalContainerAdjustment;
2156+ final double translateY = - _rect! .top + verticalContainerAdjustment;
2157+ containerElement.style
2158+ ..top = '${translateY }px'
2159+ ..left = '${translateX }px' ;
2160+ } else {
2161+ _clearSemanticElementTransform (containerElement);
21332162 }
2134- child.recomputePositionAndSize ();
21352163 }
21362164 }
21372165
@@ -2691,7 +2719,7 @@ class EngineSemanticsOwner {
26912719 removals.add (node);
26922720 } else {
26932721 assert (node._parent == parent);
2694- assert (node.element.parentNode == parent.element );
2722+ assert (node.element.parentNode == parent._childContainerElement );
26952723 }
26962724 return true ;
26972725 });
@@ -2800,9 +2828,6 @@ class EngineSemanticsOwner {
28002828 final SemanticsObject object = _semanticsTree[nodeUpdate.id]! ;
28012829 object.updateChildren ();
28022830 object._dirtyFields = 0 ;
2803-
2804- object.recomputePositionAndSize ();
2805- object.updateChildrenPositionAndSize ();
28062831 }
28072832
28082833 final SemanticsObject root = _semanticsTree[0 ]! ;
@@ -2838,6 +2863,9 @@ AFTER: $description
28382863 // Dirty fields should be cleared after the tree has been finalized.
28392864 assert (object._dirtyFields == 0 );
28402865
2866+ // Make sure a child container is created only when there are children.
2867+ assert (object._childContainerElement == null || object.hasChildren);
2868+
28412869 // Ensure child ID list is consistent with the parent-child
28422870 // relationship of the semantics tree.
28432871 if (object._childrenInTraversalOrder != null ) {
0 commit comments