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

Commit abe9826

Browse files
authored
Add accessibility semantics support to embedder (#7891)
Flutter's accessibility APIs consist of three main calls from the embedder to the Dart application: 1. FlutterEngineUpdateSemanticsEnabled: enables/disables semantics support. 2. FlutterEngineUpdateAccessibilityFeatures: sets embedder-specific accessibility features. 3. FlutterEngineDispatchSemanticsAction: dispatches an action (tap, long-press, scroll, etc.) to a semantics node. and two main callbacks triggered by Dart code: 1. FlutterUpdateSemanticsNodeCallback: notifies the embedder of updates to the properties of a given semantics node. 2. FlutterUpdateSemanticsCustomActionCallback: notifies the embedder of updates to custom semantics actions registered in Dart code. In the Flutter framework, when accessibility is first enabled, the embedder will receive a stream of update callbacks notifying the embedder of the full semantics tree. On further changes in the Dart application, only updates will be sent.
1 parent ce7016e commit abe9826

File tree

13 files changed

+809
-0
lines changed

13 files changed

+809
-0
lines changed

BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ group("flutter") {
5757
"$flutter_root/runtime:runtime_unittests",
5858
"$flutter_root/shell/common:shell_unittests",
5959
"$flutter_root/shell/platform/embedder:embedder_unittests",
60+
"$flutter_root/shell/platform/embedder:embedder_a11y_unittests", # TODO(cbracken) build these into a different kernel blob in the embedder tests and load that in a test in embedder_unittests
6061
"$flutter_root/synchronization:synchronization_unittests",
6162
"$flutter_root/third_party/txt:txt_unittests",
6263
]

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_surface_gl.cc
619619
FILE: ../../../flutter/shell/platform/embedder/embedder_surface_gl.h
620620
FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.cc
621621
FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.h
622+
FILE: ../../../flutter/shell/platform/embedder/fixtures/a11y_main.dart
622623
FILE: ../../../flutter/shell/platform/embedder/fixtures/simple_main.dart
623624
FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.cc
624625
FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.h

lib/ui/semantics.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ part of dart.ui;
66

77
/// The possible actions that can be conveyed from the operating system
88
/// accessibility APIs to a semantics node.
9+
//
10+
// When changes are made to this class, the equivalent APIs in each of the
11+
// embedders *must* be updated.
912
class SemanticsAction {
1013
const SemanticsAction._(this.index);
1114

@@ -260,6 +263,9 @@ class SemanticsAction {
260263
}
261264

262265
/// A Boolean value that can be associated with a semantics node.
266+
//
267+
// When changes are made to this class, the equivalent APIs in each of the
268+
// embedders *must* be updated.
263269
class SemanticsFlag {
264270
static const int _kHasCheckedStateIndex = 1 << 0;
265271
static const int _kIsCheckedIndex = 1 << 1;

lib/ui/window.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,9 @@ class Window {
883883
/// It is not possible to enable these settings from Flutter, instead they are
884884
/// used by the platform to indicate that additional accessibility features are
885885
/// enabled.
886+
//
887+
// When changes are made to this class, the equivalent APIs in each of the
888+
// embedders *must* be updated.
886889
class AccessibilityFeatures {
887890
const AccessibilityFeatures._(this._index);
888891

shell/platform/embedder/BUILD.gn

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@ executable("embedder_unittests") {
8080
}
8181
}
8282

83+
test_fixtures("fixtures_a11y") {
84+
fixtures = [ "fixtures/a11y_main.dart" ]
85+
}
86+
87+
executable("embedder_a11y_unittests") {
88+
testonly = true
89+
90+
include_dirs = [ "." ]
91+
92+
sources = [
93+
"tests/embedder_a11y_unittests.cc",
94+
]
95+
96+
deps = [
97+
":embedder",
98+
":fixtures_a11y",
99+
"$flutter_root/lib/ui:ui",
100+
"$flutter_root/shell/common",
101+
"$flutter_root/testing",
102+
"//third_party/skia",
103+
"//third_party/tonic",
104+
]
105+
106+
if (is_linux) {
107+
ldflags = [ "-rdynamic" ]
108+
}
109+
}
110+
83111
shared_library("flutter_engine_library") {
84112
visibility = [ ":*" ]
85113

shell/platform/embedder/embedder.cc

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,74 @@ FlutterEngineResult FlutterEngineRun(size_t version,
383383
thread_host.io_thread->GetTaskRunner() // io
384384
);
385385

386+
shell::PlatformViewEmbedder::UpdateSemanticsNodesCallback
387+
update_semantics_nodes_callback = nullptr;
388+
if (SAFE_ACCESS(args, update_semantics_node_callback, nullptr) != nullptr) {
389+
update_semantics_nodes_callback =
390+
[ptr = args->update_semantics_node_callback,
391+
user_data](blink::SemanticsNodeUpdates update) {
392+
for (const auto& value : update) {
393+
const auto& node = value.second;
394+
const auto& transform = node.transform;
395+
auto flutter_transform = FlutterTransformation{
396+
transform.get(0, 0), transform.get(0, 1), transform.get(0, 2),
397+
transform.get(1, 0), transform.get(1, 1), transform.get(1, 2),
398+
transform.get(2, 0), transform.get(2, 1), transform.get(2, 2)};
399+
const FlutterSemanticsNode embedder_node = {
400+
sizeof(FlutterSemanticsNode),
401+
node.id,
402+
static_cast<FlutterSemanticsFlag>(node.flags),
403+
static_cast<FlutterSemanticsAction>(node.actions),
404+
node.textSelectionBase,
405+
node.textSelectionExtent,
406+
node.scrollChildren,
407+
node.scrollIndex,
408+
node.scrollPosition,
409+
node.scrollExtentMax,
410+
node.scrollExtentMin,
411+
node.elevation,
412+
node.thickness,
413+
node.label.c_str(),
414+
node.hint.c_str(),
415+
node.value.c_str(),
416+
node.increasedValue.c_str(),
417+
node.decreasedValue.c_str(),
418+
static_cast<FlutterTextDirection>(node.textDirection),
419+
FlutterRect{node.rect.fLeft, node.rect.fTop, node.rect.fRight,
420+
node.rect.fBottom},
421+
flutter_transform,
422+
node.childrenInTraversalOrder.size(),
423+
&node.childrenInTraversalOrder[0],
424+
&node.childrenInHitTestOrder[0],
425+
node.customAccessibilityActions.size(),
426+
&node.customAccessibilityActions[0],
427+
};
428+
ptr(&embedder_node, user_data);
429+
}
430+
};
431+
}
432+
433+
shell::PlatformViewEmbedder::UpdateSemanticsCustomActionsCallback
434+
update_semantics_custom_actions_callback = nullptr;
435+
if (SAFE_ACCESS(args, update_semantics_custom_action_callback, nullptr) !=
436+
nullptr) {
437+
update_semantics_custom_actions_callback =
438+
[ptr = args->update_semantics_custom_action_callback,
439+
user_data](blink::CustomAccessibilityActionUpdates actions) {
440+
for (const auto& value : actions) {
441+
const auto& action = value.second;
442+
const FlutterSemanticsCustomAction embedder_action = {
443+
sizeof(FlutterSemanticsCustomAction),
444+
action.id,
445+
static_cast<FlutterSemanticsAction>(action.overrideId),
446+
action.label.c_str(),
447+
action.hint.c_str(),
448+
};
449+
ptr(&embedder_action, user_data);
450+
}
451+
};
452+
}
453+
386454
shell::PlatformViewEmbedder::PlatformMessageResponseCallback
387455
platform_message_response_callback = nullptr;
388456
if (SAFE_ACCESS(args, platform_message_callback, nullptr) != nullptr) {
@@ -403,6 +471,7 @@ FlutterEngineResult FlutterEngineRun(size_t version,
403471
}
404472

405473
shell::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table = {
474+
update_semantics_nodes_callback, update_semantics_custom_actions_callback,
406475
platform_message_response_callback, // platform_message_response_callback
407476
};
408477

@@ -688,3 +757,47 @@ FlutterEngineResult FlutterEngineMarkExternalTextureFrameAvailable(
688757
}
689758
return kSuccess;
690759
}
760+
761+
FlutterEngineResult FlutterEngineUpdateSemanticsEnabled(FlutterEngine engine,
762+
bool enabled) {
763+
if (engine == nullptr) {
764+
return kInvalidArguments;
765+
}
766+
if (!reinterpret_cast<shell::EmbedderEngine*>(engine)->SetSemanticsEnabled(
767+
enabled)) {
768+
return kInternalInconsistency;
769+
}
770+
return kSuccess;
771+
}
772+
773+
FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures(
774+
FlutterEngine engine,
775+
FlutterAccessibilityFeature flags) {
776+
if (engine == nullptr) {
777+
return kInvalidArguments;
778+
}
779+
if (!reinterpret_cast<shell::EmbedderEngine*>(engine)
780+
->SetAccessibilityFeatures(flags)) {
781+
return kInternalInconsistency;
782+
}
783+
return kSuccess;
784+
}
785+
786+
FlutterEngineResult FlutterEngineDispatchSemanticsAction(
787+
FlutterEngine engine,
788+
uint64_t id,
789+
FlutterSemanticsAction action,
790+
const uint8_t* data,
791+
size_t data_length) {
792+
if (engine == nullptr) {
793+
return kInvalidArguments;
794+
}
795+
auto engine_action = static_cast<blink::SemanticsAction>(action);
796+
if (!reinterpret_cast<shell::EmbedderEngine*>(engine)
797+
->DispatchSemanticsAction(
798+
id, engine_action,
799+
std::vector<uint8_t>({data, data + data_length}))) {
800+
return kInternalInconsistency;
801+
}
802+
return kSuccess;
803+
}

0 commit comments

Comments
 (0)