1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <string>
6#include <vector>
7
8#include "base/strings/utf_string_conversions.h"
9#include "content/browser/renderer_host/render_view_host_impl.h"
10#include "content/public/browser/notification_service.h"
11#include "content/public/browser/notification_types.h"
12#include "content/public/browser/render_widget_host_view.h"
13#include "content/shell/browser/shell.h"
14#include "content/test/accessibility_browser_test_utils.h"
15#include "content/test/content_browser_test.h"
16#include "content/test/content_browser_test_utils.h"
17
18#if defined(OS_WIN)
19#include <atlbase.h>
20#include <atlcom.h>
21#include "base/win/scoped_com_initializer.h"
22#include "ui/base/win/atl_module.h"
23#endif
24
25// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717
26#if defined(OS_WIN) && defined(ARCH_CPU_X86_64)
27#define MAYBE_TableSpan DISABLED_TableSpan
28#else
29#define MAYBE_TableSpan TableSpan
30#endif
31
32namespace content {
33
34class CrossPlatformAccessibilityBrowserTest : public ContentBrowserTest {
35 public:
36  CrossPlatformAccessibilityBrowserTest() {}
37
38  // Tell the renderer to send an accessibility tree, then wait for the
39  // notification that it's been received.
40  const AccessibilityNodeDataTreeNode& GetAccessibilityNodeDataTree(
41      AccessibilityMode accessibility_mode = AccessibilityModeComplete) {
42    AccessibilityNotificationWaiter waiter(
43        shell(), accessibility_mode, blink::WebAXEventLayoutComplete);
44    waiter.WaitForNotification();
45    return waiter.GetAccessibilityNodeDataTree();
46  }
47
48  // Make sure each node in the tree has an unique id.
49  void RecursiveAssertUniqueIds(
50      const AccessibilityNodeDataTreeNode& node, base::hash_set<int>* ids) {
51    ASSERT_TRUE(ids->find(node.id) == ids->end());
52    ids->insert(node.id);
53    for (size_t i = 0; i < node.children.size(); i++)
54      RecursiveAssertUniqueIds(node.children[i], ids);
55  }
56
57  // ContentBrowserTest
58  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE;
59  virtual void TearDownInProcessBrowserTestFixture() OVERRIDE;
60
61 protected:
62  std::string GetAttr(const AccessibilityNodeData& node,
63                      const AccessibilityNodeData::StringAttribute attr);
64  int GetIntAttr(const AccessibilityNodeData& node,
65                 const AccessibilityNodeData::IntAttribute attr);
66  bool GetBoolAttr(const AccessibilityNodeData& node,
67                   const AccessibilityNodeData::BoolAttribute attr);
68
69 private:
70#if defined(OS_WIN)
71  scoped_ptr<base::win::ScopedCOMInitializer> com_initializer_;
72#endif
73
74  DISALLOW_COPY_AND_ASSIGN(CrossPlatformAccessibilityBrowserTest);
75};
76
77void CrossPlatformAccessibilityBrowserTest::SetUpInProcessBrowserTestFixture() {
78#if defined(OS_WIN)
79  ui::win::CreateATLModuleIfNeeded();
80  com_initializer_.reset(new base::win::ScopedCOMInitializer());
81#endif
82}
83
84void
85CrossPlatformAccessibilityBrowserTest::TearDownInProcessBrowserTestFixture() {
86#if defined(OS_WIN)
87  com_initializer_.reset();
88#endif
89}
90
91// Convenience method to get the value of a particular AccessibilityNodeData
92// node attribute as a UTF-8 string.
93std::string CrossPlatformAccessibilityBrowserTest::GetAttr(
94    const AccessibilityNodeData& node,
95    const AccessibilityNodeData::StringAttribute attr) {
96  for (size_t i = 0; i < node.string_attributes.size(); ++i) {
97    if (node.string_attributes[i].first == attr)
98      return node.string_attributes[i].second;
99  }
100  return std::string();
101}
102
103// Convenience method to get the value of a particular AccessibilityNodeData
104// node integer attribute.
105int CrossPlatformAccessibilityBrowserTest::GetIntAttr(
106    const AccessibilityNodeData& node,
107    const AccessibilityNodeData::IntAttribute attr) {
108  for (size_t i = 0; i < node.int_attributes.size(); ++i) {
109    if (node.int_attributes[i].first == attr)
110      return node.int_attributes[i].second;
111  }
112  return -1;
113}
114
115// Convenience method to get the value of a particular AccessibilityNodeData
116// node boolean attribute.
117bool CrossPlatformAccessibilityBrowserTest::GetBoolAttr(
118    const AccessibilityNodeData& node,
119    const AccessibilityNodeData::BoolAttribute attr) {
120  for (size_t i = 0; i < node.bool_attributes.size(); ++i) {
121    if (node.bool_attributes[i].first == attr)
122      return node.bool_attributes[i].second;
123  }
124  return false;
125}
126
127// Marked flaky per http://crbug.com/101984
128IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
129                       DISABLED_WebpageAccessibility) {
130  // Create a data url and load it.
131  const char url_str[] =
132      "data:text/html,"
133      "<!doctype html>"
134      "<html><head><title>Accessibility Test</title></head>"
135      "<body><input type='button' value='push' /><input type='checkbox' />"
136      "</body></html>";
137  GURL url(url_str);
138  NavigateToURL(shell(), url);
139  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
140
141  // Check properties of the root element of the tree.
142  EXPECT_STREQ(url_str,
143               GetAttr(tree, AccessibilityNodeData::ATTR_DOC_URL).c_str());
144  EXPECT_STREQ(
145      "Accessibility Test",
146      GetAttr(tree, AccessibilityNodeData::ATTR_DOC_TITLE).c_str());
147  EXPECT_STREQ(
148      "html", GetAttr(tree, AccessibilityNodeData::ATTR_DOC_DOCTYPE).c_str());
149  EXPECT_STREQ(
150      "text/html",
151      GetAttr(tree, AccessibilityNodeData::ATTR_DOC_MIMETYPE).c_str());
152  EXPECT_STREQ(
153      "Accessibility Test",
154      GetAttr(tree, AccessibilityNodeData::ATTR_NAME).c_str());
155  EXPECT_EQ(blink::WebAXRoleRootWebArea, tree.role);
156
157  // Check properites of the BODY element.
158  ASSERT_EQ(1U, tree.children.size());
159  const AccessibilityNodeDataTreeNode& body = tree.children[0];
160  EXPECT_EQ(blink::WebAXRoleGroup, body.role);
161  EXPECT_STREQ("body",
162               GetAttr(body, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
163  EXPECT_STREQ("block",
164               GetAttr(body, AccessibilityNodeData::ATTR_DISPLAY).c_str());
165
166  // Check properties of the two children of the BODY element.
167  ASSERT_EQ(2U, body.children.size());
168
169  const AccessibilityNodeDataTreeNode& button = body.children[0];
170  EXPECT_EQ(blink::WebAXRoleButton, button.role);
171  EXPECT_STREQ(
172      "input", GetAttr(button, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
173  EXPECT_STREQ(
174      "push",
175      GetAttr(button, AccessibilityNodeData::ATTR_NAME).c_str());
176  EXPECT_STREQ(
177      "inline-block",
178      GetAttr(button, AccessibilityNodeData::ATTR_DISPLAY).c_str());
179  ASSERT_EQ(2U, button.html_attributes.size());
180  EXPECT_STREQ("type", button.html_attributes[0].first.c_str());
181  EXPECT_STREQ("button", button.html_attributes[0].second.c_str());
182  EXPECT_STREQ("value", button.html_attributes[1].first.c_str());
183  EXPECT_STREQ("push", button.html_attributes[1].second.c_str());
184
185  const AccessibilityNodeDataTreeNode& checkbox = body.children[1];
186  EXPECT_EQ(blink::WebAXRoleCheckBox, checkbox.role);
187  EXPECT_STREQ(
188      "input", GetAttr(checkbox, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
189  EXPECT_STREQ(
190      "inline-block",
191      GetAttr(checkbox, AccessibilityNodeData::ATTR_DISPLAY).c_str());
192  ASSERT_EQ(1U, checkbox.html_attributes.size());
193  EXPECT_STREQ("type", checkbox.html_attributes[0].first.c_str());
194  EXPECT_STREQ("checkbox", checkbox.html_attributes[0].second.c_str());
195}
196
197IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
198                       UnselectedEditableTextAccessibility) {
199  // Create a data url and load it.
200  const char url_str[] =
201      "data:text/html,"
202      "<!doctype html>"
203      "<body>"
204      "<input value=\"Hello, world.\"/>"
205      "</body></html>";
206  GURL url(url_str);
207  NavigateToURL(shell(), url);
208
209  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
210  ASSERT_EQ(1U, tree.children.size());
211  const AccessibilityNodeDataTreeNode& body = tree.children[0];
212  ASSERT_EQ(1U, body.children.size());
213  const AccessibilityNodeDataTreeNode& text = body.children[0];
214  EXPECT_EQ(blink::WebAXRoleTextField, text.role);
215  EXPECT_STREQ(
216      "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
217  EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_START));
218  EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_END));
219  EXPECT_STREQ(
220      "Hello, world.",
221      GetAttr(text, AccessibilityNodeData::ATTR_VALUE).c_str());
222
223  // TODO(dmazzoni): as soon as more accessibility code is cross-platform,
224  // this code should test that the accessible info is dynamically updated
225  // if the selection or value changes.
226}
227
228IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
229                       SelectedEditableTextAccessibility) {
230  // Create a data url and load it.
231  const char url_str[] =
232      "data:text/html,"
233      "<!doctype html>"
234      "<body onload=\"document.body.children[0].select();\">"
235      "<input value=\"Hello, world.\"/>"
236      "</body></html>";
237  GURL url(url_str);
238  NavigateToURL(shell(), url);
239
240  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
241  ASSERT_EQ(1U, tree.children.size());
242  const AccessibilityNodeDataTreeNode& body = tree.children[0];
243  ASSERT_EQ(1U, body.children.size());
244  const AccessibilityNodeDataTreeNode& text = body.children[0];
245  EXPECT_EQ(blink::WebAXRoleTextField, text.role);
246  EXPECT_STREQ(
247      "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
248  EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_START));
249  EXPECT_EQ(13, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_END));
250  EXPECT_STREQ(
251      "Hello, world.",
252      GetAttr(text, AccessibilityNodeData::ATTR_VALUE).c_str());
253}
254
255IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
256                       MultipleInheritanceAccessibility) {
257  // In a WebKit accessibility render tree for a table, each cell is a
258  // child of both a row and a column, so it appears to use multiple
259  // inheritance. Make sure that the AccessibilityNodeDataObject tree only
260  // keeps one copy of each cell, and uses an indirect child id for the
261  // additional reference to it.
262  const char url_str[] =
263      "data:text/html,"
264      "<!doctype html>"
265      "<table border=1><tr><td>1</td><td>2</td></tr></table>";
266  GURL url(url_str);
267  NavigateToURL(shell(), url);
268
269  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
270  ASSERT_EQ(1U, tree.children.size());
271  const AccessibilityNodeDataTreeNode& table = tree.children[0];
272  EXPECT_EQ(blink::WebAXRoleTable, table.role);
273  const AccessibilityNodeDataTreeNode& row = table.children[0];
274  EXPECT_EQ(blink::WebAXRoleRow, row.role);
275  const AccessibilityNodeDataTreeNode& cell1 = row.children[0];
276  EXPECT_EQ(blink::WebAXRoleCell, cell1.role);
277  const AccessibilityNodeDataTreeNode& cell2 = row.children[1];
278  EXPECT_EQ(blink::WebAXRoleCell, cell2.role);
279  const AccessibilityNodeDataTreeNode& column1 = table.children[1];
280  EXPECT_EQ(blink::WebAXRoleColumn, column1.role);
281  EXPECT_EQ(0U, column1.children.size());
282  EXPECT_EQ(1U, column1.intlist_attributes.size());
283  EXPECT_EQ(AccessibilityNodeData::ATTR_INDIRECT_CHILD_IDS,
284            column1.intlist_attributes[0].first);
285  const std::vector<int32> column1_indirect_child_ids =
286      column1.intlist_attributes[0].second;
287  EXPECT_EQ(1U, column1_indirect_child_ids.size());
288  EXPECT_EQ(cell1.id, column1_indirect_child_ids[0]);
289  const AccessibilityNodeDataTreeNode& column2 = table.children[2];
290  EXPECT_EQ(blink::WebAXRoleColumn, column2.role);
291  EXPECT_EQ(0U, column2.children.size());
292  EXPECT_EQ(AccessibilityNodeData::ATTR_INDIRECT_CHILD_IDS,
293            column2.intlist_attributes[0].first);
294  const std::vector<int32> column2_indirect_child_ids =
295      column2.intlist_attributes[0].second;
296  EXPECT_EQ(1U, column2_indirect_child_ids.size());
297  EXPECT_EQ(cell2.id, column2_indirect_child_ids[0]);
298}
299
300IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
301                       MultipleInheritanceAccessibility2) {
302  // Here's another html snippet where WebKit puts the same node as a child
303  // of two different parents. Instead of checking the exact output, just
304  // make sure that no id is reused in the resulting tree.
305  const char url_str[] =
306      "data:text/html,"
307      "<!doctype html>"
308      "<script>\n"
309      "  document.writeln('<q><section></section></q><q><li>');\n"
310      "  setTimeout(function() {\n"
311      "    document.close();\n"
312      "  }, 1);\n"
313      "</script>";
314  GURL url(url_str);
315  NavigateToURL(shell(), url);
316
317  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
318  base::hash_set<int> ids;
319  RecursiveAssertUniqueIds(tree, &ids);
320}
321
322IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
323                       IframeAccessibility) {
324  // Create a data url and load it.
325  const char url_str[] =
326      "data:text/html,"
327      "<!doctype html><html><body>"
328      "<button>Button 1</button>"
329      "<iframe src='data:text/html,"
330      "<!doctype html><html><body><button>Button 2</button></body></html>"
331      "'></iframe>"
332      "<button>Button 3</button>"
333      "</body></html>";
334  GURL url(url_str);
335  NavigateToURL(shell(), url);
336
337  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
338  ASSERT_EQ(1U, tree.children.size());
339  const AccessibilityNodeDataTreeNode& body = tree.children[0];
340  ASSERT_EQ(3U, body.children.size());
341
342  const AccessibilityNodeDataTreeNode& button1 = body.children[0];
343  EXPECT_EQ(blink::WebAXRoleButton, button1.role);
344  EXPECT_STREQ(
345      "Button 1",
346      GetAttr(button1, AccessibilityNodeData::ATTR_NAME).c_str());
347
348  const AccessibilityNodeDataTreeNode& iframe = body.children[1];
349  EXPECT_STREQ("iframe",
350               GetAttr(iframe, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
351  ASSERT_EQ(1U, iframe.children.size());
352
353  const AccessibilityNodeDataTreeNode& scroll_area = iframe.children[0];
354  EXPECT_EQ(blink::WebAXRoleScrollArea, scroll_area.role);
355  ASSERT_EQ(1U, scroll_area.children.size());
356
357  const AccessibilityNodeDataTreeNode& sub_document = scroll_area.children[0];
358  EXPECT_EQ(blink::WebAXRoleWebArea, sub_document.role);
359  ASSERT_EQ(1U, sub_document.children.size());
360
361  const AccessibilityNodeDataTreeNode& sub_body = sub_document.children[0];
362  ASSERT_EQ(1U, sub_body.children.size());
363
364  const AccessibilityNodeDataTreeNode& button2 = sub_body.children[0];
365  EXPECT_EQ(blink::WebAXRoleButton, button2.role);
366  EXPECT_STREQ("Button 2",
367               GetAttr(button2, AccessibilityNodeData::ATTR_NAME).c_str());
368
369  const AccessibilityNodeDataTreeNode& button3 = body.children[2];
370  EXPECT_EQ(blink::WebAXRoleButton, button3.role);
371  EXPECT_STREQ("Button 3",
372               GetAttr(button3, AccessibilityNodeData::ATTR_NAME).c_str());
373}
374
375IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
376                       DuplicateChildrenAccessibility) {
377  // Here's another html snippet where WebKit has a parent node containing
378  // two duplicate child nodes. Instead of checking the exact output, just
379  // make sure that no id is reused in the resulting tree.
380  const char url_str[] =
381      "data:text/html,"
382      "<!doctype html>"
383      "<em><code ><h4 ></em>";
384  GURL url(url_str);
385  NavigateToURL(shell(), url);
386
387  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
388  base::hash_set<int> ids;
389  RecursiveAssertUniqueIds(tree, &ids);
390}
391
392IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
393                       MAYBE_TableSpan) {
394  // +---+---+---+
395  // |   1   | 2 |
396  // +---+---+---+
397  // | 3 |   4   |
398  // +---+---+---+
399
400  const char url_str[] =
401      "data:text/html,"
402      "<!doctype html>"
403      "<table border=1>"
404      " <tr>"
405      "  <td colspan=2>1</td><td>2</td>"
406      " </tr>"
407      " <tr>"
408      "  <td>3</td><td colspan=2>4</td>"
409      " </tr>"
410      "</table>";
411  GURL url(url_str);
412  NavigateToURL(shell(), url);
413
414  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
415  const AccessibilityNodeDataTreeNode& table = tree.children[0];
416  EXPECT_EQ(blink::WebAXRoleTable, table.role);
417  ASSERT_GE(table.children.size(), 5U);
418  EXPECT_EQ(blink::WebAXRoleRow, table.children[0].role);
419  EXPECT_EQ(blink::WebAXRoleRow, table.children[1].role);
420  EXPECT_EQ(blink::WebAXRoleColumn, table.children[2].role);
421  EXPECT_EQ(blink::WebAXRoleColumn, table.children[3].role);
422  EXPECT_EQ(blink::WebAXRoleColumn, table.children[4].role);
423  EXPECT_EQ(3,
424            GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT));
425  EXPECT_EQ(2, GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_ROW_COUNT));
426
427  const AccessibilityNodeDataTreeNode& cell1 = table.children[0].children[0];
428  const AccessibilityNodeDataTreeNode& cell2 = table.children[0].children[1];
429  const AccessibilityNodeDataTreeNode& cell3 = table.children[1].children[0];
430  const AccessibilityNodeDataTreeNode& cell4 = table.children[1].children[1];
431
432  ASSERT_EQ(AccessibilityNodeData::ATTR_CELL_IDS,
433            table.intlist_attributes[0].first);
434  const std::vector<int32>& table_cell_ids =
435      table.intlist_attributes[0].second;
436  ASSERT_EQ(6U, table_cell_ids.size());
437  EXPECT_EQ(cell1.id, table_cell_ids[0]);
438  EXPECT_EQ(cell1.id, table_cell_ids[1]);
439  EXPECT_EQ(cell2.id, table_cell_ids[2]);
440  EXPECT_EQ(cell3.id, table_cell_ids[3]);
441  EXPECT_EQ(cell4.id, table_cell_ids[4]);
442  EXPECT_EQ(cell4.id, table_cell_ids[5]);
443
444  EXPECT_EQ(0, GetIntAttr(cell1,
445                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
446  EXPECT_EQ(0, GetIntAttr(cell1,
447                          AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX));
448  EXPECT_EQ(2, GetIntAttr(cell1,
449                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
450  EXPECT_EQ(1, GetIntAttr(cell1,
451                          AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN));
452  EXPECT_EQ(2, GetIntAttr(cell2,
453                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
454  EXPECT_EQ(1, GetIntAttr(cell2,
455                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
456  EXPECT_EQ(0, GetIntAttr(cell3,
457                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
458  EXPECT_EQ(1, GetIntAttr(cell3,
459                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
460  EXPECT_EQ(1, GetIntAttr(cell4,
461                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
462  EXPECT_EQ(2, GetIntAttr(cell4,
463                          AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
464}
465
466IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
467                       WritableElement) {
468  const char url_str[] =
469      "data:text/html,"
470      "<!doctype html>"
471      "<div role='textbox' tabindex=0>"
472      " Some text"
473      "</div>";
474  GURL url(url_str);
475  NavigateToURL(shell(), url);
476  const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
477
478  ASSERT_EQ(1U, tree.children.size());
479  const AccessibilityNodeDataTreeNode& textbox = tree.children[0];
480
481  EXPECT_EQ(
482      true, GetBoolAttr(textbox, AccessibilityNodeData::ATTR_CAN_SET_VALUE));
483}
484
485}  // namespace content
486