Skip to content

Commit 6b8b579

Browse files
authored
add semantics role and tab (#161260)
fixes flutter/flutter#157134 ## 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
1 parent 50f7120 commit 6b8b579

34 files changed

+579
-133
lines changed

engine/src/flutter/ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43284,6 +43284,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/route.dart + ../../
4328443284
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart + ../../../flutter/LICENSE
4328543285
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart + ../../../flutter/LICENSE
4328643286
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart + ../../../flutter/LICENSE
43287+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tabs.dart + ../../../flutter/LICENSE
4328743288
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart + ../../../flutter/LICENSE
4328843289
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart + ../../../flutter/LICENSE
4328943290
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/services.dart + ../../../flutter/LICENSE
@@ -46226,6 +46227,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/route.dart
4622646227
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart
4622746228
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart
4622846229
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart
46230+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tabs.dart
4622946231
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart
4623046232
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart
4623146233
FILE: ../../../flutter/lib/web_ui/lib/src/engine/services.dart

engine/src/flutter/lib/ui/fixtures/ui_test.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,59 @@ void sendSemanticsUpdate() {
238238
_semanticsUpdate(builder.build());
239239
}
240240

241+
@pragma('vm:entry-point')
242+
void sendSemanticsUpdateWithRole() {
243+
final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder();
244+
245+
final Float64List transform = Float64List(16);
246+
final Int32List childrenInTraversalOrder = Int32List(0);
247+
final Int32List childrenInHitTestOrder = Int32List(0);
248+
final Int32List additionalActions = Int32List(0);
249+
// Identity matrix 4x4.
250+
transform[0] = 1;
251+
transform[5] = 1;
252+
transform[10] = 1;
253+
builder.updateNode(
254+
id: 0,
255+
flags: 0,
256+
actions: 0,
257+
maxValueLength: 0,
258+
currentValueLength: 0,
259+
textSelectionBase: -1,
260+
textSelectionExtent: -1,
261+
platformViewId: -1,
262+
scrollChildren: 0,
263+
scrollIndex: 0,
264+
scrollPosition: 0,
265+
scrollExtentMax: 0,
266+
scrollExtentMin: 0,
267+
rect: Rect.fromLTRB(0, 0, 10, 10),
268+
elevation: 0,
269+
thickness: 0,
270+
identifier: "identifier",
271+
label: "label",
272+
labelAttributes: const <StringAttribute>[],
273+
value: "value",
274+
valueAttributes: const <StringAttribute>[],
275+
increasedValue: "increasedValue",
276+
increasedValueAttributes: const <StringAttribute>[],
277+
decreasedValue: "decreasedValue",
278+
decreasedValueAttributes: const <StringAttribute>[],
279+
hint: "hint",
280+
hintAttributes: const <StringAttribute>[],
281+
tooltip: "tooltip",
282+
textDirection: TextDirection.ltr,
283+
transform: transform,
284+
childrenInTraversalOrder: childrenInTraversalOrder,
285+
childrenInHitTestOrder: childrenInHitTestOrder,
286+
additionalActions: additionalActions,
287+
headingLevel: 0,
288+
linkUrl: '',
289+
role: SemanticsRole.tab,
290+
);
291+
_semanticsUpdate(builder.build());
292+
}
293+
241294
@pragma('vm:external-name', 'SemanticsUpdate')
242295
external void _semanticsUpdate(SemanticsUpdate update);
243296

engine/src/flutter/lib/ui/semantics.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,31 @@ class SemanticsAction {
339339
String toString() => 'SemanticsAction.$name';
340340
}
341341

342+
/// An enum to describe the role for a semantics node.
343+
///
344+
/// The roles are translated into native accessibility roles in each platform.
345+
enum SemanticsRole {
346+
/// Does not represent any role.
347+
none,
348+
349+
/// A tab button.
350+
///
351+
/// see also:
352+
///
353+
/// * [tabBar], which is the role for containers of tab buttons.
354+
tab,
355+
356+
/// Contains tab buttons.
357+
///
358+
/// see also:
359+
///
360+
/// * [tab], which is the role for tab buttons.
361+
tabBar,
362+
363+
/// The main display for a tab.
364+
tabPanel,
365+
}
366+
342367
/// A Boolean value that can be associated with a semantics node.
343368
//
344369
// When changes are made to this class, the equivalent APIs in
@@ -960,6 +985,9 @@ abstract class SemanticsUpdateBuilder {
960985
/// The `linkUrl` describes the URI that this node links to. If the node is
961986
/// not a link, this should be an empty string.
962987
///
988+
/// The `role` describes the role of this node. Defaults to
989+
/// [SemanticsRole.none] if not set.
990+
///
963991
/// See also:
964992
///
965993
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/heading_role
@@ -1000,6 +1028,7 @@ abstract class SemanticsUpdateBuilder {
10001028
required Int32List additionalActions,
10011029
int headingLevel = 0,
10021030
String linkUrl = '',
1031+
SemanticsRole role = SemanticsRole.none,
10031032
});
10041033

10051034
/// Update the custom semantics action associated with the given `id`.
@@ -1075,6 +1104,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
10751104
required Int32List additionalActions,
10761105
int headingLevel = 0,
10771106
String linkUrl = '',
1107+
SemanticsRole role = SemanticsRole.none,
10781108
}) {
10791109
assert(_matrix4IsValid(transform));
10801110
assert(
@@ -1120,6 +1150,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
11201150
additionalActions,
11211151
headingLevel,
11221152
linkUrl,
1153+
role.index,
11231154
);
11241155
}
11251156

