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
25 using webkit_glue::WebAccessibility;
26
27 namespace {
28
29 class RendererAccessibilityBrowserTest : public InProcessBrowserTest {
30 public:
RendererAccessibilityBrowserTest()31 RendererAccessibilityBrowserTest() {}
32
33 // Tell the renderer to send an accessibility tree, then wait for the
34 // notification that it's been received.
GetWebAccessibilityTree()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.
RecursiveAssertUniqueIds(const WebAccessibility & node,base::hash_set<int> * ids)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
SetUpInProcessBrowserTestFixture()65 void 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
TearDownInProcessBrowserTestFixture()76 void 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*.
GetAttr(const WebAccessibility & node,const WebAccessibility::Attribute attr)84 std::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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformWebpageAccessibility)93 IN_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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformUnselectedEditableTextAccessibility)155 IN_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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformSelectedEditableTextAccessibility)186 IN_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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformMultipleInheritanceAccessibility)213 IN_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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformMultipleInheritanceAccessibility2)249 IN_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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformIframeAccessibility)271 IN_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
IN_PROC_BROWSER_TEST_F(RendererAccessibilityBrowserTest,CrossPlatformDuplicateChildrenAccessibility)320 IN_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