1 // Copyright (c) 2010 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 #include "base/string16.h"
6 #include "base/utf_string_conversions.h"
7 #include "chrome/browser/accessibility/browser_accessibility.h"
8 #include "chrome/browser/accessibility/browser_accessibility_manager.h"
9 #include "content/common/view_messages.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11 #include "webkit/glue/webaccessibility.h"
12
13 using webkit_glue::WebAccessibility;
14
15 namespace {
16
17 // Subclass of BrowserAccessibility that counts the number of instances.
18 class CountedBrowserAccessibility : public BrowserAccessibility {
19 public:
CountedBrowserAccessibility()20 CountedBrowserAccessibility() {
21 global_obj_count_++;
22 native_ref_count_ = 1;
23 }
~CountedBrowserAccessibility()24 virtual ~CountedBrowserAccessibility() {
25 global_obj_count_--;
26 }
27
NativeAddReference()28 virtual void NativeAddReference() OVERRIDE {
29 native_ref_count_++;
30 }
31
NativeReleaseReference()32 virtual void NativeReleaseReference() OVERRIDE {
33 native_ref_count_--;
34 if (native_ref_count_ == 0)
35 delete this;
36 }
37
38 int native_ref_count_;
39 static int global_obj_count_;
40 };
41
42 int CountedBrowserAccessibility::global_obj_count_ = 0;
43
44 // Factory that creates a CountedBrowserAccessibility.
45 class CountedBrowserAccessibilityFactory
46 : public BrowserAccessibilityFactory {
47 public:
~CountedBrowserAccessibilityFactory()48 virtual ~CountedBrowserAccessibilityFactory() {}
Create()49 virtual BrowserAccessibility* Create() {
50 return new CountedBrowserAccessibility();
51 }
52 };
53
54 } // anonymous namespace
55
TEST(BrowserAccessibilityManagerTest,TestNoLeaks)56 TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
57 // Create WebAccessibility objects for a simple document tree,
58 // representing the accessibility information used to initialize
59 // BrowserAccessibilityManager.
60 WebAccessibility button;
61 button.id = 2;
62 button.name = UTF8ToUTF16("Button");
63 button.role = WebAccessibility::ROLE_BUTTON;
64 button.state = 0;
65
66 WebAccessibility checkbox;
67 checkbox.id = 3;
68 checkbox.name = UTF8ToUTF16("Checkbox");
69 checkbox.role = WebAccessibility::ROLE_CHECKBOX;
70 checkbox.state = 0;
71
72 WebAccessibility root;
73 root.id = 1;
74 root.name = UTF8ToUTF16("Document");
75 root.role = WebAccessibility::ROLE_DOCUMENT;
76 root.state = 0;
77 root.children.push_back(button);
78 root.children.push_back(checkbox);
79
80 // Construct a BrowserAccessibilityManager with this WebAccessibility tree
81 // and a factory for an instance-counting BrowserAccessibility, and ensure
82 // that exactly 3 instances were created. Note that the manager takes
83 // ownership of the factory.
84 CountedBrowserAccessibility::global_obj_count_ = 0;
85 BrowserAccessibilityManager* manager =
86 BrowserAccessibilityManager::Create(
87 NULL,
88 root,
89 NULL,
90 new CountedBrowserAccessibilityFactory());
91
92 ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
93
94 // Delete the manager and test that all 3 instances are deleted.
95 delete manager;
96 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
97
98 // Construct a manager again, and this time save references to two of
99 // the three nodes in the tree.
100 manager =
101 BrowserAccessibilityManager::Create(
102 NULL,
103 root,
104 NULL,
105 new CountedBrowserAccessibilityFactory());
106 ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
107
108 CountedBrowserAccessibility* root_accessible =
109 static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
110 root_accessible->NativeAddReference();
111 CountedBrowserAccessibility* child1_accessible =
112 static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1));
113 child1_accessible->NativeAddReference();
114
115 // Now delete the manager, and only one of the three nodes in the tree
116 // should be released.
117 delete manager;
118 ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_);
119
120 // Release each of our references and make sure that each one results in
121 // the instance being deleted as its reference count hits zero.
122 root_accessible->NativeReleaseReference();
123 ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_);
124 child1_accessible->NativeReleaseReference();
125 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
126 }
127
TEST(BrowserAccessibilityManagerTest,TestReuseBrowserAccessibilityObjects)128 TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) {
129 // Make sure that changes to a subtree reuse as many objects as possible.
130
131 // Tree 1:
132 //
133 // root
134 // child1
135 // child2
136 // child3
137
138 WebAccessibility tree1_child1;
139 tree1_child1.id = 2;
140 tree1_child1.name = UTF8ToUTF16("Child1");
141 tree1_child1.role = WebAccessibility::ROLE_BUTTON;
142 tree1_child1.state = 0;
143
144 WebAccessibility tree1_child2;
145 tree1_child2.id = 3;
146 tree1_child2.name = UTF8ToUTF16("Child2");
147 tree1_child2.role = WebAccessibility::ROLE_BUTTON;
148 tree1_child2.state = 0;
149
150 WebAccessibility tree1_child3;
151 tree1_child3.id = 4;
152 tree1_child3.name = UTF8ToUTF16("Child3");
153 tree1_child3.role = WebAccessibility::ROLE_BUTTON;
154 tree1_child3.state = 0;
155
156 WebAccessibility tree1_root;
157 tree1_root.id = 1;
158 tree1_root.name = UTF8ToUTF16("Document");
159 tree1_root.role = WebAccessibility::ROLE_DOCUMENT;
160 tree1_root.state = 0;
161 tree1_root.children.push_back(tree1_child1);
162 tree1_root.children.push_back(tree1_child2);
163 tree1_root.children.push_back(tree1_child3);
164
165 // Tree 2:
166 //
167 // root
168 // child0 <-- inserted
169 // child1
170 // child2
171 // <-- child3 deleted
172
173 WebAccessibility tree2_child0;
174 tree2_child0.id = 5;
175 tree2_child0.name = UTF8ToUTF16("Child0");
176 tree2_child0.role = WebAccessibility::ROLE_BUTTON;
177 tree2_child0.state = 0;
178
179 WebAccessibility tree2_child1;
180 tree2_child1.id = 2;
181 tree2_child1.name = UTF8ToUTF16("Child1");
182 tree2_child1.role = WebAccessibility::ROLE_BUTTON;
183 tree2_child1.state = 0;
184
185 WebAccessibility tree2_child2;
186 tree2_child2.id = 3;
187 tree2_child2.name = UTF8ToUTF16("Child2");
188 tree2_child2.role = WebAccessibility::ROLE_BUTTON;
189 tree2_child2.state = 0;
190
191 WebAccessibility tree2_root;
192 tree2_root.id = 1;
193 tree2_root.name = UTF8ToUTF16("DocumentChanged");
194 tree2_root.role = WebAccessibility::ROLE_DOCUMENT;
195 tree2_root.state = 0;
196 tree2_root.children.push_back(tree2_child0);
197 tree2_root.children.push_back(tree2_child1);
198 tree2_root.children.push_back(tree2_child2);
199
200 // Construct a BrowserAccessibilityManager with tree1.
201 CountedBrowserAccessibility::global_obj_count_ = 0;
202 BrowserAccessibilityManager* manager =
203 BrowserAccessibilityManager::Create(
204 NULL,
205 tree1_root,
206 NULL,
207 new CountedBrowserAccessibilityFactory());
208 ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
209
210 // Save references to all of the objects.
211 CountedBrowserAccessibility* root_accessible =
212 static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
213 root_accessible->NativeAddReference();
214 CountedBrowserAccessibility* child1_accessible =
215 static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0));
216 child1_accessible->NativeAddReference();
217 CountedBrowserAccessibility* child2_accessible =
218 static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1));
219 child2_accessible->NativeAddReference();
220 CountedBrowserAccessibility* child3_accessible =
221 static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(2));
222 child3_accessible->NativeAddReference();
223
224 // Check the index in parent.
225 EXPECT_EQ(0, child1_accessible->index_in_parent());
226 EXPECT_EQ(1, child2_accessible->index_in_parent());
227 EXPECT_EQ(2, child3_accessible->index_in_parent());
228
229 // Process a notification containing the changed subtree.
230 std::vector<ViewHostMsg_AccessibilityNotification_Params> params;
231 params.push_back(ViewHostMsg_AccessibilityNotification_Params());
232 ViewHostMsg_AccessibilityNotification_Params* msg = ¶ms[0];
233 msg->notification_type = ViewHostMsg_AccessibilityNotification_Type::
234 NOTIFICATION_TYPE_CHILDREN_CHANGED;
235 msg->acc_obj = tree2_root;
236 manager->OnAccessibilityNotifications(params);
237
238 // There should be 5 objects now: the 4 from the new tree, plus the
239 // reference to child3 we kept.
240 EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_);
241
242 // Check that our references to the root, child1, and child2 are still valid,
243 // but that the reference to child3 is now invalid.
244 EXPECT_TRUE(root_accessible->instance_active());
245 EXPECT_TRUE(child1_accessible->instance_active());
246 EXPECT_TRUE(child2_accessible->instance_active());
247 EXPECT_FALSE(child3_accessible->instance_active());
248
249 // Check that the index in parent has been updated.
250 EXPECT_EQ(1, child1_accessible->index_in_parent());
251 EXPECT_EQ(2, child2_accessible->index_in_parent());
252
253 // Release our references. The object count should only decrease by 1
254 // for child3.
255 root_accessible->NativeReleaseReference();
256 child1_accessible->NativeReleaseReference();
257 child2_accessible->NativeReleaseReference();
258 child3_accessible->NativeReleaseReference();
259
260 EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
261
262 // Delete the manager and make sure all memory is cleaned up.
263 delete manager;
264 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
265 }
266
TEST(BrowserAccessibilityManagerTest,TestReuseBrowserAccessibilityObjects2)267 TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) {
268 // Similar to the test above, but with a more complicated tree.
269
270 // Tree 1:
271 //
272 // root
273 // container
274 // child1
275 // grandchild1
276 // child2
277 // grandchild2
278 // child3
279 // grandchild3
280
281 WebAccessibility tree1_grandchild1;
282 tree1_grandchild1.id = 4;
283 tree1_grandchild1.name = UTF8ToUTF16("GrandChild1");
284 tree1_grandchild1.role = WebAccessibility::ROLE_BUTTON;
285 tree1_grandchild1.state = 0;
286
287 WebAccessibility tree1_child1;
288 tree1_child1.id = 3;
289 tree1_child1.name = UTF8ToUTF16("Child1");
290 tree1_child1.role = WebAccessibility::ROLE_BUTTON;
291 tree1_child1.state = 0;
292 tree1_child1.children.push_back(tree1_grandchild1);
293
294 WebAccessibility tree1_grandchild2;
295 tree1_grandchild2.id = 6;
296 tree1_grandchild2.name = UTF8ToUTF16("GrandChild1");
297 tree1_grandchild2.role = WebAccessibility::ROLE_BUTTON;
298 tree1_grandchild2.state = 0;
299
300 WebAccessibility tree1_child2;
301 tree1_child2.id = 5;
302 tree1_child2.name = UTF8ToUTF16("Child2");
303 tree1_child2.role = WebAccessibility::ROLE_BUTTON;
304 tree1_child2.state = 0;
305 tree1_child2.children.push_back(tree1_grandchild2);
306
307 WebAccessibility tree1_grandchild3;
308 tree1_grandchild3.id = 8;
309 tree1_grandchild3.name = UTF8ToUTF16("GrandChild3");
310 tree1_grandchild3.role = WebAccessibility::ROLE_BUTTON;
311 tree1_grandchild3.state = 0;
312
313 WebAccessibility tree1_child3;
314 tree1_child3.id = 7;
315 tree1_child3.name = UTF8ToUTF16("Child3");
316 tree1_child3.role = WebAccessibility::ROLE_BUTTON;
317 tree1_child3.state = 0;
318 tree1_child3.children.push_back(tree1_grandchild3);
319
320 WebAccessibility tree1_container;
321 tree1_container.id = 2;
322 tree1_container.name = UTF8ToUTF16("Container");
323 tree1_container.role = WebAccessibility::ROLE_GROUP;
324 tree1_container.state = 0;
325 tree1_container.children.push_back(tree1_child1);
326 tree1_container.children.push_back(tree1_child2);
327 tree1_container.children.push_back(tree1_child3);
328
329 WebAccessibility tree1_root;
330 tree1_root.id = 1;
331 tree1_root.name = UTF8ToUTF16("Document");
332 tree1_root.role = WebAccessibility::ROLE_DOCUMENT;
333 tree1_root.state = 0;
334 tree1_root.children.push_back(tree1_container);
335
336 // Tree 2:
337 //
338 // root
339 // container
340 // child0 <-- inserted
341 // grandchild0 <--
342 // child1
343 // grandchild1
344 // child2
345 // grandchild2
346 // <-- child3 (and grandchild3) deleted
347
348 WebAccessibility tree2_grandchild0;
349 tree2_grandchild0.id = 9;
350 tree2_grandchild0.name = UTF8ToUTF16("GrandChild0");
351 tree2_grandchild0.role = WebAccessibility::ROLE_BUTTON;
352 tree2_grandchild0.state = 0;
353
354 WebAccessibility tree2_child0;
355 tree2_child0.id = 10;
356 tree2_child0.name = UTF8ToUTF16("Child0");
357 tree2_child0.role = WebAccessibility::ROLE_BUTTON;
358 tree2_child0.state = 0;
359 tree2_child0.children.push_back(tree2_grandchild0);
360
361 WebAccessibility tree2_grandchild1;
362 tree2_grandchild1.id = 4;
363 tree2_grandchild1.name = UTF8ToUTF16("GrandChild1");
364 tree2_grandchild1.role = WebAccessibility::ROLE_BUTTON;
365 tree2_grandchild1.state = 0;
366
367 WebAccessibility tree2_child1;
368 tree2_child1.id = 3;
369 tree2_child1.name = UTF8ToUTF16("Child1");
370 tree2_child1.role = WebAccessibility::ROLE_BUTTON;
371 tree2_child1.state = 0;
372 tree2_child1.children.push_back(tree2_grandchild1);
373
374 WebAccessibility tree2_grandchild2;
375 tree2_grandchild2.id = 6;
376 tree2_grandchild2.name = UTF8ToUTF16("GrandChild1");
377 tree2_grandchild2.role = WebAccessibility::ROLE_BUTTON;
378 tree2_grandchild2.state = 0;
379
380 WebAccessibility tree2_child2;
381 tree2_child2.id = 5;
382 tree2_child2.name = UTF8ToUTF16("Child2");
383 tree2_child2.role = WebAccessibility::ROLE_BUTTON;
384 tree2_child2.state = 0;
385 tree2_child2.children.push_back(tree2_grandchild2);
386
387 WebAccessibility tree2_container;
388 tree2_container.id = 2;
389 tree2_container.name = UTF8ToUTF16("Container");
390 tree2_container.role = WebAccessibility::ROLE_GROUP;
391 tree2_container.state = 0;
392 tree2_container.children.push_back(tree2_child0);
393 tree2_container.children.push_back(tree2_child1);
394 tree2_container.children.push_back(tree2_child2);
395
396 WebAccessibility tree2_root;
397 tree2_root.id = 1;
398 tree2_root.name = UTF8ToUTF16("Document");
399 tree2_root.role = WebAccessibility::ROLE_DOCUMENT;
400 tree2_root.state = 0;
401 tree2_root.children.push_back(tree2_container);
402
403 // Construct a BrowserAccessibilityManager with tree1.
404 CountedBrowserAccessibility::global_obj_count_ = 0;
405 BrowserAccessibilityManager* manager =
406 BrowserAccessibilityManager::Create(
407 NULL,
408 tree1_root,
409 NULL,
410 new CountedBrowserAccessibilityFactory());
411 ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
412
413 // Save references to some objects.
414 CountedBrowserAccessibility* root_accessible =
415 static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
416 root_accessible->NativeAddReference();
417 CountedBrowserAccessibility* container_accessible =
418 static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0));
419 container_accessible->NativeAddReference();
420 CountedBrowserAccessibility* child2_accessible =
421 static_cast<CountedBrowserAccessibility*>(
422 container_accessible->GetChild(1));
423 child2_accessible->NativeAddReference();
424 CountedBrowserAccessibility* child3_accessible =
425 static_cast<CountedBrowserAccessibility*>(
426 container_accessible->GetChild(2));
427 child3_accessible->NativeAddReference();
428
429 // Check the index in parent.
430 EXPECT_EQ(1, child2_accessible->index_in_parent());
431 EXPECT_EQ(2, child3_accessible->index_in_parent());
432
433 // Process a notification containing the changed subtree rooted at
434 // the container.
435 std::vector<ViewHostMsg_AccessibilityNotification_Params> params;
436 params.push_back(ViewHostMsg_AccessibilityNotification_Params());
437 ViewHostMsg_AccessibilityNotification_Params* msg = ¶ms[0];
438 msg->notification_type = ViewHostMsg_AccessibilityNotification_Type::
439 NOTIFICATION_TYPE_CHILDREN_CHANGED;
440 msg->acc_obj = tree2_container;
441 manager->OnAccessibilityNotifications(params);
442
443 // There should be 9 objects now: the 8 from the new tree, plus the
444 // reference to child3 we kept.
445 EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_);
446
447 // Check that our references to the root and container and child2 are
448 // still valid, but that the reference to child3 is now invalid.
449 EXPECT_TRUE(root_accessible->instance_active());
450 EXPECT_TRUE(container_accessible->instance_active());
451 EXPECT_TRUE(child2_accessible->instance_active());
452 EXPECT_FALSE(child3_accessible->instance_active());
453
454 // Check that the index in parent has been updated.
455 EXPECT_EQ(2, child2_accessible->index_in_parent());
456
457 // Release our references. The object count should only decrease by 1
458 // for child3.
459 root_accessible->NativeReleaseReference();
460 container_accessible->NativeReleaseReference();
461 child2_accessible->NativeReleaseReference();
462 child3_accessible->NativeReleaseReference();
463
464 EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
465
466 // Delete the manager and make sure all memory is cleaned up.
467 delete manager;
468 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
469 }
470
TEST(BrowserAccessibilityManagerTest,TestMoveChildUp)471 TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) {
472 // Tree 1:
473 //
474 // 1
475 // 2
476 // 3
477 // 4
478
479 WebAccessibility tree1_4;
480 tree1_4.id = 4;
481 tree1_4.state = 0;
482
483 WebAccessibility tree1_3;
484 tree1_3.id = 3;
485 tree1_3.state = 0;
486 tree1_3.children.push_back(tree1_4);
487
488 WebAccessibility tree1_2;
489 tree1_2.id = 2;
490 tree1_2.state = 0;
491
492 WebAccessibility tree1_1;
493 tree1_1.id = 1;
494 tree1_1.state = 0;
495 tree1_1.children.push_back(tree1_2);
496 tree1_1.children.push_back(tree1_3);
497
498 // Tree 2:
499 //
500 // 1
501 // 4 <-- moves up a level and gains child
502 // 6 <-- new
503 // 5 <-- new
504
505 WebAccessibility tree2_6;
506 tree2_6.id = 6;
507 tree2_6.state = 0;
508
509 WebAccessibility tree2_5;
510 tree2_5.id = 5;
511 tree2_5.state = 0;
512
513 WebAccessibility tree2_4;
514 tree2_4.id = 4;
515 tree2_4.state = 0;
516 tree2_4.children.push_back(tree2_6);
517
518 WebAccessibility tree2_1;
519 tree2_1.id = 1;
520 tree2_1.state = 0;
521 tree2_1.children.push_back(tree2_4);
522 tree2_1.children.push_back(tree2_5);
523
524 // Construct a BrowserAccessibilityManager with tree1.
525 CountedBrowserAccessibility::global_obj_count_ = 0;
526 BrowserAccessibilityManager* manager =
527 BrowserAccessibilityManager::Create(
528 NULL,
529 tree1_1,
530 NULL,
531 new CountedBrowserAccessibilityFactory());
532 ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
533
534 // Process a notification containing the changed subtree.
535 std::vector<ViewHostMsg_AccessibilityNotification_Params> params;
536 params.push_back(ViewHostMsg_AccessibilityNotification_Params());
537 ViewHostMsg_AccessibilityNotification_Params* msg = ¶ms[0];
538 msg->notification_type = ViewHostMsg_AccessibilityNotification_Type::
539 NOTIFICATION_TYPE_CHILDREN_CHANGED;
540 msg->acc_obj = tree2_1;
541 manager->OnAccessibilityNotifications(params);
542
543 // There should be 4 objects now.
544 EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
545
546 // Delete the manager and make sure all memory is cleaned up.
547 delete manager;
548 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
549 }
550