Index: ui/accessibility/ax_tree_serializer.h |
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3ed94e9b445f057ba97f68501dc77251952559f3 |
--- /dev/null |
+++ b/ui/accessibility/ax_tree_serializer.h |
@@ -0,0 +1,252 @@ |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#ifndef UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ |
+#define UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ |
+ |
+#include <set> |
+ |
+#include "base/containers/hash_tables.h" |
+#include "base/logging.h" |
+#include "ui/accessibility/ax_tree_source.h" |
+#include "ui/accessibility/ax_tree_update.h" |
+ |
+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. |
+// |
+// 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. |
+// |
+// TODO(dmazzoni): add sample usage code. |
+template<class AXSourceNode> |
+class AXTreeSerializer { |
+ public: |
+ explicit AXTreeSerializer(AXTreeSource<AXSourceNode>* tree); |
+ |
+ // 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 |
+ // entire tree over because it assumes the client knows nothing. |
+ void Reset(); |
+ |
+ // Serialize all changes to |node| and append them to |out_update|. |
+ void SerializeChanges(const AXSourceNode* node, |
+ AXTreeUpdate* out_update); |
+ |
+ private: |
+ // Delete the given client tree node and recursively delete all of its |
+ // descendants. |
+ void DeleteClientSubtree(ClientTreeNode* client_node); |
+ |
+ void SerializeChangedNodes(const AXSourceNode* node, |
+ std::set<int>* ids_serialized, |
+ AXTreeUpdate* out_update); |
+ |
+ // The tree source. |
+ AXTreeSource<AXSourceNode>* tree_; |
+ |
+ // Our representation of the client tree. |
+ ClientTreeNode* client_root_; |
+ |
+ // A map from IDs to nodes in the client tree. |
+ base::hash_map<int32, ClientTreeNode*> client_id_map_; |
+}; |
+ |
+// In order to keep track of what nodes the client knows about, we keep a |
+// representation of the client tree - just IDs and parent/child |
+// relationships. |
+struct AX_EXPORT ClientTreeNode { |
+ ClientTreeNode(); |
+ virtual ~ClientTreeNode(); |
+ int32 id; |
+ ClientTreeNode* parent; |
+ std::vector<ClientTreeNode*> children; |
+}; |
+ |
+template<class AXSourceNode> |
+AXTreeSerializer<AXSourceNode>::AXTreeSerializer( |
+ AXTreeSource<AXSourceNode>* tree) |
+ : tree_(tree), |
+ client_root_(NULL) { |
+} |
+ |
+template<class AXSourceNode> |
+void AXTreeSerializer<AXSourceNode>::Reset() { |
+ if (client_root_) { |
+ DeleteClientSubtree(client_root_); |
+ client_root_ = 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); |
+} |
+ |
+template<class AXSourceNode> |
+void AXTreeSerializer<AXSourceNode>::DeleteClientSubtree( |
+ ClientTreeNode* client_node) { |
+ for (size_t i = 0; i < client_node->children.size(); ++i) { |
+ client_id_map_.erase(client_node->children[i]->id); |
+ DeleteClientSubtree(client_node->children[i]); |
+ } |
+ client_node->children.clear(); |
+} |
+ |
+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. |
+ // 2. Determine if |node| has any new children that the client doesn't |
+ // know about yet, and call SerializeChangedNodes recursively on those. |
+ // 3. Update our internal data structure that keeps track of what nodes |
+ // the client knows about. |
+ |
+ // First, find the ClientTreeNode for this id in our data structure where |
+ // 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 { |
+ if (client_root_) { |
+ client_id_map_.erase(client_root_->id); |
+ DeleteClientSubtree(client_root_); |
+ } |
+ client_root_ = new ClientTreeNode(); |
+ client_node = client_root_; |
+ client_node->id = id; |
+ client_node->parent = NULL; |
+ client_id_map_[client_node->id] = client_node; |
+ } |
+ |
+ // 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) { |
+ AXSourceNode* child = tree_->GetChildAtIndex(node, i); |
+ int new_child_id = tree_->GetId(child); |
+ new_child_ids.insert(new_child_id); |
+ |
+ 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); |
+ } |
+ } |
+ |
+ // Go through the old children and delete subtrees for child |
+ // ids that are no longer present, and create a map from |
+ // id to ClientTreeNode for the rest. It's important to delete |
+ // first in a separate pass so that nodes that are reparented |
+ // don't end up children of two different parents in the middle |
+ // of an update, which can lead to a double-free. |
+ base::hash_map<int32, ClientTreeNode*> client_child_id_map; |
+ std::vector<ClientTreeNode*> old_children; |
+ old_children.swap(client_node->children); |
+ for (size_t i = 0; i < old_children.size(); ++i) { |
+ ClientTreeNode* old_child = old_children[i]; |
+ int old_child_id = old_child->id; |
+ if (new_child_ids.find(old_child_id) == new_child_ids.end()) { |
+ client_id_map_.erase(old_child_id); |
+ DeleteClientSubtree(old_child); |
+ } else { |
+ client_child_id_map[old_child_id] = old_child; |
+ } |
+ } |
+ |
+ // Serialize this node. This fills in all of the fields in |
+ // AXNodeData except child_ids, which we handle below. |
+ out_update->nodes.push_back(AXNodeData()); |
+ AXNodeData* serialized_node = &out_update->nodes.back(); |
+ tree_->SerializeNode(node, serialized_node); |
+ if (serialized_node->id == client_root_->id) |
+ serialized_node->role = AX_ROLE_ROOT_WEB_AREA; |
+ serialized_node->child_ids.clear(); |
+ |
+ // Iterate over the children, make note of the ones that are new |
+ // and need to be serialized, and update the ClientTreeNode |
+ // data structure to reflect the new tree. |
+ std::vector<AXSourceNode*> children_to_serialize; |
+ client_node->children.reserve(child_count); |
+ for (int i = 0; i < child_count; ++i) { |
+ AXSourceNode* child = tree_->GetChildAtIndex(node, i); |
+ int child_id = tree_->GetId(child); |
+ |
+ // No need to do anything more with children that aren't new; |
+ // the client will reuse its existing object. |
+ if (new_child_ids.find(child_id) == new_child_ids.end()) |
+ continue; |
+ |
+ new_child_ids.erase(child_id); |
+ serialized_node->child_ids.push_back(child_id); |
+ if (client_child_id_map.find(child_id) != client_child_id_map.end()) { |
+ ClientTreeNode* reused_child = client_child_id_map[child_id]; |
+ client_node->children.push_back(reused_child); |
+ } else { |
+ ClientTreeNode* new_child = new ClientTreeNode(); |
+ new_child->id = child_id; |
+ new_child->parent = client_node; |
+ client_node->children.push_back(new_child); |
+ client_id_map_[child_id] = new_child; |
+ children_to_serialize.push_back(child); |
+ } |
+ } |
+ |
+ // 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); |
+} |
+ |
+} // namespace ui |
+ |
+#endif // UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ |