blob: 4cfeb94f772e1697d1512b4f29ee75e0fc31c1da [file] [log] [blame]
// 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/view_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";
ViewHostMsg_AccessibilityNotification_Params param;
param.notification_type = ViewHostMsg_AccEvent::CHILDREN_CHANGED;
param.acc_tree = text;
param.includes_children = true;
param.id = text.id;
std::vector<ViewHostMsg_AccessibilityNotification_Params> 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();
ViewHostMsg_AccessibilityNotification_Params param;
param.notification_type = ViewHostMsg_AccEvent::CHILDREN_CHANGED;
param.acc_tree = root;
param.includes_children = true;
param.id = root.id;
std::vector<ViewHostMsg_AccessibilityNotification_Params> 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_);
}