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