未验证 提交 58a62076 编写于 作者: A Alexander Brusher 提交者: GitHub

Adds fuchsia node roles to accessibility bridge updates. (#20385)

上级 95f2b727
......@@ -300,8 +300,12 @@ class SemanticsFlag {
static const int _kIsReadOnlyIndex = 1 << 20;
static const int _kIsFocusableIndex = 1 << 21;
static const int _kIsLinkIndex = 1 << 22;
static const int _kIsSliderIndex = 1 << 23;
// READ THIS: if you add a flag here, you MUST update the numSemanticsFlags
// value in testing/dart/semantics_test.dart, or tests will fail.
// value in testing/dart/semantics_test.dart, or tests will fail. Also,
// please update the Flag enum in
// flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java,
// and the SemanticsFlag class in lib/web_ui/lib/src/ui/semantics.dart.
const SemanticsFlag._(this.index) : assert(index != null); // ignore: unnecessary_null_comparison
......@@ -355,6 +359,9 @@ class SemanticsFlag {
/// affordances.
static const SemanticsFlag isTextField = SemanticsFlag._(_kIsTextFieldIndex);
/// Whether the semantic node represents a slider.
static const SemanticsFlag isSlider = SemanticsFlag._(_kIsSliderIndex);
/// Whether the semantic node is read only.
///
/// Only applicable when [isTextField] is true.
......@@ -551,7 +558,8 @@ class SemanticsFlag {
_kIsReadOnlyIndex: isReadOnly,
_kIsFocusableIndex: isFocusable,
_kIsLinkIndex: isLink,
};
_kIsSliderIndex: isSlider,
};
@override
String toString() {
......@@ -602,6 +610,8 @@ class SemanticsFlag {
return 'SemanticsFlag.isFocusable';
case _kIsLinkIndex:
return 'SemanticsFlag.isLink';
case _kIsSliderIndex:
return 'SemanticsFlag.isSlider';
}
assert(false, 'Unhandled index: $index');
return '';
......
......@@ -79,6 +79,7 @@ enum class SemanticsFlags : int32_t {
kIsReadOnly = 1 << 20,
kIsFocusable = 1 << 21,
kIsLink = 1 << 22,
kIsSlider = 1 << 23,
};
const int kScrollableSemanticsFlags =
......
......@@ -156,6 +156,7 @@ class SemanticsFlag {
static const int _kIsReadOnlyIndex = 1 << 20;
static const int _kIsFocusableIndex = 1 << 21;
static const int _kIsLinkIndex = 1 << 22;
static const int _kIsSliderIndex = 1 << 23;
const SemanticsFlag._(this.index) : assert(index != null); // ignore: unnecessary_null_comparison
final int index;
......@@ -182,12 +183,14 @@ class SemanticsFlag {
static const SemanticsFlag isToggled = SemanticsFlag._(_kIsToggledIndex);
static const SemanticsFlag hasImplicitScrolling = SemanticsFlag._(_kHasImplicitScrollingIndex);
static const SemanticsFlag isMultiline = SemanticsFlag._(_kIsMultilineIndex);
static const SemanticsFlag isSlider = SemanticsFlag._(_kIsSliderIndex);
static const Map<int, SemanticsFlag> values = <int, SemanticsFlag>{
_kHasCheckedStateIndex: hasCheckedState,
_kIsCheckedIndex: isChecked,
_kIsSelectedIndex: isSelected,
_kIsButtonIndex: isButton,
_kIsLinkIndex: isLink,
_kIsSliderIndex: isSlider,
_kIsTextFieldIndex: isTextField,
_kIsFocusableIndex: isFocusable,
_kIsFocusedIndex: isFocused,
......
......@@ -115,6 +115,31 @@ fuchsia::accessibility::semantics::States AccessibilityBridge::GetNodeStates(
return states;
}
fuchsia::accessibility::semantics::Role AccessibilityBridge::GetNodeRole(
const flutter::SemanticsNode& node) const {
if (node.HasFlag(flutter::SemanticsFlags::kIsButton)) {
return fuchsia::accessibility::semantics::Role::BUTTON;
}
if (node.HasFlag(flutter::SemanticsFlags::kIsHeader)) {
return fuchsia::accessibility::semantics::Role::HEADER;
}
if (node.HasFlag(flutter::SemanticsFlags::kIsImage)) {
return fuchsia::accessibility::semantics::Role::IMAGE;
}
if (node.HasFlag(flutter::SemanticsFlags::kIsTextField)) {
return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
}
if (node.HasFlag(flutter::SemanticsFlags::kIsSlider)) {
return fuchsia::accessibility::semantics::Role::SLIDER;
}
return fuchsia::accessibility::semantics::Role::UNKNOWN;
}
std::unordered_set<int32_t> AccessibilityBridge::GetDescendants(
int32_t node_id) const {
std::unordered_set<int32_t> descendents;
......@@ -227,6 +252,7 @@ void AccessibilityBridge::AddSemanticsNodeUpdate(
.set_transform(GetNodeTransform(flutter_node))
.set_attributes(GetNodeAttributes(flutter_node, &this_node_size))
.set_states(GetNodeStates(flutter_node, &this_node_size))
.set_role(GetNodeRole(flutter_node))
.set_child_ids(child_ids);
this_node_size +=
kNodeIdSize * flutter_node.childrenInTraversalOrder.size();
......
......@@ -145,6 +145,11 @@ class AccessibilityBridge
const flutter::SemanticsNode& node,
size_t* additional_size) const;
// Derives the role for a Fuchsia semantics node from a Flutter semantics
// node.
fuchsia::accessibility::semantics::Role GetNodeRole(
const flutter::SemanticsNode& node) const;
// Gets the set of reachable descendants from the given node id.
std::unordered_set<int32_t> GetDescendants(int32_t node_id) const;
......
......@@ -19,6 +19,20 @@
namespace flutter_runner_test {
namespace {
void ExpectNodeHasRole(
const fuchsia::accessibility::semantics::Node& node,
const std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role>
roles_by_node_id) {
ASSERT_TRUE(node.has_node_id());
ASSERT_NE(roles_by_node_id.find(node.node_id()), roles_by_node_id.end());
EXPECT_TRUE(node.has_role());
EXPECT_EQ(node.role(), roles_by_node_id.at(node.node_id()));
}
} // namespace
class AccessibilityBridgeTestDelegate
: public flutter_runner::AccessibilityBridge::Delegate {
public:
......@@ -89,6 +103,67 @@ TEST_F(AccessibilityBridgeTest, EnableDisable) {
EXPECT_TRUE(accessibility_delegate_.enabled());
}
TEST_F(AccessibilityBridgeTest, UpdatesNodeRoles) {
flutter::SemanticsNodeUpdates updates;
flutter::SemanticsNode node0;
node0.id = 0;
node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsButton);
node0.childrenInTraversalOrder = {1, 2, 3, 4};
node0.childrenInHitTestOrder = {1, 2, 3, 4};
updates.emplace(0, node0);
flutter::SemanticsNode node1;
node1.id = 1;
node1.flags |= static_cast<int>(flutter::SemanticsFlags::kIsHeader);
node1.childrenInTraversalOrder = {};
node1.childrenInHitTestOrder = {};
updates.emplace(1, node1);
flutter::SemanticsNode node2;
node2.id = 2;
node2.flags |= static_cast<int>(flutter::SemanticsFlags::kIsImage);
node2.childrenInTraversalOrder = {};
node2.childrenInHitTestOrder = {};
updates.emplace(2, node2);
flutter::SemanticsNode node3;
node3.id = 3;
node3.flags |= static_cast<int>(flutter::SemanticsFlags::kIsTextField);
node3.childrenInTraversalOrder = {};
node3.childrenInHitTestOrder = {};
updates.emplace(3, node3);
flutter::SemanticsNode node4;
node4.childrenInTraversalOrder = {};
node4.childrenInHitTestOrder = {};
node4.id = 4;
node4.flags |= static_cast<int>(flutter::SemanticsFlags::kIsSlider);
updates.emplace(4, node4);
accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates));
RunLoopUntilIdle();
std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role>
roles_by_node_id = {
{0u, fuchsia::accessibility::semantics::Role::BUTTON},
{1u, fuchsia::accessibility::semantics::Role::HEADER},
{2u, fuchsia::accessibility::semantics::Role::IMAGE},
{3u, fuchsia::accessibility::semantics::Role::TEXT_FIELD},
{4u, fuchsia::accessibility::semantics::Role::SLIDER}};
EXPECT_EQ(0, semantics_manager_.DeleteCount());
EXPECT_EQ(1, semantics_manager_.UpdateCount());
EXPECT_EQ(1, semantics_manager_.CommitCount());
EXPECT_EQ(5U, semantics_manager_.LastUpdatedNodes().size());
for (const auto& node : semantics_manager_.LastUpdatedNodes()) {
ExpectNodeHasRole(node, roles_by_node_id);
}
EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
}
TEST_F(AccessibilityBridgeTest, DeletesChildrenTransitively) {
// Test that when a node is deleted, so are its transitive children.
flutter::SemanticsNode node2;
......
......@@ -9,7 +9,7 @@ import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
/// Verifies Semantics flags and actions.
void main() {
// This must match the number of flags in lib/ui/semantics.dart
const int numSemanticsFlags = 23;
const int numSemanticsFlags = 24;
test('SemanticsFlag.values refers to all flags.', () async {
expect(SemanticsFlag.values.length, equals(numSemanticsFlags));
for (int index = 0; index < numSemanticsFlags; ++index) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册