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/memory/scoped_ptr.h" 6 #include "base/strings/utf_string_conversions.h" 7 #include "base/win/scoped_bstr.h" 8 #include "base/win/scoped_comptr.h" 9 #include "base/win/scoped_variant.h" 10 #include "content/browser/accessibility/browser_accessibility_manager.h" 11 #include "content/browser/accessibility/browser_accessibility_manager_win.h" 12 #include "content/browser/accessibility/browser_accessibility_state_impl.h" 13 #include "content/browser/accessibility/browser_accessibility_win.h" 14 #include "content/browser/renderer_host/legacy_render_widget_host_win.h" 15 #include "content/common/accessibility_messages.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 #include "ui/base/win/atl_module.h" 18 19 namespace content { 20 namespace { 21 22 23 // CountedBrowserAccessibility ------------------------------------------------ 24 25 // Subclass of BrowserAccessibilityWin that counts the number of instances. 26 class CountedBrowserAccessibility : public BrowserAccessibilityWin { 27 public: 28 CountedBrowserAccessibility(); 29 virtual ~CountedBrowserAccessibility(); 30 31 static void reset() { num_instances_ = 0; } 32 static int num_instances() { return num_instances_; } 33 34 private: 35 static int num_instances_; 36 37 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility); 38 }; 39 40 // static 41 int CountedBrowserAccessibility::num_instances_ = 0; 42 43 CountedBrowserAccessibility::CountedBrowserAccessibility() { 44 ++num_instances_; 45 } 46 47 CountedBrowserAccessibility::~CountedBrowserAccessibility() { 48 --num_instances_; 49 } 50 51 52 // CountedBrowserAccessibilityFactory ----------------------------------------- 53 54 // Factory that creates a CountedBrowserAccessibility. 55 class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory { 56 public: 57 CountedBrowserAccessibilityFactory(); 58 59 private: 60 virtual ~CountedBrowserAccessibilityFactory(); 61 62 virtual BrowserAccessibility* Create() OVERRIDE; 63 64 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibilityFactory); 65 }; 66 67 CountedBrowserAccessibilityFactory::CountedBrowserAccessibilityFactory() { 68 } 69 70 CountedBrowserAccessibilityFactory::~CountedBrowserAccessibilityFactory() { 71 } 72 73 BrowserAccessibility* CountedBrowserAccessibilityFactory::Create() { 74 CComObject<CountedBrowserAccessibility>* instance; 75 HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance( 76 &instance); 77 DCHECK(SUCCEEDED(hr)); 78 instance->AddRef(); 79 return instance; 80 } 81 82 } // namespace 83 84 85 // BrowserAccessibilityTest --------------------------------------------------- 86 87 class BrowserAccessibilityTest : public testing::Test { 88 public: 89 BrowserAccessibilityTest(); 90 virtual ~BrowserAccessibilityTest(); 91 92 private: 93 virtual void SetUp() OVERRIDE; 94 95 DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityTest); 96 }; 97 98 BrowserAccessibilityTest::BrowserAccessibilityTest() { 99 } 100 101 BrowserAccessibilityTest::~BrowserAccessibilityTest() { 102 } 103 104 void BrowserAccessibilityTest::SetUp() { 105 ui::win::CreateATLModuleIfNeeded(); 106 } 107 108 109 // Actual tests --------------------------------------------------------------- 110 111 // Test that BrowserAccessibilityManager correctly releases the tree of 112 // BrowserAccessibility instances upon delete. 113 TEST_F(BrowserAccessibilityTest, TestNoLeaks) { 114 // Create ui::AXNodeData objects for a simple document tree, 115 // representing the accessibility information used to initialize 116 // BrowserAccessibilityManager. 117 ui::AXNodeData button; 118 button.id = 2; 119 button.SetName("Button"); 120 button.role = ui::AX_ROLE_BUTTON; 121 button.state = 0; 122 123 ui::AXNodeData checkbox; 124 checkbox.id = 3; 125 checkbox.SetName("Checkbox"); 126 checkbox.role = ui::AX_ROLE_CHECK_BOX; 127 checkbox.state = 0; 128 129 ui::AXNodeData root; 130 root.id = 1; 131 root.SetName("Document"); 132 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 133 root.state = 0; 134 root.child_ids.push_back(2); 135 root.child_ids.push_back(3); 136 137 // Construct a BrowserAccessibilityManager with this 138 // ui::AXNodeData tree and a factory for an instance-counting 139 // BrowserAccessibility, and ensure that exactly 3 instances were 140 // created. Note that the manager takes ownership of the factory. 141 CountedBrowserAccessibility::reset(); 142 scoped_ptr<BrowserAccessibilityManager> manager( 143 BrowserAccessibilityManager::Create( 144 MakeAXTreeUpdate(root, button, checkbox), 145 NULL, new CountedBrowserAccessibilityFactory())); 146 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); 147 148 // Delete the manager and test that all 3 instances are deleted. 149 manager.reset(); 150 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 151 152 // Construct a manager again, and this time use the IAccessible interface 153 // to get new references to two of the three nodes in the tree. 154 manager.reset(BrowserAccessibilityManager::Create( 155 MakeAXTreeUpdate(root, button, checkbox), 156 NULL, new CountedBrowserAccessibilityFactory())); 157 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); 158 IAccessible* root_accessible = 159 manager->GetRoot()->ToBrowserAccessibilityWin(); 160 IDispatch* root_iaccessible = NULL; 161 IDispatch* child1_iaccessible = NULL; 162 base::win::ScopedVariant childid_self(CHILDID_SELF); 163 HRESULT hr = root_accessible->get_accChild(childid_self, &root_iaccessible); 164 ASSERT_EQ(S_OK, hr); 165 base::win::ScopedVariant one(1); 166 hr = root_accessible->get_accChild(one, &child1_iaccessible); 167 ASSERT_EQ(S_OK, hr); 168 169 // Now delete the manager, and only one of the three nodes in the tree 170 // should be released. 171 manager.reset(); 172 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances()); 173 174 // Release each of our references and make sure that each one results in 175 // the instance being deleted as its reference count hits zero. 176 root_iaccessible->Release(); 177 ASSERT_EQ(1, CountedBrowserAccessibility::num_instances()); 178 child1_iaccessible->Release(); 179 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 180 } 181 182 TEST_F(BrowserAccessibilityTest, TestChildrenChange) { 183 // Create ui::AXNodeData objects for a simple document tree, 184 // representing the accessibility information used to initialize 185 // BrowserAccessibilityManager. 186 ui::AXNodeData text; 187 text.id = 2; 188 text.role = ui::AX_ROLE_STATIC_TEXT; 189 text.SetName("old text"); 190 text.state = 0; 191 192 ui::AXNodeData root; 193 root.id = 1; 194 root.SetName("Document"); 195 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 196 root.state = 0; 197 root.child_ids.push_back(2); 198 199 // Construct a BrowserAccessibilityManager with this 200 // ui::AXNodeData tree and a factory for an instance-counting 201 // BrowserAccessibility. 202 CountedBrowserAccessibility::reset(); 203 scoped_ptr<BrowserAccessibilityManager> manager( 204 BrowserAccessibilityManager::Create( 205 MakeAXTreeUpdate(root, text), 206 NULL, new CountedBrowserAccessibilityFactory())); 207 208 // Query for the text IAccessible and verify that it returns "old text" as its 209 // value. 210 base::win::ScopedVariant one(1); 211 base::win::ScopedComPtr<IDispatch> text_dispatch; 212 HRESULT hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild( 213 one, text_dispatch.Receive()); 214 ASSERT_EQ(S_OK, hr); 215 216 base::win::ScopedComPtr<IAccessible> text_accessible; 217 hr = text_dispatch.QueryInterface(text_accessible.Receive()); 218 ASSERT_EQ(S_OK, hr); 219 220 base::win::ScopedVariant childid_self(CHILDID_SELF); 221 base::win::ScopedBstr name; 222 hr = text_accessible->get_accName(childid_self, name.Receive()); 223 ASSERT_EQ(S_OK, hr); 224 EXPECT_EQ(L"old text", base::string16(name)); 225 name.Reset(); 226 227 text_dispatch.Release(); 228 text_accessible.Release(); 229 230 // Notify the BrowserAccessibilityManager that the text child has changed. 231 ui::AXNodeData text2; 232 text2.id = 2; 233 text2.role = ui::AX_ROLE_STATIC_TEXT; 234 text2.SetName("new text"); 235 text2.SetName("old text"); 236 AccessibilityHostMsg_EventParams param; 237 param.event_type = ui::AX_EVENT_CHILDREN_CHANGED; 238 param.update.nodes.push_back(text2); 239 param.id = text2.id; 240 std::vector<AccessibilityHostMsg_EventParams> events; 241 events.push_back(param); 242 manager->OnAccessibilityEvents(events); 243 244 // Query for the text IAccessible and verify that it now returns "new text" 245 // as its value. 246 hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild( 247 one, text_dispatch.Receive()); 248 ASSERT_EQ(S_OK, hr); 249 250 hr = text_dispatch.QueryInterface(text_accessible.Receive()); 251 ASSERT_EQ(S_OK, hr); 252 253 hr = text_accessible->get_accName(childid_self, name.Receive()); 254 ASSERT_EQ(S_OK, hr); 255 EXPECT_EQ(L"new text", base::string16(name)); 256 257 text_dispatch.Release(); 258 text_accessible.Release(); 259 260 // Delete the manager and test that all BrowserAccessibility instances are 261 // deleted. 262 manager.reset(); 263 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 264 } 265 266 TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) { 267 // Create ui::AXNodeData objects for a simple document tree, 268 // representing the accessibility information used to initialize 269 // BrowserAccessibilityManager. 270 ui::AXNodeData div; 271 div.id = 2; 272 div.role = ui::AX_ROLE_GROUP; 273 div.state = 0; 274 275 ui::AXNodeData text3; 276 text3.id = 3; 277 text3.role = ui::AX_ROLE_STATIC_TEXT; 278 text3.state = 0; 279 280 ui::AXNodeData text4; 281 text4.id = 4; 282 text4.role = ui::AX_ROLE_STATIC_TEXT; 283 text4.state = 0; 284 285 div.child_ids.push_back(3); 286 div.child_ids.push_back(4); 287 288 ui::AXNodeData root; 289 root.id = 1; 290 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 291 root.state = 0; 292 root.child_ids.push_back(2); 293 294 // Construct a BrowserAccessibilityManager with this 295 // ui::AXNodeData tree and a factory for an instance-counting 296 // BrowserAccessibility and ensure that exactly 4 instances were 297 // created. Note that the manager takes ownership of the factory. 298 CountedBrowserAccessibility::reset(); 299 scoped_ptr<BrowserAccessibilityManager> manager( 300 BrowserAccessibilityManager::Create( 301 MakeAXTreeUpdate(root, div, text3, text4), 302 NULL, new CountedBrowserAccessibilityFactory())); 303 ASSERT_EQ(4, CountedBrowserAccessibility::num_instances()); 304 305 // Notify the BrowserAccessibilityManager that the div node and its children 306 // were removed and ensure that only one BrowserAccessibility instance exists. 307 root.child_ids.clear(); 308 AccessibilityHostMsg_EventParams param; 309 param.event_type = ui::AX_EVENT_CHILDREN_CHANGED; 310 param.update.nodes.push_back(root); 311 param.id = root.id; 312 std::vector<AccessibilityHostMsg_EventParams> events; 313 events.push_back(param); 314 manager->OnAccessibilityEvents(events); 315 ASSERT_EQ(1, CountedBrowserAccessibility::num_instances()); 316 317 // Delete the manager and test that all BrowserAccessibility instances are 318 // deleted. 319 manager.reset(); 320 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 321 } 322 323 TEST_F(BrowserAccessibilityTest, TestTextBoundaries) { 324 std::string text1_value = "One two three.\nFour five six."; 325 326 ui::AXNodeData text1; 327 text1.id = 11; 328 text1.role = ui::AX_ROLE_TEXT_FIELD; 329 text1.state = 0; 330 text1.AddStringAttribute(ui::AX_ATTR_VALUE, text1_value); 331 std::vector<int32> line_breaks; 332 line_breaks.push_back(15); 333 text1.AddIntListAttribute( 334 ui::AX_ATTR_LINE_BREAKS, line_breaks); 335 336 ui::AXNodeData root; 337 root.id = 1; 338 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 339 root.state = 0; 340 root.child_ids.push_back(11); 341 342 CountedBrowserAccessibility::reset(); 343 scoped_ptr<BrowserAccessibilityManager> manager( 344 BrowserAccessibilityManager::Create( 345 MakeAXTreeUpdate(root, text1), 346 NULL, new CountedBrowserAccessibilityFactory())); 347 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances()); 348 349 BrowserAccessibilityWin* root_obj = 350 manager->GetRoot()->ToBrowserAccessibilityWin(); 351 BrowserAccessibilityWin* text1_obj = 352 root_obj->PlatformGetChild(0)->ToBrowserAccessibilityWin(); 353 354 long text1_len; 355 ASSERT_EQ(S_OK, text1_obj->get_nCharacters(&text1_len)); 356 357 base::win::ScopedBstr text; 358 ASSERT_EQ(S_OK, text1_obj->get_text(0, text1_len, text.Receive())); 359 ASSERT_EQ(text1_value, base::UTF16ToUTF8(base::string16(text))); 360 text.Reset(); 361 362 ASSERT_EQ(S_OK, text1_obj->get_text(0, 4, text.Receive())); 363 ASSERT_STREQ(L"One ", text); 364 text.Reset(); 365 366 long start; 367 long end; 368 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 369 1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive())); 370 ASSERT_EQ(1, start); 371 ASSERT_EQ(2, end); 372 ASSERT_STREQ(L"n", text); 373 text.Reset(); 374 375 ASSERT_EQ(S_FALSE, text1_obj->get_textAtOffset( 376 text1_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive())); 377 ASSERT_EQ(text1_len, start); 378 ASSERT_EQ(text1_len, end); 379 text.Reset(); 380 381 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 382 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive())); 383 ASSERT_EQ(0, start); 384 ASSERT_EQ(3, end); 385 ASSERT_STREQ(L"One", text); 386 text.Reset(); 387 388 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 389 6, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive())); 390 ASSERT_EQ(4, start); 391 ASSERT_EQ(7, end); 392 ASSERT_STREQ(L"two", text); 393 text.Reset(); 394 395 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 396 text1_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive())); 397 ASSERT_EQ(25, start); 398 ASSERT_EQ(29, end); 399 ASSERT_STREQ(L"six.", text); 400 text.Reset(); 401 402 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 403 1, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive())); 404 ASSERT_EQ(0, start); 405 ASSERT_EQ(15, end); 406 ASSERT_STREQ(L"One two three.\n", text); 407 text.Reset(); 408 409 ASSERT_EQ(S_OK, 410 text1_obj->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive())); 411 ASSERT_STREQ(L"One two three.\nFour five six.", text); 412 413 // Delete the manager and test that all BrowserAccessibility instances are 414 // deleted. 415 manager.reset(); 416 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 417 } 418 419 TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) { 420 const std::string text1_name = "One two three."; 421 const std::string text2_name = " Four five six."; 422 423 ui::AXNodeData text1; 424 text1.id = 11; 425 text1.role = ui::AX_ROLE_STATIC_TEXT; 426 text1.state = 1 << ui::AX_STATE_READ_ONLY; 427 text1.SetName(text1_name); 428 429 ui::AXNodeData text2; 430 text2.id = 12; 431 text2.role = ui::AX_ROLE_STATIC_TEXT; 432 text2.state = 1 << ui::AX_STATE_READ_ONLY; 433 text2.SetName(text2_name); 434 435 ui::AXNodeData root; 436 root.id = 1; 437 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 438 root.state = 1 << ui::AX_STATE_READ_ONLY; 439 root.child_ids.push_back(11); 440 root.child_ids.push_back(12); 441 442 CountedBrowserAccessibility::reset(); 443 scoped_ptr<BrowserAccessibilityManager> manager( 444 BrowserAccessibilityManager::Create( 445 MakeAXTreeUpdate(root, root, text1, text2), 446 NULL, new CountedBrowserAccessibilityFactory())); 447 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); 448 449 BrowserAccessibilityWin* root_obj = 450 manager->GetRoot()->ToBrowserAccessibilityWin(); 451 452 long text_len; 453 ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len)); 454 455 base::win::ScopedBstr text; 456 ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive())); 457 EXPECT_EQ(text1_name + text2_name, base::UTF16ToUTF8(base::string16(text))); 458 459 long hyperlink_count; 460 ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count)); 461 EXPECT_EQ(0, hyperlink_count); 462 463 base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink; 464 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive())); 465 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive())); 466 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive())); 467 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive())); 468 469 long hyperlink_index; 470 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index)); 471 EXPECT_EQ(-1, hyperlink_index); 472 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index)); 473 EXPECT_EQ(-1, hyperlink_index); 474 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index)); 475 EXPECT_EQ(-1, hyperlink_index); 476 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index)); 477 EXPECT_EQ(-1, hyperlink_index); 478 479 // Delete the manager and test that all BrowserAccessibility instances are 480 // deleted. 481 manager.reset(); 482 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 483 } 484 485 TEST_F(BrowserAccessibilityTest, TestComplexHypertext) { 486 const std::string text1_name = "One two three."; 487 const std::string text2_name = " Four five six."; 488 const std::string button1_text_name = "red"; 489 const std::string link1_text_name = "blue"; 490 491 ui::AXNodeData text1; 492 text1.id = 11; 493 text1.role = ui::AX_ROLE_STATIC_TEXT; 494 text1.state = 1 << ui::AX_STATE_READ_ONLY; 495 text1.SetName(text1_name); 496 497 ui::AXNodeData text2; 498 text2.id = 12; 499 text2.role = ui::AX_ROLE_STATIC_TEXT; 500 text2.state = 1 << ui::AX_STATE_READ_ONLY; 501 text2.SetName(text2_name); 502 503 ui::AXNodeData button1, button1_text; 504 button1.id = 13; 505 button1_text.id = 15; 506 button1_text.SetName(button1_text_name); 507 button1.role = ui::AX_ROLE_BUTTON; 508 button1_text.role = ui::AX_ROLE_STATIC_TEXT; 509 button1.state = 1 << ui::AX_STATE_READ_ONLY; 510 button1_text.state = 1 << ui::AX_STATE_READ_ONLY; 511 button1.child_ids.push_back(15); 512 513 ui::AXNodeData link1, link1_text; 514 link1.id = 14; 515 link1_text.id = 16; 516 link1_text.SetName(link1_text_name); 517 link1.role = ui::AX_ROLE_LINK; 518 link1_text.role = ui::AX_ROLE_STATIC_TEXT; 519 link1.state = 1 << ui::AX_STATE_READ_ONLY; 520 link1_text.state = 1 << ui::AX_STATE_READ_ONLY; 521 link1.child_ids.push_back(16); 522 523 ui::AXNodeData root; 524 root.id = 1; 525 root.role = ui::AX_ROLE_ROOT_WEB_AREA; 526 root.state = 1 << ui::AX_STATE_READ_ONLY; 527 root.child_ids.push_back(11); 528 root.child_ids.push_back(13); 529 root.child_ids.push_back(12); 530 root.child_ids.push_back(14); 531 532 CountedBrowserAccessibility::reset(); 533 scoped_ptr<BrowserAccessibilityManager> manager( 534 BrowserAccessibilityManager::Create( 535 MakeAXTreeUpdate(root, 536 text1, button1, button1_text, 537 text2, link1, link1_text), 538 NULL, new CountedBrowserAccessibilityFactory())); 539 ASSERT_EQ(7, CountedBrowserAccessibility::num_instances()); 540 541 BrowserAccessibilityWin* root_obj = 542 manager->GetRoot()->ToBrowserAccessibilityWin(); 543 544 long text_len; 545 ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len)); 546 547 base::win::ScopedBstr text; 548 ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive())); 549 const std::string embed = base::UTF16ToUTF8( 550 BrowserAccessibilityWin::kEmbeddedCharacter); 551 EXPECT_EQ(text1_name + embed + text2_name + embed, 552 base::UTF16ToUTF8(base::string16(text))); 553 text.Reset(); 554 555 long hyperlink_count; 556 ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count)); 557 EXPECT_EQ(2, hyperlink_count); 558 559 base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink; 560 base::win::ScopedComPtr<IAccessibleText> hypertext; 561 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive())); 562 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive())); 563 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive())); 564 565 EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive())); 566 EXPECT_EQ(S_OK, 567 hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive())); 568 EXPECT_EQ(S_OK, hypertext->get_text(0, 3, text.Receive())); 569 EXPECT_STREQ(button1_text_name.c_str(), 570 base::UTF16ToUTF8(base::string16(text)).c_str()); 571 text.Reset(); 572 hyperlink.Release(); 573 hypertext.Release(); 574 575 EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive())); 576 EXPECT_EQ(S_OK, 577 hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive())); 578 EXPECT_EQ(S_OK, hypertext->get_text(0, 4, text.Receive())); 579 EXPECT_STREQ(link1_text_name.c_str(), 580 base::UTF16ToUTF8(base::string16(text)).c_str()); 581 text.Reset(); 582 hyperlink.Release(); 583 hypertext.Release(); 584 585 long hyperlink_index; 586 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index)); 587 EXPECT_EQ(-1, hyperlink_index); 588 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index)); 589 EXPECT_EQ(-1, hyperlink_index); 590 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index)); 591 EXPECT_EQ(0, hyperlink_index); 592 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index)); 593 EXPECT_EQ(1, hyperlink_index); 594 595 // Delete the manager and test that all BrowserAccessibility instances are 596 // deleted. 597 manager.reset(); 598 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 599 } 600 601 TEST_F(BrowserAccessibilityTest, TestCreateEmptyDocument) { 602 // Try creating an empty document with busy state. Readonly is 603 // set automatically. 604 CountedBrowserAccessibility::reset(); 605 const int32 busy_state = 1 << ui::AX_STATE_BUSY; 606 const int32 readonly_state = 1 << ui::AX_STATE_READ_ONLY; 607 const int32 enabled_state = 1 << ui::AX_STATE_ENABLED; 608 scoped_ptr<content::LegacyRenderWidgetHostHWND> accessible_hwnd( 609 content::LegacyRenderWidgetHostHWND::Create(GetDesktopWindow())); 610 scoped_ptr<BrowserAccessibilityManager> manager( 611 new BrowserAccessibilityManagerWin( 612 accessible_hwnd.get(), 613 NULL, 614 BrowserAccessibilityManagerWin::GetEmptyDocument(), 615 NULL, 616 new CountedBrowserAccessibilityFactory())); 617 618 // Verify the root is as we expect by default. 619 BrowserAccessibility* root = manager->GetRoot(); 620 EXPECT_EQ(0, root->GetId()); 621 EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA, root->GetRole()); 622 EXPECT_EQ(busy_state | readonly_state | enabled_state, root->GetState()); 623 624 // Tree with a child textfield. 625 ui::AXNodeData tree1_1; 626 tree1_1.id = 1; 627 tree1_1.role = ui::AX_ROLE_ROOT_WEB_AREA; 628 tree1_1.child_ids.push_back(2); 629 630 ui::AXNodeData tree1_2; 631 tree1_2.id = 2; 632 tree1_2.role = ui::AX_ROLE_TEXT_FIELD; 633 634 // Process a load complete. 635 std::vector<AccessibilityHostMsg_EventParams> params; 636 params.push_back(AccessibilityHostMsg_EventParams()); 637 AccessibilityHostMsg_EventParams* msg = ¶ms[0]; 638 msg->event_type = ui::AX_EVENT_LOAD_COMPLETE; 639 msg->update.nodes.push_back(tree1_1); 640 msg->update.nodes.push_back(tree1_2); 641 msg->id = tree1_1.id; 642 manager->OnAccessibilityEvents(params); 643 644 // Save for later comparison. 645 BrowserAccessibility* acc1_2 = manager->GetFromID(2); 646 647 // Verify the root has changed. 648 EXPECT_NE(root, manager->GetRoot()); 649 650 // And the proper child remains. 651 EXPECT_EQ(ui::AX_ROLE_TEXT_FIELD, acc1_2->GetRole()); 652 EXPECT_EQ(2, acc1_2->GetId()); 653 654 // Tree with a child button. 655 ui::AXNodeData tree2_1; 656 tree2_1.id = 1; 657 tree2_1.role = ui::AX_ROLE_ROOT_WEB_AREA; 658 tree2_1.child_ids.push_back(3); 659 660 ui::AXNodeData tree2_2; 661 tree2_2.id = 3; 662 tree2_2.role = ui::AX_ROLE_BUTTON; 663 664 msg->update.nodes.clear(); 665 msg->update.nodes.push_back(tree2_1); 666 msg->update.nodes.push_back(tree2_2); 667 msg->id = tree2_1.id; 668 669 // Fire another load complete. 670 manager->OnAccessibilityEvents(params); 671 672 BrowserAccessibility* acc2_2 = manager->GetFromID(3); 673 674 // Verify the root has changed. 675 EXPECT_NE(root, manager->GetRoot()); 676 677 // And the new child exists. 678 EXPECT_EQ(ui::AX_ROLE_BUTTON, acc2_2->GetRole()); 679 EXPECT_EQ(3, acc2_2->GetId()); 680 681 // Ensure we properly cleaned up. 682 manager.reset(); 683 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); 684 } 685 686 TEST(BrowserAccessibilityManagerWinTest, TestAccessibleHWND) { 687 HWND desktop_hwnd = GetDesktopWindow(); 688 base::win::ScopedComPtr<IAccessible> desktop_hwnd_iaccessible; 689 ASSERT_EQ(S_OK, AccessibleObjectFromWindow( 690 desktop_hwnd, OBJID_CLIENT, 691 IID_IAccessible, 692 reinterpret_cast<void**>(desktop_hwnd_iaccessible.Receive()))); 693 694 scoped_ptr<content::LegacyRenderWidgetHostHWND> accessible_hwnd( 695 content::LegacyRenderWidgetHostHWND::Create(GetDesktopWindow())); 696 697 scoped_ptr<BrowserAccessibilityManagerWin> manager( 698 new BrowserAccessibilityManagerWin( 699 accessible_hwnd.get(), 700 desktop_hwnd_iaccessible, 701 BrowserAccessibilityManagerWin::GetEmptyDocument(), 702 NULL)); 703 ASSERT_EQ(desktop_hwnd, manager->parent_hwnd()); 704 705 // Enabling screen reader support and calling MaybeCallNotifyWinEvent 706 // should trigger creating the AccessibleHWND, and we should now get a 707 // new parent_hwnd with the right window class to fool older screen 708 // readers. 709 BrowserAccessibilityStateImpl::GetInstance()->OnScreenReaderDetected(); 710 manager->MaybeCallNotifyWinEvent(0, 0); 711 HWND new_parent_hwnd = manager->parent_hwnd(); 712 ASSERT_NE(desktop_hwnd, new_parent_hwnd); 713 WCHAR hwnd_class_name[256]; 714 ASSERT_NE(0, GetClassName(new_parent_hwnd, hwnd_class_name, 256)); 715 ASSERT_STREQ(L"Chrome_RenderWidgetHostHWND", hwnd_class_name); 716 717 // Destroy the hwnd explicitly; that should trigger clearing parent_hwnd(). 718 DestroyWindow(new_parent_hwnd); 719 ASSERT_EQ(NULL, manager->parent_hwnd()); 720 721 // Now create it again. 722 accessible_hwnd = content::LegacyRenderWidgetHostHWND::Create( 723 GetDesktopWindow()); 724 manager.reset( 725 new BrowserAccessibilityManagerWin( 726 accessible_hwnd.get(), 727 desktop_hwnd_iaccessible, 728 BrowserAccessibilityManagerWin::GetEmptyDocument(), 729 NULL)); 730 manager->MaybeCallNotifyWinEvent(0, 0); 731 new_parent_hwnd = manager->parent_hwnd(); 732 ASSERT_FALSE(NULL == new_parent_hwnd); 733 734 // This time, destroy the manager first, make sure the AccessibleHWND doesn't 735 // crash on destruction (to be caught by SyzyASAN or other tools). 736 manager.reset(NULL); 737 } 738 739 } // namespace content 740