Index: ui/accessibility/ax_tree_serializer.h |
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h |
index cbc01c043a562ae2bd3dd9d1f989a86f7a3adffb..bce31d5f90375bf5b23241162aba5e70142c9264 100644 |
--- a/ui/accessibility/ax_tree_serializer.h |
+++ b/ui/accessibility/ax_tree_serializer.h |
@@ -9,6 +9,7 @@ |
#include "base/containers/hash_tables.h" |
#include "base/logging.h" |
+#include "base/stl_util.h" |
#include "ui/accessibility/ax_tree_source.h" |
#include "ui/accessibility/ax_tree_update.h" |
@@ -17,23 +18,44 @@ namespace ui { |
struct ClientTreeNode; |
// AXTreeSerializer is a helper class that serializes incremental |
-// updates to an AXTreeSource as a vector of AXNodeData structs. |
-// These structs can be unserialized by an AXTree. An AXTreeSerializer |
-// keeps track of the tree of node ids that its client is aware of, so |
-// it will automatically include, as part of any update, any additional nodes |
-// that the client is not aware of yet. |
+// updates to an AXTreeSource as a AXTreeUpdate struct. |
+// These structs can be unserialized by a client object such as an |
+// AXTree. An AXTreeSerializer keeps track of the tree of node ids that its |
+// client is aware of so that it will never generate an AXTreeUpdate that |
+// results in an invalid tree. |
// |
-// When the AXTreeSource changes, call SerializeChanges to serialize the |
-// changes to the tree as an AXTreeUpdate. If a single node has changed, |
-// pass that node to SerializeChanges. If a node has been added or removed, |
-// pass the parent of that node to SerializeChanges and it will automatically |
-// handle changes to its set of children. |
+// Every node in the source tree must have an id that's a unique positive |
+// integer, the same node must not appear twice. |
// |
-// TODO(dmazzoni): add sample usage code. |
+// Usage: |
+// |
+// You must call SerializeChanges() every time a node in the tree changes, |
+// and send the generated AXTreeUpdate to the client. |
+// |
+// If a node is added, call SerializeChanges on its parent. |
+// If a node is removed, call SerializeChanges on its parent. |
+// If a whole new subtree is added, just call SerializeChanges on its root. |
+// If the root of the tree changes, call SerializeChanges on the new root. |
+// |
+// AXTreeSerializer will avoid re-serializing nodes that do not change. |
+// For example, if node 1 has children 2, 3, 4, 5 and then child 2 is |
+// removed and a new child 6 is added, the AXTreeSerializer will only |
+// update nodes 1 and 6 (and any children of node 6 recursively). It will |
+// assume that nodes 3, 4, and 5 are not modified unless you explicitly |
+// call SerializeChanges() on them. |
+// |
+// As long as the source tree has unique ids for every node and no loops, |
+// and as long as every update is applied to the client tree, AXTreeSerializer |
+// will continue to work. If the source tree makes a change but fails to |
+// call SerializeChanges properly, the trees may get out of sync - but |
+// because AXTreeSerializer always keeps track of what updates it's sent, |
+// it will never send an invalid update and the client tree will not break, |
+// it just may not contain all of the changes. |
template<class AXSourceNode> |
class AXTreeSerializer { |
public: |
explicit AXTreeSerializer(AXTreeSource<AXSourceNode>* tree); |
+ ~AXTreeSerializer(); |
// Throw out the internal state that keeps track of the nodes the client |
// knows about. This has the effect that the next update will send the |
@@ -44,13 +66,71 @@ class AXTreeSerializer { |
void SerializeChanges(const AXSourceNode* node, |
AXTreeUpdate* out_update); |
+ // Only for unit testing. Normally this class relies on getting a call |
+ // to SerializeChanges() every time the source tree changes. For unit |
+ // testing, it's convenient to create a static AXTree for the initial |
+ // state and then call ChangeTreeSourceForTesting and then SerializeChanges |
+ // to simulate the changes you'd get if a tree changed from the initial |
+ // state to the second tree's state. |
+ void ChangeTreeSourceForTesting(AXTreeSource<AXSourceNode>* new_tree); |
+ |
private: |
+ // Return the least common ancestor of a node in the source tree |
+ // and a node in the client tree, or NULL if there is no such node. |
+ // The least common ancestor is the closest ancestor to |node| (which |
+ // may be |node| itself) that's in both the source tree and client tree, |
+ // and for which both the source and client tree agree on their ancestor |
+ // chain up to the root. |
+ // |
+ // Example 1: |
+ // |
+ // Client Tree Source tree |
+ // 1 1 |
+ // / \ / \ |
+ // 2 3 2 4 |
+ // |
+ // LCA(source node 2, client node 2) is node 2. |
+ // LCA(source node 3, client node 4) is node 1. |
+ // |
+ // Example 2: |
+ // |
+ // Client Tree Source tree |
+ // 1 1 |
+ // / \ / \ |
+ // 2 3 2 3 |
+ // / \ / / |
+ // 4 7 8 4 |
+ // / \ / \ |
+ // 5 6 5 6 |
+ // |
+ // LCA(source node 8, client node 7) is node 2. |
+ // LCA(source node 5, client node 5) is node 1. |
+ // It's not node 5, because the two trees disagree on the parent of |
+ // node 4, so the LCA is the first ancestor both trees agree on. |
+ const AXSourceNode* LeastCommonAncestor(const AXSourceNode* node, |
+ ClientTreeNode* client_node); |
+ |
+ // Return the least common ancestor of |node| that's in the client tree. |
+ // This just walks up the ancestors of |node| until it finds a node that's |
+ // also in the client tree, and then calls LeastCommonAncestor on the |
+ // source node and client node. |
+ const AXSourceNode* LeastCommonAncestor(const AXSourceNode* node); |
+ |
+ // Walk the subtree rooted at |node| and return true if any nodes that |
+ // would be updated are being reparented. If so, update |lca| to point |
+ // to the least common ancestor of the previous LCA and the previous |
+ // parent of the node being reparented. |
+ bool AnyDescendantWasReparented(const AXSourceNode* node, |
+ const AXSourceNode** lca); |
+ |
+ ClientTreeNode* ClientTreeNodeById(int32 id); |
+ |
// Delete the given client tree node and recursively delete all of its |
// descendants. |
void DeleteClientSubtree(ClientTreeNode* client_node); |
+ // Helper function, called recursively with each new node to serialize. |
void SerializeChangedNodes(const AXSourceNode* node, |
- std::set<int>* ids_serialized, |
AXTreeUpdate* out_update); |
// The tree source. |
@@ -82,6 +162,11 @@ AXTreeSerializer<AXSourceNode>::AXTreeSerializer( |
} |
template<class AXSourceNode> |
+AXTreeSerializer<AXSourceNode>::~AXTreeSerializer() { |
+ Reset(); |
+} |
+ |
+template<class AXSourceNode> |
void AXTreeSerializer<AXSourceNode>::Reset() { |
if (client_root_) { |
DeleteClientSubtree(client_root_); |
@@ -90,11 +175,153 @@ void AXTreeSerializer<AXSourceNode>::Reset() { |
} |
template<class AXSourceNode> |
+void AXTreeSerializer<AXSourceNode>::ChangeTreeSourceForTesting( |
+ AXTreeSource<AXSourceNode>* new_tree) { |
+ tree_ = new_tree; |
+} |
+ |
+template<class AXSourceNode> |
+const AXSourceNode* AXTreeSerializer<AXSourceNode>::LeastCommonAncestor( |
+ const AXSourceNode* node, ClientTreeNode* client_node) { |
+ if (node == NULL || client_node == NULL) |
+ return NULL; |
+ |
+ std::vector<const AXSourceNode*> ancestors; |
+ while (node) { |
+ ancestors.push_back(node); |
+ node = tree_->GetParent(node); |
+ } |
+ |
+ std::vector<ClientTreeNode*> client_ancestors; |
+ while (client_node) { |
+ client_ancestors.push_back(client_node); |
+ client_node = client_node->parent; |
+ } |
+ |
+ // Start at the root. Keep going until the source ancestor chain and |
+ // client ancestor chain disagree. The last node before they disagree |
+ // is the LCA. |
+ const AXSourceNode* lca = NULL; |
+ int source_index = static_cast<int>(ancestors.size() - 1); |
+ int client_index = static_cast<int>(client_ancestors.size() - 1); |
+ while (source_index >= 0 && client_index >= 0) { |
+ if (tree_->GetId(ancestors[source_index]) != |
+ client_ancestors[client_index]->id) { |
+ return lca; |
+ } |
+ lca = ancestors[source_index]; |
+ source_index--; |
+ client_index--; |
+ } |
+ return lca; |
+} |
+ |
+template<class AXSourceNode> |
+const AXSourceNode* AXTreeSerializer<AXSourceNode>::LeastCommonAncestor( |
+ const AXSourceNode* node) { |
+ // Walk up the tree until the source node's id also exists in the |
+ // client tree, then call LeastCommonAncestor on those two nodes. |
+ ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node)); |
+ while (node && !client_node) { |
+ node = tree_->GetParent(node); |
+ if (node) |
+ client_node = ClientTreeNodeById(tree_->GetId(node)); |
+ } |
+ return LeastCommonAncestor(node, client_node); |
+} |
+ |
+template<class AXSourceNode> |
+bool AXTreeSerializer<AXSourceNode>::AnyDescendantWasReparented( |
+ const AXSourceNode* node, const AXSourceNode** lca) { |
+ bool result = false; |
+ int id = tree_->GetId(node); |
+ int child_count = tree_->GetChildCount(node); |
+ for (int i = 0; i < child_count; ++i) { |
+ const AXSourceNode* child = tree_->GetChildAtIndex(node, i); |
+ int child_id = tree_->GetId(child); |
+ ClientTreeNode* client_child = ClientTreeNodeById(child_id); |
+ if (client_child) { |
+ if (!client_child->parent) { |
+ // If the client child has no parent, it must have been the |
+ // previous root node, so there is no LCA and we can exit early. |
+ *lca = NULL; |
+ return true; |
+ } else if (client_child->parent->id != id) { |
+ // If the client child's parent is not this node, update the LCA |
+ // and return true (reparenting was found). |
+ *lca = LeastCommonAncestor(*lca, client_child); |
+ result = true; |
+ } else { |
+ // This child is already in the client tree, we won't |
+ // recursively serialize it so we don't need to check this |
+ // subtree recursively for reparenting. |
+ continue; |
+ } |
+ } |
+ |
+ // This is a new child or reparented child, check it recursively. |
+ if (AnyDescendantWasReparented(child, lca)) |
+ result = true; |
+ } |
+ return result; |
+} |
+ |
+template<class AXSourceNode> |
+ClientTreeNode* AXTreeSerializer<AXSourceNode>::ClientTreeNodeById(int32 id) { |
+ base::hash_map<int32, ClientTreeNode*>::iterator iter = |
+ client_id_map_.find(id); |
+ if (iter != client_id_map_.end()) |
+ return iter->second; |
+ else |
+ return NULL; |
+} |
+ |
+template<class AXSourceNode> |
void AXTreeSerializer<AXSourceNode>::SerializeChanges( |
const AXSourceNode* node, |
AXTreeUpdate* out_update) { |
- std::set<int> ids_serialized; |
- SerializeChangedNodes(node, &ids_serialized, out_update); |
+ // If the node isn't in the client tree, we need to serialize starting |
+ // with the LCA. |
+ const AXSourceNode* lca = LeastCommonAncestor(node); |
+ |
+ if (client_root_) { |
+ // If the LCA is anything other than the node itself, tell the |
+ // client to first delete the subtree rooted at the LCA. |
+ bool need_delete = (lca != node); |
+ if (lca) { |
+ // Check for any reparenting within this subtree - if there is |
+ // any, we need to delete and reserialize the whole subtree |
+ // that contains the old and new parents of the reparented node. |
+ if (AnyDescendantWasReparented(lca, &lca)) |
+ need_delete = true; |
+ } |
+ |
+ if (lca == NULL) { |
+ // If there's no LCA, just tell the client to destroy the whole |
+ // tree and then we'll serialize everything from the new root. |
+ out_update->node_id_to_clear = client_root_->id; |
+ DeleteClientSubtree(client_root_); |
+ client_id_map_.erase(client_root_->id); |
+ client_root_ = NULL; |
+ } else if (need_delete) { |
+ // Otherwise, if we need to reserialize a subtree, first we need |
+ // to delete those nodes in our client tree so that |
+ // SerializeChangedNodes() will be sure to send them again. |
+ out_update->node_id_to_clear = tree_->GetId(lca); |
+ ClientTreeNode* client_lca = ClientTreeNodeById(tree_->GetId(lca)); |
+ CHECK(client_lca); |
+ for (size_t i = 0; i < client_lca->children.size(); ++i) { |
+ client_id_map_.erase(client_lca->children[i]->id); |
+ DeleteClientSubtree(client_lca->children[i]); |
+ } |
+ client_lca->children.clear(); |
+ } |
+ } |
+ |
+ // Serialize from the LCA, or from the root if there isn't one. |
+ if (!lca) |
+ lca = tree_->GetRoot(); |
+ SerializeChangedNodes(lca, out_update); |
} |
template<class AXSourceNode> |
@@ -110,13 +337,7 @@ void AXTreeSerializer<AXSourceNode>::DeleteClientSubtree( |
template<class AXSourceNode> |
void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( |
const AXSourceNode* node, |
- std::set<int>* ids_serialized, |
AXTreeUpdate* out_update) { |
- int id = tree_->GetId(node); |
- if (ids_serialized->find(id) != ids_serialized->end()) |
- return; |
- ids_serialized->insert(id); |
- |
// This method has three responsibilities: |
// 1. Serialize |node| into an AXNodeData, and append it to |
// the AXTreeUpdate to be sent to the client. |
@@ -129,14 +350,9 @@ void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( |
// we keep track of what accessibility objects the client already knows |
// about. If we don't find it, then this must be the new root of the |
// accessibility tree. |
- // |
- // TODO(dmazzoni): handle the case where the root changes. |
- ClientTreeNode* client_node = NULL; |
- base::hash_map<int32, ClientTreeNode*>::iterator iter = |
- client_id_map_.find(id); |
- if (iter != client_id_map_.end()) { |
- client_node = iter->second; |
- } else { |
+ int id = tree_->GetId(node); |
+ ClientTreeNode* client_node = ClientTreeNodeById(id); |
+ if (!client_node) { |
if (client_root_) { |
client_id_map_.erase(client_root_->id); |
DeleteClientSubtree(client_root_); |
@@ -151,9 +367,6 @@ void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( |
// Iterate over the ids of the children of |node|. |
// Create a set of the child ids so we can quickly look |
// up which children are new and which ones were there before. |
- // Also catch the case where a child is already in the client tree |
- // data structure with a different parent, and make sure the old parent |
- // clears this node first. |
base::hash_set<int32> new_child_ids; |
int child_count = tree_->GetChildCount(node); |
for (int i = 0; i < child_count; ++i) { |
@@ -161,27 +374,10 @@ void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( |
int new_child_id = tree_->GetId(child); |
new_child_ids.insert(new_child_id); |
+ // This is a sanity check - there shouldn't be any reparenting |
+ // because we've already handled it above. |
ClientTreeNode* client_child = client_id_map_[new_child_id]; |
- if (client_child && client_child->parent != client_node) { |
- // The child is being reparented. Find the source tree node |
- // corresponding to the old parent, or the closest ancestor |
- // still in the tree. |
- ClientTreeNode* client_parent = client_child->parent; |
- AXSourceNode* parent = NULL; |
- while (client_parent) { |
- parent = tree_->GetFromId(client_parent->id); |
- if (parent) |
- break; |
- client_parent = client_parent->parent; |
- } |
- CHECK(parent); |
- |
- // Call SerializeChangedNodes recursively on the old parent, |
- // so that the update that clears |child| from its old parent |
- // occurs stricly before the update that adds |child| to its |
- // new parent. |
- SerializeChangedNodes(parent, ids_serialized, out_update); |
- } |
+ CHECK(!client_child || client_child->parent == client_node); |
} |
// Go through the old children and delete subtrees for child |
@@ -244,7 +440,7 @@ void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( |
// Serialize all of the new children, recursively. |
for (size_t i = 0; i < children_to_serialize.size(); ++i) |
- SerializeChangedNodes(children_to_serialize[i], ids_serialized, out_update); |
+ SerializeChangedNodes(children_to_serialize[i], out_update); |
} |
} // namespace ui |