blob: d9460829b39fc1b6edc4df195a30b82a13c7fb18 [file] [log] [blame]
// Copyright (c) 2010 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 <atlbase.h>
#include <vector>
#include "base/file_path.h"
#include "base/scoped_comptr_win.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/renderer_host/render_widget_host_view_win.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_type.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
namespace {
class AccessibilityWinBrowserTest : public InProcessBrowserTest {
public:
AccessibilityWinBrowserTest() : screenreader_running_(FALSE) {}
// InProcessBrowserTest
void SetUpInProcessBrowserTestFixture();
void TearDownInProcessBrowserTestFixture();
protected:
IAccessible* GetRenderWidgetHostViewClientAccessible();
private:
BOOL screenreader_running_;
};
void AccessibilityWinBrowserTest::SetUpInProcessBrowserTestFixture() {
// This test assumes the windows system-wide SPI_SETSCREENREADER flag is
// cleared.
if (SystemParametersInfo(SPI_GETSCREENREADER, 0, &screenreader_running_, 0) &&
screenreader_running_) {
// Clear the SPI_SETSCREENREADER flag and notify active applications about
// the setting change.
::SystemParametersInfo(SPI_SETSCREENREADER, FALSE, NULL, 0);
::SendNotifyMessage(
HWND_BROADCAST, WM_SETTINGCHANGE, SPI_GETSCREENREADER, 0);
}
}
void AccessibilityWinBrowserTest::TearDownInProcessBrowserTestFixture() {
if (screenreader_running_) {
// Restore the SPI_SETSCREENREADER flag and notify active applications about
// the setting change.
::SystemParametersInfo(SPI_SETSCREENREADER, TRUE, NULL, 0);
::SendNotifyMessage(
HWND_BROADCAST, WM_SETTINGCHANGE, SPI_GETSCREENREADER, 0);
}
}
class AccessibleChecker {
public:
AccessibleChecker(std::wstring expected_name, int32 expected_role);
AccessibleChecker(std::wstring expected_name, std::wstring expected_role);
// Append an AccessibleChecker that verifies accessibility information for
// a child IAccessible. Order is important.
void AppendExpectedChild(AccessibleChecker* expected_child);
// Check that the name and role of the given IAccessible instance and its
// descendants match the expected names and roles that this object was
// initialized with.
void CheckAccessible(IAccessible* accessible);
typedef std::vector<AccessibleChecker*> AccessibleCheckerVector;
private:
void CheckAccessibleName(IAccessible* accessible);
void CheckAccessibleRole(IAccessible* accessible);
void CheckAccessibleChildren(IAccessible* accessible);
private:
// Expected accessible name. Checked against IAccessible::get_accName.
std::wstring name_;
// Expected accessible role. Checked against IAccessible::get_accRole.
CComVariant role_;
// Expected accessible children. Checked using IAccessible::get_accChildCount
// and ::AccessibleChildren.
AccessibleCheckerVector children_;
};
VARIANT CreateI4Variant(LONG value) {
VARIANT variant = {0};
V_VT(&variant) = VT_I4;
V_I4(&variant) = value;
return variant;
}
IAccessible* GetAccessibleFromResultVariant(IAccessible* parent, VARIANT *var) {
switch (V_VT(var)) {
case VT_DISPATCH:
return CComQIPtr<IAccessible>(V_DISPATCH(var)).Detach();
break;
case VT_I4: {
CComPtr<IDispatch> dispatch;
HRESULT hr = parent->get_accChild(CreateI4Variant(V_I4(var)), &dispatch);
EXPECT_EQ(hr, S_OK);
return CComQIPtr<IAccessible>(dispatch).Detach();
break;
}
}
return NULL;
}
// Retrieve the MSAA client accessibility object for the Render Widget Host View
// of the selected tab.
IAccessible*
AccessibilityWinBrowserTest::GetRenderWidgetHostViewClientAccessible() {
HWND hwnd_render_widget_host_view =
browser()->GetSelectedTabContents()->GetRenderWidgetHostView()->
GetNativeView();
IAccessible* accessible;
HRESULT hr = AccessibleObjectFromWindow(
hwnd_render_widget_host_view, OBJID_CLIENT,
IID_IAccessible, reinterpret_cast<void**>(&accessible));
EXPECT_EQ(S_OK, hr);
EXPECT_NE(accessible, reinterpret_cast<IAccessible*>(NULL));
return accessible;
}
AccessibleChecker::AccessibleChecker(
std::wstring expected_name, int32 expected_role) :
name_(expected_name),
role_(expected_role) {
}
AccessibleChecker::AccessibleChecker(
std::wstring expected_name, std::wstring expected_role) :
name_(expected_name),
role_(expected_role.c_str()) {
}
void AccessibleChecker::AppendExpectedChild(
AccessibleChecker* expected_child) {
children_.push_back(expected_child);
}
void AccessibleChecker::CheckAccessible(IAccessible* accessible) {
CheckAccessibleName(accessible);
CheckAccessibleRole(accessible);
CheckAccessibleChildren(accessible);
}
void AccessibleChecker::CheckAccessibleName(IAccessible* accessible) {
CComBSTR name;
HRESULT hr =
accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name);
if (name_.empty()) {
// If the object doesn't have name S_FALSE should be returned.
EXPECT_EQ(hr, S_FALSE);
} else {
// Test that the correct string was returned.
EXPECT_EQ(hr, S_OK);
EXPECT_EQ(CompareString(LOCALE_NEUTRAL, 0, name, SysStringLen(name),
name_.c_str(), name_.length()),
CSTR_EQUAL);
}
}
void AccessibleChecker::CheckAccessibleRole(IAccessible* accessible) {
VARIANT var_role = {0};
HRESULT hr =
accessible->get_accRole(CreateI4Variant(CHILDID_SELF), &var_role);
EXPECT_EQ(hr, S_OK);
ASSERT_TRUE(role_ == var_role);
}
void AccessibleChecker::CheckAccessibleChildren(IAccessible* parent) {
LONG child_count = 0;
HRESULT hr = parent->get_accChildCount(&child_count);
EXPECT_EQ(hr, S_OK);
ASSERT_EQ(child_count, children_.size());
std::auto_ptr<VARIANT> child_array(new VARIANT[child_count]);
LONG obtained_count = 0;
hr = AccessibleChildren(parent, 0, child_count,
child_array.get(), &obtained_count);
ASSERT_EQ(hr, S_OK);
ASSERT_EQ(child_count, obtained_count);
VARIANT* child = child_array.get();
for (AccessibleCheckerVector::iterator child_checker = children_.begin();
child_checker != children_.end();
++child_checker, ++child) {
ScopedComPtr<IAccessible> child_accessible;
child_accessible.Attach(GetAccessibleFromResultVariant(parent, child));
(*child_checker)->CheckAccessible(child_accessible);
}
}
IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
TestRendererAccessibilityTree) {
// By requesting an accessible chrome will believe a screen reader has been
// detected.
ScopedComPtr<IAccessible> document_accessible(
GetRenderWidgetHostViewClientAccessible());
// The initial accessible returned should have state STATE_SYSTEM_BUSY while
// the accessibility tree is being requested from the renderer.
VARIANT var_state;
HRESULT hr = document_accessible->
get_accState(CreateI4Variant(CHILDID_SELF), &var_state);
EXPECT_EQ(hr, S_OK);
EXPECT_EQ(V_VT(&var_state), VT_I4);
EXPECT_EQ(V_I4(&var_state), STATE_SYSTEM_BUSY);
// Wait for the initial accessibility tree to load.
ui_test_utils::WaitForNotification(
NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED);
GURL tree_url(
"data:text/html,<html><head><title>Accessibility Win Test</title></head>"
"<body><input type='button' value='push' /><input type='checkbox' />"
"</body></html>");
browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED);
ui_test_utils::WaitForNotification(
NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED);
document_accessible = GetRenderWidgetHostViewClientAccessible();
ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL));
AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON);
AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON);
AccessibleChecker grouping_checker(L"", L"div");
grouping_checker.AppendExpectedChild(&button_checker);
grouping_checker.AppendExpectedChild(&checkbox_checker);
AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT);
document_checker.AppendExpectedChild(&grouping_checker);
// Check the accessible tree of the renderer.
document_checker.CheckAccessible(document_accessible);
// Check that document accessible has a parent accessible.
ScopedComPtr<IDispatch> parent_dispatch;
hr = document_accessible->get_accParent(parent_dispatch.Receive());
EXPECT_EQ(hr, S_OK);
EXPECT_NE(parent_dispatch, reinterpret_cast<IDispatch*>(NULL));
// Navigate to another page.
GURL about_url("about:");
ui_test_utils::NavigateToURL(browser(), about_url);
// Verify that the IAccessible reference still points to a valid object and
// that calls to its methods fail since the tree is no longer valid after
// the page navagation.
// Todo(ctguil): Currently this is giving a false positive because E_FAIL is
// returned when BrowserAccessibilityManager::RequestAccessibilityInfo fails
// since the previous render view host connection is lost. Verify that
// instances are actually marked as invalid once the browse side cache is
// checked in.
CComBSTR name;
hr = document_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name);
ASSERT_EQ(E_FAIL, hr);
}
} // namespace.