@@ -1164,6 +1195,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
11641195
Handle,
11651196
Int32,
11661197
Handle,
1198+
Int32,
11671199
)
11681200
>(symbol: 'SemanticsUpdateBuilder::updateNode')
11691201
external void _updateNode(
@@ -1205,6 +1237,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
12051237
Int32List additionalActions,
12061238
int headingLevel,
12071239
String linkUrl,
1240+
int role,
12081241
);
12091242

12101243
@override

engine/src/flutter/lib/ui/semantics/semantics_node.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ const int kHorizontalScrollSemanticsActions =
5757
const int kScrollableSemanticsActions =
5858
kVerticalScrollSemanticsActions | kHorizontalScrollSemanticsActions;
5959

60+
/// C/C++ representation of `SemanticsRole` defined in
61+
/// `lib/ui/semantics.dart`.
62+
///\warning This must match the `SemanticsRole` enum in
63+
/// `lib/ui/semantics.dart`.
64+
/// See also:
65+
/// - file://./../../../lib/ui/semantics.dart
66+
enum class SemanticsRole : int32_t {
67+
kNone = 0,
68+
kTab = 1,
69+
kTabBar = 2,
70+
kTabPanel = 3,
71+
};
72+
6073
/// C/C++ representation of `SemanticsFlags` defined in
6174
/// `lib/ui/semantics.dart`.
6275
///\warning This must match the `SemanticsFlags` enum in
@@ -148,6 +161,7 @@ struct SemanticsNode {
148161
int32_t headingLevel = 0;
149162

150163
std::string linkUrl;
164+
SemanticsRole role;
151165
};
152166

153167
// Contains semantic nodes that need to be updated.

engine/src/flutter/lib/ui/semantics/semantics_update_builder.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ void SemanticsUpdateBuilder::updateNode(
6868
const tonic::Int32List& childrenInHitTestOrder,
6969
const tonic::Int32List& localContextActions,
7070
int headingLevel,
71-
std::string linkUrl) {
71+
std::string linkUrl,
72+
int role) {
7273
FML_CHECK(scrollChildren == 0 ||
7374
(scrollChildren > 0 && childrenInHitTestOrder.data()))
7475
<< "Semantics update contained scrollChildren but did not have "
@@ -119,10 +120,11 @@ void SemanticsUpdateBuilder::updateNode(
119120
node.customAccessibilityActions = std::vector<int32_t>(
120121
localContextActions.data(),
121122
localContextActions.data() + localContextActions.num_elements());
122-
nodes_[id] = node;
123-
124123
node.headingLevel = headingLevel;
125124
node.linkUrl = std::move(linkUrl);
125+
node.role = static_cast<SemanticsRole>(role);
126+
127+
nodes_[id] = node;
126128
}
127129

128130
void SemanticsUpdateBuilder::updateCustomAction(int id,

engine/src/flutter/lib/ui/semantics/semantics_update_builder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ class SemanticsUpdateBuilder
6767
const tonic::Int32List& childrenInHitTestOrder,
6868
const tonic::Int32List& customAccessibilityActions,
6969
int headingLevel,
70-
std::string linkUrl);
70+
std::string linkUrl,
71+
int role);
7172

