[fuchsia] Add support for a11y sliders
* Add sliders to role conversions
* Add increment and decrement actions
* Update data structures in FakeSemanticTree
* Add waits for node updates in FakeSemanticTree
* Add test only action/event pair in browsertests so waiting for actions
is more reliable
(cherry picked from commit 53a16cf1df91a19c839336d92338124ce0471d24)
Test: AccessibilityBridgeTest.Slider
Bug: fuchsia:56295, 1122806, 1136974
Change-Id: Id0658ada5b15da1c128b69c1346aee1d39f2a6f7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2314758
Commit-Queue: Sharon Yang <[email protected]>
Reviewed-by: Sergey Ulanov <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#812919}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2466320
Reviewed-by: Sharon Yang <[email protected]>
Commit-Queue: Tess Eisenberger <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#245}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/fuchsia/engine/browser/accessibility_bridge.cc b/fuchsia/engine/browser/accessibility_bridge.cc
index a7d4106..b45b4966 100644
--- a/fuchsia/engine/browser/accessibility_bridge.cc
+++ b/fuchsia/engine/browser/accessibility_bridge.cc
@@ -148,7 +148,8 @@
// Run the pending callback with the hit.
pending_hit_test_callbacks_[event.action_request_id](std::move(hit));
pending_hit_test_callbacks_.erase(event.action_request_id);
- } else if (event_received_callback_for_test_) {
+ } else if (event_received_callback_for_test_ &&
+ event.event_type == ax::mojom::Event::kEndOfTest) {
std::move(event_received_callback_for_test_).Run();
}
}
@@ -185,6 +186,13 @@
web_contents_->GetMainFrame()->AccessibilityPerformAction(action_data);
callback(true);
+
+ if (event_received_callback_for_test_) {
+ // Perform an action with a corresponding event to signal the action has
+ // been pumped through.
+ action_data.action = ax::mojom::Action::kSignalEndOfTest;
+ web_contents_->GetMainFrame()->AccessibilityPerformAction(action_data);
+ }
}
void AccessibilityBridge::HitTest(fuchsia::math::PointF local_point,
diff --git a/fuchsia/engine/browser/accessibility_bridge_browsertest.cc b/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
index 5375922c..87d73b8 100644
--- a/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
+++ b/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
@@ -33,6 +33,8 @@
const char kOffscreenNodeName[] = "offscreen node";
const size_t kPage1NodeCount = 9;
const size_t kPage2NodeCount = 190;
+const size_t kInitialRangeValue = 51;
+const size_t kStepSize = 3;
fuchsia::math::PointF GetCenterOfBox(fuchsia::ui::gfx::BoundingBox box) {
fuchsia::math::PointF center;
@@ -307,3 +309,37 @@
EXPECT_FALSE(is_offscreen);
}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, Slider) {
+ GURL page_url(embedded_test_server()->GetURL(kPage1Path));
+ ASSERT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
+ navigation_controller_.get(), fuchsia::web::LoadUrlParams(),
+ page_url.spec()));
+ navigation_listener_.RunUntilUrlAndTitleEquals(page_url, kPage1Title);
+ semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage1NodeCount);
+
+ fuchsia::accessibility::semantics::Node* node =
+ semantics_manager_.semantic_tree()->GetNodeFromRole(
+ fuchsia::accessibility::semantics::Role::SLIDER);
+ EXPECT_TRUE(node);
+ EXPECT_TRUE(node->has_states() && node->states().has_range_value());
+ EXPECT_EQ(node->states().range_value(), kInitialRangeValue);
+
+ AccessibilityBridge* bridge = frame_impl_->accessibility_bridge_for_test();
+ base::RunLoop run_loop;
+ bridge->set_event_received_callback_for_test(run_loop.QuitClosure());
+ semantics_manager_.RequestAccessibilityAction(
+ node->node_id(), fuchsia::accessibility::semantics::Action::INCREMENT);
+ semantics_manager_.RunUntilNumActionsHandledEquals(1);
+ run_loop.Run();
+
+ // Wait for the slider node to be updated, then check the value.
+ base::RunLoop run_loop2;
+ semantics_manager_.semantic_tree()->SetNodeUpdatedCallback(
+ node->node_id(), run_loop2.QuitClosure());
+ run_loop2.Run();
+
+ node = semantics_manager_.semantic_tree()->GetNodeWithId(node->node_id());
+ EXPECT_TRUE(node->has_states() && node->states().has_range_value());
+ EXPECT_EQ(node->states().range_value(), kInitialRangeValue + kStepSize);
+}
diff --git a/fuchsia/engine/browser/ax_tree_converter.cc b/fuchsia/engine/browser/ax_tree_converter.cc
index b07b328..7d1547cb1 100644
--- a/fuchsia/engine/browser/ax_tree_converter.cc
+++ b/fuchsia/engine/browser/ax_tree_converter.cc
@@ -26,6 +26,8 @@
return fuchsia::accessibility::semantics::Role::HEADER;
if (role == ax::mojom::Role::kImage)
return fuchsia::accessibility::semantics::Role::IMAGE;
+ if (role == ax::mojom::Role::kSlider)
+ return fuchsia::accessibility::semantics::Role::SLIDER;
if (role == ax::mojom::Role::kTextField)
return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
@@ -47,6 +49,23 @@
attributes.set_secondary_label(description.substr(0, MAX_LABEL_SIZE));
}
+ if (node.IsRangeValueSupported()) {
+ fuchsia::accessibility::semantics::RangeAttributes range_attributes;
+ if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange)) {
+ range_attributes.set_min_value(
+ node.GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange));
+ }
+ if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange)) {
+ range_attributes.set_max_value(
+ node.GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange));
+ }
+ if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kStepValueForRange)) {
+ range_attributes.set_step_delta(node.GetFloatAttribute(
+ ax::mojom::FloatAttribute::kStepValueForRange));
+ }
+ attributes.set_range(std::move(range_attributes));
+ }
+
return attributes;
}
@@ -96,6 +115,12 @@
states.set_value(value.substr(0, MAX_LABEL_SIZE));
}
+ // The value a range element currently has.
+ if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kValueForRange)) {
+ states.set_range_value(
+ node.GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange));
+ }
+
return states;
}
@@ -178,6 +203,12 @@
case fuchsia::accessibility::semantics::Action::DEFAULT:
*mojom_action = ax::mojom::Action::kDoDefault;
return true;
+ case fuchsia::accessibility::semantics::Action::DECREMENT:
+ *mojom_action = ax::mojom::Action::kDecrement;
+ return true;
+ case fuchsia::accessibility::semantics::Action::INCREMENT:
+ *mojom_action = ax::mojom::Action::kIncrement;
+ return true;
case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
*mojom_action = ax::mojom::Action::kScrollToMakeVisible;
return true;
diff --git a/fuchsia/engine/browser/fake_semantic_tree.cc b/fuchsia/engine/browser/fake_semantic_tree.cc
index b0cc8f7..5bb8d1c5 100644
--- a/fuchsia/engine/browser/fake_semantic_tree.cc
+++ b/fuchsia/engine/browser/fake_semantic_tree.cc
@@ -35,6 +35,7 @@
fuchsia::accessibility::semantics::Node* child = GetNodeWithId(c);
if (!child)
return false;
+
is_valid &= IsTreeValid(child, tree_size);
}
return is_valid;
@@ -60,29 +61,33 @@
run_loop.Run();
}
+void FakeSemanticTree::SetNodeUpdatedCallback(
+ uint32_t node_id,
+ base::OnceClosure node_updated_callback) {
+ node_wait_id_ = node_id;
+ on_node_updated_callback_ = std::move(node_updated_callback);
+}
+
fuchsia::accessibility::semantics::Node* FakeSemanticTree::GetNodeWithId(
uint32_t id) {
- for (auto& node : nodes_) {
- if (node.has_node_id() && node.node_id() == id) {
- return &node;
- }
- }
- return nullptr;
+ auto it = nodes_.find(id);
+ return it == nodes_.end() ? nullptr : &it->second;
}
fuchsia::accessibility::semantics::Node* FakeSemanticTree::GetNodeFromLabel(
base::StringPiece label) {
fuchsia::accessibility::semantics::Node* to_return = nullptr;
- for (auto& node : nodes_) {
- if (node.has_attributes() && node.attributes().has_label() &&
- node.attributes().label() == label) {
+ for (auto& n : nodes_) {
+ auto* node = &n.second;
+ if (node->has_attributes() && node->attributes().has_label() &&
+ node->attributes().label() == label) {
// There are sometimes multiple semantic nodes with the same label. Hit
// testing should return the node with the smallest node ID so behaviour
// is consistent with the hit testing API being called.
if (!to_return) {
- to_return = &node;
- } else if (node.node_id() < to_return->node_id()) {
- to_return = &node;
+ to_return = node;
+ } else if (node->node_id() < to_return->node_id()) {
+ to_return = node;
}
}
}
@@ -90,24 +95,34 @@
return to_return;
}
+fuchsia::accessibility::semantics::Node* FakeSemanticTree::GetNodeFromRole(
+ fuchsia::accessibility::semantics::Role role) {
+ for (auto& n : nodes_) {
+ auto* node = &n.second;
+ if (node->has_role() && node->role() == role)
+ return node;
+ }
+
+ return nullptr;
+}
+
void FakeSemanticTree::UpdateSemanticNodes(
std::vector<fuchsia::accessibility::semantics::Node> nodes) {
- nodes_.reserve(nodes.size() + nodes_.size());
+ bool wait_node_updated = false;
for (auto& node : nodes) {
- // Delete an existing node that's being updated to avoid having duplicate
- // nodes.
- DeleteSemanticNodes({node.node_id()});
- nodes_.push_back(std::move(node));
+ if (node.node_id() == node_wait_id_ && on_node_updated_callback_)
+ wait_node_updated = true;
+
+ nodes_[node.node_id()] = std::move(node);
}
+
+ if (wait_node_updated)
+ std::move(on_node_updated_callback_).Run();
}
void FakeSemanticTree::DeleteSemanticNodes(std::vector<uint32_t> node_ids) {
- for (auto id : node_ids) {
- for (uint i = 0; i < nodes_.size(); i++) {
- if (nodes_.at(i).node_id() == id)
- nodes_.erase(nodes_.begin() + i);
- }
- }
+ for (auto id : node_ids)
+ nodes_.erase(id);
}
void FakeSemanticTree::CommitUpdates(CommitUpdatesCallback callback) {
diff --git a/fuchsia/engine/browser/fake_semantic_tree.h b/fuchsia/engine/browser/fake_semantic_tree.h
index 89432f0..981f6619 100644
--- a/fuchsia/engine/browser/fake_semantic_tree.h
+++ b/fuchsia/engine/browser/fake_semantic_tree.h
@@ -8,6 +8,7 @@
#include <fuchsia/accessibility/semantics/cpp/fidl.h>
#include <fuchsia/accessibility/semantics/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding.h>
+#include <unordered_map>
#include "base/callback.h"
@@ -35,9 +36,16 @@
void Disconnect();
void RunUntilNodeCountAtLeast(size_t count);
+ void SetNodeUpdatedCallback(uint32_t node_id,
+ base::OnceClosure node_updated_callback);
fuchsia::accessibility::semantics::Node* GetNodeWithId(uint32_t id);
+
+ // For both functions below, it is possible there are multiple nodes with the
+ // same identifier.
fuchsia::accessibility::semantics::Node* GetNodeFromLabel(
base::StringPiece label);
+ fuchsia::accessibility::semantics::Node* GetNodeFromRole(
+ fuchsia::accessibility::semantics::Role role);
// fuchsia::accessibility::semantics::SemanticTree implementation.
void UpdateSemanticNodes(
@@ -50,8 +58,11 @@
private:
fidl::Binding<fuchsia::accessibility::semantics::SemanticTree>
semantic_tree_binding_;
- std::vector<fuchsia::accessibility::semantics::Node> nodes_;
+ std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Node> nodes_;
base::RepeatingClosure on_commit_updates_;
+
+ uint32_t node_wait_id_;
+ base::OnceClosure on_node_updated_callback_;
};
#endif // FUCHSIA_ENGINE_BROWSER_FAKE_SEMANTIC_TREE_H_
diff --git a/fuchsia/engine/test/data/ax1.html b/fuchsia/engine/test/data/ax1.html
index 0f2f8aa..99de9c41 100644
--- a/fuchsia/engine/test/data/ax1.html
+++ b/fuchsia/engine/test/data/ax1.html
@@ -7,6 +7,7 @@
<p>a third paragraph</p>
<button>another button</button>
<button>button 3</button>
+ <input type="range" min="0" max="100" value="51" step="3" class="slider" id="myRange">
<div style='height:1000px; width:1000px;'></div>
<p>offscreen node</p>
<button>button 4</button>