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

Commit 48e29fd

Browse files
authored
Ensures that hit testing only resturns focusable nodes. (#23931)
1 parent 4ba79a3 commit 48e29fd

3 files changed

Lines changed: 84 additions & 1 deletion

File tree

shell/platform/fuchsia/flutter/accessibility_bridge.cc

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ void AccessibilityBridge::AddSemanticsNodeUpdate(
314314
nodes_[flutter_node.id] = {
315315
.id = flutter_node.id,
316316
.flags = flutter_node.flags,
317+
.is_focusable = IsFocusable(flutter_node),
317318
.rect = flutter_node.rect,
318319
.transform = flutter_node.transform,
319320
.children_in_hit_test_order = flutter_node.childrenInHitTestOrder,
@@ -412,6 +413,7 @@ fuchsia::accessibility::semantics::Node AccessibilityBridge::GetRootNodeUpdate(
412413
nodes_[root_flutter_semantics_node_.id] = {
413414
.id = root_flutter_semantics_node_.id,
414415
.flags = root_flutter_semantics_node_.flags,
416+
.is_focusable = IsFocusable(root_flutter_semantics_node_),
415417
.rect = root_flutter_semantics_node_.rect,
416418
.transform = result,
417419
.children_in_hit_test_order =
@@ -570,7 +572,36 @@ std::optional<int32_t> AccessibilityBridge::GetHitNode(int32_t node_id,
570572
return candidate;
571573
}
572574
}
573-
return node_id;
575+
576+
if (node.is_focusable) {
577+
return node_id;
578+
}
579+
580+
return {};
581+
}
582+
583+
bool AccessibilityBridge::IsFocusable(
584+
const flutter::SemanticsNode& node) const {
585+
if (node.HasFlag(flutter::SemanticsFlags::kScopesRoute)) {
586+
return false;
587+
}
588+
589+
if (node.HasFlag(flutter::SemanticsFlags::kIsFocusable)) {
590+
return true;
591+
}
592+
593+
// Always consider platform views focusable.
594+
if (node.IsPlatformViewNode()) {
595+
return true;
596+
}
597+
598+
// Always conider actionable nodes focusable.
599+
if (node.actions != 0) {
600+
return true;
601+
}
602+
603+
// Consider text nodes focusable.
604+
return !node.label.empty() || !node.value.empty() || !node.hint.empty();
574605
}
575606

576607
// |fuchsia::accessibility::semantics::SemanticListener|

shell/platform/fuchsia/flutter/accessibility_bridge.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class AccessibilityBridge
110110
struct SemanticsNode {
111111
int32_t id;
112112
int32_t flags;
113+
bool is_focusable;
113114
SkRect rect;
114115
SkRect screen_rect;
115116
SkM44 transform;
@@ -197,6 +198,9 @@ class AccessibilityBridge
197198
// Assumes that SemanticsNode::screen_rect is up to date.
198199
std::optional<int32_t> GetHitNode(int32_t node_id, float x, float y);
199200

201+
// Returns whether the node is considered focusable.
202+
bool IsFocusable(const flutter::SemanticsNode& node) const;
203+
200204
// Converts a fuchsia::accessibility::semantics::Action to a
201205
// flutter::SemanticsAction.
202206
//

shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,23 +726,32 @@ TEST_F(AccessibilityBridgeTest, HitTest) {
726726
flutter::SemanticsNode node0;
727727
node0.id = 0;
728728
node0.rect.setLTRB(0, 0, 100, 100);
729+
node0.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable);
729730

730731
flutter::SemanticsNode node1;
731732
node1.id = 1;
732733
node1.rect.setLTRB(10, 10, 20, 20);
734+
// Setting platform view id ensures this node is considered focusable.
735+
node1.platformViewId = 1u;
733736

734737
flutter::SemanticsNode node2;
735738
node2.id = 2;
736739
node2.rect.setLTRB(25, 10, 45, 20);
740+
// Setting label ensures this node is considered focusable.
741+
node2.label = "label";
737742

738743
flutter::SemanticsNode node3;
739744
node3.id = 3;
740745
node3.rect.setLTRB(10, 25, 20, 45);
746+
// Setting actions to a nonzero value ensures this node is considered
747+
// focusable.
748+
node3.actions = 1u;
741749

742750
flutter::SemanticsNode node4;
743751
node4.id = 4;
744752
node4.rect.setLTRB(10, 10, 20, 20);
745753
node4.transform.setTranslate(20, 20, 0);
754+
node4.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable);
746755

747756
node0.childrenInTraversalOrder = {1, 2, 3, 4};
748757
node0.childrenInHitTestOrder = {1, 2, 3, 4};
@@ -782,20 +791,59 @@ TEST_F(AccessibilityBridgeTest, HitTest) {
782791
EXPECT_EQ(hit_node_id, 4u);
783792
}
784793

794+
TEST_F(AccessibilityBridgeTest, HitTestUnfocusableChild) {
795+
flutter::SemanticsNode node0;
796+
node0.id = 0;
797+
node0.rect.setLTRB(0, 0, 100, 100);
798+
799+
flutter::SemanticsNode node1;
800+
node1.id = 1;
801+
node1.rect.setLTRB(10, 10, 60, 60);
802+
803+
flutter::SemanticsNode node2;
804+
node2.id = 2;
805+
node2.rect.setLTRB(50, 50, 100, 100);
806+
node2.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable);
807+
808+
node0.childrenInTraversalOrder = {1, 2};
809+
node0.childrenInHitTestOrder = {1, 2};
810+
811+
accessibility_bridge_->AddSemanticsNodeUpdate(
812+
{
813+
{0, node0},
814+
{1, node1},
815+
{2, node2},
816+
},
817+
1.f);
818+
RunLoopUntilIdle();
819+
820+
uint32_t hit_node_id;
821+
auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
822+
EXPECT_TRUE(hit.has_node_id());
823+
hit_node_id = hit.node_id();
824+
};
825+
826+
accessibility_bridge_->HitTest({55, 55}, callback);
827+
EXPECT_EQ(hit_node_id, 2u);
828+
}
829+
785830
TEST_F(AccessibilityBridgeTest, HitTestOverlapping) {
786831
// Tests that the first node in hit test order wins, even if a later node
787832
// would be able to recieve the hit.
788833
flutter::SemanticsNode node0;
789834
node0.id = 0;
790835
node0.rect.setLTRB(0, 0, 100, 100);
836+
node0.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable);
791837

792838
flutter::SemanticsNode node1;
793839
node1.id = 1;
794840
node1.rect.setLTRB(0, 0, 100, 100);
841+
node1.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable);
795842

796843
flutter::SemanticsNode node2;
797844
node2.id = 2;
798845
node2.rect.setLTRB(25, 10, 45, 20);
846+
node2.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable);
799847

800848
node0.childrenInTraversalOrder = {1, 2};
801849
node0.childrenInHitTestOrder = {2, 1};

0 commit comments

Comments
 (0)