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 34 namespace content { 35 36 class 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 79 void CrossPlatformAccessibilityBrowserTest::SetUpInProcessBrowserTestFixture() { 80 #if defined(OS_WIN) 81 ui::win::CreateATLModuleIfNeeded(); 82 com_initializer_.reset(new base::win::ScopedCOMInitializer()); 83 #endif 84 } 85 86 void 87 CrossPlatformAccessibilityBrowserTest::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. 95 std::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. 108 int 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. 121 bool 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 133 IN_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 203 IN_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 235 IN_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 263 IN_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 309 IN_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 333 IN_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 387 IN_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 405 IN_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 480 IN_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