diff options
author | ctguil@chromium.org <ctguil@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-01 23:28:04 +0000 |
---|---|---|
committer | ctguil@chromium.org <ctguil@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-01 23:28:04 +0000 |
commit | c5c2a674dc1d102b2c1c89ef95e9a95b7fc299c4 (patch) | |
tree | de1fd47dafee134c33b4526c75efd93bd6c7824c /chrome/browser/accessibility | |
parent | 81219f7b519ebd87f8d3e97d23ebf197acfa50da (diff) | |
download | chromium_src-c5c2a674dc1d102b2c1c89ef95e9a95b7fc299c4.zip chromium_src-c5c2a674dc1d102b2c1c89ef95e9a95b7fc299c4.tar.gz chromium_src-c5c2a674dc1d102b2c1c89ef95e9a95b7fc299c4.tar.bz2 |
Make BrowserAccessibilityManager cross platform. Step 1.
1. Move windows browser accessibility source and headers into chrome\browser\accessibility
2. Rename BrowserAccessibilityManager to BrowserAccessibilityManagerWin and BrowserAccessibility to BrowserAccessibilityWin
3. Add base classes that will eventually contain common browser accessibility code.
BUG=55264
TEST=interactive_ui_tests:AccessibilityWinBrowserTest.*
TEST=unit_tests:BrowserAccessibilityTest.*
Review URL: http://codereview.chromium.org/3591003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61253 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/accessibility')
11 files changed, 3861 insertions, 0 deletions
diff --git a/chrome/browser/accessibility/accessibility_win_browsertest.cc b/chrome/browser/accessibility/accessibility_win_browsertest.cc new file mode 100644 index 0000000..8b7559e --- /dev/null +++ b/chrome/browser/accessibility/accessibility_win_browsertest.cc @@ -0,0 +1,651 @@ +// 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/scoped_comptr_win.h" +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/renderer_host/render_view_host.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" +#include "ia2_api_all.h" // Generated + +using std::auto_ptr; +using std::vector; +using std::wstring; + +namespace { + +class AccessibilityWinBrowserTest : public InProcessBrowserTest { + public: + AccessibilityWinBrowserTest() {} + + // InProcessBrowserTest + void SetUpInProcessBrowserTestFixture(); + + protected: + IAccessible* GetRendererAccessible(); + void ExecuteScript(wstring script); +}; + +void AccessibilityWinBrowserTest::SetUpInProcessBrowserTestFixture() { + // If the mouse happens to be on the document then it will have the unexpected + // STATE_SYSTEM_HOTTRACKED state. Move it to a non-document location. + ui_controls::SendMouseMove(0, 0); +} + +class AccessibleChecker { + public: + AccessibleChecker( + wstring expected_name, + int32 expected_role, + wstring expected_value); + AccessibleChecker( + wstring expected_name, + wstring expected_role, + wstring expected_value); + + // 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); + + // Set the expected value for this AccessibleChecker. + void SetExpectedValue(wstring expected_value); + + // Set the expected state for this AccessibleChecker. + void SetExpectedState(LONG expected_state); + + private: + void CheckAccessibleName(IAccessible* accessible); + void CheckAccessibleRole(IAccessible* accessible); + void CheckAccessibleValue(IAccessible* accessible); + void CheckAccessibleState(IAccessible* accessible); + void CheckAccessibleChildren(IAccessible* accessible); + + private: + typedef vector<AccessibleChecker*> AccessibleCheckerVector; + + // Expected accessible name. Checked against IAccessible::get_accName. + wstring name_; + + // Expected accessible role. Checked against IAccessible::get_accRole. + CComVariant role_; + + // Expected accessible value. Checked against IAccessible::get_accValue. + wstring value_; + + // Expected accessible state. Checked against IAccessible::get_accState. + LONG state_; + + // 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_TRUE(SUCCEEDED(hr)); + return CComQIPtr<IAccessible>(dispatch).Detach(); + break; + } + } + + return NULL; +} + +HRESULT QueryIAccessible2(IAccessible* accessible, IAccessible2** accessible2) { + // TODO(ctguil): For some reason querying the IAccessible2 interface from + // IAccessible fails. + ScopedComPtr<IServiceProvider> service_provider; + HRESULT hr = accessible->QueryInterface(service_provider.Receive()); + if (FAILED(hr)) + return hr; + + hr = service_provider->QueryService(IID_IAccessible2, accessible2); + return hr; +} + +// Sets result to true if the child is located in the parent's tree. An +// exhustive search is perform here because we determine equality using +// IAccessible2::get_uniqueID which is only supported by the child node. +void AccessibleContainsAccessible( + IAccessible* parent, IAccessible2* child, bool* result) { + vector<ScopedComPtr<IAccessible>> accessible_list; + accessible_list.push_back(ScopedComPtr<IAccessible>(parent)); + + LONG unique_id; + HRESULT hr = child->get_uniqueID(&unique_id); + ASSERT_EQ(hr, S_OK); + *result = false; + + while (accessible_list.size()) { + ScopedComPtr<IAccessible> accessible = accessible_list.back(); + accessible_list.pop_back(); + + ScopedComPtr<IAccessible2> accessible2; + hr = QueryIAccessible2(accessible, accessible2.Receive()); + if (SUCCEEDED(hr)) { + LONG child_id; + accessible2->get_uniqueID(&child_id); + if (child_id == unique_id) { + *result = true; + break; + } + } + + LONG child_count; + hr = accessible->get_accChildCount(&child_count); + ASSERT_EQ(hr, S_OK); + if (child_count == 0) + continue; + + auto_ptr<VARIANT> child_array(new VARIANT[child_count]); + LONG obtained_count = 0; + hr = AccessibleChildren( + accessible, 0, child_count, child_array.get(), &obtained_count); + ASSERT_EQ(hr, S_OK); + ASSERT_EQ(child_count, obtained_count); + + for (int index = 0; index < obtained_count; index++) { + ScopedComPtr<IAccessible> child_accessible( + GetAccessibleFromResultVariant(accessible, &child_array.get()[index])); + if (child_accessible.get()) + accessible_list.push_back(ScopedComPtr<IAccessible>(child_accessible)); + } + } +} + +// Retrieve the MSAA client accessibility object for the Render Widget Host View +// of the selected tab. +IAccessible* +AccessibilityWinBrowserTest::GetRendererAccessible() { + HWND hwnd_render_widget_host_view = + browser()->GetSelectedTabContents()->GetRenderWidgetHostView()-> + GetNativeView(); + + // By requesting an accessible chrome will believe a screen reader has been + // detected. + 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; +} + +void AccessibilityWinBrowserTest::ExecuteScript(wstring script) { + browser()->GetSelectedTabContents()->render_view_host()-> + ExecuteJavascriptInWebFrame(L"", script); +} + +AccessibleChecker::AccessibleChecker( + wstring expected_name, int32 expected_role, wstring expected_value) : + name_(expected_name), + role_(expected_role), + value_(expected_value), + state_(-1) { +} + +AccessibleChecker::AccessibleChecker( + wstring expected_name, wstring expected_role, wstring expected_value) : + name_(expected_name), + role_(expected_role.c_str()), + value_(expected_value), + state_(-1) { +} + +void AccessibleChecker::AppendExpectedChild( + AccessibleChecker* expected_child) { + children_.push_back(expected_child); +} + +void AccessibleChecker::CheckAccessible(IAccessible* accessible) { + CheckAccessibleName(accessible); + CheckAccessibleRole(accessible); + CheckAccessibleValue(accessible); + CheckAccessibleState(accessible); + CheckAccessibleChildren(accessible); +} + +void AccessibleChecker::SetExpectedValue(wstring expected_value) { + value_ = expected_value; +} + +void AccessibleChecker::SetExpectedState(LONG expected_state) { + state_ = expected_state; +} + +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_STREQ(name_.c_str(), + wstring(name.m_str, SysStringLen(name)).c_str()); + } +} + +void AccessibleChecker::CheckAccessibleRole(IAccessible* accessible) { + VARIANT var_role = {0}; + HRESULT hr = + accessible->get_accRole(CreateI4Variant(CHILDID_SELF), &var_role); + ASSERT_EQ(hr, S_OK); + EXPECT_TRUE(role_ == var_role); +} + +void AccessibleChecker::CheckAccessibleValue(IAccessible* accessible) { + CComBSTR value; + HRESULT hr = + accessible->get_accValue(CreateI4Variant(CHILDID_SELF), &value); + EXPECT_EQ(S_OK, hr); + + // Test that the correct string was returned. + EXPECT_STREQ(value_.c_str(), + wstring(value.m_str, SysStringLen(value)).c_str()); +} + +void AccessibleChecker::CheckAccessibleState(IAccessible* accessible) { + if (state_ < 0) + return; + + VARIANT var_state = {0}; + HRESULT hr = + accessible->get_accState(CreateI4Variant(CHILDID_SELF), &var_state); + EXPECT_EQ(hr, S_OK); + ASSERT_EQ(VT_I4, V_VT(&var_state)); + EXPECT_EQ(state_, V_I4(&var_state)); +} + +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()); + + 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)); + ASSERT_TRUE(child_accessible.get()); + (*child_checker)->CheckAccessible(child_accessible); + } +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestRendererAccessibilityTree) { + // The initial accessible returned should have state STATE_SYSTEM_BUSY while + // the accessibility tree is being requested from the renderer. + AccessibleChecker document1_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + document1_checker.SetExpectedState( + STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED | + STATE_SYSTEM_BUSY); + document1_checker.CheckAccessible(GetRendererAccessible()); + + // Wait for the initial accessibility tree to load. Busy state should clear. + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + document1_checker.SetExpectedState( + STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED); + document1_checker.CheckAccessible(GetRendererAccessible()); + + 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); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON, L"push"); + AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON, L""); + AccessibleChecker body_checker(L"", L"body", L""); + AccessibleChecker document2_checker( + L"Accessibility Win Test", ROLE_SYSTEM_DOCUMENT, L""); + body_checker.AppendExpectedChild(&button_checker); + body_checker.AppendExpectedChild(&checkbox_checker); + document2_checker.AppendExpectedChild(&body_checker); + document2_checker.CheckAccessible(GetRendererAccessible()); + + // Check that document accessible has a parent accessible. + ScopedComPtr<IAccessible> document_accessible(GetRendererAccessible()); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + ScopedComPtr<IDispatch> parent_dispatch; + HRESULT 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. + CComBSTR name; + hr = document_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name); + ASSERT_EQ(E_FAIL, hr); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestNotificationActiveDescendantChanged) { + GURL tree_url("data:text/html,<ul tabindex='-1' role='radiogroup'><li id='li'" + ">li</li></ul>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker list_marker_checker(L"", ROLE_SYSTEM_LISTITEM, L"\x2022"); + AccessibleChecker static_text_checker(L"", ROLE_SYSTEM_TEXT, L"li"); + AccessibleChecker list_item_checker(L"", ROLE_SYSTEM_LISTITEM, L""); + list_item_checker.SetExpectedState( + STATE_SYSTEM_READONLY); + AccessibleChecker radio_group_checker(L"", ROLE_SYSTEM_GROUPING, L""); + radio_group_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY); + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + list_item_checker.AppendExpectedChild(&list_marker_checker); + list_item_checker.AppendExpectedChild(&static_text_checker); + radio_group_checker.AppendExpectedChild(&list_item_checker); + document_checker.AppendExpectedChild(&radio_group_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Set focus to the radio group. + ExecuteScript(L"document.body.children[0].focus()"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + radio_group_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSED); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Set the active descendant of the radio group + ExecuteScript( + L"document.body.children[0].setAttribute('aria-activedescendant', 'li')"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + list_item_checker.SetExpectedState( + STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSED); + radio_group_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestNotificationCheckedStateChanged) { + GURL tree_url("data:text/html,<body><input type='checkbox' /></body>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON, L""); + checkbox_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY); + AccessibleChecker body_checker(L"", L"body", L""); + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + body_checker.AppendExpectedChild(&checkbox_checker); + document_checker.AppendExpectedChild(&body_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Check the checkbox. + ExecuteScript(L"document.body.children[0].checked=true"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + checkbox_checker.SetExpectedState( + STATE_SYSTEM_CHECKED | STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestNotificationChildrenChanged) { + // The role attribute causes the node to be in the accessibility tree. + GURL tree_url( + "data:text/html,<body role=group></body>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker body_checker(L"", L"body", L""); + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + document_checker.AppendExpectedChild(&body_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Change the children of the document body. + ExecuteScript(L"document.body.innerHTML='<b>new text</b>'"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + AccessibleChecker text_checker(L"", ROLE_SYSTEM_TEXT, L"new text"); + body_checker.AppendExpectedChild(&text_checker); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestNotificationChildrenChanged2) { + // The role attribute causes the node to be in the accessibility tree. + GURL tree_url( + "data:text/html,<div role=group style='visibility: hidden'>text" + "</div>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check the accessible tree of the browser. + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Change the children of the document body. + ExecuteScript(L"document.body.children[0].style.visibility='visible'"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + AccessibleChecker static_text_checker(L"", ROLE_SYSTEM_TEXT, L"text"); + AccessibleChecker div_checker(L"", L"div", L""); + document_checker.AppendExpectedChild(&div_checker); + div_checker.AppendExpectedChild(&static_text_checker); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestNotificationFocusChanged) { + // The role attribute causes the node to be in the accessibility tree. + GURL tree_url( + "data:text/html,<div role=group tabindex='-1'></div>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker div_checker(L"", L"div", L""); + div_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_READONLY); + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + document_checker.AppendExpectedChild(&div_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Focus the div in the document + ExecuteScript(L"document.body.children[0].focus()"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + div_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSED); + document_checker.CheckAccessible(GetRendererAccessible()); + + // TODO(ctguil): The renderer should notify the browser when focus is cleared. + // Uncomment code below when fixed. + // Focus the document accessible. This will un-focus the current node. + // http://crbug.com/57045 + ScopedComPtr<IAccessible> document_accessible(GetRendererAccessible()); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + HRESULT hr = document_accessible->accSelect( + SELFLAG_TAKEFOCUS, CreateI4Variant(CHILDID_SELF)); + ASSERT_EQ(hr, S_OK); + // ui_test_utils::WaitForNotification( + // NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + // div_checker.SetExpectedState( + // STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_READONLY); + // document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + TestNotificationValueChanged) { + GURL tree_url("data:text/html,<body><input type='text' value='old value'/>" + "</body>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker static_text_checker(L"", ROLE_SYSTEM_TEXT, L"old value"); + AccessibleChecker text_field_div_checker(L"", L"div", L""); + AccessibleChecker text_field_checker(L"", ROLE_SYSTEM_TEXT, L"old value"); + text_field_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + AccessibleChecker body_checker(L"", L"body", L""); + AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT, L""); + text_field_div_checker.AppendExpectedChild(&static_text_checker); + text_field_checker.AppendExpectedChild(&text_field_div_checker); + body_checker.AppendExpectedChild(&text_field_checker); + document_checker.AppendExpectedChild(&body_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Set the value of the text control + ExecuteScript(L"document.body.children[0].value='new value'"); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Check that the accessibility tree of the browser has been updated. + text_field_checker.SetExpectedValue(L"new value"); + static_text_checker.SetExpectedValue(L"new value"); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +// FAILS crbug.com/54220 +// This test verifies that browser-side cache of the renderer accessibility +// tree is reachable from the browser's tree. Tools that analyze windows +// accessibility trees like AccExplorer32 should be able to drill into the +// cached renderer accessibility tree. +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + DISABLED_ContainsRendererAccessibilityTree) { + GURL tree_url("data:text/html,<body><input type='checkbox' /></body>"); + browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED); + GetRendererAccessible(); + ui_test_utils::WaitForNotification( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED); + + // Get the accessibility object for the browser window. + HWND browser_hwnd = browser()->window()->GetNativeHandle(); + ScopedComPtr<IAccessible> browser_accessible; + HRESULT hr = AccessibleObjectFromWindow( + browser_hwnd, + OBJID_WINDOW, + IID_IAccessible, + reinterpret_cast<void**>(browser_accessible.Receive())); + ASSERT_EQ(S_OK, hr); + + // Get the accessibility object for the renderer client document. + ScopedComPtr<IAccessible> document_accessible(GetRendererAccessible()); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + ScopedComPtr<IAccessible2> document_accessible2; + hr = QueryIAccessible2(document_accessible, document_accessible2.Receive()); + ASSERT_EQ(S_OK, hr); + + // TODO(ctguil): Pointer comparison of retrieved IAccessible pointers dosen't + // seem to work for here. Perhaps make IAccessible2 available in views to make + // unique id comparison available. + bool found = false; + ScopedComPtr<IAccessible> parent = document_accessible; + while (parent.get()) { + ScopedComPtr<IDispatch> parent_dispatch; + hr = parent->get_accParent(parent_dispatch.Receive()); + ASSERT_TRUE(SUCCEEDED(hr)); + if (!parent_dispatch.get()) { + ASSERT_EQ(hr, S_FALSE); + break; + } + + parent.Release(); + hr = parent_dispatch.QueryInterface(parent.Receive()); + ASSERT_EQ(S_OK, hr); + + if (parent.get() == browser_accessible.get()) { + found = true; + break; + } + } + + // If pointer comparison fails resort to the exhuasive search that can use + // IAccessible2::get_uniqueID for equality comparison. + if (!found) { + AccessibleContainsAccessible( + browser_accessible, document_accessible2, &found); + } + + ASSERT_EQ(found, true); +} +} // namespace. diff --git a/chrome/browser/accessibility/browser_accessibility.cc b/chrome/browser/accessibility/browser_accessibility.cc new file mode 100644 index 0000000..fa87e09 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility.cc @@ -0,0 +1,11 @@ +// 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 "chrome/browser/accessibility/browser_accessibility.h" + +BrowserAccessibility::BrowserAccessibility() { +} + +BrowserAccessibility::~BrowserAccessibility() { +} diff --git a/chrome/browser/accessibility/browser_accessibility.h b/chrome/browser/accessibility/browser_accessibility.h new file mode 100644 index 0000000..a7d9719 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ +#define CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ +#pragma once + +#include "base/basictypes.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// BrowserAccessibility +// +// Class implementing the cross platform interface for the Browser-Renderer +// communication of accessibility information, providing accessibility +// to be used by screen readers and other assistive technology (AT). +// +// An implementation for each platform handles platform specific accessibility +// APIs. +// +//////////////////////////////////////////////////////////////////////////////// +class BrowserAccessibility { + public: + // Creates the platform specific BrowserAccessibility. Ownership passes to the + // caller. + virtual ~BrowserAccessibility(); + + protected: + BrowserAccessibility(); + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibility); +}; + +#endif // CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ diff --git a/chrome/browser/accessibility/browser_accessibility_manager.cc b/chrome/browser/accessibility/browser_accessibility_manager.cc new file mode 100644 index 0000000..256c62c --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_manager.cc @@ -0,0 +1,59 @@ +// 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 "chrome/browser/accessibility/browser_accessibility_manager.h" + +#include "chrome/common/render_messages_params.h" + +using webkit_glue::WebAccessibility; + + +BrowserAccessibilityManager::BrowserAccessibilityManager( + gfx::NativeWindow parent_window) + : parent_window_(parent_window) { +} + +BrowserAccessibilityManager::~BrowserAccessibilityManager() { +} + +void BrowserAccessibilityManager::OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { + for (uint32 index = 0; index < params.size(); index++) { + const ViewHostMsg_AccessibilityNotification_Params& param = params[index]; + + switch (param.notification_type) { + case ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_CHECK_STATE_CHANGED: + OnAccessibilityObjectStateChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_CHILDREN_CHANGED: + OnAccessibilityObjectChildrenChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_FOCUS_CHANGED: + OnAccessibilityObjectFocusChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_LOAD_COMPLETE: + OnAccessibilityObjectLoadComplete(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_VALUE_CHANGED: + OnAccessibilityObjectValueChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED: + OnAccessibilityObjectTextChange(param.acc_obj); + break; + default: + DCHECK(0); + break; + } + } +} + +gfx::NativeWindow BrowserAccessibilityManager::GetParentWindow() { + return parent_window_; +} diff --git a/chrome/browser/accessibility/browser_accessibility_manager.h b/chrome/browser/accessibility/browser_accessibility_manager.h new file mode 100644 index 0000000..984623e5 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_manager.h @@ -0,0 +1,78 @@ +// 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. + +#ifndef CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ +#define CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ +#pragma once + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <oleacc.h> +#endif + +#include <vector> + +#include "gfx/native_widget_types.h" +#include "webkit/glue/webaccessibility.h" + +struct ViewHostMsg_AccessibilityNotification_Params; + +using webkit_glue::WebAccessibility; + +// Class that can perform actions on behalf of the BrowserAccessibilityManager. +class BrowserAccessibilityDelegate { + public: + virtual ~BrowserAccessibilityDelegate() {} + virtual void SetAccessibilityFocus(int acc_obj_id) = 0; + virtual void AccessibilityDoDefaultAction(int acc_obj_id) = 0; +}; + +// Manages a tree of BrowserAccessibility objects. +class BrowserAccessibilityManager { + public: + // Creates the platform specific BrowserAccessibilityManager. Ownership passes + // to the caller. + static BrowserAccessibilityManager* Create( + gfx::NativeWindow parent_window, + const webkit_glue::WebAccessibility& src, + BrowserAccessibilityDelegate* delegate); + + virtual ~BrowserAccessibilityManager(); + + // Called when the renderer process has notified us of about tree changes. + // Send a notification to MSAA clients of the change. + void OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params); + + gfx::NativeWindow GetParentWindow(); + +#if defined(OS_WIN) + virtual IAccessible* GetRootAccessible() = 0; +#endif + + protected: + explicit BrowserAccessibilityManager(gfx::NativeWindow parent_window); + + virtual void OnAccessibilityObjectStateChange( + const webkit_glue::WebAccessibility& acc_obj) = 0; + virtual void OnAccessibilityObjectChildrenChange( + const webkit_glue::WebAccessibility& acc_obj) = 0; + virtual void OnAccessibilityObjectFocusChange( + const webkit_glue::WebAccessibility& acc_obj) = 0; + virtual void OnAccessibilityObjectLoadComplete( + const webkit_glue::WebAccessibility& acc_obj) = 0; + virtual void OnAccessibilityObjectValueChange( + const webkit_glue::WebAccessibility& acc_obj) = 0; + virtual void OnAccessibilityObjectTextChange( + const webkit_glue::WebAccessibility& acc_obj) = 0; + + private: + // The parent window. + gfx::NativeWindow parent_window_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManager); +}; + +#endif // CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ diff --git a/chrome/browser/accessibility/browser_accessibility_manager_win.cc b/chrome/browser/accessibility/browser_accessibility_manager_win.cc new file mode 100644 index 0000000..e470f24 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_manager_win.cc @@ -0,0 +1,292 @@ +// 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 "chrome/browser/accessibility/browser_accessibility_manager_win.h" + +#include "chrome/browser/accessibility/browser_accessibility_win.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" + +using webkit_glue::WebAccessibility; + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + gfx::NativeWindow parent_window, + const webkit_glue::WebAccessibility& src, + BrowserAccessibilityDelegate* delegate) { + return new BrowserAccessibilityManagerWin( + parent_window, src, delegate, new BrowserAccessibilityWinFactory()); +} + +// Factory method to create an instance of BrowserAccessibility +BrowserAccessibilityWin* BrowserAccessibilityWinFactory::Create() { + CComObject<BrowserAccessibilityWin>* instance; + HRESULT hr = CComObject<BrowserAccessibilityWin>::CreateInstance(&instance); + DCHECK(SUCCEEDED(hr)); + return instance->NewReference(); +} + +// static +// Start child IDs at -1 and decrement each time, because clients use +// child IDs of 1, 2, 3, ... to access the children of an object by +// index, so we use negative IDs to clearly distinguish between indices +// and unique IDs. +LONG BrowserAccessibilityManagerWin::next_child_id_ = -1; + +BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin( + HWND parent_window, + const webkit_glue::WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityWinFactory* factory) + : BrowserAccessibilityManager(parent_window), + delegate_(delegate), + factory_(factory), + focus_(NULL) { + HRESULT hr = ::CreateStdAccessibleObject( + parent_window, OBJID_WINDOW, IID_IAccessible, + reinterpret_cast<void **>(&window_iaccessible_)); + DCHECK(SUCCEEDED(hr)); + root_ = CreateAccessibilityTree(NULL, GetNextChildID(), src, 0); + if (!focus_) + focus_ = root_; +} + +BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() { + // Clients could still hold references to some nodes of the tree, so + // calling Inactivate will make sure that as many nodes as possible are + // released now, and remaining nodes are marked as inactive so that + // calls to any methods on them will return E_FAIL; + root_->InactivateTree(); + root_->Release(); +} + +BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetRoot() { + return root_; +} + +void BrowserAccessibilityManagerWin::Remove(LONG child_id) { + child_id_map_.erase(child_id); +} + +BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromChildID( + LONG child_id) { + base::hash_map<LONG, BrowserAccessibilityWin*>::iterator iter = + child_id_map_.find(child_id); + if (iter != child_id_map_.end()) { + return iter->second; + } else { + return NULL; + } +} + +IAccessible* BrowserAccessibilityManagerWin::GetParentWindowIAccessible() { + return window_iaccessible_; +} + +BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFocus( + BrowserAccessibilityWin* root) { + if (focus_ && (!root || focus_->IsDescendantOf(root))) + return focus_; + + return NULL; +} + +void BrowserAccessibilityManagerWin::SetFocus( + const BrowserAccessibilityWin& node) { + if (delegate_) + delegate_->SetAccessibilityFocus(node.renderer_id()); +} + +void BrowserAccessibilityManagerWin::DoDefaultAction( + const BrowserAccessibilityWin& node) { + if (delegate_) + delegate_->AccessibilityDoDefaultAction(node.renderer_id()); +} + +bool BrowserAccessibilityManagerWin::CanModifyTreeInPlace( + BrowserAccessibilityWin* current_root, + const webkit_glue::WebAccessibility& new_root) { + if (current_root->renderer_id() != new_root.id) + return false; + if (current_root->GetChildCount() != new_root.children.size()) + return false; + for (unsigned int i = 0; i < current_root->GetChildCount(); i++) { + if (!CanModifyTreeInPlace(current_root->GetChild(i), + new_root.children[i])) { + return false; + } + } + return true; +} + +void BrowserAccessibilityManagerWin::ModifyTreeInPlace( + BrowserAccessibilityWin* current_root, + const webkit_glue::WebAccessibility& new_root) { + DCHECK_EQ(current_root->renderer_id(), new_root.id); + DCHECK_EQ(current_root->GetChildCount(), new_root.children.size()); + for (unsigned int i = 0; i < current_root->GetChildCount(); i++) + ModifyTreeInPlace(current_root->GetChild(i), new_root.children[i]); + current_root->Initialize( + this, + current_root->GetParent(), + current_root->child_id(), + current_root->index_in_parent(), + new_root); +} + + +BrowserAccessibilityWin* BrowserAccessibilityManagerWin::UpdateTree( + const webkit_glue::WebAccessibility& acc_obj) { + base::hash_map<int, LONG>::iterator iter = + renderer_id_to_child_id_map_.find(acc_obj.id); + if (iter == renderer_id_to_child_id_map_.end()) + return NULL; + + LONG child_id = iter->second; + BrowserAccessibilityWin* old_browser_acc = GetFromChildID(child_id); + if (!old_browser_acc) + return NULL; + + if (CanModifyTreeInPlace(old_browser_acc, acc_obj)) { + ModifyTreeInPlace(old_browser_acc, acc_obj); + return old_browser_acc; + } + + BrowserAccessibilityWin* new_browser_acc = CreateAccessibilityTree( + old_browser_acc->GetParent(), + child_id, + acc_obj, + old_browser_acc->index_in_parent()); + + if (old_browser_acc->GetParent()) { + old_browser_acc->GetParent()->ReplaceChild( + old_browser_acc, + new_browser_acc); + } else { + DCHECK_EQ(old_browser_acc, root_); + root_ = new_browser_acc; + } + old_browser_acc->InactivateTree(); + old_browser_acc->Release(); + child_id_map_[child_id] = new_browser_acc; + + return new_browser_acc; +} + +IAccessible* BrowserAccessibilityManagerWin::GetRootAccessible() { + return root_; +} + +void BrowserAccessibilityManagerWin::OnAccessibilityObjectStateChange( + const webkit_glue::WebAccessibility& acc_obj) { + BrowserAccessibilityWin* new_browser_acc = UpdateTree(acc_obj); + if (!new_browser_acc) + return; + + LONG child_id = new_browser_acc->child_id(); + NotifyWinEvent( + EVENT_OBJECT_STATECHANGE, GetParentWindow(), OBJID_CLIENT, child_id); +} + +void BrowserAccessibilityManagerWin::OnAccessibilityObjectChildrenChange( + const webkit_glue::WebAccessibility& acc_obj) { + BrowserAccessibilityWin* new_browser_acc = UpdateTree(acc_obj); + if (!new_browser_acc) + return; + + LONG child_id; + if (root_ != new_browser_acc) { + child_id = new_browser_acc->GetParent()->child_id(); + } else { + child_id = CHILDID_SELF; + } + + NotifyWinEvent( + EVENT_OBJECT_REORDER, GetParentWindow(), OBJID_CLIENT, child_id); +} + +void BrowserAccessibilityManagerWin::OnAccessibilityObjectFocusChange( + const webkit_glue::WebAccessibility& acc_obj) { + BrowserAccessibilityWin* new_browser_acc = UpdateTree(acc_obj); + if (!new_browser_acc) + return; + + focus_ = new_browser_acc; + LONG child_id = new_browser_acc->child_id(); + NotifyWinEvent(EVENT_OBJECT_FOCUS, GetParentWindow(), OBJID_CLIENT, child_id); +} + +void BrowserAccessibilityManagerWin::OnAccessibilityObjectLoadComplete( + const webkit_glue::WebAccessibility& acc_obj) { + root_->InactivateTree(); + root_->Release(); + focus_ = NULL; + + root_ = CreateAccessibilityTree(NULL, GetNextChildID(), acc_obj, 0); + if (!focus_) + focus_ = root_; + + LONG root_id = root_->child_id(); + NotifyWinEvent(EVENT_OBJECT_FOCUS, GetParentWindow(), OBJID_CLIENT, root_id); + NotifyWinEvent( + IA2_EVENT_DOCUMENT_LOAD_COMPLETE, GetParentWindow(), OBJID_CLIENT, root_id); +} + +void BrowserAccessibilityManagerWin::OnAccessibilityObjectValueChange( + const webkit_glue::WebAccessibility& acc_obj) { + BrowserAccessibilityWin* new_browser_acc = UpdateTree(acc_obj); + if (!new_browser_acc) + return; + + LONG child_id = new_browser_acc->child_id(); + NotifyWinEvent( + EVENT_OBJECT_VALUECHANGE, GetParentWindow(), OBJID_CLIENT, child_id); +} + +void BrowserAccessibilityManagerWin::OnAccessibilityObjectTextChange( + const webkit_glue::WebAccessibility& acc_obj) { + BrowserAccessibilityWin* new_browser_acc = UpdateTree(acc_obj); + if (!new_browser_acc) + return; + + LONG child_id = new_browser_acc->child_id(); + NotifyWinEvent( + IA2_EVENT_TEXT_CARET_MOVED, GetParentWindow(), OBJID_CLIENT, child_id); +} + +LONG BrowserAccessibilityManagerWin::GetNextChildID() { + // Get the next child ID, and wrap around when we get near the end + // of a 32-bit integer range. It's okay to wrap around; we just want + // to avoid it as long as possible because clients may cache the ID of + // an object for a while to determine if they've seen it before. + next_child_id_--; + if (next_child_id_ == -2000000000) + next_child_id_ = -1; + + return next_child_id_; +} + +BrowserAccessibilityWin* +BrowserAccessibilityManagerWin::CreateAccessibilityTree( + BrowserAccessibilityWin* parent, + int child_id, + const webkit_glue::WebAccessibility& src, + int index_in_parent) { + BrowserAccessibilityWin* instance = factory_->Create(); + + instance->Initialize(this, parent, child_id, index_in_parent, src); + child_id_map_[child_id] = instance; + renderer_id_to_child_id_map_[src.id] = child_id; + if ((src.state >> WebAccessibility::STATE_FOCUSED) & 1) + focus_ = instance; + for (int i = 0; i < static_cast<int>(src.children.size()); ++i) { + BrowserAccessibilityWin* child = CreateAccessibilityTree( + instance, GetNextChildID(), src.children[i], i); + instance->AddChild(child); + } + + return instance; +} diff --git a/chrome/browser/accessibility/browser_accessibility_manager_win.h b/chrome/browser/accessibility/browser_accessibility_manager_win.h new file mode 100644 index 0000000..4bdca66 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_manager_win.h @@ -0,0 +1,145 @@ +// 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. + +#ifndef CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ +#define CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ +#pragma once + +#include <atlbase.h> +#include <atlcom.h> +#include <oleacc.h> + +#include <vector> + +#include "chrome/browser/accessibility/browser_accessibility_manager.h" +#include "base/hash_tables.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_ptr.h" +#include "webkit/glue/webaccessibility.h" + +class BrowserAccessibilityWin; +struct ViewHostMsg_AccessibilityNotification_Params; + +class BrowserAccessibilityWinFactory { + public: + virtual ~BrowserAccessibilityWinFactory() {} + + // Create an instance of BrowserAccessibilityWin and return a new + // reference to it. + virtual BrowserAccessibilityWin* Create(); +}; + +// Manages a tree of BrowserAccessibilityWin objects. +class BrowserAccessibilityManagerWin : public BrowserAccessibilityManager { + public: + BrowserAccessibilityManagerWin( + HWND parent_window, + const webkit_glue::WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityWinFactory* factory = + new BrowserAccessibilityWinFactory()); + + virtual ~BrowserAccessibilityManagerWin(); + + // Return a pointer to the root of the tree, does not make a new reference. + BrowserAccessibilityWin* GetRoot(); + + // Removes the BrowserAccessibilityWin child_id from the manager. + void Remove(LONG child_id); + + // Return a pointer to the object corresponding to the given child_id, + // does not make a new reference. + BrowserAccessibilityWin* GetFromChildID(LONG child_id); + + // Get a the default IAccessible for the parent window, does not make a + // new reference. + IAccessible* GetParentWindowIAccessible(); + + // Return the object that has focus, if it's a descandant of the + // given root (inclusive). Does not make a new reference. + BrowserAccessibilityWin* GetFocus(BrowserAccessibilityWin* root); + + // Tell the renderer to set focus to this node. + void SetFocus(const BrowserAccessibilityWin& node); + + // Tell the renderer to do the default action for this node. + void DoDefaultAction(const BrowserAccessibilityWin& node); + + // BrowserAccessibilityManager Methods + virtual IAccessible* GetRootAccessible(); + virtual void OnAccessibilityObjectStateChange( + const webkit_glue::WebAccessibility& acc_obj); + virtual void OnAccessibilityObjectChildrenChange( + const webkit_glue::WebAccessibility& acc_obj); + virtual void OnAccessibilityObjectFocusChange( + const webkit_glue::WebAccessibility& acc_obj); + virtual void OnAccessibilityObjectLoadComplete( + const webkit_glue::WebAccessibility& acc_obj); + virtual void OnAccessibilityObjectValueChange( + const webkit_glue::WebAccessibility& acc_obj); + virtual void OnAccessibilityObjectTextChange( + const webkit_glue::WebAccessibility& acc_obj); + + private: + // Recursively compare the IDs of our subtree to a new subtree received + // from the renderer and return true if their IDs match exactly. + bool CanModifyTreeInPlace( + BrowserAccessibilityWin* current_root, + const webkit_glue::WebAccessibility& new_root); + + // Recursively modify a subtree (by reinitializing) to match a new + // subtree received from the renderer process. Should only be called + // if CanModifyTreeInPlace returned true. + void ModifyTreeInPlace( + BrowserAccessibilityWin* current_root, + const webkit_glue::WebAccessibility& new_root); + + // Update the accessibility tree with an updated WebAccessibility tree or + // subtree received from the renderer process. First attempts to modify + // the tree in place, and if that fails, replaces the entire subtree. + // Returns the updated node or NULL if no node was updated. + BrowserAccessibilityWin* UpdateTree( + const webkit_glue::WebAccessibility& acc_obj); + + // Returns the next MSAA child id. + static LONG GetNextChildID(); + + // Recursively build a tree of BrowserAccessibilityWin objects from + // the WebAccessibility tree received from the renderer process. + BrowserAccessibilityWin* CreateAccessibilityTree( + BrowserAccessibilityWin* parent, + int child_id, + const webkit_glue::WebAccessibility& src, + int index_in_parent); + + // The object that can perform actions on our behalf. + BrowserAccessibilityDelegate* delegate_; + + // Factory to create BrowserAccessibility objects (for dependency injection). + scoped_ptr<BrowserAccessibilityWinFactory> factory_; + + // A default IAccessible instance for the parent window. + ScopedComPtr<IAccessible> window_iaccessible_; + + // The root of the tree of IAccessible objects and the element that + // currently has focus, if any. + BrowserAccessibilityWin* root_; + BrowserAccessibilityWin* focus_; + + // A mapping from the IDs of objects in the renderer, to the child IDs + // we use internally here. + base::hash_map<int, LONG> renderer_id_to_child_id_map_; + + // A mapping from child IDs to BrowserAccessibilityWin objects. + base::hash_map<LONG, BrowserAccessibilityWin*> child_id_map_; + + // The next child ID to use; static so that they're global to the process. + // Screen readers cache these IDs to see if they've seen the same object + // before so we should avoid reusing them within the same project. + static LONG next_child_id_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerWin); +}; + +#endif // CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ diff --git a/chrome/browser/accessibility/browser_accessibility_win.cc b/chrome/browser/accessibility/browser_accessibility_win.cc new file mode 100644 index 0000000..f3520ee --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_win.cc @@ -0,0 +1,1491 @@ +// 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 "chrome/browser/accessibility/browser_accessibility_win.h" + +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/accessibility/browser_accessibility_manager_win.h" +#include "net/base/escape.h" + +using webkit_glue::WebAccessibility; + +BrowserAccessibilityWin::BrowserAccessibilityWin() + : manager_(NULL), + parent_(NULL), + child_id_(-1), + index_in_parent_(-1), + renderer_id_(-1), + instance_active_(false) { +} + +BrowserAccessibilityWin::~BrowserAccessibilityWin() { + InactivateTree(); +} + +void BrowserAccessibilityWin::Initialize( + BrowserAccessibilityManagerWin* manager, + BrowserAccessibilityWin* parent, + LONG child_id, + LONG index_in_parent, + const webkit_glue::WebAccessibility& src) { + manager_ = manager; + parent_ = parent; + child_id_ = child_id; + index_in_parent_ = index_in_parent; + + renderer_id_ = src.id; + name_ = src.name; + value_ = src.value; + attributes_ = src.attributes; + html_attributes_ = src.html_attributes; + location_ = src.location; + src_role_ = src.role; + InitRoleAndState(src.role, src.state); + + // Expose headings levels to NVDA with the "level" object attribute. + if (src.role == WebAccessibility::ROLE_HEADING && role_name_.size() == 2 && + IsAsciiDigit(role_name_[1])) { + html_attributes_.push_back(std::make_pair(L"level", role_name_.substr(1))); + } + + // If this object doesn't have a name but it does have a description, + // use the description as its name - because some screen readers only + // announce the name. + if (name_.empty() && HasAttribute(WebAccessibility::ATTR_DESCRIPTION)) { + GetAttribute(WebAccessibility::ATTR_DESCRIPTION, &name_); + } + + instance_active_ = true; +} + +void BrowserAccessibilityWin::AddChild(BrowserAccessibilityWin* child) { + children_.push_back(child); +} + +void BrowserAccessibilityWin::InactivateTree() { + if (!instance_active_) + return; + + // Mark this object as inactive, so calls to all COM methods will return + // failure. + instance_active_ = false; + + // Now we can safely call InactivateTree on our children and remove + // references to them, so that as much of the tree as possible will be + // destroyed now - however, nodes that still have references to them + // might stick around a while until all clients have released them. + for (std::vector<BrowserAccessibilityWin*>::iterator iter = + children_.begin(); + iter != children_.end(); ++iter) { + (*iter)->InactivateTree(); + (*iter)->Release(); + } + children_.clear(); + manager_->Remove(child_id_); +} + +bool BrowserAccessibilityWin::IsDescendantOf( + BrowserAccessibilityWin* ancestor) { + if (this == ancestor) { + return true; + } else if (parent_) { + return parent_->IsDescendantOf(ancestor); + } + + return false; +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetParent() { + return parent_; +} + +uint32 BrowserAccessibilityWin::GetChildCount() { + return children_.size(); +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetChild(uint32 child_index) { + DCHECK(child_index >= 0 && child_index < children_.size()); + return children_[child_index]; +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetPreviousSibling() { + if (parent_ && index_in_parent_ > 0) + return parent_->children_[index_in_parent_ - 1]; + + return NULL; +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetNextSibling() { + if (parent_ && + index_in_parent_ >= 0 && + index_in_parent_ < static_cast<int>(parent_->children_.size() - 1)) { + return parent_->children_[index_in_parent_ + 1]; + } + + return NULL; +} + +void BrowserAccessibilityWin::ReplaceChild( + const BrowserAccessibilityWin* old_acc, BrowserAccessibilityWin* new_acc) { + DCHECK_EQ(children_[old_acc->index_in_parent_], old_acc); + + old_acc = children_[old_acc->index_in_parent_]; + children_[old_acc->index_in_parent_] = new_acc; +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::NewReference() { + AddRef(); + return this; +} + +// +// IAccessible methods. +// +// Conventions: +// * Always test for instance_active_ first and return E_FAIL if it's false. +// * Always check for invalid arguments first, even if they're unused. +// * Return S_FALSE if the only output is a string argument and it's empty. +// + +HRESULT BrowserAccessibilityWin::accDoDefaultAction(VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + manager_->DoDefaultAction(*target); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accHitTest(LONG x_left, LONG y_top, + VARIANT* child) { + if (!instance_active_) + return E_FAIL; + + if (!child) + return E_INVALIDARG; + + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::accLocation(LONG* x_left, LONG* y_top, + LONG* width, LONG* height, + VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + if (!x_left || !y_top || !width || !height) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + // Find the top left corner of the containing window in screen coords, and + // adjust the output position by this amount. + HWND parent_hwnd = manager_->GetParentWindow(); + POINT top_left = {0, 0}; + ::ClientToScreen(parent_hwnd, &top_left); + + *x_left = target->location_.x + top_left.x; + *y_top = target->location_.y + top_left.y; + *width = target->location_.width; + *height = target->location_.height; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accNavigate( + LONG nav_dir, VARIANT start, VARIANT* end) { + BrowserAccessibilityWin* target = GetTargetFromChildID(start); + if (!target) + return E_INVALIDARG; + + if ((nav_dir == NAVDIR_LASTCHILD || nav_dir == NAVDIR_FIRSTCHILD) && + start.lVal != CHILDID_SELF) { + // MSAA states that navigating to first/last child can only be from self. + return E_INVALIDARG; + } + + BrowserAccessibilityWin* result = NULL; + switch (nav_dir) { + case NAVDIR_DOWN: + case NAVDIR_UP: + case NAVDIR_LEFT: + case NAVDIR_RIGHT: + // These directions are not implemented, matching Mozilla and IE. + return E_NOTIMPL; + case NAVDIR_FIRSTCHILD: + if (target->children_.size() > 0) + result = target->children_[0]; + break; + case NAVDIR_LASTCHILD: + if (target->children_.size() > 0) + result = target->children_[target->children_.size() - 1]; + break; + case NAVDIR_NEXT: + result = target->GetNextSibling(); + break; + case NAVDIR_PREVIOUS: + result = target->GetPreviousSibling(); + break; + } + + if (!result) { + end->vt = VT_EMPTY; + return S_FALSE; + } + + end->vt = VT_DISPATCH; + end->pdispVal = result->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accChild(VARIANT var_child, + IDispatch** disp_child) { + if (!instance_active_) + return E_FAIL; + + if (!disp_child) + return E_INVALIDARG; + + *disp_child = NULL; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_child); + if (!target) + return E_INVALIDARG; + + (*disp_child) = target->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accChildCount(LONG* child_count) { + if (!instance_active_) + return E_FAIL; + + if (!child_count) + return E_INVALIDARG; + + *child_count = children_.size(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accDefaultAction(VARIANT var_id, + BSTR* def_action) { + if (!instance_active_) + return E_FAIL; + + if (!def_action) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr( + WebAccessibility::ATTR_SHORTCUT, def_action); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accDescription(VARIANT var_id, + BSTR* desc) { + if (!instance_active_) + return E_FAIL; + + if (!desc) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr(WebAccessibility::ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accFocus(VARIANT* focus_child) { + if (!instance_active_) + return E_FAIL; + + if (!focus_child) + return E_INVALIDARG; + + BrowserAccessibilityWin* focus = static_cast<BrowserAccessibilityWin*>( + manager_->GetFocus(this)); + if (focus == this) { + focus_child->vt = VT_I4; + focus_child->lVal = CHILDID_SELF; + } else if (focus == NULL) { + focus_child->vt = VT_EMPTY; + } else { + focus_child->vt = VT_DISPATCH; + focus_child->pdispVal = focus->NewReference(); + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accHelp(VARIANT var_id, BSTR* help) { + if (!instance_active_) + return E_FAIL; + + if (!help) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr(WebAccessibility::ATTR_HELP, help); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accKeyboardShortcut(VARIANT var_id, + BSTR* acc_key) { + if (!instance_active_) + return E_FAIL; + + if (!acc_key) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr(WebAccessibility::ATTR_SHORTCUT, acc_key); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accName(VARIANT var_id, BSTR* name) { + if (!instance_active_) + return E_FAIL; + + if (!name) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + if (target->name_.empty()) + return S_FALSE; + + *name = SysAllocString(target->name_.c_str()); + + DCHECK(*name); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accParent(IDispatch** disp_parent) { + if (!instance_active_) + return E_FAIL; + + if (!disp_parent) + return E_INVALIDARG; + + IAccessible* parent = parent_; + if (parent == NULL) { + // This happens if we're the root of the tree; + // return the IAccessible for the window. + parent = manager_->GetParentWindowIAccessible(); + } + + parent->AddRef(); + *disp_parent = parent; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accRole( + VARIANT var_id, VARIANT* role) { + if (!instance_active_) + return E_FAIL; + + if (!role) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + if (!target->role_name_.empty()) { + role->vt = VT_BSTR; + role->bstrVal = SysAllocString(target->role_name_.c_str()); + } else { + role->vt = VT_I4; + role->lVal = target->role_; + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accState(VARIANT var_id, + VARIANT* state) { + if (!instance_active_) + return E_FAIL; + + if (!state) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + state->vt = VT_I4; + state->lVal = target->state_; + if (manager_->GetFocus(NULL) == this) + state->lVal |= STATE_SYSTEM_FOCUSED; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accValue( + VARIANT var_id, BSTR* value) { + if (!instance_active_) + return E_FAIL; + + if (!value) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + *value = SysAllocString(target->value_.c_str()); + + DCHECK(*value); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accHelpTopic( + BSTR* help_file, VARIANT var_id, LONG* topic_id) { + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accSelection(VARIANT* selected) { + if (!instance_active_) + return E_FAIL; + + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::accSelect( + LONG flags_sel, VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + if (flags_sel & SELFLAG_TAKEFOCUS) { + manager_->SetFocus(*this); + return S_OK; + } + + return S_FALSE; +} + +// +// IAccessible2 methods. +// + +STDMETHODIMP BrowserAccessibilityWin::role(LONG* role) { + if (!instance_active_) + return E_FAIL; + + if (!role) + return E_INVALIDARG; + + *role = ia2_role_; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributes(BSTR* attributes) { + if (!instance_active_) + return E_FAIL; + + if (!attributes) + return E_INVALIDARG; + + // Follow Firefox's convention, which is to return a set of key-value pairs + // separated by semicolons, with a colon between the key and the value. + string16 str; + for (unsigned int i = 0; i < html_attributes_.size(); i++) { + if (i != 0) + str += L';'; + str += Escape(html_attributes_[i].first); + str += L':'; + str += Escape(html_attributes_[i].second); + } + + if (str.empty()) + return S_FALSE; + + *attributes = SysAllocString(str.c_str()); + DCHECK(*attributes); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_states(AccessibleStates* states) { + if (!instance_active_) + return E_FAIL; + + if (!states) + return E_INVALIDARG; + + *states = ia2_state_; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_uniqueID(LONG* unique_id) { + if (!instance_active_) + return E_FAIL; + + if (!unique_id) + return E_INVALIDARG; + + *unique_id = child_id_; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_windowHandle(HWND* window_handle) { + if (!instance_active_) + return E_FAIL; + + if (!window_handle) + return E_INVALIDARG; + + *window_handle = manager_->GetParentWindow(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_indexInParent(LONG* index_in_parent) { + if (!instance_active_) + return E_FAIL; + + if (!index_in_parent) + return E_INVALIDARG; + + *index_in_parent = index_in_parent_; + return S_OK; +} + +// +// IAccessibleImage methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_description(BSTR* desc) { + if (!instance_active_) + return E_FAIL; + + if (!desc) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP BrowserAccessibilityWin::get_imagePosition( + enum IA2CoordinateType coordinate_type, LONG* x, LONG* y) { + if (!instance_active_) + return E_FAIL; + + if (!x || !y) + return E_INVALIDARG; + + if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) { + HWND parent_hwnd = manager_->GetParentWindow(); + POINT top_left = {0, 0}; + ::ClientToScreen(parent_hwnd, &top_left); + *x = location_.x + top_left.x; + *y = location_.y + top_left.y; + } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) { + *x = location_.x; + *y = location_.y; + if (parent_) { + *x -= parent_->location_.x; + *y -= parent_->location_.y; + } + } else { + return E_INVALIDARG; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_imageSize(LONG* height, LONG* width) { + if (!instance_active_) + return E_FAIL; + + if (!height || !width) + return E_INVALIDARG; + + *height = location_.height; + *width = location_.width; + return S_OK; +} + +// +// IAccessibleText methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nCharacters(LONG* n_characters) { + if (!instance_active_) + return E_FAIL; + + if (!n_characters) + return E_INVALIDARG; + + if (src_role_ == WebAccessibility::ROLE_TEXT_FIELD) { + *n_characters = value_.length(); + } else { + *n_characters = name_.length(); + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_text( + LONG start_offset, LONG end_offset, BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!text) + return E_INVALIDARG; + + string16 text_str; + if (src_role_ == WebAccessibility::ROLE_TEXT_FIELD) { + text_str = value_; + } else { + text_str = name_; + } + + // The spec allows the arguments to be reversed. + if (start_offset > end_offset) { + LONG tmp = start_offset; + start_offset = end_offset; + end_offset = tmp; + } + + // The spec does not allow the start or end offsets to be out or range; + // we must return an error if so. + LONG len = text_str.length(); + if (start_offset < 0) + return E_INVALIDARG; + if (end_offset > len) + return E_INVALIDARG; + + string16 substr = text_str.substr(start_offset, end_offset - start_offset); + if (substr.empty()) + return S_FALSE; + + *text = SysAllocString(substr.c_str()); + DCHECK(*text); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_caretOffset(LONG* offset) { + if (!instance_active_) + return E_FAIL; + + if (!offset) + return E_INVALIDARG; + + if (src_role_ == WebAccessibility::ROLE_TEXT_FIELD) { + int sel_start = 0; + if (GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_START, &sel_start)) { + *offset = sel_start; + } else { + *offset = 0; + } + } else { + *offset = 0; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelections(LONG* n_selections) { + if (!instance_active_) + return E_FAIL; + + if (!n_selections) + return E_INVALIDARG; + + if (src_role_ == WebAccessibility::ROLE_TEXT_FIELD) { + int sel_start = 0; + int sel_end = 0; + if (GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_START, &sel_start) && + GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_END, &sel_end) && + sel_start != sel_end) { + *n_selections = 1; + } else { + *n_selections = 0; + } + } else { + *n_selections = 0; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || selection_index != 0) + return E_INVALIDARG; + + if (src_role_ == WebAccessibility::ROLE_TEXT_FIELD) { + int sel_start = 0; + int sel_end = 0; + if (GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_START, &sel_start) && + GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_END, &sel_end)) { + *start_offset = sel_start; + *end_offset = sel_end; + } else { + *start_offset = 0; + *end_offset = 0; + } + } else { + *start_offset = 0; + *end_offset = 0; + } + + return S_OK; +} + +// +// ISimpleDOMDocument methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_URL(BSTR* url) { + if (!instance_active_) + return E_FAIL; + + if (!url) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_URL, url); +} + +STDMETHODIMP BrowserAccessibilityWin::get_title(BSTR* title) { + if (!instance_active_) + return E_FAIL; + + if (!title) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_TITLE, title); +} + +STDMETHODIMP BrowserAccessibilityWin::get_mimeType(BSTR* mime_type) { + if (!instance_active_) + return E_FAIL; + + if (!mime_type) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_MIMETYPE, mime_type); +} + +STDMETHODIMP BrowserAccessibilityWin::get_docType(BSTR* doc_type) { + if (!instance_active_) + return E_FAIL; + + if (!doc_type) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_DOCTYPE, doc_type); +} + +// +// ISimpleDOMNode methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nodeInfo( + BSTR* node_name, + short* name_space_id, + BSTR* node_value, + unsigned int* num_children, + unsigned int* unique_id, + unsigned short* node_type) { + if (!instance_active_) + return E_FAIL; + + if (!node_name || !name_space_id || !node_value || !num_children || + !unique_id || !node_type) { + return E_INVALIDARG; + } + + string16 tag; + if (GetAttribute(WebAccessibility::ATTR_HTML_TAG, &tag)) + *node_name = SysAllocString(tag.c_str()); + else + *node_name = NULL; + + *name_space_id = 0; + *node_value = SysAllocString(value_.c_str()); + *num_children = children_.size(); + *unique_id = child_id_; + + if (role_ == ROLE_SYSTEM_DOCUMENT) { + *node_type = NODETYPE_DOCUMENT; + } else if (role_ == ROLE_SYSTEM_TEXT && + ((ia2_state_ & IA2_STATE_EDITABLE) == 0)) { + *node_type = NODETYPE_TEXT; + } else { + *node_type = NODETYPE_ELEMENT; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributes( + unsigned short max_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values, + unsigned short* num_attribs) { + if (!instance_active_) + return E_FAIL; + + if (!attrib_names || !name_space_id || !attrib_values || !num_attribs) + return E_INVALIDARG; + + *num_attribs = max_attribs; + if (*num_attribs > html_attributes_.size()) + *num_attribs = html_attributes_.size(); + + for (unsigned short i = 0; i < *num_attribs; ++i) { + attrib_names[i] = SysAllocString(html_attributes_[i].first.c_str()); + name_space_id[i] = 0; + attrib_values[i] = SysAllocString(html_attributes_[i].second.c_str()); + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributesForNames( + unsigned short num_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values) { + if (!instance_active_) + return E_FAIL; + + if (!attrib_names || !name_space_id || !attrib_values) + return E_INVALIDARG; + + for (unsigned short i = 0; i < num_attribs; ++i) { + name_space_id[i] = 0; + bool found = false; + string16 name = (LPCWSTR)attrib_names[i]; + for (unsigned int j = 0; j < html_attributes_.size(); ++j) { + if (html_attributes_[j].first == name) { + attrib_values[i] = SysAllocString(html_attributes_[j].second.c_str()); + found = true; + break; + } + } + if (!found) { + attrib_values[i] = NULL; + } + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_computedStyle( + unsigned short max_style_properties, + boolean use_alternate_view, + BSTR *style_properties, + BSTR *style_values, + unsigned short *num_style_properties) { + if (!instance_active_) + return E_FAIL; + + if (!style_properties || !style_values) + return E_INVALIDARG; + + // We only cache a single style property for now: DISPLAY + + if (max_style_properties == 0 || + !HasAttribute(WebAccessibility::ATTR_DISPLAY)) { + *num_style_properties = 0; + return S_OK; + } + + string16 display; + GetAttribute(WebAccessibility::ATTR_DISPLAY, &display); + *num_style_properties = 1; + style_properties[0] = SysAllocString(L"display"); + style_values[0] = SysAllocString(display.c_str()); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_computedStyleForProperties( + unsigned short num_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values) { + if (!instance_active_) + return E_FAIL; + + if (!style_properties || !style_values) + return E_INVALIDARG; + + // We only cache a single style property for now: DISPLAY + + for (unsigned short i = 0; i < num_style_properties; i++) { + string16 name = (LPCWSTR)style_properties[i]; + StringToLowerASCII(&name); + if (name == L"display") { + string16 display; + GetAttribute(WebAccessibility::ATTR_DISPLAY, &display); + style_values[i] = SysAllocString(display.c_str()); + } else { + style_values[i] = NULL; + } + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::scrollTo(boolean placeTopLeft) { + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_parentNode(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + *node = parent_->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_firstChild(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (children_.size()) { + *node = children_[0]->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_lastChild(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (children_.size()) { + *node = children_[children_.size() - 1]->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_previousSibling( + ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (parent_ && index_in_parent_ > 0) { + *node = parent_->children_[index_in_parent_ - 1]->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_nextSibling(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (parent_ && + index_in_parent_ >= 0 && + index_in_parent_ < static_cast<int>(parent_->children_.size()) - 1) { + *node = parent_->children_[index_in_parent_ + 1]->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_childAt( + unsigned int child_index, + ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (child_index < children_.size()) { + *node = children_[child_index]->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +// +// ISimpleDOMText methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_domText(BSTR* dom_text) { + if (!instance_active_) + return E_FAIL; + + if (!dom_text) + return E_INVALIDARG; + + if (name_.empty()) + return S_FALSE; + + *dom_text = SysAllocString(name_.c_str()); + DCHECK(*dom_text); + return S_OK; +} + +// +// IServiceProvider methods. +// + +STDMETHODIMP BrowserAccessibilityWin::QueryService( + REFGUID guidService, REFIID riid, void** object) { + if (!instance_active_) + return E_FAIL; + + if (guidService == IID_IAccessible || + guidService == IID_IAccessible2 || + guidService == IID_IAccessibleImage || + guidService == IID_IAccessibleText || + guidService == IID_ISimpleDOMDocument || + guidService == IID_ISimpleDOMNode || + guidService == IID_ISimpleDOMText) { + return QueryInterface(riid, object); + } + + *object = NULL; + return E_FAIL; +} + +// +// CComObjectRootEx methods. +// + +HRESULT WINAPI BrowserAccessibilityWin::InternalQueryInterface( + void* this_ptr, + const _ATL_INTMAP_ENTRY* entries, + REFIID iid, + void** object) { + if (iid == IID_IAccessibleText) { + if (role_ != ROLE_SYSTEM_LINK && role_ != ROLE_SYSTEM_TEXT) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_IAccessibleImage) { + if (role_ != ROLE_SYSTEM_GRAPHIC) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_ISimpleDOMDocument) { + if (role_ != ROLE_SYSTEM_DOCUMENT) { + *object = NULL; + return E_NOINTERFACE; + } + } + + return CComObjectRootBase::InternalQueryInterface( + this_ptr, entries, iid, object); +} + +// +// Private methods. +// + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetTargetFromChildID( + const VARIANT& var_id) { + if (var_id.vt != VT_I4) + return NULL; + + LONG child_id = var_id.lVal; + if (child_id == CHILDID_SELF) + return this; + + if (child_id >= 1 && child_id <= static_cast<LONG>(children_.size())) + return children_[child_id - 1]; + + return manager_->GetFromChildID(child_id); +} + +bool BrowserAccessibilityWin::HasAttribute( + WebAccessibility::Attribute attribute) { + return (attributes_.find(attribute) != attributes_.end()); +} + +bool BrowserAccessibilityWin::GetAttribute( + WebAccessibility::Attribute attribute, string16* value) { + std::map<int32, string16>::iterator iter = attributes_.find(attribute); + if (iter != attributes_.end()) { + *value = iter->second; + return true; + } + + return false; +} + +HRESULT BrowserAccessibilityWin::GetAttributeAsBstr( + WebAccessibility::Attribute attribute, BSTR* value_bstr) { + string16 str; + + if (!GetAttribute(attribute, &str)) + return S_FALSE; + + if (str.empty()) + return S_FALSE; + + *value_bstr = SysAllocString(str.c_str()); + DCHECK(*value_bstr); + + return S_OK; +} + +bool BrowserAccessibilityWin::GetAttributeAsInt( + WebAccessibility::Attribute attribute, int* value_int) { + string16 value_str; + + if (!GetAttribute(attribute, &value_str)) + return false; + + if (!base::StringToInt(value_str, value_int)) + return false; + + return true; +} + +string16 BrowserAccessibilityWin::Escape(string16 str) { + return EscapeQueryParamValueUTF8(str, false); +} + +void BrowserAccessibilityWin::InitRoleAndState(LONG web_role, + LONG web_state) { + state_ = 0; + ia2_state_ = IA2_STATE_OPAQUE; + + if ((web_state >> WebAccessibility::STATE_CHECKED) & 1) + state_ |= STATE_SYSTEM_CHECKED; + if ((web_state >> WebAccessibility::STATE_COLLAPSED) & 1) + state_ |= STATE_SYSTEM_COLLAPSED; + if ((web_state >> WebAccessibility::STATE_EXPANDED) & 1) + state_ |= STATE_SYSTEM_EXPANDED; + if ((web_state >> WebAccessibility::STATE_FOCUSABLE) & 1) + state_ |= STATE_SYSTEM_FOCUSABLE; + if ((web_state >> WebAccessibility::STATE_HASPOPUP) & 1) + state_ |= STATE_SYSTEM_HASPOPUP; + if ((web_state >> WebAccessibility::STATE_HOTTRACKED) & 1) + state_ |= STATE_SYSTEM_HOTTRACKED; + if ((web_state >> WebAccessibility::STATE_INDETERMINATE) & 1) + state_ |= STATE_SYSTEM_INDETERMINATE; + if ((web_state >> WebAccessibility::STATE_INVISIBLE) & 1) + state_ |= STATE_SYSTEM_INVISIBLE; + if ((web_state >> WebAccessibility::STATE_LINKED) & 1) + state_ |= STATE_SYSTEM_LINKED; + if ((web_state >> WebAccessibility::STATE_MULTISELECTABLE) & 1) + state_ |= STATE_SYSTEM_MULTISELECTABLE; + // TODO(ctguil): Support STATE_SYSTEM_EXTSELECTABLE/accSelect. + if ((web_state >> WebAccessibility::STATE_OFFSCREEN) & 1) + state_ |= STATE_SYSTEM_OFFSCREEN; + if ((web_state >> WebAccessibility::STATE_PRESSED) & 1) + state_ |= STATE_SYSTEM_PRESSED; + if ((web_state >> WebAccessibility::STATE_PROTECTED) & 1) + state_ |= STATE_SYSTEM_PROTECTED; + if ((web_state >> WebAccessibility::STATE_SELECTABLE) & 1) + state_ |= STATE_SYSTEM_SELECTABLE; + if ((web_state >> WebAccessibility::STATE_SELECTED) & 1) + state_ |= STATE_SYSTEM_SELECTED; + if ((web_state >> WebAccessibility::STATE_READONLY) & 1) + state_ |= STATE_SYSTEM_READONLY; + if ((web_state >> WebAccessibility::STATE_TRAVERSED) & 1) + state_ |= STATE_SYSTEM_TRAVERSED; + if ((web_state >> WebAccessibility::STATE_BUSY) & 1) + state_ |= STATE_SYSTEM_BUSY; + if ((web_state >> WebAccessibility::STATE_UNAVAILABLE) & 1) + state_ |= STATE_SYSTEM_UNAVAILABLE; + + role_ = 0; + ia2_role_ = 0; + switch (web_role) { + case WebAccessibility::ROLE_ALERT: + case WebAccessibility::ROLE_ALERT_DIALOG: + role_ = ROLE_SYSTEM_ALERT; + break; + case WebAccessibility::ROLE_APPLICATION: + role_ = ROLE_SYSTEM_APPLICATION; + break; + case WebAccessibility::ROLE_ARTICLE: + role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_BUTTON: + role_ = ROLE_SYSTEM_PUSHBUTTON; + break; + case WebAccessibility::ROLE_CELL: + role_ = ROLE_SYSTEM_CELL; + break; + case WebAccessibility::ROLE_CHECKBOX: + role_ = ROLE_SYSTEM_CHECKBUTTON; + break; + case WebAccessibility::ROLE_COLOR_WELL: + role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_COLOR_CHOOSER; + break; + case WebAccessibility::ROLE_COLUMN: + role_ = ROLE_SYSTEM_COLUMN; + break; + case WebAccessibility::ROLE_COLUMN_HEADER: + role_ = ROLE_SYSTEM_COLUMNHEADER; + break; + case WebAccessibility::ROLE_COMBO_BOX: + role_ = ROLE_SYSTEM_COMBOBOX; + break; + case WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION: + GetAttribute(WebAccessibility::ATTR_HTML_TAG, &role_name_); + ia2_role_ = IA2_ROLE_PARAGRAPH; + break; + case WebAccessibility::ROLE_DEFINITION_LIST_TERM: + role_ = ROLE_SYSTEM_LISTITEM; + break; + case WebAccessibility::ROLE_DIALOG: + role_ = ROLE_SYSTEM_DIALOG; + break; + case WebAccessibility::ROLE_DOCUMENT: + case WebAccessibility::ROLE_WEB_AREA: + role_ = ROLE_SYSTEM_DOCUMENT; + state_ |= STATE_SYSTEM_READONLY; + state_ |= STATE_SYSTEM_FOCUSABLE; + break; + case WebAccessibility::ROLE_EDITABLE_TEXT: + role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_SINGLE_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + break; + case WebAccessibility::ROLE_GRID: + role_ = ROLE_SYSTEM_TABLE; + break; + case WebAccessibility::ROLE_GROUP: + GetAttribute(WebAccessibility::ATTR_HTML_TAG, &role_name_); + if (role_name_.empty()) + role_name_ = L"div"; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_HEADING: + GetAttribute(WebAccessibility::ATTR_HTML_TAG, &role_name_); + ia2_role_ = IA2_ROLE_HEADING; + break; + case WebAccessibility::ROLE_IMAGE: + role_ = ROLE_SYSTEM_GRAPHIC; + break; + case WebAccessibility::ROLE_IMAGE_MAP: + GetAttribute(WebAccessibility::ATTR_HTML_TAG, &role_name_); + ia2_role_ = IA2_ROLE_IMAGE_MAP; + break; + case WebAccessibility::ROLE_IMAGE_MAP_LINK: + role_ = ROLE_SYSTEM_LINK; + state_ |= STATE_SYSTEM_LINKED; + break; + case WebAccessibility::ROLE_LANDMARK_APPLICATION: + case WebAccessibility::ROLE_LANDMARK_BANNER: + case WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY: + case WebAccessibility::ROLE_LANDMARK_CONTENTINFO: + case WebAccessibility::ROLE_LANDMARK_MAIN: + case WebAccessibility::ROLE_LANDMARK_NAVIGATION: + case WebAccessibility::ROLE_LANDMARK_SEARCH: + role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_LINK: + case WebAccessibility::ROLE_WEBCORE_LINK: + role_ = ROLE_SYSTEM_LINK; + state_ |= STATE_SYSTEM_LINKED; + break; + case WebAccessibility::ROLE_LIST: + role_ = ROLE_SYSTEM_LIST; + break; + case WebAccessibility::ROLE_LISTBOX: + role_ = ROLE_SYSTEM_LIST; + break; + case WebAccessibility::ROLE_LISTBOX_OPTION: + case WebAccessibility::ROLE_LIST_ITEM: + case WebAccessibility::ROLE_LIST_MARKER: + role_ = ROLE_SYSTEM_LISTITEM; + break; + case WebAccessibility::ROLE_MENU: + case WebAccessibility::ROLE_MENU_BUTTON: + role_ = ROLE_SYSTEM_MENUPOPUP; + break; + case WebAccessibility::ROLE_MENU_BAR: + role_ = ROLE_SYSTEM_MENUBAR; + break; + case WebAccessibility::ROLE_MENU_ITEM: + case WebAccessibility::ROLE_MENU_LIST_OPTION: + role_ = ROLE_SYSTEM_MENUITEM; + break; + case WebAccessibility::ROLE_MENU_LIST_POPUP: + role_ = ROLE_SYSTEM_MENUPOPUP; + break; + case WebAccessibility::ROLE_NOTE: + role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_NOTE; + break; + case WebAccessibility::ROLE_OUTLINE: + role_ = ROLE_SYSTEM_OUTLINE; + break; + case WebAccessibility::ROLE_POPUP_BUTTON: + role_ = ROLE_SYSTEM_COMBOBOX; + break; + case WebAccessibility::ROLE_PROGRESS_INDICATOR: + role_ = ROLE_SYSTEM_PROGRESSBAR; + break; + case WebAccessibility::ROLE_RADIO_BUTTON: + role_ = ROLE_SYSTEM_RADIOBUTTON; + break; + case WebAccessibility::ROLE_RADIO_GROUP: + role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_REGION: + role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_ROW: + role_ = ROLE_SYSTEM_ROW; + break; + case WebAccessibility::ROLE_ROW_HEADER: + role_ = ROLE_SYSTEM_ROWHEADER; + break; + case WebAccessibility::ROLE_RULER: + role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_RULER; + break; + case WebAccessibility::ROLE_SCROLLAREA: + role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_SCROLL_PANE; + break; + case WebAccessibility::ROLE_SCROLLBAR: + role_ = ROLE_SYSTEM_SCROLLBAR; + break; + case WebAccessibility::ROLE_SLIDER: + role_ = ROLE_SYSTEM_SLIDER; + break; + case WebAccessibility::ROLE_SPLIT_GROUP: + role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_SPLIT_PANE; + break; + case WebAccessibility::ROLE_ANNOTATION: + case WebAccessibility::ROLE_STATIC_TEXT: + role_ = ROLE_SYSTEM_TEXT; + break; + case WebAccessibility::ROLE_STATUS: + role_ = ROLE_SYSTEM_STATUSBAR; + break; + case WebAccessibility::ROLE_TAB: + role_ = ROLE_SYSTEM_PAGETAB; + break; + case WebAccessibility::ROLE_TABLE: + role_ = ROLE_SYSTEM_TABLE; + break; + case WebAccessibility::ROLE_TABLE_HEADER_CONTAINER: + role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_TAB_GROUP: + case WebAccessibility::ROLE_TAB_LIST: + case WebAccessibility::ROLE_TAB_PANEL: + role_ = ROLE_SYSTEM_PAGETABLIST; + break; + case WebAccessibility::ROLE_TEXTAREA: + role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_MULTI_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + break; + case WebAccessibility::ROLE_TEXT_FIELD: + role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_SINGLE_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + break; + case WebAccessibility::ROLE_TOOLBAR: + role_ = ROLE_SYSTEM_TOOLBAR; + break; + case WebAccessibility::ROLE_TOOLTIP: + role_ = ROLE_SYSTEM_TOOLTIP; + break; + case WebAccessibility::ROLE_TREE: + role_ = ROLE_SYSTEM_OUTLINE; + break; + case WebAccessibility::ROLE_TREE_GRID: + role_ = ROLE_SYSTEM_OUTLINE; + break; + case WebAccessibility::ROLE_TREE_ITEM: + role_ = ROLE_SYSTEM_OUTLINEITEM; + break; + case WebAccessibility::ROLE_WINDOW: + role_ = ROLE_SYSTEM_WINDOW; + break; + + // TODO(dmazzoni): figure out the proper MSAA role for all of these. + case WebAccessibility::ROLE_BROWSER: + case WebAccessibility::ROLE_BUSY_INDICATOR: + case WebAccessibility::ROLE_DIRECTORY: + case WebAccessibility::ROLE_DISCLOSURE_TRIANGLE: + case WebAccessibility::ROLE_DRAWER: + case WebAccessibility::ROLE_GROW_AREA: + case WebAccessibility::ROLE_HELP_TAG: + case WebAccessibility::ROLE_IGNORED: + case WebAccessibility::ROLE_INCREMENTOR: + case WebAccessibility::ROLE_LOG: + case WebAccessibility::ROLE_MARQUEE: + case WebAccessibility::ROLE_MATH: + case WebAccessibility::ROLE_MATTE: + case WebAccessibility::ROLE_RULER_MARKER: + case WebAccessibility::ROLE_SHEET: + case WebAccessibility::ROLE_SLIDER_THUMB: + case WebAccessibility::ROLE_SPLITTER: + case WebAccessibility::ROLE_SYSTEM_WIDE: + case WebAccessibility::ROLE_TIMER: + case WebAccessibility::ROLE_VALUE_INDICATOR: + default: + role_ = ROLE_SYSTEM_CLIENT; + break; + } + + // The role should always be set. + DCHECK(!role_name_.empty() || role_); + + // If we didn't explicitly set the IAccessible2 role, make it the same + // as the MSAA role. + if (!ia2_role_) + ia2_role_ = role_; +} diff --git a/chrome/browser/accessibility/browser_accessibility_win.h b/chrome/browser/accessibility/browser_accessibility_win.h new file mode 100644 index 0000000..26522b1 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_win.h @@ -0,0 +1,567 @@ +// 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. + +#ifndef CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ +#define CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ +#pragma once + +#include <atlbase.h> +#include <atlcom.h> +#include <oleacc.h> + +#include <vector> + +#include "chrome/browser/accessibility/browser_accessibility.h" +#include "ia2_api_all.h" // Generated +#include "ISimpleDOMDocument.h" // Generated +#include "ISimpleDOMNode.h" // Generated +#include "ISimpleDOMText.h" // Generated +#include "webkit/glue/webaccessibility.h" + +class BrowserAccessibilityManagerWin; + +using webkit_glue::WebAccessibility; + +//////////////////////////////////////////////////////////////////////////////// +// +// BrowserAccessibilityWin +// +// Class implementing the windows accessible interface for the Browser-Renderer +// communication of accessibility information, providing accessibility +// to be used by screen readers and other assistive technology (AT). +// +//////////////////////////////////////////////////////////////////////////////// +class BrowserAccessibilityWin + : public BrowserAccessibility, + public CComObjectRootEx<CComMultiThreadModel>, + public IDispatchImpl<IAccessible2, &IID_IAccessible2, + &LIBID_IAccessible2Lib>, + public IAccessibleImage, + public IAccessibleText, + public IServiceProvider, + public ISimpleDOMDocument, + public ISimpleDOMNode, + public ISimpleDOMText { + public: + BEGIN_COM_MAP(BrowserAccessibilityWin) + COM_INTERFACE_ENTRY2(IDispatch, IAccessible2) + COM_INTERFACE_ENTRY2(IAccessible, IAccessible2) + COM_INTERFACE_ENTRY(IAccessible2) + COM_INTERFACE_ENTRY(IAccessibleImage) + COM_INTERFACE_ENTRY(IAccessibleText) + COM_INTERFACE_ENTRY(IServiceProvider) + COM_INTERFACE_ENTRY(ISimpleDOMDocument) + COM_INTERFACE_ENTRY(ISimpleDOMNode) + COM_INTERFACE_ENTRY(ISimpleDOMText) + END_COM_MAP() + + BrowserAccessibilityWin(); + + virtual ~BrowserAccessibilityWin(); + + // Initialize this object and mark it as active. + void Initialize(BrowserAccessibilityManagerWin* manager, + BrowserAccessibilityWin* parent, + LONG child_id, + LONG index_in_parent, + const webkit_glue::WebAccessibility& src); + + // Add a child of this object. + void AddChild(BrowserAccessibilityWin* child); + + // Mark this object as inactive, and remove references to all children. + // When no other clients hold any references to this object it will be + // deleted, and in the meantime, calls to any methods will return E_FAIL. + void InactivateTree(); + + // Return true if this object is equal to or a descendant of |ancestor|. + bool IsDescendantOf(BrowserAccessibilityWin* ancestor); + + // Returns the parent of this object, or NULL if it's the + // BrowserAccessibilityWin root. + BrowserAccessibilityWin* GetParent(); + + // Returns the number of children of this BrowserAccessibilityWin object. + uint32 GetChildCount(); + + // Return a pointer to the child with the given index. + BrowserAccessibilityWin* GetChild(uint32 child_index); + + // Return the previous sibling of this object, or NULL if it's the first + // child of its parent. + BrowserAccessibilityWin* GetPreviousSibling(); + + // Return the next sibling of this object, or NULL if it's the last child + // of its parent. + BrowserAccessibilityWin* GetNextSibling(); + + // Replace a child BrowserAccessibilityWin object. Used when updating the + // accessibility tree. + void ReplaceChild( + const BrowserAccessibilityWin* old_acc, BrowserAccessibilityWin* new_acc); + + // Accessors + LONG child_id() const { return child_id_; } + int32 renderer_id() const { return renderer_id_; } + LONG index_in_parent() const { return index_in_parent_; } + + // Add one to the reference count and return the same object. Always + // use this method when returning a BrowserAccessibilityWin object as + // an output parameter to a COM interface, never use it otherwise. + BrowserAccessibilityWin* NewReference(); + + // + // IAccessible methods. + // + + // Performs the default action on a given object. + STDMETHODIMP accDoDefaultAction(VARIANT var_id); + + // Retrieves the child element or child object at a given point on the screen. + STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child); + + // Retrieves the specified object's current screen location. + STDMETHODIMP accLocation(LONG* x_left, + LONG* y_top, + LONG* width, + LONG* height, + VARIANT var_id); + + // Traverses to another UI element and retrieves the object. + STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end); + + // Retrieves an IDispatch interface pointer for the specified child. + STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child); + + // Retrieves the number of accessible children. + STDMETHODIMP get_accChildCount(LONG* child_count); + + // Retrieves a string that describes the object's default action. + STDMETHODIMP get_accDefaultAction(VARIANT var_id, BSTR* default_action); + + // Retrieves the object's description. + STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc); + + // Retrieves the object that has the keyboard focus. + STDMETHODIMP get_accFocus(VARIANT* focus_child); + + // Retrieves the help information associated with the object. + STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help); + + // Retrieves the specified object's shortcut. + STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, BSTR* access_key); + + // Retrieves the name of the specified object. + STDMETHODIMP get_accName(VARIANT var_id, BSTR* name); + + // Retrieves the IDispatch interface of the object's parent. + STDMETHODIMP get_accParent(IDispatch** disp_parent); + + // Retrieves information describing the role of the specified object. + STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role); + + // Retrieves the current state of the specified object. + STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state); + + // Returns the value associated with the object. + STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value); + + // Make an object take focus or extend the selection. + STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id); + + STDMETHODIMP get_accHelpTopic(BSTR* help_file, + VARIANT var_id, + LONG* topic_id); + + STDMETHODIMP get_accSelection(VARIANT* selected); + + // Deprecated methods, not implemented. + STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) { + return E_NOTIMPL; + } + STDMETHODIMP put_accValue(VARIANT var_id, BSTR put_val) { + return E_NOTIMPL; + } + + // + // IAccessible2 methods. + // + + // Returns role from a longer list of possible roles. + STDMETHODIMP role(LONG* role); + + // Returns the state bitmask from a larger set of possible states. + STDMETHODIMP get_states(AccessibleStates* states); + + // Returns the attributes specific to this IAccessible2 object, + // such as a cell's formula. + STDMETHODIMP get_attributes(BSTR* attributes); + + // Get the unique ID of this object so that the client knows if it's + // been encountered previously. + STDMETHODIMP get_uniqueID(LONG* unique_id); + + // Get the window handle of the enclosing window. + STDMETHODIMP get_windowHandle(HWND* window_handle); + + // Get this object's index in its parent object. + STDMETHODIMP get_indexInParent(LONG* index_in_parent); + + // IAccessible2 methods not implemented. + STDMETHODIMP get_extendedRole(BSTR* extended_role) { + return E_NOTIMPL; + } + STDMETHODIMP get_nRelations(LONG* n_relations) { + return E_NOTIMPL; + } + STDMETHODIMP get_relation(LONG relation_index, + IAccessibleRelation** relation) { + return E_NOTIMPL; + } + STDMETHODIMP get_relations(LONG max_relations, + IAccessibleRelation** relations, + LONG* n_relations) { + return E_NOTIMPL; + } + STDMETHODIMP scrollTo(enum IA2ScrollType scroll_type) { + return E_NOTIMPL; + } + STDMETHODIMP scrollToPoint(enum IA2CoordinateType coordinate_type, + LONG x, + LONG y) { + return E_NOTIMPL; + } + STDMETHODIMP get_groupPosition(LONG* group_level, + LONG* similar_items_in_group, + LONG* position_in_group) { + return E_NOTIMPL; + } + STDMETHODIMP get_localizedExtendedRole(BSTR* localized_extended_role) { + return E_NOTIMPL; + } + STDMETHODIMP get_nExtendedStates(LONG* n_extended_states) { + return E_NOTIMPL; + } + STDMETHODIMP get_extendedStates(LONG max_extended_states, + BSTR** extended_states, + LONG* n_extended_states) { + return E_NOTIMPL; + } + STDMETHODIMP get_localizedExtendedStates(LONG max_localized_extended_states, + BSTR** localized_extended_states, + LONG* n_localized_extended_states) { + return E_NOTIMPL; + } + STDMETHODIMP get_locale(IA2Locale* locale) { + return E_NOTIMPL; + } + + // + // IAccessibleImage methods. + // + + STDMETHODIMP get_description(BSTR* description); + + STDMETHODIMP get_imagePosition(enum IA2CoordinateType coordinate_type, + LONG* x, LONG* y); + + STDMETHODIMP get_imageSize(LONG* height, LONG* width); + + // + // IAccessibleText methods. + // + + STDMETHODIMP get_nCharacters(LONG* n_characters); + + STDMETHODIMP get_text(LONG start_offset, LONG end_offset, BSTR* text); + + STDMETHODIMP get_caretOffset(LONG* offset); + + STDMETHODIMP get_nSelections(LONG* n_selections); + + STDMETHODIMP get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset); + + + // IAccessibleText methods not implemented. + STDMETHODIMP addSelection(LONG start_offset, LONG end_offset) { + return E_NOTIMPL; + } + STDMETHODIMP get_attributes(LONG offset, LONG* start_offset, LONG* end_offset, + BSTR* text_attributes) { + return E_NOTIMPL; + } + STDMETHODIMP get_characterExtents(LONG offset, + enum IA2CoordinateType coord_type, + LONG* x, LONG* y, + LONG* width, LONG* height) { + return E_NOTIMPL; + } + STDMETHODIMP get_offsetAtPoint(LONG x, LONG y, + enum IA2CoordinateType coord_type, + LONG* offset) { + return E_NOTIMPL; + } + STDMETHODIMP get_textBeforeOffset(LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + return E_NOTIMPL; + } + STDMETHODIMP get_textAfterOffset(LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + return E_NOTIMPL; + } + STDMETHODIMP get_textAtOffset(LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + return E_NOTIMPL; + } + STDMETHODIMP removeSelection(LONG selection_index) { + return E_NOTIMPL; + } + STDMETHODIMP setCaretOffset(LONG offset) { + return E_NOTIMPL; + } + STDMETHODIMP setSelection(LONG selection_index, + LONG start_offset, + LONG end_offset) { + return E_NOTIMPL; + } + STDMETHODIMP scrollSubstringTo(LONG start_index, + LONG end_index, + enum IA2ScrollType scroll_type) { + return E_NOTIMPL; + } + STDMETHODIMP scrollSubstringToPoint(LONG start_index, LONG end_index, + enum IA2CoordinateType coordinate_type, + LONG x, LONG y) { + return E_NOTIMPL; + } + STDMETHODIMP get_newText(IA2TextSegment* new_text) { + return E_NOTIMPL; + } + STDMETHODIMP get_oldText(IA2TextSegment* old_text) { + return E_NOTIMPL; + } + + // + // ISimpleDOMDocument methods. + // + + STDMETHODIMP get_URL(BSTR* url); + + STDMETHODIMP get_title(BSTR* title); + + STDMETHODIMP get_mimeType(BSTR* mime_type); + + STDMETHODIMP get_docType(BSTR* doc_type); + + STDMETHODIMP get_nameSpaceURIForID( + short name_space_id, BSTR *name_space_uri) { + return E_NOTIMPL; + } + STDMETHODIMP put_alternateViewMediaTypes( + BSTR *comma_separated_media_types) { + return E_NOTIMPL; + } + + // + // ISimpleDOMNode methods. + // + + STDMETHODIMP get_nodeInfo( + BSTR* node_name, + short* name_space_id, + BSTR* node_value, + unsigned int* num_children, + unsigned int* unique_id, + unsigned short* node_type); + + STDMETHODIMP get_attributes( + unsigned short max_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values, + unsigned short* num_attribs); + + STDMETHODIMP get_attributesForNames( + unsigned short num_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values); + + STDMETHODIMP get_computedStyle( + unsigned short max_style_properties, + boolean use_alternate_view, + BSTR *style_properties, + BSTR *style_values, + unsigned short *num_style_properties); + + STDMETHODIMP get_computedStyleForProperties( + unsigned short num_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values); + + STDMETHODIMP scrollTo(boolean placeTopLeft); + + STDMETHODIMP get_parentNode(ISimpleDOMNode** node); + + STDMETHODIMP get_firstChild(ISimpleDOMNode** node); + + STDMETHODIMP get_lastChild(ISimpleDOMNode** node); + + STDMETHODIMP get_previousSibling(ISimpleDOMNode** node); + + STDMETHODIMP get_nextSibling(ISimpleDOMNode** node); + + STDMETHODIMP get_childAt( + unsigned int child_index, + ISimpleDOMNode** node); + + STDMETHODIMP get_innerHTML(BSTR* innerHTML) { + return E_NOTIMPL; + } + + STDMETHODIMP get_localInterface(void** local_interface) { + return E_NOTIMPL; + } + + STDMETHODIMP get_language(BSTR* language) { + return E_NOTIMPL; + } + + // + // ISimpleDOMText methods. + // + + STDMETHODIMP get_domText(BSTR* dom_text); + + STDMETHODIMP get_clippedSubstringBounds( + unsigned int start_index, + unsigned int end_index, + int* x, + int* y, + int* width, + int* height) { + return E_NOTIMPL; + } + + STDMETHODIMP get_unclippedSubstringBounds( + unsigned int start_index, + unsigned int end_index, + int* x, + int* y, + int* width, + int* height) { + return E_NOTIMPL; + } + + STDMETHODIMP scrollToSubstring( + unsigned int start_index, + unsigned int end_index) { + return E_NOTIMPL; + } + + STDMETHODIMP get_fontFamily(BSTR *font_family) { + return E_NOTIMPL; + } + + // + // IServiceProvider methods. + // + + STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void** object); + + // + // CComObjectRootEx methods. + // + + HRESULT WINAPI InternalQueryInterface(void* this_ptr, + const _ATL_INTMAP_ENTRY* entries, + REFIID iid, + void** object); + + private: + // Many MSAA methods take a var_id parameter indicating that the operation + // should be performed on a particular child ID, rather than this object. + // This method tries to figure out the target object from |var_id| and + // returns a pointer to the target object if it exists, otherwise NULL. + // Does not return a new reference. + BrowserAccessibilityWin* GetTargetFromChildID(const VARIANT& var_id); + + // Initialize the role and state metadata from the role enum and state + // bitmasks defined in webkit/glue/webaccessibility.h. + void InitRoleAndState(LONG web_accessibility_role, + LONG web_accessibility_state); + + // Return true if this attribute is in the attributes map. + bool HasAttribute(WebAccessibility::Attribute attribute); + + // Retrieve the string value of an attribute from the attribute map and + // returns true if found. + bool GetAttribute(WebAccessibility::Attribute attribute, string16* value); + + // Retrieve the string value of an attribute from the attribute map and + // if found and nonempty, allocate a new BSTR (with SysAllocString) + // and return S_OK. If not found or empty, return S_FALSE. + HRESULT GetAttributeAsBstr( + WebAccessibility::Attribute attribute, BSTR* value_bstr); + + // Retrieve the value of an attribute from the attribute map and + // if found and nonempty, try to convert it to an integer. + // Returns true only if both the attribute was found and it was successfully + // converted to an integer. + bool GetAttributeAsInt( + WebAccessibility::Attribute attribute, int* value_int); + + // Escape a string like it would be escaped for a URL or HTML form. + string16 Escape(string16 str); + + // The manager of this tree of accessibility objects; needed for + // global operations like focus tracking. + BrowserAccessibilityManagerWin* manager_; + // The parent of this object, may be NULL if we're the root object. + BrowserAccessibilityWin* parent_; + // The ID of this object; globally unique within the browser process. + LONG child_id_; + // The index of this within its parent object. + LONG index_in_parent_; + // The ID of this object in the renderer process. + int32 renderer_id_; + + // The children of this object. + std::vector<BrowserAccessibilityWin*> children_; + + // Accessibility metadata from the renderer, used to respond to MSAA + // events. + string16 name_; + string16 value_; + std::map<int32, string16> attributes_; + std::vector<std::pair<string16, string16> > html_attributes_; + + int src_role_; + LONG role_; + LONG state_; + string16 role_name_; + LONG ia2_role_; + LONG ia2_state_; + WebKit::WebRect location_; + + // COM objects are reference-counted. When we're done with this object + // and it's removed from our accessibility tree, a client may still be + // holding onto a pointer to this object, so we mark it as inactive + // so that calls to any of this object's methods immediately return + // failure. + bool instance_active_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityWin); +}; + +#endif // CHROME_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ diff --git a/chrome/browser/accessibility/browser_accessibility_win_unittest.cc b/chrome/browser/accessibility/browser_accessibility_win_unittest.cc new file mode 100644 index 0000000..abcb388 --- /dev/null +++ b/chrome/browser/accessibility/browser_accessibility_win_unittest.cc @@ -0,0 +1,266 @@ +// 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 "base/scoped_ptr.h" +#include "base/scoped_comptr_win.h" +#include "chrome/browser/accessibility/browser_accessibility_manager_win.h" +#include "chrome/browser/accessibility/browser_accessibility_win.h" +#include "chrome/common/render_messages_params.h" +#include "testing/gtest/include/gtest/gtest.h" + +using webkit_glue::WebAccessibility; + +// Subclass of BrowserAccessibility that counts the number of instances. +class CountedBrowserAccessibilityWin : public BrowserAccessibilityWin { + public: + CountedBrowserAccessibilityWin() { global_obj_count_++; } + virtual ~CountedBrowserAccessibilityWin() { global_obj_count_--; } + static int global_obj_count_; +}; + +int CountedBrowserAccessibilityWin::global_obj_count_ = 0; + +// Factory that creates a CountedBrowserAccessibilityWin. +class CountedBrowserAccessibilityWinFactory + : public BrowserAccessibilityWinFactory { + public: + virtual ~CountedBrowserAccessibilityWinFactory() {} + virtual BrowserAccessibilityWin* Create() { + CComObject<CountedBrowserAccessibilityWin>* instance; + HRESULT hr = CComObject<CountedBrowserAccessibilityWin>::CreateInstance( + &instance); + DCHECK(SUCCEEDED(hr)); + return instance->NewReference(); + } +}; + +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() { + // ATL needs a pointer to a COM module. + static CComModule module; + _pAtlModule = &module; + + // Make sure COM is initialized for this thread; it's safe to call twice. + ::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. + CountedBrowserAccessibilityWin::global_obj_count_ = 0; + // TODO: Use BrowserAccessibilityManager::Create instead of new below. + BrowserAccessibilityManager* manager = + new BrowserAccessibilityManagerWin( + GetDesktopWindow(), root, NULL, + new CountedBrowserAccessibilityWinFactory()); + ASSERT_EQ(3, CountedBrowserAccessibilityWin::global_obj_count_); + + // Delete the manager and test that all 3 instances are deleted. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibilityWin::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 = new BrowserAccessibilityManagerWin( + GetDesktopWindow(), root, NULL, + new CountedBrowserAccessibilityWinFactory()); + ASSERT_EQ(3, CountedBrowserAccessibilityWin::global_obj_count_); + IAccessible* root_accessible = manager->GetRootAccessible(); + 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, CountedBrowserAccessibilityWin::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, CountedBrowserAccessibilityWin::global_obj_count_); + child1_iaccessible->Release(); + ASSERT_EQ(0, CountedBrowserAccessibilityWin::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.value = 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. + CountedBrowserAccessibilityWin::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + new BrowserAccessibilityManagerWin( + GetDesktopWindow(), root, NULL, + new CountedBrowserAccessibilityWinFactory()); + + // Query for the text IAccessible and verify that it returns "old text" as its + // value. + ScopedComPtr<IDispatch> text_dispatch; + HRESULT hr = manager->GetRootAccessible()->get_accChild( + CreateI4Variant(1), text_dispatch.Receive()); + ASSERT_EQ(S_OK, hr); + + ScopedComPtr<IAccessible> text_accessible; + hr = text_dispatch.QueryInterface(text_accessible.Receive()); + ASSERT_EQ(S_OK, hr); + + CComBSTR value; + hr = text_accessible->get_accValue(CreateI4Variant(CHILDID_SELF), &value); + ASSERT_EQ(S_OK, hr); + EXPECT_STREQ(L"old text", value.m_str); + + text_dispatch.Release(); + text_accessible.Release(); + + // Notify the BrowserAccessibilityManager that the text child has changed. + text.value = L"new text"; + ViewHostMsg_AccessibilityNotification_Params param; + param.notification_type = + ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_CHILDREN_CHANGED; + param.acc_obj = text; + 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->GetRootAccessible()->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_accValue(CreateI4Variant(CHILDID_SELF), &value); + ASSERT_EQ(S_OK, hr); + EXPECT_STREQ(L"new text", value.m_str); + + text_dispatch.Release(); + text_accessible.Release(); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibilityWin::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. + CountedBrowserAccessibilityWin::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + new BrowserAccessibilityManagerWin( + GetDesktopWindow(), root, NULL, + new CountedBrowserAccessibilityWinFactory()); + ASSERT_EQ(4, CountedBrowserAccessibilityWin::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_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_CHILDREN_CHANGED; + param.acc_obj = root; + std::vector<ViewHostMsg_AccessibilityNotification_Params> notifications; + notifications.push_back(param); + manager->OnAccessibilityNotifications(notifications); + ASSERT_EQ(1, CountedBrowserAccessibilityWin::global_obj_count_); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibilityWin::global_obj_count_); +} diff --git a/chrome/browser/accessibility/browser_views_accessibility_browsertest.cc b/chrome/browser/accessibility/browser_views_accessibility_browsertest.cc new file mode 100644 index 0000000..af33e2c --- /dev/null +++ b/chrome/browser/accessibility/browser_views_accessibility_browsertest.cc @@ -0,0 +1,265 @@ +// 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 <oleacc.h> + +#include "app/l10n_util.h" +#include "base/scoped_comptr_win.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/view_ids.h" +#include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/browser/views/toolbar_view.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/in_process_browser_test.h" +#include "chrome/test/ui_test_utils.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "views/accessibility/view_accessibility_wrapper.h" +#include "views/widget/root_view.h" +#include "views/widget/widget_win.h" +#include "views/window/window.h" + +namespace { + +VARIANT id_self = {VT_I4, CHILDID_SELF}; + +// Dummy class to force creation of ATL module, needed by COM to instantiate +// ViewAccessibility. +class TestAtlModule : public CAtlDllModuleT< TestAtlModule > {}; +TestAtlModule test_atl_module_; + +class BrowserViewsAccessibilityTest : public InProcessBrowserTest { + public: + BrowserViewsAccessibilityTest() { + ::CoInitialize(NULL); + } + + ~BrowserViewsAccessibilityTest() { + ::CoUninitialize(); + } + + // Retrieves an instance of BrowserWindowTesting + BrowserWindowTesting* GetBrowserWindowTesting() { + BrowserWindow* browser_window = browser()->window(); + + if (!browser_window) + return NULL; + + return browser_window->GetBrowserWindowTesting(); + } + + // Retrieve an instance of BrowserView + BrowserView* GetBrowserView() { + return BrowserView::GetBrowserViewForNativeWindow( + browser()->window()->GetNativeHandle()); + } + + // Retrieves and initializes an instance of ToolbarView. + ToolbarView* GetToolbarView() { + BrowserWindowTesting* browser_window_testing = GetBrowserWindowTesting(); + + if (!browser_window_testing) + return NULL; + + return browser_window_testing->GetToolbarView(); + } + + // Retrieves and initializes an instance of BookmarkBarView. + BookmarkBarView* GetBookmarkBarView() { + BrowserWindowTesting* browser_window_testing = GetBrowserWindowTesting(); + + if (!browser_window_testing) + return NULL; + + return browser_window_testing->GetBookmarkBarView(); + } + + // Retrieves and verifies the accessibility object for the given View. + void TestViewAccessibilityObject(views::View* view, std::wstring name, + int32 role) { + ASSERT_TRUE(NULL != view); + + IAccessible* acc_obj = NULL; + HRESULT hr = view->GetViewAccessibilityWrapper()->GetInstance( + IID_IAccessible, reinterpret_cast<void**>(&acc_obj)); + ASSERT_EQ(S_OK, hr); + ASSERT_TRUE(NULL != acc_obj); + + TestAccessibilityInfo(acc_obj, name, role); + } + + + // Verifies MSAA Name and Role properties of the given IAccessible. + void TestAccessibilityInfo(IAccessible* acc_obj, std::wstring name, + int32 role) { + // Verify MSAA Name property. + BSTR acc_name; + + HRESULT hr = acc_obj->get_accName(id_self, &acc_name); + ASSERT_EQ(S_OK, hr); + EXPECT_STREQ(acc_name, name.c_str()); + + // Verify MSAA Role property. + VARIANT acc_role; + ::VariantInit(&acc_role); + + hr = acc_obj->get_accRole(id_self, &acc_role); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(VT_I4, acc_role.vt); + EXPECT_EQ(role, acc_role.lVal); + + ::VariantClear(&acc_role); + ::SysFreeString(acc_name); + } +}; + +// Retrieve accessibility object for main window and verify accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, + TestChromeWindowAccObj) { + BrowserWindow* browser_window = browser()->window(); + ASSERT_TRUE(NULL != browser_window); + + HWND hwnd = browser_window->GetNativeHandle(); + ASSERT_TRUE(NULL != hwnd); + + // Get accessibility object. + ScopedComPtr<IAccessible> acc_obj; + HRESULT hr = ::AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, + reinterpret_cast<void**>(&acc_obj)); + ASSERT_EQ(S_OK, hr); + ASSERT_TRUE(NULL != acc_obj); + + ui_test_utils::NavigateToURL(browser(), GURL(chrome::kAboutBlankURL)); + std::wstring title = + l10n_util::GetStringF(IDS_BROWSER_WINDOW_TITLE_FORMAT, + ASCIIToWide(chrome::kAboutBlankURL)); + TestAccessibilityInfo(acc_obj, title, ROLE_SYSTEM_WINDOW); +} + +// Retrieve accessibility object for non client view and verify accessibility +// info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestNonClientViewAccObj) { + views::View* non_client_view = + GetBrowserView()->GetWindow()->GetNonClientView(); + + TestViewAccessibilityObject(non_client_view, + l10n_util::GetString(IDS_PRODUCT_NAME), + ROLE_SYSTEM_WINDOW); +} + +// Retrieve accessibility object for browser root view and verify +// accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, + TestBrowserRootViewAccObj) { + views::View* browser_root_view = + GetBrowserView()->frame()->GetFrameView()->GetRootView(); + + TestViewAccessibilityObject(browser_root_view, + l10n_util::GetString(IDS_PRODUCT_NAME), + ROLE_SYSTEM_APPLICATION); +} + +// Retrieve accessibility object for browser view and verify accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestBrowserViewAccObj) { + // Verify root view MSAA name and role. + TestViewAccessibilityObject(GetBrowserView(), + l10n_util::GetString(IDS_PRODUCT_NAME), + ROLE_SYSTEM_CLIENT); +} + +// Retrieve accessibility object for toolbar view and verify accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestToolbarViewAccObj) { + // Verify toolbar MSAA name and role. + TestViewAccessibilityObject(GetToolbarView(), + l10n_util::GetString(IDS_ACCNAME_TOOLBAR), + ROLE_SYSTEM_TOOLBAR); +} + +// Retrieve accessibility object for Back button and verify accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestBackButtonAccObj) { + // Verify Back button MSAA name and role. + TestViewAccessibilityObject( + GetToolbarView()->GetViewByID(VIEW_ID_BACK_BUTTON), + l10n_util::GetString(IDS_ACCNAME_BACK), ROLE_SYSTEM_BUTTONDROPDOWN); +} + +// Retrieve accessibility object for Forward button and verify accessibility +// info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestForwardButtonAccObj) { + // Verify Forward button MSAA name and role. + TestViewAccessibilityObject( + GetToolbarView()->GetViewByID(VIEW_ID_FORWARD_BUTTON), + l10n_util::GetString(IDS_ACCNAME_FORWARD), ROLE_SYSTEM_BUTTONDROPDOWN); +} + +// Retrieve accessibility object for Reload button and verify accessibility +// info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestReloadButtonAccObj) { + // Verify Reload button MSAA name and role. + TestViewAccessibilityObject( + GetToolbarView()->GetViewByID(VIEW_ID_RELOAD_BUTTON), + l10n_util::GetString(IDS_ACCNAME_RELOAD), ROLE_SYSTEM_PUSHBUTTON); +} + +// Retrieve accessibility object for Home button and verify accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestHomeButtonAccObj) { + // Verify Home button MSAA name and role. + TestViewAccessibilityObject( + GetToolbarView()->GetViewByID(VIEW_ID_HOME_BUTTON), + l10n_util::GetString(IDS_ACCNAME_HOME), ROLE_SYSTEM_PUSHBUTTON); +} + +// Retrieve accessibility object for Star button and verify accessibility info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, + TestStarButtonAccObj) { + // Verify Star button MSAA name and role. + TestViewAccessibilityObject( + GetToolbarView()->GetViewByID(VIEW_ID_STAR_BUTTON), + l10n_util::GetString(IDS_ACCNAME_STAR), ROLE_SYSTEM_PUSHBUTTON); +} + +// Retrieve accessibility object for App menu button and verify accessibility +// info. +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, TestAppMenuAccObj) { + // Verify App menu button MSAA name and role. + TestViewAccessibilityObject(GetToolbarView()->GetViewByID(VIEW_ID_APP_MENU), + l10n_util::GetString(IDS_ACCNAME_APP), + ROLE_SYSTEM_BUTTONMENU); +} + +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, + TestBookmarkBarViewAccObj) { + TestViewAccessibilityObject(GetBookmarkBarView(), + l10n_util::GetString(IDS_ACCNAME_BOOKMARKS), + ROLE_SYSTEM_TOOLBAR); +} + +IN_PROC_BROWSER_TEST_F(BrowserViewsAccessibilityTest, + TestAboutChromeViewAccObj) { + // Firstly, test that the WindowDelegate got updated. + views::Window* aboutChromeWindow = GetBrowserView()->ShowAboutChromeDialog(); + EXPECT_STREQ(aboutChromeWindow->GetDelegate()->GetWindowTitle().c_str(), + l10n_util::GetString(IDS_ABOUT_CHROME_TITLE).c_str()); + EXPECT_EQ(aboutChromeWindow->GetDelegate()->accessible_role(), + AccessibilityTypes::ROLE_DIALOG); + + // Also test the accessibility object directly. + IAccessible* acc_obj = NULL; + HRESULT hr = + ::AccessibleObjectFromWindow(aboutChromeWindow->GetNativeWindow(), + OBJID_CLIENT, + IID_IAccessible, + reinterpret_cast<void**>(&acc_obj)); + ASSERT_EQ(S_OK, hr); + ASSERT_TRUE(NULL != acc_obj); + + TestAccessibilityInfo(acc_obj, l10n_util::GetString(IDS_ABOUT_CHROME_TITLE), + ROLE_SYSTEM_DIALOG); + + acc_obj->Release(); +} +} // Namespace. |