7273
void updateCustomAction(int id,
7374
std::string label,

engine/src/flutter/lib/ui/semantics/semantics_update_builder_unittests.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,48 @@ TEST_F(SemanticsUpdateBuilderTest, CanHandleAttributedStrings) {
8888
DestroyShell(std::move(shell), task_runners);
8989
}
9090

91+
TEST_F(SemanticsUpdateBuilderTest, CanHandleSemanticsRole) {
92+
auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>();
93+
94+
auto nativeSemanticsUpdate = [message_latch](Dart_NativeArguments args) {
95+
auto handle = Dart_GetNativeArgument(args, 0);
96+
intptr_t peer = 0;
97+
Dart_Handle result = Dart_GetNativeInstanceField(
98+
handle, tonic::DartWrappable::kPeerIndex, &peer);
99+
ASSERT_FALSE(Dart_IsError(result));
100+
SemanticsUpdate* update = reinterpret_cast<SemanticsUpdate*>(peer);
101+
SemanticsNodeUpdates nodes = update->takeNodes();
102+
ASSERT_EQ(nodes.size(), (size_t)1);
103+
auto node = nodes.find(0)->second;
104+
// Should match the updateNode in ui_test.dart.
105+
ASSERT_EQ(node.role, SemanticsRole::kTab);
106+
message_latch->Signal();
107+
};
108+
109+
Settings settings = CreateSettingsForFixture();
110+
TaskRunners task_runners("test", // label
111+
GetCurrentTaskRunner(), // platform
112+
CreateNewThread(), // raster
113+
CreateNewThread(), // ui
114+
CreateNewThread() // io
115+
);
116+
117+
AddNativeCallback("SemanticsUpdate",
118+
CREATE_NATIVE_ENTRY(nativeSemanticsUpdate));
119+
120+
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
121+
122+
ASSERT_TRUE(shell->IsSetup());
123+
auto configuration = RunConfiguration::InferFromSettings(settings);
124+
configuration.SetEntrypoint("sendSemanticsUpdateWithRole");
125+
126+
shell->RunEngine(std::move(configuration), [](auto result) {
127+
ASSERT_EQ(result, Engine::RunStatus::Success);
128+
});
129+
130+
message_latch->Wait();
131+
DestroyShell(std::move(shell), task_runners);
132+
}
133+
91134
} // namespace testing
92135
} // namespace flutter

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ class SemanticsFlag {
255255
String toString() => 'SemanticsFlag.$name';
256256
}
257257

258+
// Mirrors engine/src/flutter/lib/ui/semantics.dart
259+
enum SemanticsRole { none, tab, tabBar, tabPanel }
260+
258261
// When adding a new StringAttributeType, the classes in these file must be
259262
// updated as well.
260263
// * engine/src/flutter/lib/ui/semantics.dart
@@ -341,6 +344,7 @@ class SemanticsUpdateBuilder {
341344
required Int32List additionalActions,
342345
int headingLevel = 0,
343346
String? linkUrl,
347+
SemanticsRole role = SemanticsRole.none,
344348
}) {
345349
if (transform.length != 16) {
346350
throw ArgumentError('transform argument must have 16 entries.');
@@ -382,6 +386,7 @@ class SemanticsUpdateBuilder {
382386
platformViewId: platformViewId,
383387
headingLevel: headingLevel,
384388
linkUrl: linkUrl,
389+
role: role,
385390
),
386391
);
387392
}

engine/src/flutter/lib/web_ui/lib/src/engine.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export 'engine/semantics/route.dart';
159159
export 'engine/semantics/scrollable.dart';
160160
export 'engine/semantics/semantics.dart';
161161
export 'engine/semantics/semantics_helper.dart';
162+
export 'engine/semantics/tabs.dart';
162163
export 'engine/semantics/tappable.dart';
163164
export 'engine/semantics/text_field.dart';
164165
export 'engine/services/buffers.dart';

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ export 'semantics/platform_view.dart';
1616
export 'semantics/scrollable.dart';
1717
export 'semantics/semantics.dart';
1818
export 'semantics/semantics_helper.dart';
19+
export 'semantics/tabs.dart';
1920
export 'semantics/tappable.dart';
2021
export 'semantics/text_field.dart';

0 commit comments

Comments
 (0)