summaryrefslogtreecommitdiffstats
path: root/chrome/browser/accessibility_win_browsertest.cc
blob: 74283edbd5d22e6d5c1724451b6ec2d8b8fafcc6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <atlbase.h>
#include <vector>

#include "base/file_path.h"
#include "base/scoped_comptr_win.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/renderer_host/render_widget_host_view_win.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_type.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"

#if defined(OS_WIN)
// http://crbug.com/48655
#define SKIP_WIN(test) DISABLED_##test
#else
#define SKIP_WIN(test) test
#endif

namespace {

class AccessibilityWinBrowserTest : public InProcessBrowserTest {
 public:
  AccessibilityWinBrowserTest() : screenreader_running_(FALSE) {}

  // InProcessBrowserTest
  void SetUpInProcessBrowserTestFixture();
  void TearDownInProcessBrowserTestFixture();

 protected:
  IAccessible* GetRenderWidgetHostViewClientAccessible();

 private:
  BOOL screenreader_running_;
};

void AccessibilityWinBrowserTest::SetUpInProcessBrowserTestFixture() {
  // This test assumes the windows system-wide SPI_SETSCREENREADER flag is
  // cleared.
  if (SystemParametersInfo(SPI_GETSCREENREADER, 0, &screenreader_running_, 0) &&
      screenreader_running_) {
    // Clear the SPI_SETSCREENREADER flag and notify active applications about
    // the setting change.
    ::SystemParametersInfo(SPI_SETSCREENREADER, FALSE, NULL, 0);
    ::SendNotifyMessage(
        HWND_BROADCAST, WM_SETTINGCHANGE, SPI_GETSCREENREADER, 0);
  }
}

void AccessibilityWinBrowserTest::TearDownInProcessBrowserTestFixture() {
  if (screenreader_running_) {
    // Restore the SPI_SETSCREENREADER flag and notify active applications about
    // the setting change.
    ::SystemParametersInfo(SPI_SETSCREENREADER, TRUE, NULL, 0);
    ::SendNotifyMessage(
        HWND_BROADCAST, WM_SETTINGCHANGE, SPI_GETSCREENREADER, 0);
  }
}

class AccessibleChecker {
 public:
  AccessibleChecker(std::wstring expected_name, int32 expected_role);
  AccessibleChecker(std::wstring expected_name, std::wstring expected_role);

  // Append an AccessibleChecker that verifies accessibility information for
  // a child IAccessible. Order is important.
  void AppendExpectedChild(AccessibleChecker* expected_child);

  // Check that the name and role of the given IAccessible instance and its
  // descendants match the expected names and roles that this object was
  // initialized with.
  void CheckAccessible(IAccessible* accessible);

  typedef std::vector<AccessibleChecker*> AccessibleCheckerVector;

 private:
  void CheckAccessibleName(IAccessible* accessible);
  void CheckAccessibleRole(IAccessible* accessible);
  void CheckAccessibleChildren(IAccessible* accessible);

 private:
  // Expected accessible name. Checked against IAccessible::get_accName.
  std::wstring name_;

  // Expected accessible role. Checked against IAccessible::get_accRole.
  CComVariant role_;

  // Expected accessible children. Checked using IAccessible::get_accChildCount
  // and ::AccessibleChildren.
  AccessibleCheckerVector children_;
};

VARIANT CreateI4Variant(LONG value) {
  VARIANT variant = {0};

  V_VT(&variant) = VT_I4;
  V_I4(&variant) = value;

  return variant;
}

IAccessible* GetAccessibleFromResultVariant(IAccessible* parent, VARIANT *var) {
  switch (V_VT(var)) {
    case VT_DISPATCH:
      return CComQIPtr<IAccessible>(V_DISPATCH(var)).Detach();
      break;

    case VT_I4: {
      CComPtr<IDispatch> dispatch;
      HRESULT hr = parent->get_accChild(CreateI4Variant(V_I4(var)), &dispatch);
      EXPECT_EQ(hr, S_OK);
      return CComQIPtr<IAccessible>(dispatch).Detach();
      break;
    }
  }

  return NULL;
}

// Retrieve the MSAA client accessibility object for the Render Widget Host View
// of the selected tab.
IAccessible*
AccessibilityWinBrowserTest::GetRenderWidgetHostViewClientAccessible() {
  HWND hwnd_render_widget_host_view =
      browser()->GetSelectedTabContents()->GetRenderWidgetHostView()->
          GetNativeView();

  IAccessible* accessible;
  HRESULT hr = AccessibleObjectFromWindow(
      hwnd_render_widget_host_view, OBJID_CLIENT,
      IID_IAccessible, reinterpret_cast<void**>(&accessible));
  EXPECT_EQ(S_OK, hr);
  EXPECT_NE(accessible, reinterpret_cast<IAccessible*>(NULL));

  return accessible;
}

AccessibleChecker::AccessibleChecker(
    std::wstring expected_name, int32 expected_role) :
    name_(expected_name),
    role_(expected_role) {
}

AccessibleChecker::AccessibleChecker(
    std::wstring expected_name, std::wstring expected_role) :
    name_(expected_name),
    role_(expected_role.c_str()) {
}

void AccessibleChecker::AppendExpectedChild(
    AccessibleChecker* expected_child) {
  children_.push_back(expected_child);
}

void AccessibleChecker::CheckAccessible(IAccessible* accessible) {
  CheckAccessibleName(accessible);
  CheckAccessibleRole(accessible);
  CheckAccessibleChildren(accessible);
}

void AccessibleChecker::CheckAccessibleName(IAccessible* accessible) {
  CComBSTR name;
  HRESULT hr =
      accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name);

