OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #ifndef UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ | |
6 #define UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ | |
7 | |
8 #include <set> | |
9 | |
10 #include "base/containers/hash_tables.h" | |
11 #include "base/logging.h" | |
12 #include "ui/accessibility/ax_tree_source.h" | |
13 #include "ui/accessibility/ax_tree_update.h" | |
14 | |
15 namespace ui { | |
16 | |
17 struct ClientTreeNode; | |
18 | |
19 // AXTreeSerializer is a helper class that serializes incremental | |
20 // updates to an AXTreeSource as a vector of AXNodeData structs. | |
21 // These structs can be unserialized by an AXTree. An AXTreeSerializer | |
22 // keeps track of the tree of node ids that its client is aware of, so | |
23 // it will automatically include, as part of any update, any additional nodes | |
24 // that the client is not aware of yet. | |
25 // | |
26 // Note that AXTreeSerializer does not know if any nodes in the tree | |
27 // change state. You should call SerializeChanges() individually on | |
28 // 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.
| |
29 // the node that's changed has any ancestors or descendants that haven't | |
30 // been serialized to send to the client at all yet, they'll be included | |
31 // in the update to ensure that the client always has a valid tree. | |
32 template<class AXSourceNode> | |
33 class AX_EXPORT AXTreeSerializer { | |
34 public: | |
35 explicit AXTreeSerializer(AXTreeSource<AXSourceNode>* tree); | |
36 | |
37 // Throw out the internal state that keeps track of the nodes the client | |
38 // knows about. This has the effect that the next update will send the | |
39 // entire tree over because it assumes the client knows nothing. | |
40 void Reset(); | |
41 | |
42 // Serialize all changes to |node| and append them to |out_update|. | |
43 void SerializeChanges(const AXSourceNode* node, | |
44 AXTreeUpdate* out_update); | |
45 | |
46 private: | |
47 // Delete the given client tree node and recursively delete all of its | |
48 // descendants. | |
49 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.
| |
50 | |
51 void SerializeChangedNodes(const AXSourceNode* node, | |
52 std::set<int>* ids_serialized, | |
53 AXTreeUpdate* out_update); | |
54 | |
55 // The tree source. | |
56 AXTreeSource<AXSourceNode>* tree_; | |
57 | |
58 // Our representation of the client tree. | |
59 ClientTreeNode* client_root_; | |
60 | |
61 // A map from IDs to nodes in the client tree. | |
62 base::hash_map<int32, ClientTreeNode*> client_id_map_; | |
63 }; | |
64 | |
65 // In order to keep track of what nodes the client knows about, we keep a | |
66 // representation of the client tree - just IDs and parent/child | |
67 // relationships. | |
68 struct AX_EXPORT ClientTreeNode { | |
69 ClientTreeNode(); | |
70 virtual ~ClientTreeNode(); | |
71 int32 id; | |
72 ClientTreeNode* parent; | |
73 std::vector<ClientTreeNode*> children; | |
74 }; | |
75 | |
76 template<class AXSourceNode> | |
77 AXTreeSerializer<AXSourceNode>::AXTreeSerializer( | |
78 AXTreeSource<AXSourceNode>* tree) | |
79 : tree_(tree), | |
80 client_root_(NULL) { | |
81 } | |
82 | |
83 template<class AXSourceNode> | |
84 void AXTreeSerializer<AXSourceNode>::Reset() { | |
85 if (client_root_) { | |
86 DeleteClientTreeNodeAndDescendants(client_root_); | |
87 client_root_ = NULL; | |
88 } | |
89 } | |
90 | |
91 template<class AXSourceNode> | |
92 void AXTreeSerializer<AXSourceNode>::SerializeChanges( | |
93 const AXSourceNode* node, | |
94 AXTreeUpdate* out_update) { | |
95 std::set<int> ids_serialized; | |
96 SerializeChangedNodes(node, &ids_serialized, out_update); | |
97 } | |
98 | |
99 template<class AXSourceNode> | |
100 void AXTreeSerializer<AXSourceNode>::DeleteClientTreeNodeAndDescendants( | |
101 ClientTreeNode* client_node) { | |
102 for (size_t i = 0; i < client_node->children.size(); ++i) { | |
103 client_id_map_.erase(client_node->children[i]->id); | |
104 DeleteClientTreeNodeAndDescendants(client_node->children[i]); | |
105 } | |
106 client_node->children.clear(); | |
107 } | |
108 | |
109 template<class AXSourceNode> | |
110 void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( | |
111 const AXSourceNode* node, | |
112 std::set<int>* ids_serialized, | |
113 AXTreeUpdate* out_update) { | |
114 int id = tree_->GetId(node); | |
115 if (ids_serialized->find(id) != ids_serialized->end()) | |
116 return; | |
117 ids_serialized->insert(id); | |
118 | |
119 // This method has three responsibilities: | |
120 // 1. Serialize |node| into an AXNodeData, and append it to | |
121 // the AXTreeUpdate to be sent to the client. | |
122 // 2. Determine if |node| has any new children that the client doesn't | |
123 // know about yet, and call SerializeChangedNodes recursively on those. | |
124 // 3. Update our internal data structure that keeps track of what nodes | |
125 // the client knows about. | |
126 | |
127 // First, find the ClientTreeNode for this id in our data structure where | |
128 // we keep track of what accessibility objects the client already knows | |
129 // 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
| |
130 // accessibility tree. | |
131 ClientTreeNode* client_node = NULL; | |
132 base::hash_map<int32, ClientTreeNode*>::iterator iter = | |
133 client_id_map_.find(id); | |
134 if (iter != client_id_map_.end()) { | |
135 client_node = iter->second; | |
136 } else { | |
137 if (client_root_) { | |
138 client_id_map_.erase(client_root_->id); | |
139 DeleteClientTreeNodeAndDescendants(client_root_); | |
140 } | |
141 client_root_ = new ClientTreeNode(); | |
142 client_node = client_root_; | |
143 client_node->id = id; | |
144 //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
| |
145 client_node->parent = NULL; | |
146 client_id_map_[client_node->id] = client_node; | |
147 } | |
148 | |
149 // Iterate over the ids of the children of |node|. | |
150 // Create a set of the child ids so we can quickly look | |
151 // up which children are new and which ones were there before. | |
152 // Also catch the case where a child is already in the client tree | |
153 // data structure with a different parent, and make sure the old parent | |
154 // clears this node first. | |
155 base::hash_set<int32> new_child_ids; | |
156 int child_count = tree_->GetChildCount(node); | |
157 for (int i = 0; i < child_count; ++i) { | |
158 AXSourceNode* child = tree_->GetChildAtIndex(node, i); | |
159 int new_child_id = tree_->GetId(child); | |
160 new_child_ids.insert(new_child_id); | |
161 | |
162 ClientTreeNode* client_child = client_id_map_[new_child_id]; | |
163 if (client_child && client_child->parent != client_node) { | |
164 // The child is being reparented. Find the source tree node | |
165 // corresponding to the old parent, or the closest ancestor | |
166 // still in the tree. | |
167 ClientTreeNode* client_parent = client_child->parent; | |
168 AXSourceNode* parent = NULL; | |
169 while (client_parent) { | |
170 parent = tree_->GetFromId(client_parent->id); | |
171 if (parent) | |
172 break; | |
173 client_parent = client_parent->parent; | |
174 } | |
175 CHECK(parent); | |
176 | |
177 // Call SerializeChangedNodes recursively on the old parent, | |
178 // so that the update that clears |child| from its old parent | |
179 // occurs stricly before the update that adds |child| to its | |
180 // new parent. | |
181 SerializeChangedNodes(parent, ids_serialized, out_update); | |
182 } | |
183 } | |
184 | |
185 // Go through the old children and delete subtrees for child | |
186 // ids that are no longer present, and create a map from | |
187 // id to ClientTreeNode for the rest. It's important to delete | |
188 // first in a separate pass so that nodes that are reparented | |
189 // don't end up children of two different parents in the middle | |
190 // of an update, which can lead to a double-free. | |
191 base::hash_map<int32, ClientTreeNode*> client_child_id_map; | |
192 std::vector<ClientTreeNode*> old_children; | |
193 old_children.swap(client_node->children); | |
194 for (size_t i = 0; i < old_children.size(); ++i) { | |
195 ClientTreeNode* old_child = old_children[i]; | |
196 int old_child_id = old_child->id; | |
197 if (new_child_ids.find(old_child_id) == new_child_ids.end()) { | |
198 client_id_map_.erase(old_child_id); | |
199 DeleteClientTreeNodeAndDescendants(old_child); | |
200 } else { | |
201 client_child_id_map[old_child_id] = old_child; | |
202 } | |
203 } | |
204 | |
205 // Serialize this node. This fills in all of the fields in | |
206 // AXNodeData except child_ids, which we handle below. | |
207 out_update->nodes.push_back(AXNodeData()); | |
208 AXNodeData* serialized_node = &out_update->nodes.back(); | |
209 tree_->SerializeNode(node, serialized_node); | |
210 if (serialized_node->id == client_root_->id) | |
211 serialized_node->role = AX_ROLE_ROOT_WEB_AREA; | |
212 serialized_node->child_ids.clear(); | |
213 | |
214 // Iterate over the children, make note of the ones that are new | |
215 // and need to be serialized, and update the ClientTreeNode | |
216 // data structure to reflect the new tree. | |
217 std::vector<AXSourceNode*> children_to_serialize; | |
218 client_node->children.reserve(child_count); | |
219 for (int i = 0; i < child_count; ++i) { | |
220 AXSourceNode* child = tree_->GetChildAtIndex(node, i); | |
221 int child_id = tree_->GetId(child); | |
222 | |
223 // No need to do anything more with children that aren't new; | |
224 // the client will reuse its existing object. | |
225 if (new_child_ids.find(child_id) == new_child_ids.end()) | |
226 continue; | |
227 | |
228 new_child_ids.erase(child_id); | |
229 serialized_node->child_ids.push_back(child_id); | |
230 if (client_child_id_map.find(child_id) != client_child_id_map.end()) { | |
231 ClientTreeNode* reused_child = client_child_id_map[child_id]; | |
232 //reused_child->location = obj.boundingBoxRect(); | |
David Tseng
2013/11/20 23:31:05
?
dmazzoni
2013/11/21 17:27:43
Done.
| |
233 client_node->children.push_back(reused_child); | |
234 } else { | |
235 ClientTreeNode* new_child = new ClientTreeNode(); | |
236 new_child->id = child_id; | |
237 //new_child->location = obj.boundingBoxRect(); | |
238 new_child->parent = client_node; | |
239 client_node->children.push_back(new_child); | |
240 client_id_map_[child_id] = new_child; | |
241 children_to_serialize.push_back(child); | |
242 } | |
243 } | |
244 | |
245 // Serialize all of the new children, recursively. | |
246 for (size_t i = 0; i < children_to_serialize.size(); ++i) | |
247 SerializeChangedNodes(children_to_serialize[i], ids_serialized, out_update); | |
248 } | |
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
| |
249 | |
250 } // namespace ui | |
251 | |
252 #endif // UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ | |
OLD | NEW |