[web] remove Tappable from basic set of a11y roles; add it case by case (flutter/engine#51466)

Remove the `Tappable` role from the `PrimaryRoleManager.withBasics` constructor. Only add `Tappable` to primary roles that know for sure they need it.

Fixes https://github.com/flutter/flutter/issues/144364
This commit is contained in:
Yegor 2024-03-18 10:35:21 -07:00 committed by GitHub
parent 375b19aae9
commit 896df78f52
5 changed files with 89 additions and 4 deletions

View File

@ -56,7 +56,9 @@ class Checkable extends PrimaryRoleManager {
PrimaryRole.checkable,
semanticsObject,
labelRepresentation: LeafLabelRepresentation.ariaLabel,
);
) {
addTappable();
}
final _CheckableKind _kind;

View File

@ -11,7 +11,9 @@ class Link extends PrimaryRoleManager {
PrimaryRole.link,
semanticsObject,
labelRepresentation: LeafLabelRepresentation.domText,
);
) {
addTappable();
}
@override
DomElement createElement() {

View File

@ -439,7 +439,6 @@ abstract class PrimaryRoleManager {
addLiveRegion();
addRouteName();
addLabelAndValue(labelRepresentation: labelRepresentation);
addTappable();
}
/// Initializes a blank role for a [semanticsObject].
@ -625,7 +624,17 @@ final class GenericRole extends PrimaryRoleManager {
PrimaryRole.generic,
semanticsObject,
labelRepresentation: LeafLabelRepresentation.domText,
);
) {
// Typically a tappable widget would have a more specific role, such as
// "link", "button", "checkbox", etc. However, there are situations when a
// tappable is not a leaf node, but contains other nodes, which can also be
// tappable. For example, the dismiss barrier of a pop-up menu is a tappable
// ancestor of the menu itself, while the menu may contain tappable
// children.
if (semanticsObject.isTappable) {
addTappable();
}
}
@override
void update() {

View File

@ -12,6 +12,7 @@ class Button extends PrimaryRoleManager {
semanticsObject,
labelRepresentation: LeafLabelRepresentation.domText,
) {
addTappable();
setAriaRole('button');
}

View File

@ -51,6 +51,9 @@ void runSemanticsTests() {
group('Role managers', () {
_testRoleManagerLifecycle();
});
group('Text', () {
_testText();
});
group('container', () {
_testContainer();
});
@ -718,6 +721,74 @@ void _testLongestIncreasingSubsequence() {
});
}
void _testText() {
test('renders a piece of plain text', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
updateNode(
builder,
label: 'plain text',
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
);
owner().updateSemantics(builder.build());
expectSemanticsTree(
owner(),
'''<sem role="text" style="$rootSemanticStyle">plain text</sem>''',
);
final SemanticsObject node = owner().debugSemanticsTree![0]!;
expect(node.primaryRole?.role, PrimaryRole.generic);
expect(
node.primaryRole!.secondaryRoleManagers!.map((RoleManager m) => m.runtimeType).toList(),
<Type>[
Focusable,
LiveRegion,
RouteName,
LabelAndValue,
],
);
semantics().semanticsEnabled = false;
});
test('renders a tappable piece of text', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;
final SemanticsTester tester = SemanticsTester(owner());
tester.updateNode(
id: 0,
hasTap: true,
label: 'tappable text',
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
);
tester.apply();
expectSemanticsTree(
owner(),
'''<sem flt-tappable="" role="text" style="$rootSemanticStyle">tappable text</sem>''',
);
final SemanticsObject node = owner().debugSemanticsTree![0]!;
expect(node.primaryRole?.role, PrimaryRole.generic);
expect(
node.primaryRole!.secondaryRoleManagers!.map((RoleManager m) => m.runtimeType).toList(),
<Type>[
Focusable,
LiveRegion,
RouteName,
LabelAndValue,
Tappable,
],
);
semantics().semanticsEnabled = false;
});
}
void _testContainer() {
test('container node has no transform when there is no rect offset',
() async {