Commit ca61642a authored by Sharon Yang's avatar Sharon Yang Committed by Commit Bot

[Fuchsia] Add special handling of a11y root node

The root of the Fuchsia semantic tree always has root id 0. This isn't
a constraint in Chromium, so some special handling has been added to
ensure the semantic tree received by Fuchsia maintains a consistent,
valid state.

Test: Expanded AccessibilityBridgeTest.

Bug: fuchsia:42172
Change-Id: Ic1e2398e0a6793bcbfef36643af64b78f79fea22
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1954321
Commit-Queue: Sharon Yang <yangsharon@chromium.org>
Reviewed-by: default avatarKevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#726157}
parent 44f8883c
...@@ -22,25 +22,6 @@ constexpr uint32_t kSemanticNodeRootId = 0; ...@@ -22,25 +22,6 @@ constexpr uint32_t kSemanticNodeRootId = 0;
// maximum sizes of serialized Semantic Nodes. // maximum sizes of serialized Semantic Nodes.
constexpr size_t kMaxNodesPerUpdate = 16; constexpr size_t kMaxNodesPerUpdate = 16;
// Template function to handle batching and sending of FIDL messages when
// updating or deleting nodes.
template <typename T>
void SendBatches(std::vector<T> pending_items,
base::RepeatingCallback<void(std::vector<T>)> callback) {
std::vector<T> nodes_to_send;
for (size_t i = 0; i < pending_items.size(); i++) {
nodes_to_send.push_back(std::move(pending_items.at(i)));
if (nodes_to_send.size() == kMaxNodesPerUpdate) {
callback.Run(std::move(nodes_to_send));
nodes_to_send.clear();
}
}
if (!nodes_to_send.empty()) {
callback.Run(std::move(nodes_to_send));
}
}
} // namespace } // namespace
AccessibilityBridge::AccessibilityBridge( AccessibilityBridge::AccessibilityBridge(
...@@ -63,35 +44,59 @@ AccessibilityBridge::AccessibilityBridge( ...@@ -63,35 +44,59 @@ AccessibilityBridge::AccessibilityBridge(
AccessibilityBridge::~AccessibilityBridge() = default; AccessibilityBridge::~AccessibilityBridge() = default;
void AccessibilityBridge::TryCommit() { void AccessibilityBridge::TryCommit() {
if (commit_inflight_ || (to_send_.empty() && to_delete_.empty())) if (commit_inflight_ || to_send_.empty())
return; return;
if (!to_send_.empty()) { SemanticUpdateOrDelete::Type current = to_send_.at(0).type;
SendBatches<fuchsia::accessibility::semantics::Node>( int range_start = 0;
std::move(to_send_), for (size_t i = 1; i < to_send_.size(); i++) {
base::BindRepeating( if (to_send_.at(i).type == current &&
[](SemanticTree* tree, (i - range_start < kMaxNodesPerUpdate)) {
std::vector<fuchsia::accessibility::semantics::Node> nodes) { continue;
tree->UpdateSemanticNodes(std::move(nodes)); } else {
}, DispatchSemanticsMessages(range_start, i - range_start);
base::Unretained(tree_ptr_.get()))); current = to_send_.at(i).type;
} range_start = i;
}
if (!to_delete_.empty()) {
SendBatches<uint32_t>(
std::move(to_delete_),
base::BindRepeating(
[](SemanticTree* tree, std::vector<uint32_t> nodes) {
tree->DeleteSemanticNodes(std::move(nodes));
},
base::Unretained(tree_ptr_.get())));
} }
DispatchSemanticsMessages(range_start, to_send_.size() - range_start);
tree_ptr_->CommitUpdates( tree_ptr_->CommitUpdates(
fit::bind_member(this, &AccessibilityBridge::OnCommitComplete)); fit::bind_member(this, &AccessibilityBridge::OnCommitComplete));
commit_inflight_ = true; commit_inflight_ = true;
to_send_.clear();
} }
void AccessibilityBridge::DispatchSemanticsMessages(size_t start, size_t size) {
if (to_send_.at(start).type == SemanticUpdateOrDelete::Type::UPDATE) {
std::vector<fuchsia::accessibility::semantics::Node> updates;
for (size_t i = start; i < start + size; i++) {
DCHECK(to_send_.at(i).type == SemanticUpdateOrDelete::Type::UPDATE);
updates.push_back(std::move(to_send_.at(i).update_node));
}
tree_ptr_->UpdateSemanticNodes(std::move(updates));
} else if (to_send_.at(start).type == SemanticUpdateOrDelete::Type::DELETE) {
std::vector<uint32_t> deletes;
for (size_t i = start; i < start + size; i++) {
DCHECK(to_send_.at(i).type == SemanticUpdateOrDelete::Type::DELETE);
deletes.push_back(to_send_.at(i).id_to_delete);
}
tree_ptr_->DeleteSemanticNodes(deletes);
}
}
AccessibilityBridge::SemanticUpdateOrDelete::SemanticUpdateOrDelete(
AccessibilityBridge::SemanticUpdateOrDelete&& m)
: type(m.type),
update_node(std::move(m.update_node)),
id_to_delete(m.id_to_delete) {}
AccessibilityBridge::SemanticUpdateOrDelete::SemanticUpdateOrDelete(
Type type,
fuchsia::accessibility::semantics::Node node,
uint32_t id_to_delete)
: type(type), update_node(std::move(node)), id_to_delete(id_to_delete) {}
void AccessibilityBridge::OnCommitComplete() { void AccessibilityBridge::OnCommitComplete() {
commit_inflight_ = false; commit_inflight_ = false;
TryCommit(); TryCommit();
...@@ -163,7 +168,6 @@ void AccessibilityBridge::OnSemanticsModeChanged( ...@@ -163,7 +168,6 @@ void AccessibilityBridge::OnSemanticsModeChanged(
// The SemanticsManager will clear all state in this case, which is mirrored // The SemanticsManager will clear all state in this case, which is mirrored
// here. // here.
to_send_.clear(); to_send_.clear();
to_delete_.clear();
commit_inflight_ = false; commit_inflight_ = false;
} }
...@@ -171,9 +175,31 @@ void AccessibilityBridge::OnSemanticsModeChanged( ...@@ -171,9 +175,31 @@ void AccessibilityBridge::OnSemanticsModeChanged(
callback(); callback();
} }
void AccessibilityBridge::DeleteSubtree(ui::AXTree* tree, ui::AXNode* node) {
DCHECK(tree);
DCHECK(node);
// When navigating, page 1, including the root, is deleted after page 2 has
// loaded. Since the root id is the same for page 1 and 2, page 2's root id
// ends up getting deleted. To handle this, the root will only be updated.
if (node->id() != root_id_) {
to_send_.push_back(
SemanticUpdateOrDelete(SemanticUpdateOrDelete::Type::DELETE, {},
ConvertToFuchsiaNodeId(node->id())));
}
for (ui::AXNode* child : node->children())
DeleteSubtree(tree, child);
}
void AccessibilityBridge::OnNodeWillBeDeleted(ui::AXTree* tree, void AccessibilityBridge::OnNodeWillBeDeleted(ui::AXTree* tree,
ui::AXNode* node) { ui::AXNode* node) {
to_delete_.push_back(ConvertToFuchsiaNodeId(node->id())); DeleteSubtree(tree, node);
TryCommit();
}
void AccessibilityBridge::OnSubtreeWillBeDeleted(ui::AXTree* tree,
ui::AXNode* node) {
DeleteSubtree(tree, node);
TryCommit(); TryCommit();
} }
...@@ -183,16 +209,23 @@ void AccessibilityBridge::OnAtomicUpdateFinished( ...@@ -183,16 +209,23 @@ void AccessibilityBridge::OnAtomicUpdateFinished(
const std::vector<ui::AXTreeObserver::Change>& changes) { const std::vector<ui::AXTreeObserver::Change>& changes) {
root_id_ = tree_.root()->id(); root_id_ = tree_.root()->id();
for (const ui::AXTreeObserver::Change& change : changes) { for (const ui::AXTreeObserver::Change& change : changes) {
// Reparent changes aren't included here because they consist of a delete ui::AXNodeData ax_data;
// and create change, which are already being handled. switch (change.type) {
if (change.type == ui::AXTreeObserver::NODE_CREATED || case ui::AXTreeObserver::NODE_CREATED:
change.type == ui::AXTreeObserver::SUBTREE_CREATED || case ui::AXTreeObserver::SUBTREE_CREATED:
change.type == ui::AXTreeObserver::NODE_CHANGED) { case ui::AXTreeObserver::NODE_CHANGED:
ui::AXNodeData ax_data = change.node->data(); ax_data = change.node->data();
if (change.node->id() == root_id_) { if (change.node->id() == root_id_) {
ax_data.id = kSemanticNodeRootId; ax_data.id = kSemanticNodeRootId;
} }
to_send_.push_back(AXNodeDataToSemanticNode(ax_data)); to_send_.push_back(
SemanticUpdateOrDelete(SemanticUpdateOrDelete::Type::UPDATE,
AXNodeDataToSemanticNode(ax_data), 0));
break;
case ui::AXTreeObserver::NODE_REPARENTED:
case ui::AXTreeObserver::SUBTREE_REPARENTED:
DeleteSubtree(tree, change.node);
break;
} }
} }
TryCommit(); TryCommit();
......
...@@ -50,9 +50,30 @@ class WEB_ENGINE_EXPORT AccessibilityBridge ...@@ -50,9 +50,30 @@ class WEB_ENGINE_EXPORT AccessibilityBridge
private: private:
FRIEND_TEST_ALL_PREFIXES(AccessibilityBridgeTest, OnSemanticsModeChanged); FRIEND_TEST_ALL_PREFIXES(AccessibilityBridgeTest, OnSemanticsModeChanged);
// Handles batching of semantic nodes and committing them to the SemanticTree. // A struct used for caching semantic information. This allows for updates and
// deletes to be stored in the same vector to preserve all ordering
// information.
struct SemanticUpdateOrDelete {
enum Type { UPDATE, DELETE };
SemanticUpdateOrDelete(SemanticUpdateOrDelete&& m);
SemanticUpdateOrDelete(Type type,
fuchsia::accessibility::semantics::Node node,
uint32_t id_to_delete);
~SemanticUpdateOrDelete() = default;
Type type;
fuchsia::accessibility::semantics::Node update_node;
uint32_t id_to_delete;
};
// Processes pending data and commits it to the Semantic Tree.
void TryCommit(); void TryCommit();
// Helper function for TryCommit() that sends the contents of |to_send_| to
// the Semantic Tree, starting at |start|.
void DispatchSemanticsMessages(size_t start, size_t size);
// Callback for SemanticTree::CommitUpdates. // Callback for SemanticTree::CommitUpdates.
void OnCommitComplete(); void OnCommitComplete();
...@@ -60,6 +81,11 @@ class WEB_ENGINE_EXPORT AccessibilityBridge ...@@ -60,6 +81,11 @@ class WEB_ENGINE_EXPORT AccessibilityBridge
// root. // root.
uint32_t ConvertToFuchsiaNodeId(int32_t ax_node_id); uint32_t ConvertToFuchsiaNodeId(int32_t ax_node_id);
// Deletes all nodes in subtree rooted at and including |node|, unless |node|
// is the root of the tree.
// |tree| and |node| are owned by the accessibility bridge.
void DeleteSubtree(ui::AXTree* tree, ui::AXNode* node);
// content::WebContentsObserver implementation. // content::WebContentsObserver implementation.
void AccessibilityEventReceived( void AccessibilityEventReceived(
const content::AXEventNotificationDetails& details) override; const content::AXEventNotificationDetails& details) override;
...@@ -76,6 +102,7 @@ class WEB_ENGINE_EXPORT AccessibilityBridge ...@@ -76,6 +102,7 @@ class WEB_ENGINE_EXPORT AccessibilityBridge
// ui::AXTreeObserver implementation. // ui::AXTreeObserver implementation.
void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
void OnAtomicUpdateFinished( void OnAtomicUpdateFinished(
ui::AXTree* tree, ui::AXTree* tree,
bool root_changed, bool root_changed,
...@@ -86,8 +113,8 @@ class WEB_ENGINE_EXPORT AccessibilityBridge ...@@ -86,8 +113,8 @@ class WEB_ENGINE_EXPORT AccessibilityBridge
content::WebContents* web_contents_; content::WebContents* web_contents_;
ui::AXSerializableTree tree_; ui::AXSerializableTree tree_;
std::vector<fuchsia::accessibility::semantics::Node> to_send_; // Cache for pending data to be sent to the Semantic Tree between commits.
std::vector<uint32_t> to_delete_; std::vector<SemanticUpdateOrDelete> to_send_;
bool commit_inflight_ = false; bool commit_inflight_ = false;
// Maintain a map of callbacks as multiple hit test events can happen at once. // Maintain a map of callbacks as multiple hit test events can happen at once.
......
...@@ -266,5 +266,13 @@ IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, TestNavigation) { ...@@ -266,5 +266,13 @@ IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, TestNavigation) {
controller.get(), fuchsia::web::LoadUrlParams(), title2.spec())); controller.get(), fuchsia::web::LoadUrlParams(), title2.spec()));
semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage2NodeCount); semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage2NodeCount);
EXPECT_TRUE(
semantics_manager_.semantic_tree()->HasNodeWithLabel(kPage2Title));
EXPECT_TRUE(semantics_manager_.semantic_tree()->HasNodeWithLabel(kNodeName)); EXPECT_TRUE(semantics_manager_.semantic_tree()->HasNodeWithLabel(kNodeName));
// Check that data from the first page has been deleted successfully.
EXPECT_FALSE(
semantics_manager_.semantic_tree()->HasNodeWithLabel(kButtonName));
EXPECT_FALSE(
semantics_manager_.semantic_tree()->HasNodeWithLabel(kParagraphName));
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment