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

Commit 38bb7c5

Browse files
[fuchsia][a11y] Adds inspect data to the a11y bridge. (#25868)
This change adds inspect data to the a11y bridge, which can be requested via the command line. Inspect nodes are lazy computed, meaning that they are only processed when invoked, so no extra space is used during normal use. Bug: fxbug.dev/75100 Test: AccessibilityBridgeTest.InspectData
1 parent 00bbae1 commit 38bb7c5

File tree

8 files changed

+572
-104
lines changed

8 files changed

+572
-104
lines changed

shell/platform/fuchsia/flutter/accessibility_bridge.cc

Lines changed: 361 additions & 44 deletions
Large diffs are not rendered by default.

shell/platform/fuchsia/flutter/accessibility_bridge.h

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <fuchsia/ui/gfx/cpp/fidl.h>
1515
#include <lib/fidl/cpp/binding_set.h>
1616
#include <lib/sys/cpp/service_directory.h>
17+
#include <lib/sys/inspect/cpp/component.h>
1718
#include <zircon/types.h>
1819

1920
#include <memory>
@@ -40,13 +41,9 @@ namespace flutter_runner {
4041
class AccessibilityBridge
4142
: public fuchsia::accessibility::semantics::SemanticListener {
4243
public:
43-
// A delegate to call when semantics are enabled or disabled.
44-
class Delegate {
45-
public:
46-
virtual void SetSemanticsEnabled(bool enabled) = 0;
47-
virtual void DispatchSemanticsAction(int32_t node_id,
48-
flutter::SemanticsAction action) = 0;
49-
};
44+
using SetSemanticsEnabledCallback = std::function<void(bool)>;
45+
using DispatchSemanticsActionCallback =
46+
std::function<void(int32_t, flutter::SemanticsAction)>;
5047

5148
// TODO(MI4-2531, FIDL-718): Remove this. We shouldn't be worried about
5249
// batching messages at this level.
@@ -67,9 +64,18 @@ class AccessibilityBridge
6764
"flutter::SemanticsNode::id and "
6865
"fuchsia::accessibility::semantics::Node::node_id differ in size.");
6966

70-
AccessibilityBridge(Delegate& delegate,
71-
const std::shared_ptr<sys::ServiceDirectory> services,
72-
fuchsia::ui::views::ViewRef view_ref);
67+
AccessibilityBridge(
68+
SetSemanticsEnabledCallback set_semantics_enabled_callback,
69+
DispatchSemanticsActionCallback dispatch_semantics_action_callback,
70+
const std::shared_ptr<sys::ServiceDirectory> services,
71+
fuchsia::ui::views::ViewRef view_ref);
72+
73+
AccessibilityBridge(
74+
SetSemanticsEnabledCallback set_semantics_enabled_callback,
75+
DispatchSemanticsActionCallback dispatch_semantics_action_callback,
76+
const std::shared_ptr<sys::ServiceDirectory> services,
77+
inspect::Node inspect_node,
78+
fuchsia::ui::views::ViewRef view_ref);
7379

7480
// Returns true if accessible navigation is enabled.
7581
bool GetSemanticsEnabled() const;
@@ -105,28 +111,16 @@ class AccessibilityBridge
105111
OnAccessibilityActionRequestedCallback callback) override;
106112

107113
private:
108-
// Holds only the fields we need for hit testing.
114+
// Fuchsia's default root semantic node id.
115+
static constexpr int32_t kRootNodeId = 0;
116+
117+
// Holds a flutter semantic node and some extra info.
109118
// In particular, it adds a screen_rect field to flutter::SemanticsNode.
110119
struct SemanticsNode {
111-
int32_t id;
112-
int32_t flags;
113-
bool is_focusable;
114-
SkRect rect;
120+
flutter::SemanticsNode data;
115121
SkRect screen_rect;
116-
SkM44 transform;
117-
std::vector<int32_t> children_in_hit_test_order;
118122
};
119123

120-
AccessibilityBridge::Delegate& delegate_;
121-
122-
static constexpr int32_t kRootNodeId = 0;
123-
flutter::SemanticsNode root_flutter_semantics_node_;
124-
float last_seen_view_pixel_ratio_ = 1.f;
125-
fidl::Binding<fuchsia::accessibility::semantics::SemanticListener> binding_;
126-
fuchsia::accessibility::semantics::SemanticsManagerPtr
127-
fuchsia_semantics_manager_;
128-
fuchsia::accessibility::semantics::SemanticTreePtr tree_ptr_;
129-
bool semantics_enabled_;
130124
// This is the cache of all nodes we've sent to Fuchsia's SemanticsManager.
131125
// Assists with pruning unreachable nodes and hit testing.
132126
std::unordered_map<int32_t, SemanticsNode> nodes_;
@@ -214,6 +208,34 @@ class AccessibilityBridge
214208
void OnSemanticsModeChanged(bool enabled,
215209
OnSemanticsModeChangedCallback callback) override;
216210

211+
#if !FLUTTER_RELEASE
212+
// Fills the inspect tree with debug information about the semantic tree.
213+
void FillInspectTree(int32_t flutter_node_id,
214+
int32_t current_level,
215+
inspect::Node inspect_node,
216+
inspect::Inspector* inspector) const;
217+
#endif // !FLUTTER_RELEASE
218+
219+
SetSemanticsEnabledCallback set_semantics_enabled_callback_;
220+
DispatchSemanticsActionCallback dispatch_semantics_action_callback_;
221+
flutter::SemanticsNode root_flutter_semantics_node_;
222+
float last_seen_view_pixel_ratio_ = 1.f;
223+
fidl::Binding<fuchsia::accessibility::semantics::SemanticListener> binding_;
224+
fuchsia::accessibility::semantics::SemanticsManagerPtr
225+
fuchsia_semantics_manager_;
226+
fuchsia::accessibility::semantics::SemanticTreePtr tree_ptr_;
227+
bool semantics_enabled_;
228+
229+
// Node to publish inspect data.
230+
inspect::Node inspect_node_;
231+
232+
#if !FLUTTER_RELEASE
233+
// Inspect node to store a dump of the semantic tree. Note that this only gets
234+
// computed if requested, so it does not use memory to store the dump unless
235+
// an explicit request is made.
236+
inspect::LazyNode inspect_node_tree_dump_;
237+
#endif // !FLUTTER_RELEASE
238+
217239
FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge);
218240
};
219241
} // namespace flutter_runner

shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
#include <gtest/gtest.h>
88
#include <lib/async-loop/cpp/loop.h>
99
#include <lib/async-loop/default.h>
10+
#include <lib/async/cpp/executor.h>
1011
#include <lib/fidl/cpp/binding_set.h>
1112
#include <lib/fidl/cpp/interface_request.h>
13+
#include <lib/inspect/cpp/hierarchy.h>
14+
#include <lib/inspect/cpp/inspector.h>
15+
#include <lib/inspect/cpp/reader.h>
1216
#include <lib/sys/cpp/testing/service_directory_provider.h>
1317
#include <zircon/types.h>
1418

@@ -33,12 +37,11 @@ void ExpectNodeHasRole(
3337

3438
} // namespace
3539

36-
class AccessibilityBridgeTestDelegate
37-
: public flutter_runner::AccessibilityBridge::Delegate {
40+
class AccessibilityBridgeTestDelegate {
3841
public:
39-
void SetSemanticsEnabled(bool enabled) override { enabled_ = enabled; }
42+
void SetSemanticsEnabled(bool enabled) { enabled_ = enabled; }
4043
void DispatchSemanticsAction(int32_t node_id,
41-
flutter::SemanticsAction action) override {
44+
flutter::SemanticsAction action) {
4245
actions.push_back(std::make_pair(node_id, action));
4346
}
4447

@@ -53,7 +56,8 @@ class AccessibilityBridgeTest : public testing::Test {
5356
public:
5457
AccessibilityBridgeTest()
5558
: loop_(&kAsyncLoopConfigAttachToCurrentThread),
56-
services_provider_(loop_.dispatcher()) {
59+
services_provider_(loop_.dispatcher()),
60+
executor_(loop_.dispatcher()) {
5761
services_provider_.AddService(
5862
semantics_manager_.GetHandler(loop_.dispatcher()),
5963
SemanticsManager::Name_);
@@ -64,16 +68,44 @@ class AccessibilityBridgeTest : public testing::Test {
6468
loop_.ResetQuit();
6569
}
6670

71+
void RunPromiseToCompletion(fit::promise<> promise) {
72+
bool done = false;
73+
executor_.schedule_task(
74+
std::move(promise).and_then([&done]() { done = true; }));
75+
while (loop_.GetState() == ASYNC_LOOP_RUNNABLE) {
76+
if (done) {
77+
loop_.ResetQuit();
78+
return;
79+
}
80+
81+
loop_.Run(zx::deadline_after(zx::duration::infinite()), true);
82+
}
83+
loop_.ResetQuit();
84+
}
85+
6786
protected:
6887
void SetUp() override {
6988
zx_status_t status = zx::eventpair::create(
7089
/*flags*/ 0u, &view_ref_control_.reference, &view_ref_.reference);
7190
EXPECT_EQ(status, ZX_OK);
7291

7392
accessibility_delegate_.actions.clear();
93+
inspector_ = std::make_unique<inspect::Inspector>();
94+
flutter_runner::AccessibilityBridge::SetSemanticsEnabledCallback
95+
set_semantics_enabled_callback = [this](bool enabled) {
96+
accessibility_delegate_.SetSemanticsEnabled(enabled);
97+
};
98+
flutter_runner::AccessibilityBridge::DispatchSemanticsActionCallback
99+
dispatch_semantics_action_callback =
100+
[this](int32_t node_id, flutter::SemanticsAction action) {
101+
accessibility_delegate_.DispatchSemanticsAction(node_id, action);
102+
};
74103
accessibility_bridge_ =
75104
std::make_unique<flutter_runner::AccessibilityBridge>(
76-
accessibility_delegate_, services_provider_.service_directory(),
105+
std::move(set_semantics_enabled_callback),
106+
std::move(dispatch_semantics_action_callback),
107+
services_provider_.service_directory(),
108+
inspector_->GetRoot().CreateChild("test_node"),
77109
std::move(view_ref_));
78110
RunLoopUntilIdle();
79111
}
@@ -85,10 +117,14 @@ class AccessibilityBridgeTest : public testing::Test {
85117
MockSemanticsManager semantics_manager_;
86118
AccessibilityBridgeTestDelegate accessibility_delegate_;
87119
std::unique_ptr<flutter_runner::AccessibilityBridge> accessibility_bridge_;
120+
// Required to verify inspect metrics.
121+
std::unique_ptr<inspect::Inspector> inspector_;
88122

89123
private:
90124
async::Loop loop_;
91125
sys::testing::ServiceDirectoryProvider services_provider_;
126+
// Required to retrieve inspect metrics.
127+
async::Executor executor_;
92128
};
93129

94130
TEST_F(AccessibilityBridgeTest, RegistersViewRef) {
@@ -952,4 +988,54 @@ TEST_F(AccessibilityBridgeTest, Actions) {
952988
EXPECT_EQ(accessibility_delegate_.actions.back(),
953989
std::make_pair(0, flutter::SemanticsAction::kDecrease));
954990
}
991+
992+
#if !FLUTTER_RELEASE
993+
TEST_F(AccessibilityBridgeTest, InspectData) {
994+
flutter::SemanticsNodeUpdates updates;
995+
flutter::SemanticsNode node0;
996+
node0.id = 0;
997+
node0.label = "node0";
998+
node0.hint = "node0_hint";
999+
node0.value = "value";
1000+
node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsButton);
1001+
node0.childrenInTraversalOrder = {1};
1002+
node0.childrenInHitTestOrder = {1};
1003+
node0.rect.setLTRB(0, 0, 100, 100);
1004+
updates.emplace(0, node0);
1005+
1006+
flutter::SemanticsNode node1;
1007+
node1.id = 1;
1008+
node1.flags |= static_cast<int>(flutter::SemanticsFlags::kIsHeader);
1009+
node1.childrenInTraversalOrder = {};
1010+
node1.childrenInHitTestOrder = {};
1011+
updates.emplace(1, node1);
1012+
1013+
accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates), 1.f);
1014+
RunLoopUntilIdle();
1015+
1016+
fit::result<inspect::Hierarchy> hierarchy;
1017+
ASSERT_FALSE(hierarchy.is_ok());
1018+
RunPromiseToCompletion(
1019+
inspect::ReadFromInspector(*inspector_)
1020+
.then([&hierarchy](fit::result<inspect::Hierarchy>& result) {
1021+
hierarchy = std::move(result);
1022+
}));
1023+
ASSERT_TRUE(hierarchy.is_ok());
1024+
1025+
auto tree_inspect_hierarchy = hierarchy.value().GetByPath({"test_node"});
1026+
ASSERT_NE(tree_inspect_hierarchy, nullptr);
1027+
// TODO(http://fxbug.dev/75841): Rewrite flutter engine accessibility bridge
1028+
// tests using inspect matchers. The checks bellow verify that the tree was
1029+
// built, and that it matches the format of the input tree. This will be
1030+
// updated in the future when test matchers are available to verify individual
1031+
// property values.
1032+
const auto& root = tree_inspect_hierarchy->children();
1033+
ASSERT_EQ(root.size(), 1u);
1034+
EXPECT_EQ(root[0].name(), "semantic_tree_root");
1035+
const auto& child = root[0].children();
1036+
ASSERT_EQ(child.size(), 1u);
1037+
EXPECT_EQ(child[0].name(), "node_1");
1038+
}
1039+
#endif // !FLUTTER_RELEASE
1040+
9551041
} // namespace flutter_runner_test

shell/platform/fuchsia/flutter/engine.cc

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ Engine::Engine(Delegate& delegate,
102102
// refs are not copyable, and multiple consumers need view refs.
103103
fuchsia::ui::views::ViewRef platform_view_ref;
104104
view_ref_pair.view_ref.Clone(&platform_view_ref);
105+
fuchsia::ui::views::ViewRef accessibility_bridge_view_ref;
106+
view_ref_pair.view_ref.Clone(&accessibility_bridge_view_ref);
105107
fuchsia::ui::views::ViewRef isolate_view_ref;
106108
view_ref_pair.view_ref.Clone(&isolate_view_ref);
107109
// Input3 keyboard listener registration requires a ViewRef as an event
@@ -171,6 +173,30 @@ Engine::Engine(Delegate& delegate,
171173
environment->GetServices(parent_environment_service_provider.NewRequest());
172174
environment.Unbind();
173175

176+
AccessibilityBridge::SetSemanticsEnabledCallback
177+
set_semantics_enabled_callback = [this](bool enabled) {
178+
auto platform_view = shell_->GetPlatformView();
179+
180+
if (platform_view) {
181+
platform_view->SetSemanticsEnabled(enabled);
182+
}
183+
};
184+
185+
AccessibilityBridge::DispatchSemanticsActionCallback
186+
dispatch_semantics_action_callback =
187+
[this](int32_t node_id, flutter::SemanticsAction action) {
188+
auto platform_view = shell_->GetPlatformView();
189+
190+
if (platform_view) {
191+
platform_view->DispatchSemanticsAction(node_id, action, {});
192+
}
193+
};
194+
195+
accessibility_bridge_ = std::make_unique<AccessibilityBridge>(
196+
std::move(set_semantics_enabled_callback),
197+
std::move(dispatch_semantics_action_callback), svc,
198+
std::move(accessibility_bridge_view_ref));
199+
174200
OnEnableWireframe on_enable_wireframe_callback = std::bind(
175201
&Engine::DebugWireframeSettingsChanged, this, std::placeholders::_1);
176202

@@ -227,6 +253,16 @@ Engine::Engine(Delegate& delegate,
227253
keyboard_svc_->AddListener(std::move(keyboard_view_ref),
228254
keyboard_listener.Bind(), [] {});
229255

256+
OnSemanticsNodeUpdate on_semantics_node_update_callback =
257+
[this](flutter::SemanticsNodeUpdates updates, float pixel_ratio) {
258+
accessibility_bridge_->AddSemanticsNodeUpdate(updates, pixel_ratio);
259+
};
260+
261+
OnRequestAnnounce on_request_announce_callback =
262+
[this](const std::string& message) {
263+
accessibility_bridge_->RequestAnnounce(message);
264+
};
265+
230266
// Setup the callback that will instantiate the platform view.
231267
flutter::Shell::CreateCallback<flutter::PlatformView>
232268
on_create_platform_view = fml::MakeCopyable(
@@ -244,6 +280,10 @@ Engine::Engine(Delegate& delegate,
244280
on_update_view_callback = std::move(on_update_view_callback),
245281
on_destroy_view_callback = std::move(on_destroy_view_callback),
246282
on_create_surface_callback = std::move(on_create_surface_callback),
283+
on_semantics_node_update_callback =
284+
std::move(on_semantics_node_update_callback),
285+
on_request_announce_callback =
286+
std::move(on_request_announce_callback),
247287
external_view_embedder = GetExternalViewEmbedder(),
248288
keyboard_listener_request = std::move(keyboard_listener_request),
249289
await_vsync_callback =
@@ -271,7 +311,9 @@ Engine::Engine(Delegate& delegate,
271311
std::move(on_create_view_callback),
272312
std::move(on_update_view_callback),
273313
std::move(on_destroy_view_callback),
274-
std::move(on_create_surface_callback), external_view_embedder,
314+
std::move(on_create_surface_callback),
315+
std::move(on_semantics_node_update_callback),
316+
std::move(on_request_announce_callback), external_view_embedder,
275317
// Callbacks for VsyncWaiter to call into SessionConnection.
276318
await_vsync_callback,
277319
await_vsync_for_secondary_callback_callback);

shell/platform/fuchsia/flutter/engine.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "flutter/flow/surface.h"
2222
#include "flutter/fml/macros.h"
2323
#include "flutter/shell/common/shell.h"
24+
#include "flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h"
2425

2526
#include "default_session_connection.h"
2627
#include "flutter_runner_product_configuration.h"
@@ -83,6 +84,7 @@ class Engine final {
8384

8485
std::unique_ptr<IsolateConfigurator> isolate_configurator_;
8586
std::unique_ptr<flutter::Shell> shell_;
87+
std::unique_ptr<AccessibilityBridge> accessibility_bridge_;
8688

8789
fuchsia::intl::PropertyProviderPtr intl_property_provider_;
8890

0 commit comments

Comments
 (0)