// 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 #include #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" #if defined(OS_WIN) // http://crbug.com/48655 #define SKIP_WIN(test) DISABLED_##test #else #define SKIP_WIN(test) test #endif 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 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(V_DISPATCH(var)).Detach(); break; case VT_I4: { CComPtr dispatch; HRESULT hr = parent->get_accChild(CreateI4Variant(V_I4(var)), &dispatch); EXPECT_EQ(hr, S_OK); return CComQIPtr(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(&accessible)); EXPECT_EQ(S_OK, hr); EXPECT_NE(accessible, reinterpret_cast(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 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 child_accessible; child_accessible.Attach(GetAccessibleFromResultVariant(parent, child)); (*child_checker)->CheckAccessible(child_accessible); } } IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, SKIP_WIN(TestRendererAccessibilityTree)) { // By requesting an accessible chrome will believe a screen reader has been // detected. ScopedComPtr 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); GURL tree_url( "data:text/html,Accessibility Win Test" "" ""); 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(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 parent_dispatch; hr = document_accessible->get_accParent(parent_dispatch.Receive()); EXPECT_EQ(hr, S_OK); EXPECT_NE(parent_dispatch, reinterpret_cast(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 it 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.