Chromium Code Reviews| 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..67bf44da1f94502252e79552788eb305a3ee96fc |
| --- /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. |
| +// |
| +// Note that AXTreeSerializer does not know if any nodes in the tree |
| +// change state. You should call SerializeChanges() individually on |
| +// every node that changes state. What's handled automatically is if |
|
aboxhall
2013/11/18 21:57:10
Some sample code might be useful here. Up to you.
dmazzoni
2013/11/19 17:13:54
I think it will become more apparent in the next c
David Tseng
2013/11/20 23:31:05
How about removing the comment block here and addi
dmazzoni
2013/11/21 17:27:43
I clarified the comment and added a TODO.
|
| +// the node that's changed has any ancestors or descendants that haven't |
| +// been serialized to send to the client at all yet, they'll be included |
| +// in the update to ensure that the client always has a valid tree. |
| +template<class AXSourceNode> |
| +class AX_EXPORT 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 DeleteClientTreeNodeAndDescendants(ClientTreeNode* client_node); |
|
aboxhall
2013/11/18 21:57:10
Could this be accurately renamed ClearClientTreeNo
dmazzoni
2013/11/19 17:13:54
Clear sounds confusing to me, it implies clearing
aboxhall
2013/11/19 17:18:23
Sure, or even DeleteSubtree (since the ClientTreeN
dmazzoni
2013/11/21 17:27:43
How about DeleteClientSubtree?
aboxhall
2013/11/21 22:15:47
Seems legit.
|
| + |
| + 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_) { |
| + DeleteClientTreeNodeAndDescendants(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>::DeleteClientTreeNodeAndDescendants( |
| + ClientTreeNode* client_node) { |
| + for (size_t i = 0; i < client_node->children.size(); ++i) { |
| + client_id_map_.erase(client_node->children[i]->id); |
| + DeleteClientTreeNodeAndDescendants(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 |
|
David Tseng
2013/11/20 23:31:05
This assumes that the caller always first tries to
dmazzoni
2013/11/21 17:27:43
Added a TODO. Previously we handled that case but
|
| + // accessibility tree. |
| + 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); |
| + DeleteClientTreeNodeAndDescendants(client_root_); |
| + } |
| + client_root_ = new ClientTreeNode(); |
| + client_node = client_root_; |
| + client_node->id = id; |
| + //client_node->location = ...; |
|
David Tseng
2013/11/20 23:31:05
TODO? Do we need to save any more info (besides id
dmazzoni
2013/11/21 17:27:43
I just deleted those for now, I'll add them back i
|
| + 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); |
| + DeleteClientTreeNodeAndDescendants(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]; |
| + //reused_child->location = obj.boundingBoxRect(); |
|
David Tseng
2013/11/20 23:31:05
?
dmazzoni
2013/11/21 17:27:43
Done.
|
| + client_node->children.push_back(reused_child); |
| + } else { |
| + ClientTreeNode* new_child = new ClientTreeNode(); |
| + new_child->id = child_id; |
| + //new_child->location = obj.boundingBoxRect(); |
| + 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); |
| +} |
|
David Tseng
2013/11/20 23:31:05
Move these methods to impl file (.cc).
dmazzoni
2013/11/21 17:27:43
I can't because the class is templatized.
If I ma
|
| + |
| +} // namespace ui |
| + |
| +#endif // UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ |