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: 20 CountedBrowserAccessibility() { 21 global_obj_count_++; 22 native_ref_count_ = 1; 23 } 24 virtual ~CountedBrowserAccessibility() { 25 global_obj_count_--; 26 } 27 28 virtual void NativeAddReference() OVERRIDE { 29 native_ref_count_++; 30 } 31 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: 48 virtual ~CountedBrowserAccessibilityFactory() {} 49 virtual BrowserAccessibility* Create() { 50 return new CountedBrowserAccessibility(); 51 } 52 }; 53 54 } // anonymous namespace 55 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 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 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 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