Skip to content

Commit

Permalink
Adds fuchsia node roles to accessibility bridge updates. (flutter#20385)
Browse files Browse the repository at this point in the history
  • Loading branch information
abrush21 authored Sep 1, 2020
1 parent 95f2b72 commit 58a6207
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 3 deletions.
14 changes: 12 additions & 2 deletions lib/ui/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -551,7 +558,8 @@ class SemanticsFlag {
_kIsReadOnlyIndex: isReadOnly,
_kIsFocusableIndex: isFocusable,
_kIsLinkIndex: isLink,
};
_kIsSliderIndex: isSlider,
};

@override
String toString() {
Expand Down Expand Up @@ -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 '';
Expand Down
1 change: 1 addition & 0 deletions lib/ui/semantics/semantics_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ enum class SemanticsFlags : int32_t {
kIsReadOnly = 1 << 20,
kIsFocusable = 1 << 21,
kIsLink = 1 << 22,
kIsSlider = 1 << 23,
};

const int kScrollableSemanticsFlags =
Expand Down
3 changes: 3 additions & 0 deletions lib/web_ui/lib/src/ui/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions shell/platform/fuchsia/flutter/accessibility_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions shell/platform/fuchsia/flutter/accessibility_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
75 changes: 75 additions & 0 deletions shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion testing/dart/semantics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 58a6207

Please sign in to comment.