diff --git a/DEPS b/DEPS index 892aeb5752663..caf5e7815a8d6 100644 --- a/DEPS +++ b/DEPS @@ -35,7 +35,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/master/DEPS. # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'bc1fa173477efb6192bddd5f95c1ad0cccbc8d6f', + 'dart_revision': 'cc15e7439ae2c596b2ca0c5c032e294a20843e30', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index bd3f5416a0475..4ee685de4e308 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 65f332e775d31fee5a51ee0a303354c3 +Signature: bbf49eb830f03b288bb2a45383c3fe1a UNUSED LICENSES: diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.cc b/shell/platform/fuchsia/flutter/accessibility_bridge.cc index 9a0039f50fa24..0d801fbffb87f 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.cc +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.cc @@ -314,6 +314,7 @@ void AccessibilityBridge::AddSemanticsNodeUpdate( nodes_[flutter_node.id] = { .id = flutter_node.id, .flags = flutter_node.flags, + .is_focusable = IsFocusable(flutter_node), .rect = flutter_node.rect, .transform = flutter_node.transform, .children_in_hit_test_order = flutter_node.childrenInHitTestOrder, @@ -412,6 +413,7 @@ fuchsia::accessibility::semantics::Node AccessibilityBridge::GetRootNodeUpdate( nodes_[root_flutter_semantics_node_.id] = { .id = root_flutter_semantics_node_.id, .flags = root_flutter_semantics_node_.flags, + .is_focusable = IsFocusable(root_flutter_semantics_node_), .rect = root_flutter_semantics_node_.rect, .transform = result, .children_in_hit_test_order = @@ -570,7 +572,36 @@ std::optional AccessibilityBridge::GetHitNode(int32_t node_id, return candidate; } } - return node_id; + + if (node.is_focusable) { + return node_id; + } + + return {}; +} + +bool AccessibilityBridge::IsFocusable( + const flutter::SemanticsNode& node) const { + if (node.HasFlag(flutter::SemanticsFlags::kScopesRoute)) { + return false; + } + + if (node.HasFlag(flutter::SemanticsFlags::kIsFocusable)) { + return true; + } + + // Always consider platform views focusable. + if (node.IsPlatformViewNode()) { + return true; + } + + // Always conider actionable nodes focusable. + if (node.actions != 0) { + return true; + } + + // Consider text nodes focusable. + return !node.label.empty() || !node.value.empty() || !node.hint.empty(); } // |fuchsia::accessibility::semantics::SemanticListener| diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.h b/shell/platform/fuchsia/flutter/accessibility_bridge.h index 4328c8b46d660..8b0e939047105 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.h +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.h @@ -110,6 +110,7 @@ class AccessibilityBridge struct SemanticsNode { int32_t id; int32_t flags; + bool is_focusable; SkRect rect; SkRect screen_rect; SkM44 transform; @@ -197,6 +198,9 @@ class AccessibilityBridge // Assumes that SemanticsNode::screen_rect is up to date. std::optional GetHitNode(int32_t node_id, float x, float y); + // Returns whether the node is considered focusable. + bool IsFocusable(const flutter::SemanticsNode& node) const; + // Converts a fuchsia::accessibility::semantics::Action to a // flutter::SemanticsAction. // diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc b/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc index c9de9f75cb51f..44d5174190d10 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc +++ b/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc @@ -726,23 +726,32 @@ TEST_F(AccessibilityBridgeTest, HitTest) { flutter::SemanticsNode node0; node0.id = 0; node0.rect.setLTRB(0, 0, 100, 100); + node0.flags |= static_cast(flutter::SemanticsFlags::kIsFocusable); flutter::SemanticsNode node1; node1.id = 1; node1.rect.setLTRB(10, 10, 20, 20); + // Setting platform view id ensures this node is considered focusable. + node1.platformViewId = 1u; flutter::SemanticsNode node2; node2.id = 2; node2.rect.setLTRB(25, 10, 45, 20); + // Setting label ensures this node is considered focusable. + node2.label = "label"; flutter::SemanticsNode node3; node3.id = 3; node3.rect.setLTRB(10, 25, 20, 45); + // Setting actions to a nonzero value ensures this node is considered + // focusable. + node3.actions = 1u; flutter::SemanticsNode node4; node4.id = 4; node4.rect.setLTRB(10, 10, 20, 20); node4.transform.setTranslate(20, 20, 0); + node4.flags |= static_cast(flutter::SemanticsFlags::kIsFocusable); node0.childrenInTraversalOrder = {1, 2, 3, 4}; node0.childrenInHitTestOrder = {1, 2, 3, 4}; @@ -782,20 +791,59 @@ TEST_F(AccessibilityBridgeTest, HitTest) { EXPECT_EQ(hit_node_id, 4u); } +TEST_F(AccessibilityBridgeTest, HitTestUnfocusableChild) { + flutter::SemanticsNode node0; + node0.id = 0; + node0.rect.setLTRB(0, 0, 100, 100); + + flutter::SemanticsNode node1; + node1.id = 1; + node1.rect.setLTRB(10, 10, 60, 60); + + flutter::SemanticsNode node2; + node2.id = 2; + node2.rect.setLTRB(50, 50, 100, 100); + node2.flags |= static_cast(flutter::SemanticsFlags::kIsFocusable); + + node0.childrenInTraversalOrder = {1, 2}; + node0.childrenInHitTestOrder = {1, 2}; + + accessibility_bridge_->AddSemanticsNodeUpdate( + { + {0, node0}, + {1, node1}, + {2, node2}, + }, + 1.f); + RunLoopUntilIdle(); + + uint32_t hit_node_id; + auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) { + EXPECT_TRUE(hit.has_node_id()); + hit_node_id = hit.node_id(); + }; + + accessibility_bridge_->HitTest({55, 55}, callback); + EXPECT_EQ(hit_node_id, 2u); +} + TEST_F(AccessibilityBridgeTest, HitTestOverlapping) { // Tests that the first node in hit test order wins, even if a later node // would be able to recieve the hit. flutter::SemanticsNode node0; node0.id = 0; node0.rect.setLTRB(0, 0, 100, 100); + node0.flags |= static_cast(flutter::SemanticsFlags::kIsFocusable); flutter::SemanticsNode node1; node1.id = 1; node1.rect.setLTRB(0, 0, 100, 100); + node1.flags |= static_cast(flutter::SemanticsFlags::kIsFocusable); flutter::SemanticsNode node2; node2.id = 2; node2.rect.setLTRB(25, 10, 45, 20); + node2.flags |= static_cast(flutter::SemanticsFlags::kIsFocusable); node0.childrenInTraversalOrder = {1, 2}; node0.childrenInHitTestOrder = {2, 1};