1 // Copyright (c) 2012 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/strings/string16.h" 6 #include "base/strings/utf_string_conversions.h" 7 #include "content/browser/accessibility/browser_accessibility.h" 8 #include "content/browser/accessibility/browser_accessibility_manager.h" 9 #if defined(OS_WIN) 10 #include "content/browser/accessibility/browser_accessibility_win.h" 11 #endif 12 #include "content/common/accessibility_messages.h" 13 #include "testing/gtest/include/gtest/gtest.h" 14 15 namespace content { 16 namespace { 17 18 // Subclass of BrowserAccessibility that counts the number of instances. 19 class CountedBrowserAccessibility : public BrowserAccessibility { 20 public: 21 CountedBrowserAccessibility() { 22 global_obj_count_++; 23 native_ref_count_ = 1; 24 } 25 virtual ~CountedBrowserAccessibility() { 26 global_obj_count_--; 27 } 28 29 virtual void NativeAddReference() OVERRIDE { 30 native_ref_count_++; 31 } 32 33 virtual void NativeReleaseReference() OVERRIDE { 34 native_ref_count_--; 35 if (native_ref_count_ == 0) 36 delete this; 37 } 38 39 int native_ref_count_; 40 static int global_obj_count_; 41 42 #if defined(OS_WIN) 43 // Adds some padding to prevent a heap-buffer-overflow when an instance of 44 // this class is casted into a BrowserAccessibilityWin pointer. 45 // http://crbug.com/235508 46 // TODO(dmazzoni): Fix this properly. 47 static const size_t kDataSize = sizeof(int) + sizeof(BrowserAccessibility); 48 uint8 padding_[sizeof(BrowserAccessibilityWin) - kDataSize]; 49 #endif 50 }; 51 52 int CountedBrowserAccessibility::global_obj_count_ = 0; 53 54 // Factory that creates a CountedBrowserAccessibility. 55 class CountedBrowserAccessibilityFactory 56 : public BrowserAccessibilityFactory { 57 public: 58 virtual ~CountedBrowserAccessibilityFactory() {} 59 virtual BrowserAccessibility* Create() OVERRIDE { 60 return new CountedBrowserAccessibility(); 61 } 62 }; 63 64 class TestBrowserAccessibilityDelegate 65 : public BrowserAccessibilityDelegate { 66 public: 67 TestBrowserAccessibilityDelegate() 68 : got_fatal_error_(false) {} 69 70 virtual void AccessibilitySetFocus(int acc_obj_id) OVERRIDE {} 71 virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE {} 72 virtual void AccessibilityShowMenu(const gfx::Point& point) OVERRIDE {} 73 virtual void AccessibilityScrollToMakeVisible( 74 int acc_obj_id, const gfx::Rect& subfocus) OVERRIDE {} 75 virtual void AccessibilityScrollToPoint( 76 int acc_obj_id, const gfx::Point& point) OVERRIDE {} 77 virtual void AccessibilitySetTextSelection( 78 int acc_obj_id, int start_offset, int end_offset) OVERRIDE {} 79 virtual bool AccessibilityViewHasFocus() const OVERRIDE { 80 return false; 81 } 82 virtual gfx::Rect AccessibilityGetViewBounds() const OVERRIDE { 83 return gfx::Rect(); 84 } 85 virtual gfx::Point AccessibilityOriginInScreen( 86 const gfx::Rect& bounds) const OVERRIDE { 87 return gfx::Point(); 88 } 89 virtual void AccessibilityHitTest(const gfx::Point& point) OVERRIDE {} 90 virtual void AccessibilityFatalError() OVERRIDE { 91 got_fatal_error_ = true; 92 } 93 virtual gfx::AcceleratedWidget AccessibilityGetAcceleratedWidget() OVERRIDE { 94 return gfx::kNullAcceleratedWidget; 95 } 96 virtual gfx::NativeViewAccessible AccessibilityGetNativeViewAccessible() 97 OVERRIDE { 98 return NULL; 99 } 100 virtual BrowserAccessibilityManager* AccessibilityGetChildFrame( 101 int accessibility_node_id) OVERRIDE { 102 return NULL; 103 } 104 virtual BrowserAccessibility* AccessibilityGetParentFrame() OVERRIDE { 105 return NULL; 106 } 107 108 bool got_fatal_error() const { return got_fatal_error_; } 109 void reset_got_fatal_error() { got_fatal_error_ = false; } 110 111 private: 112 bool got_fatal_error_; 113 }; 114 115 } // anonymous namespace 116 117 TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { 118 // Create ui::AXNodeData objects for a simple document tree, 119 // representing the accessibility information used to initialize 120 // BrowserAccessibilityManager. 121 ui::AXNodeData button; 122 button.id = 2; 123 button.SetName("Button"); 124 button.role = ui::AX_ROLE_BUTTON; 125 button.state = 0; 126 127 ui::AXNodeData checkbox; 128 checkbox.id = 3; 129 checkbox.SetName("Checkbox"); 130 checkbox.role = ui::AX_ROLE_CHECK_BOX; 131 checkbox.state = 0; 132 133 ui::AXNodeData root; 134 root.id = 1; 135 root.SetName("Document"); 136 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 137 root.state = 0; 138 root.child_ids.push_back(2); 139 root.child_ids.push_back(3); 140 141 // Construct a BrowserAccessibilityManager with this 142 // ui::AXNodeData tree and a factory for an instance-counting 143 // BrowserAccessibility, and ensure that exactly 3 instances were 144 // created. Note that the manager takes ownership of the factory. 145 CountedBrowserAccessibility::global_obj_count_ = 0; 146 BrowserAccessibilityManager* manager = 147 BrowserAccessibilityManager::Create( 148 MakeAXTreeUpdate(root, button, checkbox), 149 NULL, 150 new CountedBrowserAccessibilityFactory()); 151 152 ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); 153 154 // Delete the manager and test that all 3 instances are deleted. 155 delete manager; 156 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); 157 158 // Construct a manager again, and this time save references to two of 159 // the three nodes in the tree. 160 manager = 161 BrowserAccessibilityManager::Create( 162 MakeAXTreeUpdate(root, button, checkbox), 163 NULL, 164 new CountedBrowserAccessibilityFactory()); 165 ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); 166 167 CountedBrowserAccessibility* root_accessible = 168 static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); 169 root_accessible->NativeAddReference(); 170 CountedBrowserAccessibility* child1_accessible = 171 static_cast<CountedBrowserAccessibility*>( 172 root_accessible->PlatformGetChild(1)); 173 child1_accessible->NativeAddReference(); 174 175 // Now delete the manager, and only one of the three nodes in the tree 176 // should be released. 177 delete manager; 178 ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_); 179 180 // Release each of our references and make sure that each one results in 181 // the instance being deleted as its reference count hits zero. 182 root_accessible->NativeReleaseReference(); 183 ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_); 184 child1_accessible->NativeReleaseReference(); 185 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); 186 } 187 188 TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { 189 // Make sure that changes to a subtree reuse as many objects as possible. 190 191 // Tree 1: 192 // 193 // root 194 // child1 195 // child2 196 // child3 197 198 ui::AXNodeData tree1_child1; 199 tree1_child1.id = 2; 200 tree1_child1.SetName("Child1"); 201 tree1_child1.role = ui::AX_ROLE_BUTTON; 202 tree1_child1.state = 0; 203 204 ui::AXNodeData tree1_child2; 205 tree1_child2.id = 3; 206 tree1_child2.SetName("Child2"); 207 tree1_child2.role = ui::AX_ROLE_BUTTON; 208 tree1_child2.state = 0; 209 210 ui::AXNodeData tree1_child3; 211 tree1_child3.id = 4; 212 tree1_child3.SetName("Child3"); 213 tree1_child3.role = ui::AX_ROLE_BUTTON; 214 tree1_child3.state = 0; 215 216 ui::AXNodeData tree1_root; 217 tree1_root.id = 1; 218 tree1_root.SetName("Document"); 219 tree1_root.role = ui::AX_ROLE_ROOT_WEB_AREA; 220 tree1_root.state = 0; 221 tree1_root.child_ids.push_back(2); 222 tree1_root.child_ids.push_back(3); 223 tree1_root.child_ids.push_back(4); 224 225 // Tree 2: 226 // 227 // root 228 // child0 <-- inserted 229 // child1 230 // child2 231 // <-- child3 deleted 232 233 ui::AXNodeData tree2_child0; 234 tree2_child0.id = 5; 235 tree2_child0.SetName("Child0"); 236 tree2_child0.role = ui::AX_ROLE_BUTTON; 237 tree2_child0.state = 0; 238 239 ui::AXNodeData tree2_root; 240 tree2_root.id = 1; 241 tree2_root.SetName("DocumentChanged"); 242 tree2_root.role = ui::AX_ROLE_ROOT_WEB_AREA; 243 tree2_root.state = 0; 244 tree2_root.child_ids.push_back(5); 245 tree2_root.child_ids.push_back(2); 246 tree2_root.child_ids.push_back(3); 247 248 // Construct a BrowserAccessibilityManager with tree1. 249 CountedBrowserAccessibility::global_obj_count_ = 0; 250 BrowserAccessibilityManager* manager = 251 BrowserAccessibilityManager::Create( 252 MakeAXTreeUpdate(tree1_root, 253 tree1_child1, tree1_child2, tree1_child3), 254 NULL, 255 new CountedBrowserAccessibilityFactory()); 256 ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); 257 258 // Save references to all of the objects. 259 CountedBrowserAccessibility* root_accessible = 260 static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); 261 root_accessible->NativeAddReference(); 262 CountedBrowserAccessibility* child1_accessible = 263 static_cast<CountedBrowserAccessibility*>( 264 root_accessible->PlatformGetChild(0)); 265 child1_accessible->NativeAddReference(); 266 CountedBrowserAccessibility* child2_accessible = 267 static_cast<CountedBrowserAccessibility*>( 268 root_accessible->PlatformGetChild(1)); 269 child2_accessible->NativeAddReference(); 270 CountedBrowserAccessibility* child3_accessible = 271 static_cast<CountedBrowserAccessibility*>( 272 root_accessible->PlatformGetChild(2)); 273 child3_accessible->NativeAddReference(); 274 275 // Check the index in parent. 276 EXPECT_EQ(0, child1_accessible->GetIndexInParent()); 277 EXPECT_EQ(1, child2_accessible->GetIndexInParent()); 278 EXPECT_EQ(2, child3_accessible->GetIndexInParent()); 279 280 // Process a notification containing the changed subtree. 281 std::vector<AccessibilityHostMsg_EventParams> params; 282 params.push_back(AccessibilityHostMsg_EventParams()); 283 AccessibilityHostMsg_EventParams* msg = ¶ms[0]; 284 msg->event_type = ui::AX_EVENT_CHILDREN_CHANGED; 285 msg->update.nodes.push_back(tree2_root); 286 msg->update.nodes.push_back(tree2_child0); 287 msg->id = tree2_root.id; 288 manager->OnAccessibilityEvents(params); 289 290 // There should be 5 objects now: the 4 from the new tree, plus the 291 // reference to child3 we kept. 292 EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_); 293 294 // Check that our references to the root, child1, and child2 are still valid, 295 // but that the reference to child3 is now invalid. 296 EXPECT_TRUE(root_accessible->instance_active()); 297 EXPECT_TRUE(child1_accessible->instance_active()); 298 EXPECT_TRUE(child2_accessible->instance_active()); 299 EXPECT_FALSE(child3_accessible->instance_active()); 300 301 // Check that the index in parent has been updated. 302 EXPECT_EQ(1, child1_accessible->GetIndexInParent()); 303 EXPECT_EQ(2, child2_accessible->GetIndexInParent()); 304 305 // Release our references. The object count should only decrease by 1 306 // for child3. 307 root_accessible->NativeReleaseReference(); 308 child1_accessible->NativeReleaseReference(); 309 child2_accessible->NativeReleaseReference(); 310 child3_accessible->NativeReleaseReference(); 311 312 EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_); 313 314 // Delete the manager and make sure all memory is cleaned up. 315 delete manager; 316 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); 317 } 318 319 TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { 320 // Similar to the test above, but with a more complicated tree. 321 322 // Tree 1: 323 // 324 // root 325 // container 326 // child1 327 // grandchild1 328 // child2 329 // grandchild2 330 // child3 331 // grandchild3 332 333 ui::AXNodeData tree1_grandchild1; 334 tree1_grandchild1.id = 4; 335 tree1_grandchild1.SetName("GrandChild1"); 336 tree1_grandchild1.role = ui::AX_ROLE_BUTTON; 337 tree1_grandchild1.state = 0; 338 339 ui::AXNodeData tree1_child1; 340 tree1_child1.id = 3; 341 tree1_child1.SetName("Child1"); 342 tree1_child1.role = ui::AX_ROLE_BUTTON; 343 tree1_child1.state = 0; 344 tree1_child1.child_ids.push_back(4); 345 346 ui::AXNodeData tree1_grandchild2; 347 tree1_grandchild2.id = 6; 348 tree1_grandchild2.SetName("GrandChild1"); 349 tree1_grandchild2.role = ui::AX_ROLE_BUTTON; 350 tree1_grandchild2.state = 0; 351 352 ui::AXNodeData tree1_child2; 353 tree1_child2.id = 5; 354 tree1_child2.SetName("Child2"); 355 tree1_child2.role = ui::AX_ROLE_BUTTON; 356 tree1_child2.state = 0; 357 tree1_child2.child_ids.push_back(6); 358 359 ui::AXNodeData tree1_grandchild3; 360 tree1_grandchild3.id = 8; 361 tree1_grandchild3.SetName("GrandChild3"); 362 tree1_grandchild3.role = ui::AX_ROLE_BUTTON; 363 tree1_grandchild3.state = 0; 364 365 ui::AXNodeData tree1_child3; 366 tree1_child3.id = 7; 367 tree1_child3.SetName("Child3"); 368 tree1_child3.role = ui::AX_ROLE_BUTTON; 369 tree1_child3.state = 0; 370 tree1_child3.child_ids.push_back(8); 371 372 ui::AXNodeData tree1_container; 373 tree1_container.id = 2; 374 tree1_container.SetName("Container"); 375 tree1_container.role = ui::AX_ROLE_GROUP; 376 tree1_container.state = 0; 377 tree1_container.child_ids.push_back(3); 378 tree1_container.child_ids.push_back(5); 379 tree1_container.child_ids.push_back(7); 380 381 ui::AXNodeData tree1_root; 382 tree1_root.id = 1; 383 tree1_root.SetName("Document"); 384 tree1_root.role = ui::AX_ROLE_ROOT_WEB_AREA; 385 tree1_root.state = 0; 386 tree1_root.child_ids.push_back(2); 387 388 // Tree 2: 389 // 390 // root 391 // container 392 // child0 <-- inserted 393 // grandchild0 <-- 394 // child1 395 // grandchild1 396 // child2 397 // grandchild2 398 // <-- child3 (and grandchild3) deleted 399 400 ui::AXNodeData tree2_grandchild0; 401 tree2_grandchild0.id = 9; 402 tree2_grandchild0.SetName("GrandChild0"); 403 tree2_grandchild0.role = ui::AX_ROLE_BUTTON; 404 tree2_grandchild0.state = 0; 405 406 ui::AXNodeData tree2_child0; 407 tree2_child0.id = 10; 408 tree2_child0.SetName("Child0"); 409 tree2_child0.role = ui::AX_ROLE_BUTTON; 410 tree2_child0.state = 0; 411 tree2_child0.child_ids.push_back(9); 412 413 ui::AXNodeData tree2_container; 414 tree2_container.id = 2; 415 tree2_container.SetName("Container"); 416 tree2_container.role = ui::AX_ROLE_GROUP; 417 tree2_container.state = 0; 418 tree2_container.child_ids.push_back(10); 419 tree2_container.child_ids.push_back(3); 420 tree2_container.child_ids.push_back(5); 421 422 // Construct a BrowserAccessibilityManager with tree1. 423 CountedBrowserAccessibility::global_obj_count_ = 0; 424 BrowserAccessibilityManager* manager = 425 BrowserAccessibilityManager::Create( 426 MakeAXTreeUpdate(tree1_root, tree1_container, 427 tree1_child1, tree1_grandchild1, 428 tree1_child2, tree1_grandchild2, 429 tree1_child3, tree1_grandchild3), 430 NULL, 431 new CountedBrowserAccessibilityFactory()); 432 ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_); 433 434 // Save references to some objects. 435 CountedBrowserAccessibility* root_accessible = 436 static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); 437 root_accessible->NativeAddReference(); 438 CountedBrowserAccessibility* container_accessible = 439 static_cast<CountedBrowserAccessibility*>( 440 root_accessible->PlatformGetChild(0)); 441 container_accessible->NativeAddReference(); 442 CountedBrowserAccessibility* child2_accessible = 443 static_cast<CountedBrowserAccessibility*>( 444 container_accessible->PlatformGetChild(1)); 445 child2_accessible->NativeAddReference(); 446 CountedBrowserAccessibility* child3_accessible = 447 static_cast<CountedBrowserAccessibility*>( 448 container_accessible->PlatformGetChild(2)); 449 child3_accessible->NativeAddReference(); 450 451 // Check the index in parent. 452 EXPECT_EQ(1, child2_accessible->GetIndexInParent()); 453 EXPECT_EQ(2, child3_accessible->GetIndexInParent()); 454 455 // Process a notification containing the changed subtree rooted at 456 // the container. 457 std::vector<AccessibilityHostMsg_EventParams> params; 458 params.push_back(AccessibilityHostMsg_EventParams()); 459 AccessibilityHostMsg_EventParams* msg = ¶ms[0]; 460 msg->event_type = ui::AX_EVENT_CHILDREN_CHANGED; 461 msg->update.nodes.push_back(tree2_container); 462 msg->update.nodes.push_back(tree2_child0); 463 msg->update.nodes.push_back(tree2_grandchild0); 464 msg->id = tree2_container.id; 465 manager->OnAccessibilityEvents(params); 466 467 // There should be 9 objects now: the 8 from the new tree, plus the 468 // reference to child3 we kept. 469 EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_); 470 471 // Check that our references to the root and container and child2 are 472 // still valid, but that the reference to child3 is now invalid. 473 EXPECT_TRUE(root_accessible->instance_active()); 474 EXPECT_TRUE(container_accessible->instance_active()); 475 EXPECT_TRUE(child2_accessible->instance_active()); 476 EXPECT_FALSE(child3_accessible->instance_active()); 477 478 // Ensure that we retain the parent of the detached subtree. 479 EXPECT_EQ(root_accessible, container_accessible->GetParent()); 480 EXPECT_EQ(0, container_accessible->GetIndexInParent()); 481 482 // Check that the index in parent has been updated. 483 EXPECT_EQ(2, child2_accessible->GetIndexInParent()); 484 485 // Release our references. The object count should only decrease by 1 486 // for child3. 487 root_accessible->NativeReleaseReference(); 488 container_accessible->NativeReleaseReference(); 489 child2_accessible->NativeReleaseReference(); 490 child3_accessible->NativeReleaseReference(); 491 492 EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_); 493 494 // Delete the manager and make sure all memory is cleaned up. 495 delete manager; 496 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); 497 } 498 499 TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { 500 // Tree 1: 501 // 502 // 1 503 // 2 504 // 3 505 // 4 506 507 ui::AXNodeData tree1_4; 508 tree1_4.id = 4; 509 tree1_4.state = 0; 510 511 ui::AXNodeData tree1_3; 512 tree1_3.id = 3; 513 tree1_3.state = 0; 514 tree1_3.child_ids.push_back(4); 515 516 ui::AXNodeData tree1_2; 517 tree1_2.id = 2; 518 tree1_2.state = 0; 519 520 ui::AXNodeData tree1_1; 521 tree1_1.id = 1; 522 tree1_1.role = ui::AX_ROLE_ROOT_WEB_AREA; 523 tree1_1.state = 0; 524 tree1_1.child_ids.push_back(2); 525 tree1_1.child_ids.push_back(3); 526 527 // Tree 2: 528 // 529 // 1 530 // 4 <-- moves up a level and gains child 531 // 6 <-- new 532 // 5 <-- new 533 534 ui::AXNodeData tree2_6; 535 tree2_6.id = 6; 536 tree2_6.state = 0; 537 538 ui::AXNodeData tree2_5; 539 tree2_5.id = 5; 540 tree2_5.state = 0; 541 542 ui::AXNodeData tree2_4; 543 tree2_4.id = 4; 544 tree2_4.state = 0; 545 tree2_4.child_ids.push_back(6); 546 547 ui::AXNodeData tree2_1; 548 tree2_1.id = 1; 549 tree2_1.state = 0; 550 tree2_1.child_ids.push_back(4); 551 tree2_1.child_ids.push_back(5); 552 553 // Construct a BrowserAccessibilityManager with tree1. 554 CountedBrowserAccessibility::global_obj_count_ = 0; 555 BrowserAccessibilityManager* manager = 556 BrowserAccessibilityManager::Create( 557 MakeAXTreeUpdate(tree1_1, tree1_2, tree1_3, tree1_4), 558 NULL, 559 new CountedBrowserAccessibilityFactory()); 560 ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); 561 562 // Process a notification containing the changed subtree. 563 std::vector<AccessibilityHostMsg_EventParams> params; 564 params.push_back(AccessibilityHostMsg_EventParams()); 565 AccessibilityHostMsg_EventParams* msg = ¶ms[0]; 566 msg->event_type = ui::AX_EVENT_CHILDREN_CHANGED; 567 msg->update.nodes.push_back(tree2_1); 568 msg->update.nodes.push_back(tree2_4); 569 msg->update.nodes.push_back(tree2_5); 570 msg->update.nodes.push_back(tree2_6); 571 msg->id = tree2_1.id; 572 manager->OnAccessibilityEvents(params); 573 574 // There should be 4 objects now. 575 EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_); 576 577 // Delete the manager and make sure all memory is cleaned up. 578 delete manager; 579 ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); 580 } 581 582 TEST(BrowserAccessibilityManagerTest, TestFatalError) { 583 // Test that BrowserAccessibilityManager raises a fatal error 584 // (which will crash the renderer) if the same id is used in 585 // two places in the tree. 586 587 ui::AXNodeData root; 588 root.id = 1; 589 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 590 root.child_ids.push_back(2); 591 root.child_ids.push_back(2); 592 593 CountedBrowserAccessibilityFactory* factory = 594 new CountedBrowserAccessibilityFactory(); 595 scoped_ptr<TestBrowserAccessibilityDelegate> delegate( 596 new TestBrowserAccessibilityDelegate()); 597 scoped_ptr<BrowserAccessibilityManager> manager; 598 ASSERT_FALSE(delegate->got_fatal_error()); 599 manager.reset(BrowserAccessibilityManager::Create( 600 MakeAXTreeUpdate(root), 601 delegate.get(), 602 factory)); 603 ASSERT_TRUE(delegate->got_fatal_error()); 604 605 ui::AXNodeData root2; 606 root2.id = 1; 607 root2.role = ui::AX_ROLE_ROOT_WEB_AREA; 608 root2.child_ids.push_back(2); 609 root2.child_ids.push_back(3); 610 611 ui::AXNodeData child1; 612 child1.id = 2; 613 child1.child_ids.push_back(4); 614 child1.child_ids.push_back(5); 615 616 ui::AXNodeData child2; 617 child2.id = 3; 618 child2.child_ids.push_back(6); 619 child2.child_ids.push_back(5); // Duplicate 620 621 ui::AXNodeData grandchild4; 622 grandchild4.id = 4; 623 624 ui::AXNodeData grandchild5; 625 grandchild5.id = 5; 626 627 ui::AXNodeData grandchild6; 628 grandchild6.id = 6; 629 630 delegate->reset_got_fatal_error(); 631 factory = new CountedBrowserAccessibilityFactory(); 632 manager.reset(BrowserAccessibilityManager::Create( 633 MakeAXTreeUpdate(root2, child1, child2, 634 grandchild4, grandchild5, grandchild6), 635 delegate.get(), 636 factory)); 637 ASSERT_TRUE(delegate->got_fatal_error()); 638 } 639 640 TEST(BrowserAccessibilityManagerTest, BoundsForRange) { 641 ui::AXNodeData root; 642 root.id = 1; 643 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 644 645 ui::AXNodeData static_text; 646 static_text.id = 2; 647 static_text.SetValue("Hello, world."); 648 static_text.role = ui::AX_ROLE_STATIC_TEXT; 649 static_text.location = gfx::Rect(100, 100, 29, 18); 650 root.child_ids.push_back(2); 651 652 ui::AXNodeData inline_text1; 653 inline_text1.id = 3; 654 inline_text1.SetValue("Hello, "); 655 inline_text1.role = ui::AX_ROLE_INLINE_TEXT_BOX; 656 inline_text1.location = gfx::Rect(100, 100, 29, 9); 657 inline_text1.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 658 ui::AX_TEXT_DIRECTION_LR); 659 std::vector<int32> character_offsets1; 660 character_offsets1.push_back(6); // 0 661 character_offsets1.push_back(11); // 1 662 character_offsets1.push_back(16); // 2 663 character_offsets1.push_back(21); // 3 664 character_offsets1.push_back(26); // 4 665 character_offsets1.push_back(29); // 5 666 character_offsets1.push_back(29); // 6 (note that the space has no width) 667 inline_text1.AddIntListAttribute( 668 ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets1); 669 static_text.child_ids.push_back(3); 670 671 ui::AXNodeData inline_text2; 672 inline_text2.id = 4; 673 inline_text2.SetValue("world."); 674 inline_text2.role = ui::AX_ROLE_INLINE_TEXT_BOX; 675 inline_text2.location = gfx::Rect(100, 109, 28, 9); 676 inline_text2.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 677 ui::AX_TEXT_DIRECTION_LR); 678 std::vector<int32> character_offsets2; 679 character_offsets2.push_back(5); 680 character_offsets2.push_back(10); 681 character_offsets2.push_back(15); 682 character_offsets2.push_back(20); 683 character_offsets2.push_back(25); 684 character_offsets2.push_back(28); 685 inline_text2.AddIntListAttribute( 686 ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets2); 687 static_text.child_ids.push_back(4); 688 689 scoped_ptr<BrowserAccessibilityManager> manager( 690 BrowserAccessibilityManager::Create( 691 MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2), 692 NULL, 693 new CountedBrowserAccessibilityFactory())); 694 695 BrowserAccessibility* root_accessible = manager->GetRoot(); 696 BrowserAccessibility* static_text_accessible = 697 root_accessible->PlatformGetChild(0); 698 699 EXPECT_EQ(gfx::Rect(100, 100, 6, 9).ToString(), 700 static_text_accessible->GetLocalBoundsForRange(0, 1).ToString()); 701 702 EXPECT_EQ(gfx::Rect(100, 100, 26, 9).ToString(), 703 static_text_accessible->GetLocalBoundsForRange(0, 5).ToString()); 704 705 EXPECT_EQ(gfx::Rect(100, 109, 5, 9).ToString(), 706 static_text_accessible->GetLocalBoundsForRange(7, 1).ToString()); 707 708 EXPECT_EQ(gfx::Rect(100, 109, 25, 9).ToString(), 709 static_text_accessible->GetLocalBoundsForRange(7, 5).ToString()); 710 711 EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(), 712 static_text_accessible->GetLocalBoundsForRange(5, 3).ToString()); 713 714 EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(), 715 static_text_accessible->GetLocalBoundsForRange(0, 13).ToString()); 716 717 // Test range that's beyond the text. 718 EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(), 719 static_text_accessible->GetLocalBoundsForRange(-1, 999).ToString()); 720 721 // Test that we can call bounds for range on the parent element, too, 722 // and it still works. 723 EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(), 724 root_accessible->GetLocalBoundsForRange(0, 13).ToString()); 725 } 726 727 TEST(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) { 728 // In this example, we assume that the string "123abc" is rendered with 729 // "123" going left-to-right and "abc" going right-to-left. In other 730 // words, on-screen it would look like "123cba". This is possible to 731 // acheive if the source string had unicode control characters 732 // to switch directions. This test doesn't worry about how, though - it just 733 // tests that if something like that were to occur, GetLocalBoundsForRange 734 // returns the correct bounds for different ranges. 735 736 ui::AXNodeData root; 737 root.id = 1; 738 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 739 740 ui::AXNodeData static_text; 741 static_text.id = 2; 742 static_text.SetValue("123abc"); 743 static_text.role = ui::AX_ROLE_STATIC_TEXT; 744 static_text.location = gfx::Rect(100, 100, 60, 20); 745 root.child_ids.push_back(2); 746 747 ui::AXNodeData inline_text1; 748 inline_text1.id = 3; 749 inline_text1.SetValue("123"); 750 inline_text1.role = ui::AX_ROLE_INLINE_TEXT_BOX; 751 inline_text1.location = gfx::Rect(100, 100, 30, 20); 752 inline_text1.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 753 ui::AX_TEXT_DIRECTION_LR); 754 std::vector<int32> character_offsets1; 755 character_offsets1.push_back(10); // 0 756 character_offsets1.push_back(20); // 1 757 character_offsets1.push_back(30); // 2 758 inline_text1.AddIntListAttribute( 759 ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets1); 760 static_text.child_ids.push_back(3); 761 762 ui::AXNodeData inline_text2; 763 inline_text2.id = 4; 764 inline_text2.SetValue("abc"); 765 inline_text2.role = ui::AX_ROLE_INLINE_TEXT_BOX; 766 inline_text2.location = gfx::Rect(130, 100, 30, 20); 767 inline_text2.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 768 ui::AX_TEXT_DIRECTION_RL); 769 std::vector<int32> character_offsets2; 770 character_offsets2.push_back(10); 771 character_offsets2.push_back(20); 772 character_offsets2.push_back(30); 773 inline_text2.AddIntListAttribute( 774 ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets2); 775 static_text.child_ids.push_back(4); 776 777 scoped_ptr<BrowserAccessibilityManager> manager( 778 BrowserAccessibilityManager::Create( 779 MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2), 780 NULL, 781 new CountedBrowserAccessibilityFactory())); 782 783 BrowserAccessibility* root_accessible = manager->GetRoot(); 784 BrowserAccessibility* static_text_accessible = 785 root_accessible->PlatformGetChild(0); 786 787 EXPECT_EQ(gfx::Rect(100, 100, 60, 20).ToString(), 788 static_text_accessible->GetLocalBoundsForRange(0, 6).ToString()); 789 790 EXPECT_EQ(gfx::Rect(100, 100, 10, 20).ToString(), 791 static_text_accessible->GetLocalBoundsForRange(0, 1).ToString()); 792 793 EXPECT_EQ(gfx::Rect(100, 100, 30, 20).ToString(), 794 static_text_accessible->GetLocalBoundsForRange(0, 3).ToString()); 795 796 EXPECT_EQ(gfx::Rect(150, 100, 10, 20).ToString(), 797 static_text_accessible->GetLocalBoundsForRange(3, 1).ToString()); 798 799 EXPECT_EQ(gfx::Rect(130, 100, 30, 20).ToString(), 800 static_text_accessible->GetLocalBoundsForRange(3, 3).ToString()); 801 802 // This range is only two characters, but because of the direction switch 803 // the bounds are as wide as four characters. 804 EXPECT_EQ(gfx::Rect(120, 100, 40, 20).ToString(), 805 static_text_accessible->GetLocalBoundsForRange(2, 2).ToString()); 806 } 807 808 #if defined(OS_WIN) 809 #define MAYBE_BoundsForRangeOnParentElement \ 810 DISABLED_BoundsForRangeOnParentElement 811 #else 812 #define MAYBE_BoundsForRangeOnParentElement BoundsForRangeOnParentElement 813 #endif 814 TEST(BrowserAccessibilityManagerTest, MAYBE_BoundsForRangeOnParentElement) { 815 ui::AXNodeData root; 816 root.id = 1; 817 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 818 root.child_ids.push_back(2); 819 820 ui::AXNodeData div; 821 div.id = 2; 822 div.role = ui::AX_ROLE_DIV; 823 div.location = gfx::Rect(100, 100, 100, 20); 824 div.child_ids.push_back(3); 825 div.child_ids.push_back(4); 826 div.child_ids.push_back(5); 827 828 ui::AXNodeData static_text1; 829 static_text1.id = 3; 830 static_text1.SetValue("AB"); 831 static_text1.role = ui::AX_ROLE_STATIC_TEXT; 832 static_text1.location = gfx::Rect(100, 100, 40, 20); 833 static_text1.child_ids.push_back(6); 834 835 ui::AXNodeData img; 836 img.id = 4; 837 img.role = ui::AX_ROLE_IMAGE; 838 img.location = gfx::Rect(140, 100, 20, 20); 839 840 ui::AXNodeData static_text2; 841 static_text2.id = 5; 842 static_text2.SetValue("CD"); 843 static_text2.role = ui::AX_ROLE_STATIC_TEXT; 844 static_text2.location = gfx::Rect(160, 100, 40, 20); 845 static_text2.child_ids.push_back(7); 846 847 ui::AXNodeData inline_text1; 848 inline_text1.id = 6; 849 inline_text1.SetValue("AB"); 850 inline_text1.role = ui::AX_ROLE_INLINE_TEXT_BOX; 851 inline_text1.location = gfx::Rect(100, 100, 40, 20); 852 inline_text1.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 853 ui::AX_TEXT_DIRECTION_LR); 854 std::vector<int32> character_offsets1; 855 character_offsets1.push_back(20); // 0 856 character_offsets1.push_back(40); // 1 857 inline_text1.AddIntListAttribute( 858 ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets1); 859 860 ui::AXNodeData inline_text2; 861 inline_text2.id = 7; 862 inline_text2.SetValue("CD"); 863 inline_text2.role = ui::AX_ROLE_INLINE_TEXT_BOX; 864 inline_text2.location = gfx::Rect(160, 100, 40, 20); 865 inline_text2.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 866 ui::AX_TEXT_DIRECTION_LR); 867 std::vector<int32> character_offsets2; 868 character_offsets2.push_back(20); // 0 869 character_offsets2.push_back(40); // 1 870 inline_text2.AddIntListAttribute( 871 ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets2); 872 873 scoped_ptr<BrowserAccessibilityManager> manager( 874 BrowserAccessibilityManager::Create( 875 MakeAXTreeUpdate( 876 root, div, static_text1, img, 877 static_text2, inline_text1, inline_text2), 878 NULL, 879 new CountedBrowserAccessibilityFactory())); 880 BrowserAccessibility* root_accessible = manager->GetRoot(); 881 882 EXPECT_EQ(gfx::Rect(100, 100, 20, 20).ToString(), 883 root_accessible->GetLocalBoundsForRange(0, 1).ToString()); 884 885 EXPECT_EQ(gfx::Rect(100, 100, 40, 20).ToString(), 886 root_accessible->GetLocalBoundsForRange(0, 2).ToString()); 887 888 EXPECT_EQ(gfx::Rect(100, 100, 80, 20).ToString(), 889 root_accessible->GetLocalBoundsForRange(0, 3).ToString()); 890 891 EXPECT_EQ(gfx::Rect(120, 100, 60, 20).ToString(), 892 root_accessible->GetLocalBoundsForRange(1, 2).ToString()); 893 894 EXPECT_EQ(gfx::Rect(120, 100, 80, 20).ToString(), 895 root_accessible->GetLocalBoundsForRange(1, 3).ToString()); 896 897 EXPECT_EQ(gfx::Rect(100, 100, 100, 20).ToString(), 898 root_accessible->GetLocalBoundsForRange(0, 4).ToString()); 899 } 900 901 TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) { 902 ui::AXNodeData root; 903 root.id = 1; 904 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 905 906 ui::AXNodeData node2; 907 node2.id = 2; 908 root.child_ids.push_back(2); 909 910 ui::AXNodeData node3; 911 node3.id = 3; 912 root.child_ids.push_back(3); 913 914 ui::AXNodeData node4; 915 node4.id = 4; 916 node3.child_ids.push_back(4); 917 918 ui::AXNodeData node5; 919 node5.id = 5; 920 root.child_ids.push_back(5); 921 922 scoped_ptr<BrowserAccessibilityManager> manager( 923 BrowserAccessibilityManager::Create( 924 MakeAXTreeUpdate(root, node2, node3, node4, node5), 925 NULL, 926 new CountedBrowserAccessibilityFactory())); 927 928 BrowserAccessibility* root_accessible = manager->GetRoot(); 929 BrowserAccessibility* node2_accessible = root_accessible->PlatformGetChild(0); 930 BrowserAccessibility* node3_accessible = root_accessible->PlatformGetChild(1); 931 BrowserAccessibility* node4_accessible = 932 node3_accessible->PlatformGetChild(0); 933 BrowserAccessibility* node5_accessible = root_accessible->PlatformGetChild(2); 934 935 ASSERT_EQ(NULL, manager->NextInTreeOrder(NULL)); 936 ASSERT_EQ(node2_accessible, manager->NextInTreeOrder(root_accessible)); 937 ASSERT_EQ(node3_accessible, manager->NextInTreeOrder(node2_accessible)); 938 ASSERT_EQ(node4_accessible, manager->NextInTreeOrder(node3_accessible)); 939 ASSERT_EQ(node5_accessible, manager->NextInTreeOrder(node4_accessible)); 940 ASSERT_EQ(NULL, manager->NextInTreeOrder(node5_accessible)); 941 942 ASSERT_EQ(NULL, manager->PreviousInTreeOrder(NULL)); 943 ASSERT_EQ(node4_accessible, manager->PreviousInTreeOrder(node5_accessible)); 944 ASSERT_EQ(node3_accessible, manager->PreviousInTreeOrder(node4_accessible)); 945 ASSERT_EQ(node2_accessible, manager->PreviousInTreeOrder(node3_accessible)); 946 ASSERT_EQ(root_accessible, manager->PreviousInTreeOrder(node2_accessible)); 947 } 948 949 } // namespace content 950