diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 08e96af35da..44beba5ae99 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -70,7 +70,6 @@ class SemanticScrollable extends SemanticRole { final bool doScrollForward = _domScrollPosition > _effectiveNeutralScrollPosition; _neutralizeDomScrollPosition(); semanticsObject.recomputePositionAndSize(); - semanticsObject.updateChildrenPositionAndSize(); final int semanticsId = semanticsObject.id; if (doScrollForward) { @@ -132,7 +131,6 @@ class SemanticScrollable extends SemanticRole { semanticsObject.owner.addOneTimePostUpdateCallback(() { _neutralizeDomScrollPosition(); semanticsObject.recomputePositionAndSize(); - semanticsObject.updateChildrenPositionAndSize(); }); if (_scrollListener == null) { @@ -205,8 +203,8 @@ class SemanticScrollable extends SemanticRole { // Read back because the effective value depends on the amount of content. _effectiveNeutralScrollPosition = element.scrollTop.toInt(); semanticsObject - ..verticalScrollAdjustment = _effectiveNeutralScrollPosition.toDouble() - ..horizontalScrollAdjustment = 0.0; + ..verticalContainerAdjustment = _effectiveNeutralScrollPosition.toDouble() + ..horizontalContainerAdjustment = 0.0; } else { // Place the _scrollOverflowElement at the end of the content and // make sure that when we neutralize the scrolling position, @@ -221,8 +219,8 @@ class SemanticScrollable extends SemanticRole { // Read back because the effective value depends on the amount of content. _effectiveNeutralScrollPosition = element.scrollLeft.toInt(); semanticsObject - ..verticalScrollAdjustment = 0.0 - ..horizontalScrollAdjustment = _effectiveNeutralScrollPosition.toDouble(); + ..verticalContainerAdjustment = 0.0 + ..horizontalContainerAdjustment = _effectiveNeutralScrollPosition.toDouble(); } } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart index a2c3aae0abf..4e23e4baf67 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -1358,6 +1358,33 @@ class SemanticsObject { /// The dom element of this semantics object. DomElement get element => semanticRole!.element; + /// Returns the HTML element that contains the HTML elements of direct + /// children of this object. + /// + /// The element is created lazily. When the child list is empty this element + /// is not created. This is necessary for "aria-label" to function correctly. + /// The browser will ignore the [label] of HTML element that contain child + /// elements. + DomElement? getOrCreateChildContainer() { + if (_childContainerElement == null) { + _childContainerElement = createDomElement('flt-semantics-container'); + _childContainerElement!.style + ..position = 'absolute' + // Ignore pointer events on child container so that platform views + // behind it can be reached. + ..pointerEvents = 'none'; + element.append(_childContainerElement!); + } + return _childContainerElement; + } + + /// The element that contains the elements belonging to the child semantics + /// nodes. + /// + /// This element is used to correct for [_rect] offsets. It is only non-`null` + /// when there are non-zero children (i.e. when [hasChildren] is `true`). + DomElement? _childContainerElement; + /// The parent of this semantics object. /// /// This value is not final until the tree is finalized. It is not safe to @@ -1655,15 +1682,22 @@ class SemanticsObject { // Trivial case: remove all children. if (_childrenInHitTestOrder == null || _childrenInHitTestOrder!.isEmpty) { if (_currentChildrenInRenderOrder == null || _currentChildrenInRenderOrder!.isEmpty) { + // A container element must not have been created when child list is empty. + assert(_childContainerElement == null); _currentChildrenInRenderOrder = null; return; } + // A container element must have been created when child list is not empty. + assert(_childContainerElement != null); + // Remove all children from this semantics object. final int len = _currentChildrenInRenderOrder!.length; for (int i = 0; i < len; i++) { owner._detachObject(_currentChildrenInRenderOrder![i].id); } + _childContainerElement!.remove(); + _childContainerElement = null; _currentChildrenInRenderOrder = null; return; } @@ -1672,6 +1706,7 @@ class SemanticsObject { final Int32List childrenInTraversalOrder = _childrenInTraversalOrder!; final Int32List childrenInHitTestOrder = _childrenInHitTestOrder!; final int childCount = childrenInHitTestOrder.length; + final DomElement? containerElement = getOrCreateChildContainer(); assert(childrenInTraversalOrder.length == childrenInHitTestOrder.length); @@ -1703,7 +1738,7 @@ class SemanticsObject { // Trivial case: previous list was empty => just populate the container. if (_currentChildrenInRenderOrder == null || _currentChildrenInRenderOrder!.isEmpty) { for (final SemanticsObject child in childrenInRenderOrder) { - element.append(child.element); + containerElement!.append(child.element); owner._attachObject(parent: this, child: child); } _currentChildrenInRenderOrder = childrenInRenderOrder; @@ -1787,9 +1822,9 @@ class SemanticsObject { final SemanticsObject child = childrenInRenderOrder[i]; if (!stationaryIds.contains(child.id)) { if (refNode == null) { - element.append(child.element); + containerElement!.append(child.element); } else { - element.insertBefore(child.element, refNode); + containerElement!.insertBefore(child.element, refNode); } owner._attachObject(parent: this, child: child); } else { @@ -1955,6 +1990,10 @@ class SemanticsObject { // Reparent element. if (previousElement != element) { + final DomElement? container = _childContainerElement; + if (container != null) { + element.append(container); + } final DomElement? parent = previousElement?.parent; if (parent != null) { parent.insertBefore(element, previousElement); @@ -2040,74 +2079,60 @@ class SemanticsObject { /// Indicates whether the node is currently expanded. bool get isExpanded => hasFlag(ui.SemanticsFlag.isExpanded); - /// Role-specific adjustment of the vertical position of the children. + /// Role-specific adjustment of the vertical position of the child container. /// /// This is used, for example, by the [SemanticScrollable] to compensate for the /// `scrollTop` offset in the DOM. /// /// This field must not be null. - double verticalScrollAdjustment = 0.0; + double verticalContainerAdjustment = 0.0; - /// Role-specific adjustment of the horizontal position of children. + /// Role-specific adjustment of the horizontal position of the child + /// container. /// /// This is used, for example, by the [SemanticScrollable] to compensate for the /// `scrollLeft` offset in the DOM. /// /// This field must not be null. - double horizontalScrollAdjustment = 0.0; - - double verticalAdjustmentFromParent = 0.0; - double horizontalAdjustmentFromParent = 0.0; + double horizontalContainerAdjustment = 0.0; /// Computes the size and position of [element] and, if this element - /// [hasChildren], computes the parent adjustment for each child. + /// [hasChildren], of [getOrCreateChildContainer]. void recomputePositionAndSize() { element.style ..width = '${_rect!.width}px' ..height = '${_rect!.height}px'; + final DomElement? containerElement = hasChildren ? getOrCreateChildContainer() : null; + final bool hasZeroRectOffset = _rect!.top == 0.0 && _rect!.left == 0.0; final Float32List? transform = _transform; final bool hasIdentityTransform = transform == null || isIdentityFloat32ListTransform(transform); - // If this node has children, we need to compensate for the parent's rect and - // pass down the scroll adjustments. - if (hasChildren) { - final double translateX = -_rect!.left + horizontalScrollAdjustment; - final double translateY = -_rect!.top + verticalScrollAdjustment; - - for (final childIndex in _childrenInTraversalOrder!) { - final child = owner._semanticsTree[childIndex]; - if (child == null) { - continue; - } - child.horizontalAdjustmentFromParent = translateX; - child.verticalAdjustmentFromParent = translateY; - } - } - if (hasZeroRectOffset && hasIdentityTransform && - verticalAdjustmentFromParent == 0.0 && - horizontalAdjustmentFromParent == 0.0) { + verticalContainerAdjustment == 0.0 && + horizontalContainerAdjustment == 0.0) { _clearSemanticElementTransform(element); + if (containerElement != null) { + _clearSemanticElementTransform(containerElement); + } return; } late Matrix4 effectiveTransform; bool effectiveTransformIsIdentity = true; - - final double left = _rect!.left + horizontalAdjustmentFromParent; - final double top = _rect!.top + verticalAdjustmentFromParent; - - if (left != 0.0 || top != 0.0) { + if (!hasZeroRectOffset) { if (transform == null) { + final double left = _rect!.left; + final double top = _rect!.top; effectiveTransform = Matrix4.translationValues(left, top, 0.0); - effectiveTransformIsIdentity = false; + effectiveTransformIsIdentity = left == 0.0 && top == 0.0; } else { // Clone to avoid mutating _transform. - effectiveTransform = Matrix4.fromFloat32List(transform).clone()..translate(left, top); + effectiveTransform = + Matrix4.fromFloat32List(transform).clone()..translate(_rect!.left, _rect!.top); effectiveTransformIsIdentity = effectiveTransform.isIdentity(); } } else if (!hasIdentityTransform) { @@ -2122,16 +2147,19 @@ class SemanticsObject { } else { _clearSemanticElementTransform(element); } - } - /// Computes the size and position of children. - void updateChildrenPositionAndSize() { - for (final childIndex in _childrenInTraversalOrder!) { - final child = owner._semanticsTree[childIndex]; - if (child == null) { - continue; + if (containerElement != null) { + if (!hasZeroRectOffset || + verticalContainerAdjustment != 0.0 || + horizontalContainerAdjustment != 0.0) { + final double translateX = -_rect!.left + horizontalContainerAdjustment; + final double translateY = -_rect!.top + verticalContainerAdjustment; + containerElement.style + ..top = '${translateY}px' + ..left = '${translateX}px'; + } else { + _clearSemanticElementTransform(containerElement); } - child.recomputePositionAndSize(); } } @@ -2691,7 +2719,7 @@ class EngineSemanticsOwner { removals.add(node); } else { assert(node._parent == parent); - assert(node.element.parentNode == parent.element); + assert(node.element.parentNode == parent._childContainerElement); } return true; }); @@ -2800,9 +2828,6 @@ class EngineSemanticsOwner { final SemanticsObject object = _semanticsTree[nodeUpdate.id]!; object.updateChildren(); object._dirtyFields = 0; - - object.recomputePositionAndSize(); - object.updateChildrenPositionAndSize(); } final SemanticsObject root = _semanticsTree[0]!; @@ -2838,6 +2863,9 @@ AFTER: $description // Dirty fields should be cleared after the tree has been finalized. assert(object._dirtyFields == 0); + // Make sure a child container is created only when there are children. + assert(object._childContainerElement == null || object.hasChildren); + // Ensure child ID list is consistent with the parent-child // relationship of the semantics tree. if (object._childrenInTraversalOrder != null) { diff --git a/engine/src/flutter/lib/web_ui/test/common/matchers.dart b/engine/src/flutter/lib/web_ui/test/common/matchers.dart index e12d1a90251..7537b40cfd8 100644 --- a/engine/src/flutter/lib/web_ui/test/common/matchers.dart +++ b/engine/src/flutter/lib/web_ui/test/common/matchers.dart @@ -274,6 +274,7 @@ class HtmlPatternMatcher extends Matcher { static bool _areTagsEqual(html.Element a, html.Element b) { const Map synonyms = { 'sem': 'flt-semantics', + 'sem-c': 'flt-semantics-container', 'sem-img': 'flt-semantics-img', 'sem-tf': 'flt-semantics-text-field', }; diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_multi_view_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_multi_view_test.dart index 8fce7b62e99..676c41d8b21 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_multi_view_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_multi_view_test.dart @@ -99,11 +99,15 @@ Future testMain() async { // Test that each view renders its own semantics tree. expectSemanticsTree(view1.semantics, ''' + + '''); expectSemanticsTree(view2.semantics, ''' + + '''); diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart index ba593ca8d66..ce9d9db01ac 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -158,8 +158,10 @@ void _testSemanticRole() { tester.expectSemantics(''' + + '''); tester.updateNode( @@ -173,8 +175,10 @@ void _testSemanticRole() { tester.expectSemantics(''' + + '''); tester.updateNode( @@ -189,9 +193,11 @@ void _testSemanticRole() { tester.expectSemantics(''' + + '''); }); } @@ -514,7 +520,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + Hello + '''); // Update @@ -522,7 +530,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + World + '''); // Remove @@ -530,7 +540,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + + '''); semantics().semanticsEnabled = false; @@ -551,7 +563,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + Hello + '''); // Update @@ -564,7 +578,9 @@ void _testEngineSemanticsOwner() { expect(tree[1]!.element.tagName.toLowerCase(), 'a'); expectSemanticsTree(owner(), ''' + Hello + '''); expect(existingParent, tree[1]!.element.parent); @@ -586,7 +602,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + tooltip + '''); // Update @@ -594,7 +612,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + tooltip\nHello + '''); // Remove @@ -602,7 +622,9 @@ void _testEngineSemanticsOwner() { expectSemanticsTree(owner(), ''' + + '''); semantics().semanticsEnabled = false; @@ -827,7 +849,7 @@ void _testHeader() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' -
+
'''); semantics().semanticsEnabled = false; @@ -934,7 +956,7 @@ label hint'''); } void _testContainer() { - test('child node has no transform when there is no rect offset', () async { + test('container node has no transform when there is no rect offset', () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) ..semanticsEnabled = true; @@ -951,30 +973,35 @@ void _testContainer() { updateNode(builder, id: 1, transform: Matrix4.identity().toFloat64(), rect: zeroOffsetRect); owner().updateSemantics(builder.build()); - expectSemanticsTree(owner(), ''''''); + expectSemanticsTree(owner(), ''' + + + + +'''); final DomElement parentElement = owner().semanticsHost.querySelector('flt-semantics')!; - final DomElement childElement = owner().semanticsHost.querySelector('#flt-semantic-node-1')!; + final DomElement container = owner().semanticsHost.querySelector('flt-semantics-container')!; if (isMacOrIOS) { expect(parentElement.style.top, '0px'); expect(parentElement.style.left, '0px'); - expect(childElement.style.top, '0px'); - expect(childElement.style.left, '0px'); + expect(container.style.top, '0px'); + expect(container.style.left, '0px'); } else { expect(parentElement.style.top, ''); expect(parentElement.style.left, ''); - expect(childElement.style.top, ''); - expect(childElement.style.left, ''); + expect(container.style.top, ''); + expect(container.style.left, ''); } expect(parentElement.style.transform, ''); expect(parentElement.style.transformOrigin, ''); - expect(childElement.style.transform, ''); - expect(childElement.style.transformOrigin, ''); + expect(container.style.transform, ''); + expect(container.style.transformOrigin, ''); semantics().semanticsEnabled = false; }); - test('child node transform compensates for parent rect offset', () async { + test('container node compensates for rect offset', () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) ..semanticsEnabled = true; @@ -991,14 +1018,19 @@ void _testContainer() { builder, id: 1, transform: Matrix4.identity().toFloat64(), - rect: const ui.Rect.fromLTRB(0, 0, 5, 5), + rect: const ui.Rect.fromLTRB(10, 10, 20, 20), ); owner().updateSemantics(builder.build()); - expectSemanticsTree(owner(), ''''''); + expectSemanticsTree(owner(), ''' + + + + +'''); final DomElement parentElement = owner().semanticsHost.querySelector('flt-semantics')!; - final DomElement childElement = owner().semanticsHost.querySelector('#flt-semantic-node-1')!; + final DomElement container = owner().semanticsHost.querySelector('flt-semantics-container')!; expect(parentElement.style.transform, 'matrix(1, 0, 0, 1, 10, 10)'); if (isSafari) { @@ -1007,103 +1039,63 @@ void _testContainer() { parentElement.style.transformOrigin, anyOf(contains('0px 0px 0px'), contains('0px 0px')), ); - expect( - childElement.style.transformOrigin, - anyOf(contains('0px 0px 0px'), contains('0px 0px')), - ); } else { expect(parentElement.style.transformOrigin, '0px 0px 0px'); - expect(childElement.style.transformOrigin, '0px 0px 0px'); } - expect(childElement.style.transform, 'matrix(1, 0, 0, 1, -10, -10)'); - expect(childElement.style.left == '0px' || childElement.style.left == '', isTrue); - expect(childElement.style.top == '0px' || childElement.style.top == '', isTrue); - + expect(container.style.top, '-10px'); + expect(container.style.left, '-10px'); semantics().semanticsEnabled = false; }); - test( - 'child node transform compensates for parent rect offset when parent rect changed', - () async { - semantics() - ..debugOverrideTimestampFunction(() => _testTime) - ..semanticsEnabled = true; + test('0 offsets are not removed for voiceover', () async { + semantics() + ..debugOverrideTimestampFunction(() => _testTime) + ..semanticsEnabled = true; - final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); - updateNode( - builder, - transform: Matrix4.identity().toFloat64(), - rect: const ui.Rect.fromLTRB(10, 10, 20, 20), - childrenInHitTestOrder: Int32List.fromList([1]), - childrenInTraversalOrder: Int32List.fromList([1]), - ); - updateNode( - builder, - id: 1, - transform: Matrix4.identity().toFloat64(), - rect: const ui.Rect.fromLTRB(0, 0, 5, 5), - ); + final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); + updateNode( + builder, + transform: Matrix4.identity().toFloat64(), + rect: const ui.Rect.fromLTRB(0, 0, 20, 20), + childrenInHitTestOrder: Int32List.fromList([1]), + childrenInTraversalOrder: Int32List.fromList([1]), + ); + updateNode( + builder, + id: 1, + transform: Matrix4.identity().toFloat64(), + rect: const ui.Rect.fromLTRB(10, 10, 20, 20), + ); - owner().updateSemantics(builder.build()); - expectSemanticsTree(owner(), ''''''); + owner().updateSemantics(builder.build()); + expectSemanticsTree(owner(), ''' + + + + +'''); - final DomElement parentElement = owner().semanticsHost.querySelector('flt-semantics')!; - final DomElement childElement = owner().semanticsHost.querySelector('#flt-semantic-node-1')!; + final DomElement parentElement = owner().semanticsHost.querySelector('flt-semantics')!; + final DomElement container = owner().semanticsHost.querySelector('flt-semantics-container')!; - expect(parentElement.style.transform, 'matrix(1, 0, 0, 1, 10, 10)'); - if (isSafari) { - // macOS 13 returns different values than macOS 12. - expect( - parentElement.style.transformOrigin, - anyOf(contains('0px 0px 0px'), contains('0px 0px')), - ); - expect( - childElement.style.transformOrigin, - anyOf(contains('0px 0px 0px'), contains('0px 0px')), - ); - } else { - expect(parentElement.style.transformOrigin, '0px 0px 0px'); - expect(childElement.style.transformOrigin, '0px 0px 0px'); - } - expect(childElement.style.transform, 'matrix(1, 0, 0, 1, -10, -10)'); - expect(childElement.style.left == '0px' || childElement.style.left == '', isTrue); - expect(childElement.style.top == '0px' || childElement.style.top == '', isTrue); + if (isMacOrIOS) { + expect(parentElement.style.top, '0px'); + expect(parentElement.style.left, '0px'); + expect(container.style.top, '0px'); + expect(container.style.left, '0px'); + } else { + expect(parentElement.style.top, ''); + expect(parentElement.style.left, ''); + expect(container.style.top, ''); + expect(container.style.left, ''); + } + expect(parentElement.style.transform, ''); + expect(parentElement.style.transformOrigin, ''); + expect(container.style.transform, ''); + expect(container.style.transformOrigin, ''); - final ui.SemanticsUpdateBuilder builder2 = ui.SemanticsUpdateBuilder(); - - updateNode( - builder2, - transform: Matrix4.identity().toFloat64(), - rect: const ui.Rect.fromLTRB(33, 33, 20, 20), - childrenInHitTestOrder: Int32List.fromList([1]), - childrenInTraversalOrder: Int32List.fromList([1]), - ); - - owner().updateSemantics(builder2.build()); - expectSemanticsTree(owner(), ''''''); - - expect(parentElement.style.transform, 'matrix(1, 0, 0, 1, 33, 33)'); - if (isSafari) { - // macOS 13 returns different values than macOS 12. - expect( - parentElement.style.transformOrigin, - anyOf(contains('0px 0px 0px'), contains('0px 0px')), - ); - expect( - childElement.style.transformOrigin, - anyOf(contains('0px 0px 0px'), contains('0px 0px')), - ); - } else { - expect(parentElement.style.transformOrigin, '0px 0px 0px'); - expect(childElement.style.transformOrigin, '0px 0px 0px'); - } - expect(childElement.style.transform, 'matrix(1, 0, 0, 1, -33, -33)'); - expect(childElement.style.left == '0px' || childElement.style.left == '', isTrue); - expect(childElement.style.top == '0px' || childElement.style.top == '', isTrue); - - semantics().semanticsEnabled = false; - }, - ); + semantics().semanticsEnabled = false; + }); test('renders in traversal order, hit-tests in reverse z-index order', () async { semantics() @@ -1126,10 +1118,12 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); } @@ -1144,10 +1138,12 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); } @@ -1162,10 +1158,12 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); } @@ -1180,10 +1178,12 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); } @@ -1207,8 +1207,10 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!; @@ -1241,8 +1243,10 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!; @@ -1250,6 +1254,7 @@ void _testContainer() { semantics().semanticsEnabled = false; }); + test('container can be opaque if it is a text field', () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) @@ -1269,8 +1274,10 @@ void _testContainer() { expectSemanticsTree(owner(), ''' + + '''); final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!; @@ -1311,14 +1318,20 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + + + + + '''); expect( @@ -1345,11 +1358,15 @@ void _testContainer() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + + + '''); expect(owner().debugSemanticsTree!.keys.toList(), unorderedEquals([0, 1, 3, 4, 6])); @@ -1435,7 +1452,9 @@ void _testVerticalScrolling() { expectSemanticsTree(owner(), ''' + + '''); final DomElement scrollable = findScrollable(owner()); @@ -1489,9 +1508,11 @@ void _testVerticalScrolling() { expectSemanticsTree(owner(), ''' + + '''); final DomElement scrollable = owner().debugSemanticsTree![0]!.element; @@ -1568,9 +1589,11 @@ void _testVerticalScrolling() { expectSemanticsTree(owner(), ''' + + '''); final DomElement scrollable = owner().debugSemanticsTree![0]!.element; @@ -1656,7 +1679,9 @@ void _testHorizontalScrolling() { expectSemanticsTree(owner(), ''' + + '''); final DomElement scrollable = findScrollable(owner()); @@ -1710,9 +1735,11 @@ void _testHorizontalScrolling() { expectSemanticsTree(owner(), ''' + + '''); final DomElement scrollable = findScrollable(owner()); @@ -2267,8 +2294,10 @@ void _testCheckables() { expectSemanticsTree(owner(), ''' + + '''); @@ -2366,9 +2395,11 @@ void _testSelectables() { expectSemanticsTree(owner(), ''' + + '''); @@ -2393,9 +2424,11 @@ void _testSelectables() { expectSemanticsTree(owner(), ''' + + '''); @@ -2460,9 +2493,11 @@ void _testExpandables() { expectSemanticsTree(owner(), ''' + + '''); @@ -2487,9 +2522,11 @@ void _testExpandables() { expectSemanticsTree(owner(), ''' + + '''); @@ -2743,7 +2780,9 @@ void _testTappable() { expectSemanticsTree(owner(), ''' + + '''); @@ -2834,7 +2873,9 @@ void _testImage() { expectSemanticsTree(owner(), ''' + + '''); semantics().semanticsEnabled = false; @@ -2884,7 +2925,9 @@ void _testImage() { expectSemanticsTree(owner(), ''' + + '''); semantics().semanticsEnabled = false; @@ -3101,9 +3144,11 @@ void _testPlatformView() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' + + '''); final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!; @@ -3197,7 +3242,7 @@ void _testGroup() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' - + '''); semantics().semanticsEnabled = false; @@ -3229,7 +3274,7 @@ void _testRoute() { owner().updateSemantics(builder.build()); expectSemanticsTree(owner(), ''' - + '''); expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route); @@ -3268,7 +3313,7 @@ void _testRoute() { // But still sets the dialog role. expectSemanticsTree(owner(), ''' - + '''); expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route); @@ -3300,9 +3345,13 @@ void _testRoute() { expectSemanticsTree(owner(), ''' + + $label + + '''); } @@ -3362,9 +3411,13 @@ void _testRoute() { expectSemanticsTree(owner(), ''' + + Hello + + '''); @@ -3541,12 +3594,16 @@ void _testRoute() { tester.expectSemantics(''' + + Heading Click me! + + '''); final DomElement span = owner().debugSemanticsTree![2]!.element.querySelectorAll('span').single; @@ -3646,9 +3703,13 @@ void _testDialogs() { expectSemanticsTree(owner(), ''' + + $label + + '''); } @@ -3807,7 +3868,9 @@ void _testFocusable() { expectSemanticsTree(owner(), ''' + focusable text + '''); @@ -4198,9 +4261,11 @@ void _testRequirable() { expectSemanticsTree(owner(), ''' + + '''); @@ -4225,9 +4290,11 @@ void _testRequirable() { expectSemanticsTree(owner(), ''' + + '''); @@ -4238,9 +4305,11 @@ void _testRequirable() { expectSemanticsTree(owner(), ''' + + '''); diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_text_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_text_test.dart index 12cb5619f8f..ccb7aec643e 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_text_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_text_test.dart @@ -110,7 +110,9 @@ Future testMain() async { expectSemanticsTree(owner(), ''' + I am a child + '''); semantics().semanticsEnabled = false; @@ -157,7 +159,9 @@ Future testMain() async { expectSemanticsTree(owner(), ''' + I am a child + '''); }