Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(256)

Side by Side Diff: ui/accessibility/ax_tree_serializer.h

Issue 90853002: Make tree serialization more robust and add more unit tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #ifndef UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ 5 #ifndef UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_
6 #define UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ 6 #define UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_
7 7
8 #include <set> 8 #include <set>
9 9
10 #include "base/containers/hash_tables.h" 10 #include "base/containers/hash_tables.h"
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/stl_util.h"
12 #include "ui/accessibility/ax_tree_source.h" 13 #include "ui/accessibility/ax_tree_source.h"
13 #include "ui/accessibility/ax_tree_update.h" 14 #include "ui/accessibility/ax_tree_update.h"
14 15
15 namespace ui { 16 namespace ui {
16 17
17 struct ClientTreeNode; 18 struct ClientTreeNode;
18 19
19 // AXTreeSerializer is a helper class that serializes incremental 20 // AXTreeSerializer is a helper class that serializes incremental
20 // updates to an AXTreeSource as a vector of AXNodeData structs. 21 // updates to an AXTreeSource as a AXTreeUpdate struct.
21 // These structs can be unserialized by an AXTree. An AXTreeSerializer 22 // These structs can be unserialized by a client object such as an
22 // keeps track of the tree of node ids that its client is aware of, so 23 // XTree. An AXTreeSerializer keeps track of the tree of node ids that its
aboxhall 2013/12/02 17:18:08 AXTree (although I do miss XTree Gold)
dmazzoni 2013/12/03 08:35:04 Done. (ha ha! I'll bet you don't miss 8.3 filename
23 // it will automatically include, as part of any update, any additional nodes 24 // client is aware of so that it will never generate an AXTreeUpdate that
24 // that the client is not aware of yet. 25 // results in an invalid tree.
25 // 26 //
26 // When the AXTreeSource changes, call SerializeChanges to serialize the 27 // Every node in the source tree must have an id that's a unique positive
27 // changes to the tree as an AXTreeUpdate. If a single node has changed, 28 // integer, the same node must not appear twice.
28 // pass that node to SerializeChanges. If a node has been added or removed,
29 // pass the parent of that node to SerializeChanges and it will automatically
30 // handle changes to its set of children.
31 // 29 //
32 // TODO(dmazzoni): add sample usage code. 30 // Usage:
31 //
32 // You must call SerializeChanges() every time a node in the tree changes,
33 // and send the generated AXTreeUpdate to the client.
34 //
35 // If a node is added, call SerializeChanges on its parent.
36 // If a node is removed, call SerializeChanges on its parent.
37 // If a whole new subtree is added, just call SerializeChanges on its root.
38 // If the root of the tree changes, call SerializeChanges on the new root.
39 //
40 // AXTreeSerializer will avoid re-serializing nodes that do not change.
41 // For example, if node 1 has children 2, 3, 4, 5 and then child 2 is
42 // removed and a new child 6 is added, the AXTreeSerializer will only
43 // update nodes 1 and 6 (and any children of node 6 recursively). It will
44 // assume that nodes 3, 4, and 5 are not modified unless you explicitly
45 // call SerializeChanges() on them.
46 //
47 // As long as the source tree has unique ids for every node and no loops,
48 // and as long as every update is applied to the client tree, AXTreeSerializer
49 // will continue to work. If the source tree makes a change but fails to
50 // call SerializeChanges properly, the trees may get out of sync - but
51 // because AXTreeSerializer always keeps track of what updates it's sent,
52 // it will never send an invalid update and the client tree will not break,
53 // it just may not contain all of the changes.
33 template<class AXSourceNode> 54 template<class AXSourceNode>
34 class AXTreeSerializer { 55 class AXTreeSerializer {
35 public: 56 public:
36 explicit AXTreeSerializer(AXTreeSource<AXSourceNode>* tree); 57 explicit AXTreeSerializer(AXTreeSource<AXSourceNode>* tree);
58 ~AXTreeSerializer();
37 59
38 // Throw out the internal state that keeps track of the nodes the client 60 // Throw out the internal state that keeps track of the nodes the client
39 // knows about. This has the effect that the next update will send the 61 // knows about. This has the effect that the next update will send the
40 // entire tree over because it assumes the client knows nothing. 62 // entire tree over because it assumes the client knows nothing.
41 void Reset(); 63 void Reset();
42 64
43 // Serialize all changes to |node| and append them to |out_update|. 65 // Serialize all changes to |node| and append them to |out_update|.
44 void SerializeChanges(const AXSourceNode* node, 66 void SerializeChanges(const AXSourceNode* node,
45 AXTreeUpdate* out_update); 67 AXTreeUpdate* out_update);
46 68
69 // Only for unit testing. Normally this class relies on getting a call
70 // to SerializeChanges() every time the source tree changes. For unit
71 // testing, it's convenient to create a static AXTree for the initial
72 // state and then call ChangeTreeSourceForTesting and then SerializeChanges
73 // to simulate the changes you'd get if a tree changed from the initial
74 // state to the second tree's state.
75 void ChangeTreeSourceForTesting(AXTreeSource<AXSourceNode>* new_tree);
76
47 private: 77 private:
78 // Return the least common ancestor of a node in the source tree
79 // and a node in the client tree, or NULL if there is no such node.
80 // The least common ancestor is the closest ancestor to |node| (which
aboxhall 2013/12/02 17:18:08 This open parenthesis doesn't have a matching clos
dmazzoni 2013/12/03 08:35:04 Done.
81 // may be |node| itself that's in both the source tree and client tree,
82 // and for which both the source and client tree agree on their ancestor
83 // chain up to the root.
84 //
85 // Example 1:
86 //
87 // Client Tree Source tree
88 // 1 1
89 // / \ / \
90 // 2 3 2 4
91 //
92 // LCA(source node 2, client node 2) is node 2.
93 // LCA(source node 3, client node 4) is node 1.
94 //
95 // Example 2:
96 //
97 // Client Tree Source tree
98 // 1 1
99 // / \ / \
100 // 2 3 2 3
101 // / \ / /
102 // 4 7 8 4
103 // / \ / \
104 // 5 6 5 6
105 //
106 // LCA(source node 8, client node 7) is node 2.
107 // LCA(source node 5, client node 5) is node 1.
108 // It's not node 5, because the two trees disagree on the parent of
109 // node 4, so the LCA is the first ancestor both trees agree on.
110 const AXSourceNode* LeastCommonAncestor(const AXSourceNode* node,
111 ClientTreeNode* client_node);
112
113 // Return the least common ancestor of |node| that's in the client tree.
114 // This just walks up the ancestors of |node| until it finds a node that's
115 // also in the client tree, and then calls LeastCommonAncestor on the
116 // source node and client node.
117 const AXSourceNode* LeastCommonAncestor(const AXSourceNode* node);
118
119 // Walk the subtree rooted at |node| and return true if any nodes that
120 // would be updated are being reparented. If so, update |lca| to point
121 // to the least common ancestor of the previous LCA and the previous
122 // parent of the node being reparented.
123 bool CheckForReparenting(const AXSourceNode* node,
124 const AXSourceNode** lca);
125
126 ClientTreeNode* ClientTreeNodeById(int32 id);
127
48 // Delete the given client tree node and recursively delete all of its 128 // Delete the given client tree node and recursively delete all of its
49 // descendants. 129 // descendants.
50 void DeleteClientSubtree(ClientTreeNode* client_node); 130 void DeleteClientSubtree(ClientTreeNode* client_node);
51 131
132 // Helper function, called recursively with each new node to serialize.
52 void SerializeChangedNodes(const AXSourceNode* node, 133 void SerializeChangedNodes(const AXSourceNode* node,
53 std::set<int>* ids_serialized,
54 AXTreeUpdate* out_update); 134 AXTreeUpdate* out_update);
55 135
56 // The tree source. 136 // The tree source.
57 AXTreeSource<AXSourceNode>* tree_; 137 AXTreeSource<AXSourceNode>* tree_;
58 138
59 // Our representation of the client tree. 139 // Our representation of the client tree.
60 ClientTreeNode* client_root_; 140 ClientTreeNode* client_root_;
61 141
62 // A map from IDs to nodes in the client tree. 142 // A map from IDs to nodes in the client tree.
63 base::hash_map<int32, ClientTreeNode*> client_id_map_; 143 base::hash_map<int32, ClientTreeNode*> client_id_map_;
(...skipping 11 matching lines...) Expand all
75 }; 155 };
76 156
77 template<class AXSourceNode> 157 template<class AXSourceNode>
78 AXTreeSerializer<AXSourceNode>::AXTreeSerializer( 158 AXTreeSerializer<AXSourceNode>::AXTreeSerializer(
79 AXTreeSource<AXSourceNode>* tree) 159 AXTreeSource<AXSourceNode>* tree)
80 : tree_(tree), 160 : tree_(tree),
81 client_root_(NULL) { 161 client_root_(NULL) {
82 } 162 }
83 163
84 template<class AXSourceNode> 164 template<class AXSourceNode>
165 AXTreeSerializer<AXSourceNode>::~AXTreeSerializer() {
166 Reset();
167 }
168
169 template<class AXSourceNode>
85 void AXTreeSerializer<AXSourceNode>::Reset() { 170 void AXTreeSerializer<AXSourceNode>::Reset() {
86 if (client_root_) { 171 if (client_root_) {
87 DeleteClientSubtree(client_root_); 172 DeleteClientSubtree(client_root_);
88 client_root_ = NULL; 173 client_root_ = NULL;
89 } 174 }
90 } 175 }
91 176
92 template<class AXSourceNode> 177 template<class AXSourceNode>
178 void AXTreeSerializer<AXSourceNode>::ChangeTreeSourceForTesting(
179 AXTreeSource<AXSourceNode>* new_tree) {
180 tree_ = new_tree;
181 }
182
183 template<class AXSourceNode>
184 const AXSourceNode* AXTreeSerializer<AXSourceNode>::LeastCommonAncestor(
185 const AXSourceNode* node, ClientTreeNode* client_node) {
186 if (node == NULL || client_node == NULL)
187 return NULL;
188
189 std::vector<const AXSourceNode*> ancestors;
190 while (node) {
191 ancestors.push_back(node);
192 node = tree_->GetParent(node);
193 }
194
195 std::vector<ClientTreeNode*> client_ancestors;
196 while (client_node) {
197 client_ancestors.push_back(client_node);
198 client_node = client_node->parent;
199 }
200
201 // Start at the root. Keep going until the source ancestor chain and
202 // client ancestor chain disagree. The last node before they disagree
203 // is the LCA.
204 size_t i = 0;
205 const AXSourceNode* lca = NULL;
206 while (i < std::min(ancestors.size(), client_ancestors.size())) {
aboxhall 2013/12/02 17:18:08 Will this call to min() reliably be pulled out of
dmazzoni 2013/12/03 08:35:04 Moot, I like your idea below.
207 if (tree_->GetId(ancestors[ancestors.size() - i - 1]) !=
208 client_ancestors[client_ancestors.size() - i - 1]->id) {
aboxhall 2013/12/02 17:18:08 Why not have two index variables which count down
dmazzoni 2013/12/03 08:35:04 Good idea. I have faith that the compiler could ha
209 return lca;
210 }
211 lca = ancestors[ancestors.size() - i - 1];
212 i++;
213 }
214 return lca;
215 }
216
217 template<class AXSourceNode>
218 const AXSourceNode* AXTreeSerializer<AXSourceNode>::LeastCommonAncestor(
219 const AXSourceNode* node) {
220 // Walk up the tree until the source node's id also exists in the
221 // client tree, then call LeastCommonAncestor on those two nodes.
222 ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node));
223 while (node && !client_node) {
224 node = tree_->GetParent(node);
225 if (node)
226 client_node = ClientTreeNodeById(tree_->GetId(node));
227 }
228 return LeastCommonAncestor(node, client_node);
229 }
230
231 template<class AXSourceNode>
232 bool AXTreeSerializer<AXSourceNode>::CheckForReparenting(
aboxhall 2013/12/02 17:18:08 AnyDescendantWasReparented() ?
dmazzoni 2013/12/03 08:35:04 I like that.
233 const AXSourceNode* node, const AXSourceNode** lca) {
234 bool result = false;
235 int id = tree_->GetId(node);
236 int child_count = tree_->GetChildCount(node);
237 for (int i = 0; i < child_count; ++i) {
238 const AXSourceNode* child = tree_->GetChildAtIndex(node, i);
239 int child_id = tree_->GetId(child);
240 ClientTreeNode* client_child = ClientTreeNodeById(child_id);
241 if (client_child) {
242 if (!client_child->parent) {
243 // If the client child has no parent, it must have been the
244 // previous root node, so there is no LCA and we can exit early.
245 *lca = NULL;
246 return true;
247 } else if (client_child->parent->id != id) {
248 // If the client child's parent is not this node, update the LCA
249 // and return true (reparenting was found).
250 *lca = LeastCommonAncestor(*lca, client_child);
251 result = true;
252 } else {
253 // This child is already in the client tree, we won't
254 // recursively serialize it so we don't need to check this
255 // subtree recursively for reparenting.
256 continue;
257 }
258 }
259
260 // This is a new child or reparented child, check it recursively.
261 if (CheckForReparenting(child, lca))
262 result = true;
263 }
264 return result;
265 }
266
267 template<class AXSourceNode>
268 ClientTreeNode* AXTreeSerializer<AXSourceNode>::ClientTreeNodeById(int32 id) {
269 base::hash_map<int32, ClientTreeNode*>::iterator iter =
270 client_id_map_.find(id);
271 if (iter != client_id_map_.end())
272 return iter->second;
273 else
274 return NULL;
275 }
276
277 template<class AXSourceNode>
93 void AXTreeSerializer<AXSourceNode>::SerializeChanges( 278 void AXTreeSerializer<AXSourceNode>::SerializeChanges(
94 const AXSourceNode* node, 279 const AXSourceNode* node,
95 AXTreeUpdate* out_update) { 280 AXTreeUpdate* out_update) {
96 std::set<int> ids_serialized; 281 // If the node isn't in the client tree, we need to serialize starting
97 SerializeChangedNodes(node, &ids_serialized, out_update); 282 // with the LCA.
283 const AXSourceNode* lca = LeastCommonAncestor(node);
284
285 if (client_root_) {
286 // If the LCA is anything other than the node itself, tell the
287 // client to first delete the subtree rooted at the LCA.
288 bool need_delete = (lca != node);
289 if (lca) {
290 // Check for any reparenting within this subtree - if there is
291 // any, we need to delete and reserialize the whole subtree
292 // that contains the old and new parents of the reparented node.
293 if (CheckForReparenting(lca, &lca))
294 need_delete = true;
aboxhall 2013/12/02 17:18:08 Trivial suggestion: need_delete = CheckForReparent
dmazzoni 2013/12/03 08:35:04 No, because it's possible that need_delete was tru
295 }
296
297 if (lca == NULL) {
298 // If there's no LCA, just tell the client to destroy the whole
299 // tree and then we'll serialize everything from the new root.
300 out_update->node_id_to_clear = client_root_->id;
301 DeleteClientSubtree(client_root_);
302 client_id_map_.erase(client_root_->id);
303 client_root_ = NULL;
304 } else if (need_delete) {
305 // Otherwise, if we need to reserialize a subtree, first we need
306 // to delete those nodes in our client tree so that
307 // SerializeChangedNodes() will be sure to send them again.
308 out_update->node_id_to_clear = tree_->GetId(lca);
309 ClientTreeNode* client_lca = ClientTreeNodeById(tree_->GetId(lca));
310 CHECK(client_lca);
311 for (size_t i = 0; i < client_lca->children.size(); ++i) {
312 client_id_map_.erase(client_lca->children[i]->id);
313 DeleteClientSubtree(client_lca->children[i]);
314 }
315 client_lca->children.clear();
316 }
317 }
318
319 // Serialize from the LCA, or from the root if there isn't one.
320 if (!lca)
321 lca = tree_->GetRoot();
322 SerializeChangedNodes(lca, out_update);
98 } 323 }
99 324
100 template<class AXSourceNode> 325 template<class AXSourceNode>
101 void AXTreeSerializer<AXSourceNode>::DeleteClientSubtree( 326 void AXTreeSerializer<AXSourceNode>::DeleteClientSubtree(
102 ClientTreeNode* client_node) { 327 ClientTreeNode* client_node) {
103 for (size_t i = 0; i < client_node->children.size(); ++i) { 328 for (size_t i = 0; i < client_node->children.size(); ++i) {
104 client_id_map_.erase(client_node->children[i]->id); 329 client_id_map_.erase(client_node->children[i]->id);
105 DeleteClientSubtree(client_node->children[i]); 330 DeleteClientSubtree(client_node->children[i]);
106 } 331 }
107 client_node->children.clear(); 332 client_node->children.clear();
108 } 333 }
109 334
110 template<class AXSourceNode> 335 template<class AXSourceNode>
111 void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes( 336 void AXTreeSerializer<AXSourceNode>::SerializeChangedNodes(
112 const AXSourceNode* node, 337 const AXSourceNode* node,
113 std::set<int>* ids_serialized,
114 AXTreeUpdate* out_update) { 338 AXTreeUpdate* out_update) {
115 int id = tree_->GetId(node);
116 if (ids_serialized->find(id) != ids_serialized->end())
117 return;
118 ids_serialized->insert(id);
119
120 // This method has three responsibilities: 339 // This method has three responsibilities:
121 // 1. Serialize |node| into an AXNodeData, and append it to 340 // 1. Serialize |node| into an AXNodeData, and append it to
122 // the AXTreeUpdate to be sent to the client. 341 // the AXTreeUpdate to be sent to the client.
123 // 2. Determine if |node| has any new children that the client doesn't 342 // 2. Determine if |node| has any new children that the client doesn't
124 // know about yet, and call SerializeChangedNodes recursively on those. 343 // know about yet, and call SerializeChangedNodes recursively on those.
125 // 3. Update our internal data structure that keeps track of what nodes 344 // 3. Update our internal data structure that keeps track of what nodes
126 // the client knows about. 345 // the client knows about.
127 346
128 // First, find the ClientTreeNode for this id in our data structure where 347 // First, find the ClientTreeNode for this id in our data structure where
129 // we keep track of what accessibility objects the client already knows 348 // we keep track of what accessibility objects the client already knows
130 // about. If we don't find it, then this must be the new root of the 349 // about. If we don't find it, then this must be the new root of the
131 // accessibility tree. 350 // accessibility tree.
132 // 351 int id = tree_->GetId(node);
133 // TODO(dmazzoni): handle the case where the root changes. 352 ClientTreeNode* client_node = ClientTreeNodeById(id);
134 ClientTreeNode* client_node = NULL; 353 if (!client_node) {
135 base::hash_map<int32, ClientTreeNode*>::iterator iter =
136 client_id_map_.find(id);
137 if (iter != client_id_map_.end()) {
138 client_node = iter->second;
139 } else {
140 if (client_root_) { 354 if (client_root_) {
141 client_id_map_.erase(client_root_->id); 355 client_id_map_.erase(client_root_->id);
142 DeleteClientSubtree(client_root_); 356 DeleteClientSubtree(client_root_);
143 } 357 }
144 client_root_ = new ClientTreeNode(); 358 client_root_ = new ClientTreeNode();
145 client_node = client_root_; 359 client_node = client_root_;
146 client_node->id = id; 360 client_node->id = id;
147 client_node->parent = NULL; 361 client_node->parent = NULL;
148 client_id_map_[client_node->id] = client_node; 362 client_id_map_[client_node->id] = client_node;
149 } 363 }
150 364
151 // Iterate over the ids of the children of |node|. 365 // Iterate over the ids of the children of |node|.
152 // Create a set of the child ids so we can quickly look 366 // Create a set of the child ids so we can quickly look
153 // up which children are new and which ones were there before. 367 // up which children are new and which ones were there before.
154 // Also catch the case where a child is already in the client tree
155 // data structure with a different parent, and make sure the old parent
156 // clears this node first.
157 base::hash_set<int32> new_child_ids; 368 base::hash_set<int32> new_child_ids;
158 int child_count = tree_->GetChildCount(node); 369 int child_count = tree_->GetChildCount(node);
159 for (int i = 0; i < child_count; ++i) { 370 for (int i = 0; i < child_count; ++i) {
160 AXSourceNode* child = tree_->GetChildAtIndex(node, i); 371 AXSourceNode* child = tree_->GetChildAtIndex(node, i);
161 int new_child_id = tree_->GetId(child); 372 int new_child_id = tree_->GetId(child);
162 new_child_ids.insert(new_child_id); 373 new_child_ids.insert(new_child_id);
163 374
164 ClientTreeNode* client_child = client_id_map_[new_child_id]; 375 ClientTreeNode* client_child = client_id_map_[new_child_id];
165 if (client_child && client_child->parent != client_node) { 376 CHECK(!client_child || client_child->parent == client_node);
aboxhall 2013/12/02 17:18:08 Maybe comment the purpose of this CHECK.
dmazzoni 2013/12/03 08:35:04 Done.
166 // The child is being reparented. Find the source tree node
167 // corresponding to the old parent, or the closest ancestor
168 // still in the tree.
169 ClientTreeNode* client_parent = client_child->parent;
170 AXSourceNode* parent = NULL;
171 while (client_parent) {
172 parent = tree_->GetFromId(client_parent->id);
173 if (parent)
174 break;
175 client_parent = client_parent->parent;
176 }
177 CHECK(parent);
178
179 // Call SerializeChangedNodes recursively on the old parent,
180 // so that the update that clears |child| from its old parent
181 // occurs stricly before the update that adds |child| to its
182 // new parent.
183 SerializeChangedNodes(parent, ids_serialized, out_update);
dmazzoni 2013/11/27 07:23:08 Note: this line was buggy. Basically, if a node wa
aboxhall 2013/12/02 17:18:08 Thanks for the explanation. The new code is also (
184 }
185 } 377 }
186 378
187 // Go through the old children and delete subtrees for child 379 // Go through the old children and delete subtrees for child
188 // ids that are no longer present, and create a map from 380 // ids that are no longer present, and create a map from
189 // id to ClientTreeNode for the rest. It's important to delete 381 // id to ClientTreeNode for the rest. It's important to delete
190 // first in a separate pass so that nodes that are reparented 382 // first in a separate pass so that nodes that are reparented
191 // don't end up children of two different parents in the middle 383 // don't end up children of two different parents in the middle
192 // of an update, which can lead to a double-free. 384 // of an update, which can lead to a double-free.
193 base::hash_map<int32, ClientTreeNode*> client_child_id_map; 385 base::hash_map<int32, ClientTreeNode*> client_child_id_map;
194 std::vector<ClientTreeNode*> old_children; 386 std::vector<ClientTreeNode*> old_children;
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
237 new_child->id = child_id; 429 new_child->id = child_id;
238 new_child->parent = client_node; 430 new_child->parent = client_node;
239 client_node->children.push_back(new_child); 431 client_node->children.push_back(new_child);
240 client_id_map_[child_id] = new_child; 432 client_id_map_[child_id] = new_child;
241 children_to_serialize.push_back(child); 433 children_to_serialize.push_back(child);
242 } 434 }
243 } 435 }
244 436
245 // Serialize all of the new children, recursively. 437 // Serialize all of the new children, recursively.
246 for (size_t i = 0; i < children_to_serialize.size(); ++i) 438 for (size_t i = 0; i < children_to_serialize.size(); ++i)
247 SerializeChangedNodes(children_to_serialize[i], ids_serialized, out_update); 439 SerializeChangedNodes(children_to_serialize[i], out_update);
248 } 440 }
249 441
250 } // namespace ui 442 } // namespace ui
251 443
252 #endif // UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_ 444 #endif // UI_ACCESSIBILITY_AX_TREE_SERIALIZER_H_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698