1// Copyright (c) 2011 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/utf_string_conversions.h"
9#include "chrome/browser/ui/browser.h"
10#include "chrome/browser/ui/browser_window.h"
11#include "chrome/common/render_messages.h"
12#include "chrome/test/in_process_browser_test.h"
13#include "chrome/test/ui_test_utils.h"
14#include "content/browser/renderer_host/render_view_host.h"
15#include "content/browser/renderer_host/render_widget_host.h"
16#include "content/browser/renderer_host/render_widget_host_view.h"
17#include "content/browser/tab_contents/tab_contents.h"
18#include "content/common/notification_type.h"
19
20#if defined(OS_WIN)
21#include <atlbase.h>
22#include <atlcom.h>
23#endif
24
25using webkit_glue::WebAccessibility;
26
27namespace {
28
29class RendererAccessibilityBrowserTest : public InProcessBrowserTest {
30 public:
31  RendererAccessibilityBrowserTest() {}
32
33  // Tell the renderer to send an accessibility tree, then wait for the
34  // notification that it's been received.
35  const WebAccessibility& GetWebAccessibilityTree() {
36    RenderWidgetHostView* host_view =
37        browser()->GetSelectedTabContents()->GetRenderWidgetHostView();
38    RenderWidgetHost* host = host_view->GetRenderWidgetHost();
39    RenderViewHost* view_host = static_cast<RenderViewHost*>(host);
40    view_host->set_save_accessibility_tree_for_testing(true);
41    view_host->EnableRendererAccessibility();
42    ui_test_utils::WaitForNotification(
43        NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED);
44    return view_host->accessibility_tree();
45  }
46
47  // Make sure each node in the tree has an unique id.
48  void RecursiveAssertUniqueIds(
49      const WebAccessibility& node, base::hash_set<int>* ids) {
50    ASSERT_TRUE(ids->find(node.id) == ids->end());
51    ids->insert(node.id);
52    for (size_t i = 0; i < node.children.size(); i++)
53      RecursiveAssertUniqueIds(node.children[i], ids);
54  }
55
56  // InProcessBrowserTest
57  void SetUpInProcessBrowserTestFixture();
58  void TearDownInProcessBrowserTestFixture();
59
60 protected:
61  std::string GetAttr(const WebAccessibility& node,
62                      const WebAccessibility::Attribute attr);
63};
64
65void RendererAccessibilityBrowserTest::SetUpInProcessBrowserTestFixture() {
66#if defined(OS_WIN)
67  // ATL needs a pointer to a COM module.
68  static CComModule module;
69  _pAtlModule = &module;
70
71  // Make sure COM is initialized for this thread; it's safe to call twice.
72  ::CoInitialize(NULL);
73#endif
74}
75
76void RendererAccessibilityBrowserTest::TearDownInProcessBrowserTestFixture() {
77#if defined(OS_WIN)
78  ::CoUninitialize();
79#endif
80}
81
82// Convenience method to get the value of a particular WebAccessibility
83// node attribute as a UTF-8 const char*.
84std::string RendererAccessibilityBrowserTest::GetAttr(
85    const WebAccessibility& node, const WebAccessibility::Attribute attr) {
86  std::map<int32, string16>::const_iterator iter = node.attributes.find(attr);
87  if (iter != node.attributes.end())
88    return UTF16ToUTF8(iter->second);
89  else
90    return "";
91}
92
93IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
94                       CrossPlatformWebpageAccessibility) {
95  // Create a data url and load it.
96  const char url_str[] =
97      "data:text/html,"
98      "<!doctype html>"
99      "<html><head><title>Accessibility Test</title></head>"
100      "<body><input type='button' value='push' /><input type='checkbox' />"
101      "</body></html>";
102  GURL url(url_str);
103  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
104  const WebAccessibility& tree = GetWebAccessibilityTree();
105
106  // Check properties of the root element of the tree.
107  EXPECT_STREQ(url_str, GetAttr(tree, WebAccessibility::ATTR_DOC_URL).c_str());
108  EXPECT_STREQ(
109      "Accessibility Test",
110      GetAttr(tree, WebAccessibility::ATTR_DOC_TITLE).c_str());
111  EXPECT_STREQ(
112      "html", GetAttr(tree, WebAccessibility::ATTR_DOC_DOCTYPE).c_str());
113  EXPECT_STREQ(
114      "text/html", GetAttr(tree, WebAccessibility::ATTR_DOC_MIMETYPE).c_str());
115  EXPECT_STREQ("Accessibility Test", UTF16ToUTF8(tree.name).c_str());
116  EXPECT_EQ(WebAccessibility::ROLE_WEB_AREA, tree.role);
117
118  // Check properites of the BODY element.
119  ASSERT_EQ(1U, tree.children.size());
120  const WebAccessibility& body = tree.children[0];
121  EXPECT_EQ(WebAccessibility::ROLE_GROUP, body.role);
122  EXPECT_STREQ("body", GetAttr(body, WebAccessibility::ATTR_HTML_TAG).c_str());
123  EXPECT_STREQ("block", GetAttr(body, WebAccessibility::ATTR_DISPLAY).c_str());
124
125  // Check properties of the two children of the BODY element.
126  ASSERT_EQ(2U, body.children.size());
127
128  const WebAccessibility& button = body.children[0];
129  EXPECT_EQ(WebAccessibility::ROLE_BUTTON, button.role);
130  EXPECT_STREQ(
131      "input", GetAttr(button, WebAccessibility::ATTR_HTML_TAG).c_str());
132  EXPECT_STREQ("push", UTF16ToUTF8(button.name).c_str());
133  EXPECT_STREQ(
134      "inline-block", GetAttr(button, WebAccessibility::ATTR_DISPLAY).c_str());
135  ASSERT_EQ(2U, button.html_attributes.size());
136  EXPECT_STREQ("type", UTF16ToUTF8(button.html_attributes[0].first).c_str());
137  EXPECT_STREQ("button", UTF16ToUTF8(button.html_attributes[0].second).c_str());
138  EXPECT_STREQ("value", UTF16ToUTF8(button.html_attributes[1].first).c_str());
139  EXPECT_STREQ("push", UTF16ToUTF8(button.html_attributes[1].second).c_str());
140
141  const WebAccessibility& checkbox = body.children[1];
142  EXPECT_EQ(WebAccessibility::ROLE_CHECKBOX, checkbox.role);
143  EXPECT_STREQ(
144      "input", GetAttr(checkbox, WebAccessibility::ATTR_HTML_TAG).c_str());
145  EXPECT_STREQ(
146      "inline-block",
147      GetAttr(checkbox, WebAccessibility::ATTR_DISPLAY).c_str());
148  ASSERT_EQ(1U, checkbox.html_attributes.size());
149  EXPECT_STREQ(
150      "type", UTF16ToUTF8(checkbox.html_attributes[0].first).c_str());
151  EXPECT_STREQ(
152    "checkbox", UTF16ToUTF8(checkbox.html_attributes[0].second).c_str());
153}
154
155IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
156                       CrossPlatformUnselectedEditableTextAccessibility) {
157  // Create a data url and load it.
158  const char url_str[] =
159      "data:text/html,"
160      "<!doctype html>"
161      "<body>"
162      "<input value=\"Hello, world.\"/>"
163      "</body></html>";
164  GURL url(url_str);
165  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
166
167  const WebAccessibility& tree = GetWebAccessibilityTree();
168  ASSERT_EQ(1U, tree.children.size());
169  const WebAccessibility& body = tree.children[0];
170  ASSERT_EQ(1U, body.children.size());
171  const WebAccessibility& text = body.children[0];
172  EXPECT_EQ(WebAccessibility::ROLE_TEXT_FIELD, text.role);
173  EXPECT_STREQ(
174      "input", GetAttr(text, WebAccessibility::ATTR_HTML_TAG).c_str());
175  EXPECT_STREQ(
176      "0", GetAttr(text, WebAccessibility::ATTR_TEXT_SEL_START).c_str());
177  EXPECT_STREQ(
178      "0", GetAttr(text, WebAccessibility::ATTR_TEXT_SEL_END).c_str());
179  EXPECT_STREQ("Hello, world.", UTF16ToUTF8(text.value).c_str());
180
181  // TODO(dmazzoni): as soon as more accessibility code is cross-platform,
182  // this code should test that the accessible info is dynamically updated
183  // if the selection or value changes.
184}
185
186IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
187                       CrossPlatformSelectedEditableTextAccessibility) {
188  // Create a data url and load it.
189  const char url_str[] =
190      "data:text/html,"
191      "<!doctype html>"
192      "<body onload=\"document.body.children[0].select();\">"
193      "<input value=\"Hello, world.\"/>"
194      "</body></html>";
195  GURL url(url_str);
196  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
197
198  const WebAccessibility& tree = GetWebAccessibilityTree();
199  ASSERT_EQ(1U, tree.children.size());
200  const WebAccessibility& body = tree.children[0];
201  ASSERT_EQ(1U, body.children.size());
202  const WebAccessibility& text = body.children[0];
203  EXPECT_EQ(WebAccessibility::ROLE_TEXT_FIELD, text.role);
204  EXPECT_STREQ(
205      "input", GetAttr(text, WebAccessibility::ATTR_HTML_TAG).c_str());
206  EXPECT_STREQ(
207      "0", GetAttr(text, WebAccessibility::ATTR_TEXT_SEL_START).c_str());
208  EXPECT_STREQ(
209      "13", GetAttr(text, WebAccessibility::ATTR_TEXT_SEL_END).c_str());
210  EXPECT_STREQ("Hello, world.", UTF16ToUTF8(text.value).c_str());
211}
212
213IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
214                       CrossPlatformMultipleInheritanceAccessibility) {
215  // In a WebKit accessibility render tree for a table, each cell is a
216  // child of both a row and a column, so it appears to use multiple
217  // inheritance. Make sure that the WebAccessibilityObject tree only
218  // keeps one copy of each cell, and uses an indirect child id for the
219  // additional reference to it.
220  const char url_str[] =
221      "data:text/html,"
222      "<!doctype html>"
223      "<table border=1><tr><td>1</td><td>2</td></tr></table>";
224  GURL url(url_str);
225  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
226
227  const WebAccessibility& tree = GetWebAccessibilityTree();
228  ASSERT_EQ(1U, tree.children.size());
229  const WebAccessibility& table = tree.children[0];
230  EXPECT_EQ(WebAccessibility::ROLE_TABLE, table.role);
231  const WebAccessibility& row = table.children[0];
232  EXPECT_EQ(WebAccessibility::ROLE_ROW, row.role);
233  const WebAccessibility& cell1 = row.children[0];
234  EXPECT_EQ(WebAccessibility::ROLE_CELL, cell1.role);
235  const WebAccessibility& cell2 = row.children[1];
236  EXPECT_EQ(WebAccessibility::ROLE_CELL, cell2.role);
237  const WebAccessibility& column1 = table.children[1];
238  EXPECT_EQ(WebAccessibility::ROLE_COLUMN, column1.role);
239  EXPECT_EQ(0U, column1.children.size());
240  EXPECT_EQ(1U, column1.indirect_child_ids.size());
241  EXPECT_EQ(cell1.id, column1.indirect_child_ids[0]);
242  const WebAccessibility& column2 = table.children[2];
243  EXPECT_EQ(WebAccessibility::ROLE_COLUMN, column2.role);
244  EXPECT_EQ(0U, column2.children.size());
245  EXPECT_EQ(1U, column2.indirect_child_ids.size());
246  EXPECT_EQ(cell2.id, column2.indirect_child_ids[0]);
247}
248
249IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
250                       CrossPlatformMultipleInheritanceAccessibility2) {
251  // Here's another html snippet where WebKit puts the same node as a child
252  // of two different parents. Instead of checking the exact output, just
253  // make sure that no id is reused in the resulting tree.
254  const char url_str[] =
255      "data:text/html,"
256      "<!doctype html>"
257      "<script>\n"
258      "  document.writeln('<q><section></section></q><q><li>');\n"
259      "  setTimeout(function() {\n"
260      "    document.close();\n"
261      "  }, 1);\n"
262      "</script>";
263  GURL url(url_str);
264  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
265
266  const WebAccessibility& tree = GetWebAccessibilityTree();
267  base::hash_set<int> ids;
268  RecursiveAssertUniqueIds(tree, &ids);
269}
270
271IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
272                       CrossPlatformIframeAccessibility) {
273  // Create a data url and load it.
274  const char url_str[] =
275      "data:text/html,"
276      "<!doctype html><html><body>"
277      "<button>Button 1</button>"
278      "<iframe src='data:text/html,"
279      "<!doctype html><html><body><button>Button 2</button></body></html>"
280      "'></iframe>"
281      "<button>Button 3</button>"
282      "</body></html>";
283  GURL url(url_str);
284  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
285
286  const WebAccessibility& tree = GetWebAccessibilityTree();
287  ASSERT_EQ(1U, tree.children.size());
288  const WebAccessibility& body = tree.children[0];
289  ASSERT_EQ(3U, body.children.size());
290
291  const WebAccessibility& button1 = body.children[0];
292  EXPECT_EQ(WebAccessibility::ROLE_BUTTON, button1.role);
293  EXPECT_STREQ("Button 1", UTF16ToUTF8(button1.name).c_str());
294
295  const WebAccessibility& iframe = body.children[1];
296  EXPECT_STREQ("iframe",
297               GetAttr(iframe, WebAccessibility::ATTR_HTML_TAG).c_str());
298  ASSERT_EQ(1U, iframe.children.size());
299
300  const WebAccessibility& scroll_area = iframe.children[0];
301  EXPECT_EQ(WebAccessibility::ROLE_SCROLLAREA, scroll_area.role);
302  ASSERT_EQ(1U, scroll_area.children.size());
303
304  const WebAccessibility& sub_document = scroll_area.children[0];
305  EXPECT_EQ(WebAccessibility::ROLE_WEB_AREA, sub_document.role);
306  ASSERT_EQ(1U, sub_document.children.size());
307
308  const WebAccessibility& sub_body = sub_document.children[0];
309  ASSERT_EQ(1U, sub_body.children.size());
310
311  const WebAccessibility& button2 = sub_body.children[0];
312  EXPECT_EQ(WebAccessibility::ROLE_BUTTON, button2.role);
313  EXPECT_STREQ("Button 2", UTF16ToUTF8(button2.name).c_str());
314
315  const WebAccessibility& button3 = body.children[2];
316  EXPECT_EQ(WebAccessibility::ROLE_BUTTON, button3.role);
317  EXPECT_STREQ("Button 3", UTF16ToUTF8(button3.name).c_str());
318}
319
320IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,
321                       CrossPlatformDuplicateChildrenAccessibility) {
322  // Here's another html snippet where WebKit has a parent node containing
323  // two duplicate child nodes. Instead of checking the exact output, just
324  // make sure that no id is reused in the resulting tree.
325  const char url_str[] =
326      "data:text/html,"
327      "<!doctype html>"
328      "<em><code ><h4 ></em>";
329  GURL url(url_str);
330  browser()->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED);
331
332  const WebAccessibility& tree = GetWebAccessibilityTree();
333  base::hash_set<int> ids;
334  RecursiveAssertUniqueIds(tree, &ids);
335}
336
337}  // namespace
338