Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 241f8ea

Browse files
authored
[web] Add 'flt-semantics-identifier' attribute to semantics nodes (#53278)
Make [`Semantics(identifier: '...')`](https://api.flutter.dev/flutter/semantics/SemanticsProperties/identifier.html) useful on the web. This PR plugs the Semantics `identifier` property as an HTML attribute `semantics-identifier` onto semantics elements. This is useful in some scenarios: - In testing to check if a certain semantics node has made it to the page ([example](flutter/flutter#97455)). - In apps and/or packages to be able to lookup the DOM element that corresponds to a certain semantics node ([example](flutter/packages#6711)). Fixes flutter/flutter#97455
1 parent 1ad7953 commit 241f8ea

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

lib/web_ui/lib/src/engine/semantics/semantics.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,18 @@ abstract class PrimaryRoleManager {
612612
for (final RoleManager secondaryRole in secondaryRoles) {
613613
secondaryRole.update();
614614
}
615+
616+
if (semanticsObject.isIdentifierDirty) {
617+
_updateIdentifier();
618+
}
619+
}
620+
621+
void _updateIdentifier() {
622+
if (semanticsObject.hasIdentifier) {
623+
setAttribute('flt-semantics-identifier', semanticsObject.identifier!);
624+
} else {
625+
removeAttribute('flt-semantics-identifier');
626+
}
615627
}
616628

617629
/// Whether this role manager was disposed of.
@@ -1119,6 +1131,21 @@ class SemanticsObject {
11191131
_dirtyFields |= _headingLevelIndex;
11201132
}
11211133

1134+
/// See [ui.SemanticsUpdateBuilder.updateNode].
1135+
String? get identifier => _identifier;
1136+
String? _identifier;
1137+
1138+
bool get hasIdentifier => _identifier != null && _identifier!.isNotEmpty;
1139+
1140+
static const int _identifierIndex = 1 << 25;
1141+
1142+
/// Whether the [identifier] field has been updated but has not been
1143+
/// applied to the DOM yet.
1144+
bool get isIdentifierDirty => _isDirty(_identifierIndex);
1145+
void _markIdentifierDirty() {
1146+
_dirtyFields |= _identifierIndex;
1147+
}
1148+
11221149
/// A unique permanent identifier of the semantics node in the tree.
11231150
final int id;
11241151

@@ -1278,6 +1305,11 @@ class SemanticsObject {
12781305
_markFlagsDirty();
12791306
}
12801307

1308+
if (_identifier != update.identifier) {
1309+
_identifier = update.identifier;
1310+
_markIdentifierDirty();
1311+
}
1312+
12811313
if (_value != update.value) {
12821314
_value = update.value;
12831315
_markValueDirty();

lib/web_ui/test/engine/semantics/semantics_test.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ void runSemanticsTests() {
4848
group('longestIncreasingSubsequence', () {
4949
_testLongestIncreasingSubsequence();
5050
});
51+
group(PrimaryRoleManager, () {
52+
_testPrimaryRoleManager();
53+
});
5154
group('Role managers', () {
5255
_testRoleManagerLifecycle();
5356
});
@@ -107,6 +110,68 @@ void runSemanticsTests() {
107110
});
108111
}
109112

113+
void _testPrimaryRoleManager() {
114+
test('Sets id and flt-semantics-identifier on the element', () {
115+
semantics()
116+
..debugOverrideTimestampFunction(() => _testTime)
117+
..semanticsEnabled = true;
118+
119+
final SemanticsTester tester = SemanticsTester(owner());
120+
tester.updateNode(
121+
id: 0,
122+
children: <SemanticsNodeUpdate>[
123+
tester.updateNode(id: 372),
124+
tester.updateNode(id: 599),
125+
],
126+
);
127+
tester.apply();
128+
129+
tester.expectSemantics('''
130+
<sem id="flt-semantic-node-0">
131+
<sem-c>
132+
<sem id="flt-semantic-node-372"></sem>
133+
<sem id="flt-semantic-node-599"></sem>
134+
</sem-c>
135+
</sem>''');
136+
137+
tester.updateNode(
138+
id: 0,
139+
children: <SemanticsNodeUpdate>[
140+
tester.updateNode(id: 372, identifier: 'test-id-123'),
141+
tester.updateNode(id: 599),
142+
],
143+
);
144+
tester.apply();
145+
146+
tester.expectSemantics('''
147+
<sem id="flt-semantic-node-0">
148+
<sem-c>
149+
<sem id="flt-semantic-node-372" flt-semantics-identifier="test-id-123"></sem>
150+
<sem id="flt-semantic-node-599"></sem>
151+
</sem-c>
152+
</sem>''');
153+
154+
tester.updateNode(
155+
id: 0,
156+
children: <SemanticsNodeUpdate>[
157+
tester.updateNode(id: 372),
158+
tester.updateNode(id: 599, identifier: 'test-id-211'),
159+
tester.updateNode(id: 612, identifier: 'test-id-333'),
160+
],
161+
);
162+
tester.apply();
163+
164+
tester.expectSemantics('''
165+
<sem id="flt-semantic-node-0">
166+
<sem-c>
167+
<sem id="flt-semantic-node-372"></sem>
168+
<sem id="flt-semantic-node-599" flt-semantics-identifier="test-id-211"></sem>
169+
<sem id="flt-semantic-node-612" flt-semantics-identifier="test-id-333"></sem>
170+
</sem-c>
171+
</sem>''');
172+
});
173+
}
174+
110175
void _testRoleManagerLifecycle() {
111176
test('Secondary role managers are added upon node initialization', () {
112177
semantics()

0 commit comments

Comments
 (0)