Reverts "[web][a11y]Delete _childContainerElement (#163662)" (#165416)

<!-- start_original_pr_link -->
Reverts: flutter/flutter#163662
<!-- end_original_pr_link -->
<!-- start_initiating_author -->
Initiated by: hannah-hyj
<!-- end_initiating_author -->
<!-- start_revert_reason -->
Reason for reverting: google 3 failure
<!-- end_revert_reason -->
<!-- start_original_pr_author -->
Original PR Author: hannah-hyj
<!-- end_original_pr_author -->

<!-- start_reviewers -->
Reviewed By: {yjbanov}
<!-- end_reviewers -->

<!-- start_revert_body -->
This change reverts the following previous change:
delete _childContainerElement , add the rect compensate and scrolling
adjustment to the children

## 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.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

<!-- end_revert_body -->

Co-authored-by: auto-submit[bot] <flutter-engprod-team@google.com>
This commit is contained in:
auto-submit[bot] 2025-03-18 18:18:47 +00:00 committed by GitHub
parent 155f6dc7f9
commit 4e5a2dbf1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 261 additions and 157 deletions

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -274,6 +274,7 @@ class HtmlPatternMatcher extends Matcher {
static bool _areTagsEqual(html.Element a, html.Element b) {
const Map<String, String> synonyms = <String, String>{
'sem': 'flt-semantics',
'sem-c': 'flt-semantics-container',
'sem-img': 'flt-semantics-img',
'sem-tf': 'flt-semantics-text-field',
};

View File

@ -99,11 +99,15 @@ Future<void> testMain() async {
// Test that each view renders its own semantics tree.
expectSemanticsTree(view1.semantics, '''
<sem style="filter: opacity(0%); color: rgba(0, 0, 0, 0)">
<sem-c>
<sem flt-tappable="" role="button"></sem>
</sem-c>
</sem>''');
expectSemanticsTree(view2.semantics, '''
<sem style="filter: opacity(0%); color: rgba(0, 0, 0, 0)">
<sem-c>
<sem aria-label="d"><input aria-valuemax="1" aria-valuemin="1" aria-valuenow="1" aria-valuetext="" role="slider"></sem>
</sem-c>
</sem>
''');

View File

@ -158,8 +158,10 @@ void _testSemanticRole() {
tester.expectSemantics('''
<sem id="flt-semantic-node-0">
<sem-c>
<sem id="flt-semantic-node-372"></sem>
<sem id="flt-semantic-node-599"></sem>
</sem-c>
</sem>''');
tester.updateNode(
@ -173,8 +175,10 @@ void _testSemanticRole() {
tester.expectSemantics('''
<sem id="flt-semantic-node-0">
<sem-c>
<sem id="flt-semantic-node-372" flt-semantics-identifier="test-id-123"></sem>
<sem id="flt-semantic-node-599"></sem>
</sem-c>
</sem>''');
tester.updateNode(
@ -189,9 +193,11 @@ void _testSemanticRole() {
tester.expectSemantics('''
<sem id="flt-semantic-node-0">
<sem-c>
<sem id="flt-semantic-node-372"></sem>
<sem id="flt-semantic-node-599" flt-semantics-identifier="test-id-211"></sem>
<sem id="flt-semantic-node-612" flt-semantics-identifier="test-id-333"></sem>
</sem-c>
</sem>''');
});
}
@ -514,7 +520,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem><span>Hello</span></sem>
</sem-c>
</sem>''');
// Update
@ -522,7 +530,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem><span>World</span></sem>
</sem-c>
</sem>''');
// Remove
@ -530,7 +540,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
semantics().semanticsEnabled = false;
@ -551,7 +563,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem><span>Hello</span></sem>
</sem-c>
</sem>''');
// Update
@ -564,7 +578,9 @@ void _testEngineSemanticsOwner() {
expect(tree[1]!.element.tagName.toLowerCase(), 'a');
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<a style="display: block;">Hello</a>
</sem-c>
</sem>''');
expect(existingParent, tree[1]!.element.parent);
@ -586,7 +602,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem><span>tooltip</span></sem>
</sem-c>
</sem>''');
// Update
@ -594,7 +612,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem><span>tooltip\nHello</span></sem>
</sem-c>
</sem>''');
// Remove
@ -602,7 +622,9 @@ void _testEngineSemanticsOwner() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
semantics().semanticsEnabled = false;
@ -827,7 +849,7 @@ void _testHeader() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<header aria-label="Header of the page"><sem></sem></header>
<header aria-label="Header of the page"><sem-c><sem></sem></sem-c></header>
''');
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(), '''<sem><sem></sem></sem>''');
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
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(), '''<sem><sem></sem></sem>''');
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
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(<int>[1]),
childrenInTraversalOrder: Int32List.fromList(<int>[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(<int>[1]),
childrenInTraversalOrder: Int32List.fromList(<int>[1]),
);
updateNode(
builder,
id: 1,
transform: Matrix4.identity().toFloat64(),
rect: const ui.Rect.fromLTRB(10, 10, 20, 20),
);
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''<sem><sem></sem></sem>''');
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
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(<int>[1]),
childrenInTraversalOrder: Int32List.fromList(<int>[1]),
);
owner().updateSemantics(builder2.build());
expectSemanticsTree(owner(), '''<sem><sem></sem></sem>''');
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(), '''
<sem>
<sem-c>
<sem style="z-index: 4"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 3"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
}
@ -1144,10 +1138,12 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 4"></sem>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
}
@ -1162,10 +1158,12 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 1"></sem>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 4"></sem>
</sem-c>
</sem>''');
}
@ -1180,10 +1178,12 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 2"></sem>
<sem style="z-index: 4"></sem>
<sem style="z-index: 1"></sem>
<sem style="z-index: 3"></sem>
</sem-c>
</sem>''');
}
@ -1207,8 +1207,10 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!;
@ -1241,8 +1243,10 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
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(), '''
<sem>
<input>
<sem-c>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!;
@ -1311,14 +1318,20 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 2">
<sem-c>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>
<sem style="z-index: 1">
<sem-c>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>
</sem-c>
</sem>''');
expect(
@ -1345,11 +1358,15 @@ void _testContainer() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 2">
<sem-c>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>
</sem-c>
</sem>''');
expect(owner().debugSemanticsTree!.keys.toList(), unorderedEquals(<int>[0, 1, 3, 4, 6]));
@ -1435,7 +1452,9 @@ void _testVerticalScrolling() {
expectSemanticsTree(owner(), '''
<sem style="touch-action: none; overflow-y: scroll">
<flt-semantics-scroll-overflow></flt-semantics-scroll-overflow>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
final DomElement scrollable = findScrollable(owner());
@ -1489,9 +1508,11 @@ void _testVerticalScrolling() {
expectSemanticsTree(owner(), '''
<sem style="touch-action: none; overflow-y: scroll">
<flt-semantics-scroll-overflow></flt-semantics-scroll-overflow>
<sem-c>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
final DomElement scrollable = owner().debugSemanticsTree![0]!.element;
@ -1568,9 +1589,11 @@ void _testVerticalScrolling() {
expectSemanticsTree(owner(), '''
<sem style="touch-action: none; overflow-y: scroll">
<flt-semantics-scroll-overflow></flt-semantics-scroll-overflow>
<sem-c>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
final DomElement scrollable = owner().debugSemanticsTree![0]!.element;
@ -1656,7 +1679,9 @@ void _testHorizontalScrolling() {
expectSemanticsTree(owner(), '''
<sem style="touch-action: none; overflow-x: scroll">
<flt-semantics-scroll-overflow></flt-semantics-scroll-overflow>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
final DomElement scrollable = findScrollable(owner());
@ -1710,9 +1735,11 @@ void _testHorizontalScrolling() {
expectSemanticsTree(owner(), '''
<sem style="touch-action: none; overflow-x: scroll">
<flt-semantics-scroll-overflow></flt-semantics-scroll-overflow>
<sem-c>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
final DomElement scrollable = findScrollable(owner());
@ -2267,8 +2294,10 @@ void _testCheckables() {
expectSemanticsTree(owner(), '''
<sem role="radiogroup">
<sem-c>
<sem aria-checked="false"></sem>
<sem aria-checked="true"></sem>
</sem-c>
</sem>
''');
@ -2366,9 +2395,11 @@ void _testSelectables() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem aria-selected="false"></sem>
<sem aria-selected="true"></sem>
</sem-c>
</sem>
''');
@ -2393,9 +2424,11 @@ void _testSelectables() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem aria-selected="true"></sem>
<sem aria-selected="false"></sem>
</sem-c>
</sem>
''');
@ -2460,9 +2493,11 @@ void _testExpandables() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem aria-expanded="false"></sem>
<sem aria-expanded="true"></sem>
</sem-c>
</sem>
''');
@ -2487,9 +2522,11 @@ void _testExpandables() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem aria-expanded="true"></sem>
<sem aria-expanded="false"></sem>
</sem-c>
</sem>
''');
@ -2743,7 +2780,9 @@ void _testTappable() {
expectSemanticsTree(owner(), '''
<sem flt-tappable role="button">
<sem-c>
<sem flt-tappable role="button"></sem>
</sem-c>
</sem>
''');
@ -2834,7 +2873,9 @@ void _testImage() {
expectSemanticsTree(owner(), '''
<sem>
<sem-img role="img" aria-label="Test Image Label"></sem-img>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
semantics().semanticsEnabled = false;
@ -2884,7 +2925,9 @@ void _testImage() {
expectSemanticsTree(owner(), '''
<sem>
<sem-img role="img"></sem-img>
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
semantics().semanticsEnabled = false;
@ -3101,9 +3144,11 @@ void _testPlatformView() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2" aria-owns="flt-pv-0"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
final DomElement root = owner().semanticsHost.querySelector('#flt-semantic-node-0')!;
@ -3197,7 +3242,7 @@ void _testGroup() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem role="group" aria-label="this is a label for a group of elements"><sem></sem></sem>
<sem role="group" aria-label="this is a label for a group of elements"><sem-c><sem></sem></sem-c></sem>
''');
semantics().semanticsEnabled = false;
@ -3229,7 +3274,7 @@ void _testRoute() {
owner().updateSemantics(builder.build());
expectSemanticsTree(owner(), '''
<sem role="dialog" aria-label="this is a route label"><sem></sem></sem>
<sem role="dialog" aria-label="this is a route label"><sem-c><sem></sem></sem-c></sem>
''');
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route);
@ -3268,7 +3313,7 @@ void _testRoute() {
// But still sets the dialog role.
expectSemanticsTree(owner(), '''
<sem role="dialog" aria-label=""><sem></sem></sem>
<sem role="dialog" aria-label=""><sem-c><sem></sem></sem-c></sem>
''');
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route);
@ -3300,9 +3345,13 @@ void _testRoute() {
expectSemanticsTree(owner(), '''
<sem role="dialog" aria-describedby="flt-semantic-node-2">
<sem-c>
<sem>
<sem-c>
<sem><span>$label</span></sem>
</sem-c>
</sem>
</sem-c>
</sem>
''');
}
@ -3362,9 +3411,13 @@ void _testRoute() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem>
<sem-c>
<sem><span>Hello</span></sem>
</sem-c>
</sem>
</sem-c>
</sem>
''');
@ -3541,12 +3594,16 @@ void _testRoute() {
tester.expectSemantics('''
<flt-semantics>
<flt-semantics-container>
<flt-semantics>
<flt-semantics-container>
<flt-semantics id="flt-semantic-node-2">
<span tabindex="-1">Heading</span>
</flt-semantics>
<flt-semantics role="button" tabindex="0" flt-tappable="">Click me!</flt-semantics>
</flt-semantics-container>
</flt-semantics>
</flt-semantics-container>
</flt-semantics>''');
final DomElement span = owner().debugSemanticsTree![2]!.element.querySelectorAll('span').single;
@ -3646,9 +3703,13 @@ void _testDialogs() {
expectSemanticsTree(owner(), '''
<sem role="dialog" aria-describedby="flt-semantic-node-2">
<sem-c>
<sem>
<sem-c>
<sem><span>$label</span></sem>
</sem-c>
</sem>
</sem-c>
</sem>
''');
}
@ -3807,7 +3868,9 @@ void _testFocusable() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem><span>focusable text</span></sem>
</sem-c>
</sem>
''');
@ -4198,9 +4261,11 @@ void _testRequirable() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem aria-required="false"></sem>
<sem aria-required="true"></sem>
</sem-c>
</sem>
''');
@ -4225,9 +4290,11 @@ void _testRequirable() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem aria-required="true"></sem>
<sem aria-required="false"></sem>
</sem-c>
</sem>
''');
@ -4238,9 +4305,11 @@ void _testRequirable() {
expectSemanticsTree(owner(), '''
<sem>
<sem-c>
<sem></sem>
<sem></sem>
<sem></sem>
</sem-c>
</sem>
''');

View File

@ -110,7 +110,9 @@ Future<void> testMain() async {
expectSemanticsTree(owner(), '''
<sem aria-label="I am a parent" role="group">
<sem-c>
<sem><span>I am a child</span></sem>
</sem-c>
</sem>''');
semantics().semanticsEnabled = false;
@ -157,7 +159,9 @@ Future<void> testMain() async {
expectSemanticsTree(owner(), '''
<sem aria-label="I am a parent" role="group">
<sem-c>
<sem><span>I am a child</span></sem>
</sem-c>
</sem>''');
}