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: 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 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 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*. 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 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 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 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 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 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 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 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