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