// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/memory/scoped_ptr.h" #include "base/win/scoped_comptr.h" #include "content/browser/accessibility/browser_accessibility_manager.h" #include "content/browser/accessibility/browser_accessibility_win.h" #include "content/common/accessibility_messages.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/win/atl_module.h" using webkit_glue::WebAccessibility; namespace { // Subclass of BrowserAccessibilityWin that counts the number of instances. class CountedBrowserAccessibility : public BrowserAccessibilityWin { public: CountedBrowserAccessibility() { global_obj_count_++; } virtual ~CountedBrowserAccessibility() { global_obj_count_--; } static int global_obj_count_; }; int CountedBrowserAccessibility::global_obj_count_ = 0; // Factory that creates a CountedBrowserAccessibility. class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory { public: virtual ~CountedBrowserAccessibilityFactory() {} virtual BrowserAccessibility* Create() { CComObject<CountedBrowserAccessibility>* instance; HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance( &instance); DCHECK(SUCCEEDED(hr)); instance->AddRef(); return instance; } }; } // anonymous namespace VARIANT CreateI4Variant(LONG value) { VARIANT variant = {0}; V_VT(&variant) = VT_I4; V_I4(&variant) = value; return variant; } class BrowserAccessibilityTest : public testing::Test { protected: virtual void SetUp() { ui::win::CreateATLModuleIfNeeded(); ::CoInitialize(NULL); } virtual void TearDown() { ::CoUninitialize(); } }; // Test that BrowserAccessibilityManager correctly releases the tree of // BrowserAccessibility instances upon delete. TEST_F(BrowserAccessibilityTest, TestNoLeaks) { // Create WebAccessibility objects for a simple document tree, // representing the accessibility information used to initialize // BrowserAccessibilityManager. WebAccessibility button; button.id = 2; button.name = L"Button"; button.role = WebAccessibility::ROLE_BUTTON; button.state = 0; WebAccessibility checkbox; checkbox.id = 3; checkbox.name = L"Checkbox"; checkbox.role = WebAccessibility::ROLE_CHECKBOX; checkbox.state = 0; WebAccessibility root; root.id = 1; root.name = L"Document"; root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(button); root.children.push_back(checkbox); // Construct a BrowserAccessibilityManager with this WebAccessibility tree // and a factory for an instance-counting BrowserAccessibility, and ensure // that exactly 3 instances were created. Note that the manager takes // ownership of the factory. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and test that all 3 instances are deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); // Construct a manager again, and this time use the IAccessible interface // to get new references to two of the three nodes in the tree. manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); IAccessible* root_accessible = manager->GetRoot()->toBrowserAccessibilityWin(); IDispatch* root_iaccessible = NULL; IDispatch* child1_iaccessible = NULL; VARIANT var_child; var_child.vt = VT_I4; var_child.lVal = CHILDID_SELF; HRESULT hr = root_accessible->get_accChild(var_child, &root_iaccessible); ASSERT_EQ(S_OK, hr); var_child.lVal = 1; hr = root_accessible->get_accChild(var_child, &child1_iaccessible); ASSERT_EQ(S_OK, hr); // Now delete the manager, and only one of the three nodes in the tree // should be released. delete manager; ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_); // Release each of our references and make sure that each one results in // the instance being deleted as its reference count hits zero. root_iaccessible->Release(); ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_); child1_iaccessible->Release(); ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST_F(BrowserAccessibilityTest, TestChildrenChange) { // Create WebAccessibility objects for a simple document tree, // representing the accessibility information used to initialize // BrowserAccessibilityManager. WebAccessibility text; text.id = 2; text.role = WebAccessibility::ROLE_STATIC_TEXT; text.name = L"old text"; text.state = 0; WebAccessibility root; root.id = 1; root.name = L"Document"; root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(text); // Construct a BrowserAccessibilityManager with this WebAccessibility tree // and a factory for an instance-counting BrowserAccessibility. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); // Query for the text IAccessible and verify that it returns "old text" as its // value. base::win::ScopedComPtr<IDispatch> text_dispatch; HRESULT hr = manager->GetRoot()->toBrowserAccessibilityWin()->get_accChild( CreateI4Variant(1), text_dispatch.Receive()); ASSERT_EQ(S_OK, hr); base::win::ScopedComPtr<IAccessible> text_accessible; hr = text_dispatch.QueryInterface(text_accessible.Receive()); ASSERT_EQ(S_OK, hr); CComBSTR name; hr = text_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name); ASSERT_EQ(S_OK, hr); EXPECT_STREQ(L"old text", name.m_str); text_dispatch.Release(); text_accessible.Release(); // Notify the BrowserAccessibilityManager that the text child has changed. text.name = L"new text"; AccessibilityHostMsg_NotificationParams param; param.notification_type = AccessibilityNotificationChildrenChanged; param.acc_tree = text; param.includes_children = true; param.id = text.id; std::vector<AccessibilityHostMsg_NotificationParams> notifications; notifications.push_back(param); manager->OnAccessibilityNotifications(notifications); // Query for the text IAccessible and verify that it now returns "new text" // as its value. hr = manager->GetRoot()->toBrowserAccessibilityWin()->get_accChild( CreateI4Variant(1), text_dispatch.Receive()); ASSERT_EQ(S_OK, hr); hr = text_dispatch.QueryInterface(text_accessible.Receive()); ASSERT_EQ(S_OK, hr); hr = text_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name); ASSERT_EQ(S_OK, hr); EXPECT_STREQ(L"new text", name.m_str); text_dispatch.Release(); text_accessible.Release(); // Delete the manager and test that all BrowserAccessibility instances are // deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) { // Create WebAccessibility objects for a simple document tree, // representing the accessibility information used to initialize // BrowserAccessibilityManager. WebAccessibility text; text.id = 3; text.role = WebAccessibility::ROLE_STATIC_TEXT; text.state = 0; WebAccessibility div; div.id = 2; div.role = WebAccessibility::ROLE_GROUP; div.state = 0; div.children.push_back(text); text.id = 4; div.children.push_back(text); WebAccessibility root; root.id = 1; root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(div); // Construct a BrowserAccessibilityManager with this WebAccessibility tree // and a factory for an instance-counting BrowserAccessibility and ensure // that exactly 4 instances were created. Note that the manager takes // ownership of the factory. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Notify the BrowserAccessibilityManager that the div node and its children // were removed and ensure that only one BrowserAccessibility instance exists. root.children.clear(); AccessibilityHostMsg_NotificationParams param; param.notification_type = AccessibilityNotificationChildrenChanged; param.acc_tree = root; param.includes_children = true; param.id = root.id; std::vector<AccessibilityHostMsg_NotificationParams> notifications; notifications.push_back(param); manager->OnAccessibilityNotifications(notifications); ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and test that all BrowserAccessibility instances are // deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST_F(BrowserAccessibilityTest, TestTextBoundaries) { WebAccessibility text1; text1.id = 11; text1.role = WebAccessibility::ROLE_TEXT_FIELD; text1.state = 0; text1.value = L"One two three.\nFour five six."; text1.line_breaks.push_back(15); WebAccessibility root; root.id = 1; root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(text1); CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_); BrowserAccessibilityWin* root_obj = manager->GetRoot()->toBrowserAccessibilityWin(); BrowserAccessibilityWin* text1_obj = root_obj->GetChild(0)->toBrowserAccessibilityWin(); BSTR text; long start; long end; long text1_len; ASSERT_EQ(S_OK, text1_obj->get_nCharacters(&text1_len)); ASSERT_EQ(S_OK, text1_obj->get_text(0, text1_len, &text)); ASSERT_EQ(text, text1.value); SysFreeString(text); ASSERT_EQ(S_OK, text1_obj->get_text(0, 4, &text)); ASSERT_EQ(text, string16(L"One ")); SysFreeString(text); ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, &text)); ASSERT_EQ(start, 1); ASSERT_EQ(end, 2); ASSERT_EQ(text, string16(L"n")); SysFreeString(text); ASSERT_EQ(S_FALSE, text1_obj->get_textAtOffset( text1_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, &text)); ASSERT_EQ(start, text1_len); ASSERT_EQ(end, text1_len); ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, &text)); ASSERT_EQ(start, 0); ASSERT_EQ(end, 3); ASSERT_EQ(text, string16(L"One")); SysFreeString(text); ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 6, IA2_TEXT_BOUNDARY_WORD, &start, &end, &text)); ASSERT_EQ(start, 4); ASSERT_EQ(end, 7); ASSERT_EQ(text, string16(L"two")); SysFreeString(text); ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( text1_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, &text)); ASSERT_EQ(start, 25); ASSERT_EQ(end, 29); ASSERT_EQ(text, string16(L"six.")); SysFreeString(text); ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( 1, IA2_TEXT_BOUNDARY_LINE, &start, &end, &text)); ASSERT_EQ(start, 0); ASSERT_EQ(end, 15); ASSERT_EQ(text, string16(L"One two three.\n")); SysFreeString(text); ASSERT_EQ(S_OK, text1_obj->get_text(0, IA2_TEXT_OFFSET_LENGTH, &text)); ASSERT_EQ(text, string16(L"One two three.\nFour five six.")); SysFreeString(text); // Delete the manager and test that all BrowserAccessibility instances are // deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) { WebAccessibility text1; text1.id = 11; text1.role = WebAccessibility::ROLE_STATIC_TEXT; text1.state = 0; text1.name = L"One two three."; WebAccessibility text2; text2.id = 12; text2.role = WebAccessibility::ROLE_STATIC_TEXT; text2.state = 0; text2.name = L" Four five six."; WebAccessibility root; root.id = 1; root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(text1); root.children.push_back(text2); CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); BrowserAccessibilityWin* root_obj = manager->GetRoot()->toBrowserAccessibilityWin(); BSTR text; long text_len; ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len)); ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, &text)); EXPECT_EQ(text, text1.name + text2.name); SysFreeString(text); long hyperlink_count; ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count)); EXPECT_EQ(0, hyperlink_count); base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink; EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive())); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive())); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive())); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive())); long hyperlink_index; EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index)); EXPECT_EQ(-1, hyperlink_index); EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index)); EXPECT_EQ(-1, hyperlink_index); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index)); EXPECT_EQ(-1, hyperlink_index); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index)); EXPECT_EQ(-1, hyperlink_index); // Delete the manager and test that all BrowserAccessibility instances are // deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST_F(BrowserAccessibilityTest, TestComplexHypertext) { WebAccessibility text1; text1.id = 11; text1.role = WebAccessibility::ROLE_STATIC_TEXT; text1.state = 0; text1.name = L"One two three."; WebAccessibility text2; text2.id = 12; text2.role = WebAccessibility::ROLE_STATIC_TEXT; text2.state = 0; text2.name = L" Four five six."; WebAccessibility button1, button1_text; button1.id = 13; button1_text.id = 15; button1_text.name = L"red"; button1.role = WebAccessibility::ROLE_BUTTON; button1_text.role = WebAccessibility::ROLE_STATIC_TEXT; button1.state = 0; button1.children.push_back(button1_text); WebAccessibility link1, link1_text; link1.id = 14; link1_text.id = 16; link1_text.name = L"blue"; link1.role = WebAccessibility::ROLE_LINK; link1_text.role = WebAccessibility::ROLE_STATIC_TEXT; link1.state = 0; link1.children.push_back(link1_text); WebAccessibility root; root.id = 1; root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(text1); root.children.push_back(button1); root.children.push_back(text2); root.children.push_back(link1); CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(7, CountedBrowserAccessibility::global_obj_count_); BrowserAccessibilityWin* root_obj = manager->GetRoot()->toBrowserAccessibilityWin(); BSTR text; long text_len; ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len)); ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, &text)); const string16 embed = BrowserAccessibilityWin::kEmbeddedCharacter; EXPECT_EQ(text, text1.name + embed + text2.name + embed); SysFreeString(text); long hyperlink_count; ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count)); EXPECT_EQ(2, hyperlink_count); base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink; base::win::ScopedComPtr<IAccessibleText> hypertext; EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive())); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive())); EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive())); EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive())); EXPECT_EQ(S_OK, hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive())); EXPECT_EQ(S_OK, hypertext->get_text(0, 3, &text)); EXPECT_EQ(text, string16(L"red")); SysFreeString(text); hyperlink.Release(); hypertext.Release(); EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive())); EXPECT_EQ(S_OK, hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive())); EXPECT_EQ(S_OK, hypertext->get_text(0, 4, &text)); EXPECT_EQ(text, string16(L"blue")); SysFreeString(text); hyperlink.Release(); hypertext.Release(); long hyperlink_index; EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index)); EXPECT_EQ(-1, hyperlink_index); EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index)); EXPECT_EQ(-1, hyperlink_index); EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index)); EXPECT_EQ(0, hyperlink_index); EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index)); EXPECT_EQ(1, hyperlink_index); // Delete the manager and test that all BrowserAccessibility instances are // deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); }