  if (name_.empty()) {
    // If the object doesn't have name S_FALSE should be returned.
    EXPECT_EQ(hr, S_FALSE);
  } else {
    // Test that the correct string was returned.
    EXPECT_EQ(hr, S_OK);
    EXPECT_EQ(CompareString(LOCALE_NEUTRAL, 0, name, SysStringLen(name),
                  name_.c_str(), name_.length()),
              CSTR_EQUAL);
  }
}

void AccessibleChecker::CheckAccessibleRole(IAccessible* accessible) {
  VARIANT var_role = {0};
  HRESULT hr =
      accessible->get_accRole(CreateI4Variant(CHILDID_SELF), &var_role);
  EXPECT_EQ(hr, S_OK);
  ASSERT_TRUE(role_ == var_role);
}

void AccessibleChecker::CheckAccessibleChildren(IAccessible* parent) {
  LONG child_count = 0;
  HRESULT hr = parent->get_accChildCount(&child_count);
  EXPECT_EQ(hr, S_OK);
  ASSERT_EQ(child_count, children_.size());

  std::auto_ptr<VARIANT> child_array(new VARIANT[child_count]);
  LONG obtained_count = 0;
  hr = AccessibleChildren(parent, 0, child_count,
                          child_array.get(), &obtained_count);
  ASSERT_EQ(hr, S_OK);
  ASSERT_EQ(child_count, obtained_count);

  VARIANT* child = child_array.get();
  for (AccessibleCheckerVector::iterator child_checker = children_.begin();
       child_checker != children_.end();
       ++child_checker, ++child) {
    ScopedComPtr<IAccessible> child_accessible;
    child_accessible.Attach(GetAccessibleFromResultVariant(parent, child));
    (*child_checker)->CheckAccessible(child_accessible);
  }
}

IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
                       SKIP_WIN(TestRendererAccessibilityTree)) {
  // By requesting an accessible chrome will believe a screen reader has been
  // detected.
  ScopedComPtr<IAccessible> document_accessible(
      GetRenderWidgetHostViewClientAccessible());

  // The initial accessible returned should have state STATE_SYSTEM_BUSY while
  // the accessibility tree is being requested from the renderer.
  VARIANT var_state;
  HRESULT hr = document_accessible->
      get_accState(CreateI4Variant(CHILDID_SELF), &var_state);
  EXPECT_EQ(hr, S_OK);
  EXPECT_EQ(V_VT(&var_state), VT_I4);
  EXPECT_EQ(V_I4(&var_state), STATE_SYSTEM_BUSY);

  GURL tree_url(
      "data:text/html,<html><head><title>Accessibility Win Test</title></head>"
      "<body><input type='button' value='push' /><input type='checkbox' />"
      "</body></html>");
  browser()->OpenURL(tree_url, GURL(), CURRENT_TAB, PageTransition::TYPED);
  ui_test_utils::WaitForNotification(
      NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED);

  document_accessible = GetRenderWidgetHostViewClientAccessible();
  ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL));

  AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON);
  AccessibleChecker checkbox_checker(L"", ROLE_SYSTEM_CHECKBUTTON);

  AccessibleChecker grouping_checker(L"", L"div");
  grouping_checker.AppendExpectedChild(&button_checker);
  grouping_checker.AppendExpectedChild(&checkbox_checker);

  AccessibleChecker document_checker(L"", ROLE_SYSTEM_DOCUMENT);
  document_checker.AppendExpectedChild(&grouping_checker);

  // Check the accessible tree of the renderer.
  document_checker.CheckAccessible(document_accessible);

  // Check that document accessible has a parent accessible.
  ScopedComPtr<IDispatch> parent_dispatch;
  hr = document_accessible->get_accParent(parent_dispatch.Receive());
  EXPECT_EQ(hr, S_OK);
  EXPECT_NE(parent_dispatch, reinterpret_cast<IDispatch*>(NULL));

  // Navigate to another page.
  GURL about_url("about:");
  ui_test_utils::NavigateToURL(browser(), about_url);

  // Verify that the IAccessible reference still points to a valid object and
  // that it calls to its methods fail since the tree is no longer valid after
  // the page navagation.
  // Todo(ctguil): Currently this is giving a false positive because E_FAIL is
  // returned when BrowserAccessibilityManager::RequestAccessibilityInfo fails
  // since the previous render view host connection is lost. Verify that
  // instances are actually marked as invalid once the browse side cache is
  // checked in.
  CComBSTR name;
  hr = document_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name);
  ASSERT_EQ(E_FAIL, hr);
}
}  // namespace.