Skip to content

Commit abbeefe

Browse files
committed
Block focus actions for accessibility-blocked nodes
1 parent fa0bb23 commit abbeefe

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

flutter/shell/platform/common/flutter_platform_node_delegate.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ bool FlutterPlatformNodeDelegate::AccessibilityPerformAction(
3838
target, FlutterSemanticsAction::kFlutterSemanticsActionTap, {});
3939
return true;
4040
case ax::mojom::Action::kFocus:
41+
if (!CanReceiveAccessibilityFocus()) {
42+
return false;
43+
}
4144
bridge_ptr->SetLastFocusedId(target);
4245
bridge_ptr->DispatchAccessibilityAction(
4346
target,
@@ -57,6 +60,11 @@ bool FlutterPlatformNodeDelegate::AccessibilityPerformAction(
5760
return false;
5861
}
5962

63+
bool FlutterPlatformNodeDelegate::CanReceiveAccessibilityFocus() const {
64+
return !GetData().IsIgnored() &&
65+
GetData().HasState(ax::mojom::State::kFocusable);
66+
}
67+
6068
const ui::AXNodeData& FlutterPlatformNodeDelegate::GetData() const {
6169
return ax_node_->data();
6270
}

flutter/shell/platform/common/flutter_platform_node_delegate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase {
172172
virtual const ui::AXTree::Selection GetUnignoredSelection() const override;
173173

174174
private:
175+
bool CanReceiveAccessibilityFocus() const;
176+
175177
ui::AXNode* ax_node_;
176178
std::weak_ptr<OwnerBridge> bridge_;
177179
ui::AXUniqueId unique_id_;

flutter/shell/platform/tizen/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ executable("flutter_tizen_unittests") {
246246
testonly = true
247247

248248
sources = [
249+
"accessibility_bridge_unittests.cc",
249250
"channels/lifecycle_channel_unittests.cc",
250251
"channels/settings_channel_unittests.cc",
251252
"flutter_project_bundle_unittests.cc",
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/common/test_accessibility_bridge.h"
6+
7+
#include "flutter/third_party/accessibility/ax/ax_action_data.h"
8+
#include "gtest/gtest.h"
9+
10+
namespace flutter {
11+
namespace {
12+
13+
FlutterTransformation IdentityTransform() {
14+
return FlutterTransformation{
15+
1.0, 0.0, 0.0,
16+
0.0, 1.0, 0.0,
17+
0.0, 0.0, 1.0,
18+
};
19+
}
20+
21+
FlutterSemanticsFlags MakeFlags(bool blocked) {
22+
FlutterSemanticsFlags flags = {};
23+
flags.struct_size = sizeof(FlutterSemanticsFlags);
24+
flags.is_accessibility_focus_blocked = blocked;
25+
return flags;
26+
}
27+
28+
FlutterSemanticsNode2 MakeNode(int32_t id, FlutterSemanticsFlags* flags) {
29+
FlutterSemanticsNode2 node = {};
30+
node.struct_size = sizeof(FlutterSemanticsNode2);
31+
node.id = id;
32+
node.actions = kFlutterSemanticsActionTap;
33+
node.label = "target";
34+
node.text_direction = kFlutterTextDirectionLTR;
35+
node.rect = FlutterRect{0.0, 0.0, 10.0, 10.0};
36+
node.transform = IdentityTransform();
37+
node.platform_view_id = -1;
38+
node.flags2 = flags;
39+
return node;
40+
}
41+
42+
std::shared_ptr<FlutterPlatformNodeDelegate> GetDelegate(
43+
TestAccessibilityBridge& bridge,
44+
int32_t node_id) {
45+
auto delegate = bridge.GetFlutterPlatformNodeDelegateFromID(node_id).lock();
46+
EXPECT_NE(delegate, nullptr);
47+
return delegate;
48+
}
49+
50+
} // namespace
51+
52+
TEST(AccessibilityBridgeTest, BlockedNodeDoesNotBecomeFocusable) {
53+
TestAccessibilityBridge bridge;
54+
auto flags = MakeFlags(true);
55+
auto node = MakeNode(1, &flags);
56+
57+
bridge.AddFlutterSemanticsNodeUpdate(node);
58+
bridge.CommitUpdates();
59+
60+
auto* ax_node = bridge.GetNodeFromTree(1);
61+
ASSERT_NE(ax_node, nullptr);
62+
EXPECT_FALSE(ax_node->data().HasState(ax::mojom::State::kFocusable));
63+
}
64+
65+
TEST(AccessibilityBridgeTest, BlockedNodeRejectsAccessibilityFocusAction) {
66+
TestAccessibilityBridge bridge;
67+
auto flags = MakeFlags(true);
68+
auto node = MakeNode(1, &flags);
69+
70+
bridge.AddFlutterSemanticsNodeUpdate(node);
71+
bridge.CommitUpdates();
72+
73+
auto delegate = GetDelegate(bridge, node.id);
74+
ASSERT_NE(delegate, nullptr);
75+
76+
ui::AXActionData action_data;
77+
action_data.action = ax::mojom::Action::kFocus;
78+
79+
EXPECT_FALSE(delegate->AccessibilityPerformAction(action_data));
80+
EXPECT_TRUE(bridge.performed_actions.empty());
81+
EXPECT_EQ(bridge.GetLastFocusedId(), ui::AXNode::kInvalidAXID);
82+
}
83+
84+
TEST(AccessibilityBridgeTest, UnblockedNodeAcceptsAccessibilityFocusAction) {
85+
TestAccessibilityBridge bridge;
86+
auto flags = MakeFlags(false);
87+
auto node = MakeNode(1, &flags);
88+
89+
bridge.AddFlutterSemanticsNodeUpdate(node);
90+
bridge.CommitUpdates();
91+
92+
auto delegate = GetDelegate(bridge, node.id);
93+
ASSERT_NE(delegate, nullptr);
94+
95+
ui::AXActionData action_data;
96+
action_data.action = ax::mojom::Action::kFocus;
97+
98+
EXPECT_TRUE(delegate->AccessibilityPerformAction(action_data));
99+
ASSERT_EQ(bridge.performed_actions.size(), 1u);
100+
EXPECT_EQ(
101+
bridge.performed_actions.front(),
102+
FlutterSemanticsAction::kFlutterSemanticsActionDidGainAccessibilityFocus);
103+
EXPECT_EQ(bridge.GetLastFocusedId(), node.id);
104+
}

flutter/third_party/accessibility/ax/platform/ax_platform_node_auralinux.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4358,6 +4358,9 @@ AXPlatformNodeAuraLinux::HitTestSync(gint x, gint y, AtkCoordType coord_type) {
43584358
}
43594359

43604360
bool AXPlatformNodeAuraLinux::GrabFocus() {
4361+
if (!GetData().HasState(ax::mojom::State::kFocusable))
4362+
return false;
4363+
43614364
AXActionData action_data;
43624365
action_data.action = ax::mojom::Action::kFocus;
43634366
return delegate_->AccessibilityPerformAction(action_data);

0 commit comments

Comments
 (0)