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 "content/renderer/accessibility/accessibility_node_serializer.h" 6 7 #include <set> 8 9 #include "base/strings/string_number_conversions.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "third_party/WebKit/public/platform/WebRect.h" 13 #include "third_party/WebKit/public/platform/WebSize.h" 14 #include "third_party/WebKit/public/platform/WebString.h" 15 #include "third_party/WebKit/public/platform/WebVector.h" 16 #include "third_party/WebKit/public/web/WebAccessibilityObject.h" 17 #include "third_party/WebKit/public/web/WebAccessibilityRole.h" 18 #include "third_party/WebKit/public/web/WebDocument.h" 19 #include "third_party/WebKit/public/web/WebDocumentType.h" 20 #include "third_party/WebKit/public/web/WebElement.h" 21 #include "third_party/WebKit/public/web/WebFormControlElement.h" 22 #include "third_party/WebKit/public/web/WebFrame.h" 23 #include "third_party/WebKit/public/web/WebInputElement.h" 24 #include "third_party/WebKit/public/web/WebNode.h" 25 26 using WebKit::WebAccessibilityRole; 27 using WebKit::WebAccessibilityObject; 28 using WebKit::WebDocument; 29 using WebKit::WebDocumentType; 30 using WebKit::WebElement; 31 using WebKit::WebNode; 32 using WebKit::WebVector; 33 34 namespace content { 35 namespace { 36 37 // Returns true if |ancestor| is the first unignored parent of |child|, 38 // which means that when walking up the parent chain from |child|, 39 // |ancestor| is the *first* ancestor that isn't marked as 40 // accessibilityIsIgnored(). 41 bool IsParentUnignoredOf(const WebAccessibilityObject& ancestor, 42 const WebAccessibilityObject& child) { 43 WebAccessibilityObject parent = child.parentObject(); 44 while (!parent.isDetached() && parent.accessibilityIsIgnored()) 45 parent = parent.parentObject(); 46 return parent.equals(ancestor); 47 } 48 49 // Provides a conversion between the WebKit::WebAccessibilityRole and a role 50 // supported on the Browser side. Listed alphabetically by the 51 // WebKit::WebAccessibilityRole (except for default role). 52 AccessibilityNodeData::Role ConvertRole(WebKit::WebAccessibilityRole role) { 53 switch (role) { 54 case WebKit::WebAccessibilityRoleAnnotation: 55 return AccessibilityNodeData::ROLE_ANNOTATION; 56 case WebKit::WebAccessibilityRoleApplication: 57 return AccessibilityNodeData::ROLE_APPLICATION; 58 case WebKit::WebAccessibilityRoleApplicationAlert: 59 return AccessibilityNodeData::ROLE_ALERT; 60 case WebKit::WebAccessibilityRoleApplicationAlertDialog: 61 return AccessibilityNodeData::ROLE_ALERT_DIALOG; 62 case WebKit::WebAccessibilityRoleApplicationDialog: 63 return AccessibilityNodeData::ROLE_DIALOG; 64 case WebKit::WebAccessibilityRoleApplicationLog: 65 return AccessibilityNodeData::ROLE_LOG; 66 case WebKit::WebAccessibilityRoleApplicationMarquee: 67 return AccessibilityNodeData::ROLE_MARQUEE; 68 case WebKit::WebAccessibilityRoleApplicationStatus: 69 return AccessibilityNodeData::ROLE_STATUS; 70 case WebKit::WebAccessibilityRoleApplicationTimer: 71 return AccessibilityNodeData::ROLE_TIMER; 72 case WebKit::WebAccessibilityRoleBrowser: 73 return AccessibilityNodeData::ROLE_BROWSER; 74 case WebKit::WebAccessibilityRoleBusyIndicator: 75 return AccessibilityNodeData::ROLE_BUSY_INDICATOR; 76 case WebKit::WebAccessibilityRoleButton: 77 return AccessibilityNodeData::ROLE_BUTTON; 78 case WebKit::WebAccessibilityRoleCanvas: 79 return AccessibilityNodeData::ROLE_CANVAS; 80 case WebKit::WebAccessibilityRoleCell: 81 return AccessibilityNodeData::ROLE_CELL; 82 case WebKit::WebAccessibilityRoleCheckBox: 83 return AccessibilityNodeData::ROLE_CHECKBOX; 84 case WebKit::WebAccessibilityRoleColorWell: 85 return AccessibilityNodeData::ROLE_COLOR_WELL; 86 case WebKit::WebAccessibilityRoleColumn: 87 return AccessibilityNodeData::ROLE_COLUMN; 88 case WebKit::WebAccessibilityRoleColumnHeader: 89 return AccessibilityNodeData::ROLE_COLUMN_HEADER; 90 case WebKit::WebAccessibilityRoleComboBox: 91 return AccessibilityNodeData::ROLE_COMBO_BOX; 92 case WebKit::WebAccessibilityRoleDefinition: 93 return AccessibilityNodeData::ROLE_DEFINITION; 94 case WebKit::WebAccessibilityRoleDescriptionListTerm: 95 return AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM; 96 case WebKit::WebAccessibilityRoleDescriptionListDetail: 97 return AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL; 98 case WebKit::WebAccessibilityRoleDirectory: 99 return AccessibilityNodeData::ROLE_DIRECTORY; 100 case WebKit::WebAccessibilityRoleDisclosureTriangle: 101 return AccessibilityNodeData::ROLE_DISCLOSURE_TRIANGLE; 102 case WebKit::WebAccessibilityRoleDiv: 103 return AccessibilityNodeData::ROLE_DIV; 104 case WebKit::WebAccessibilityRoleDocument: 105 return AccessibilityNodeData::ROLE_DOCUMENT; 106 case WebKit::WebAccessibilityRoleDocumentArticle: 107 return AccessibilityNodeData::ROLE_ARTICLE; 108 case WebKit::WebAccessibilityRoleDocumentMath: 109 return AccessibilityNodeData::ROLE_MATH; 110 case WebKit::WebAccessibilityRoleDocumentNote: 111 return AccessibilityNodeData::ROLE_NOTE; 112 case WebKit::WebAccessibilityRoleDocumentRegion: 113 return AccessibilityNodeData::ROLE_REGION; 114 case WebKit::WebAccessibilityRoleDrawer: 115 return AccessibilityNodeData::ROLE_DRAWER; 116 case WebKit::WebAccessibilityRoleEditableText: 117 return AccessibilityNodeData::ROLE_EDITABLE_TEXT; 118 case WebKit::WebAccessibilityRoleFooter: 119 return AccessibilityNodeData::ROLE_FOOTER; 120 case WebKit::WebAccessibilityRoleForm: 121 return AccessibilityNodeData::ROLE_FORM; 122 case WebKit::WebAccessibilityRoleGrid: 123 return AccessibilityNodeData::ROLE_GRID; 124 case WebKit::WebAccessibilityRoleGroup: 125 return AccessibilityNodeData::ROLE_GROUP; 126 case WebKit::WebAccessibilityRoleGrowArea: 127 return AccessibilityNodeData::ROLE_GROW_AREA; 128 case WebKit::WebAccessibilityRoleHeading: 129 return AccessibilityNodeData::ROLE_HEADING; 130 case WebKit::WebAccessibilityRoleHelpTag: 131 return AccessibilityNodeData::ROLE_HELP_TAG; 132 case WebKit::WebAccessibilityRoleHorizontalRule: 133 return AccessibilityNodeData::ROLE_HORIZONTAL_RULE; 134 case WebKit::WebAccessibilityRoleIgnored: 135 return AccessibilityNodeData::ROLE_IGNORED; 136 case WebKit::WebAccessibilityRoleImage: 137 return AccessibilityNodeData::ROLE_IMAGE; 138 case WebKit::WebAccessibilityRoleImageMap: 139 return AccessibilityNodeData::ROLE_IMAGE_MAP; 140 case WebKit::WebAccessibilityRoleImageMapLink: 141 return AccessibilityNodeData::ROLE_IMAGE_MAP_LINK; 142 case WebKit::WebAccessibilityRoleIncrementor: 143 return AccessibilityNodeData::ROLE_INCREMENTOR; 144 case WebKit::WebAccessibilityRoleLabel: 145 return AccessibilityNodeData::ROLE_LABEL; 146 case WebKit::WebAccessibilityRoleLandmarkApplication: 147 return AccessibilityNodeData::ROLE_LANDMARK_APPLICATION; 148 case WebKit::WebAccessibilityRoleLandmarkBanner: 149 return AccessibilityNodeData::ROLE_LANDMARK_BANNER; 150 case WebKit::WebAccessibilityRoleLandmarkComplementary: 151 return AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY; 152 case WebKit::WebAccessibilityRoleLandmarkContentInfo: 153 return AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO; 154 case WebKit::WebAccessibilityRoleLandmarkMain: 155 return AccessibilityNodeData::ROLE_LANDMARK_MAIN; 156 case WebKit::WebAccessibilityRoleLandmarkNavigation: 157 return AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION; 158 case WebKit::WebAccessibilityRoleLandmarkSearch: 159 return AccessibilityNodeData::ROLE_LANDMARK_SEARCH; 160 case WebKit::WebAccessibilityRoleLink: 161 return AccessibilityNodeData::ROLE_LINK; 162 case WebKit::WebAccessibilityRoleList: 163 return AccessibilityNodeData::ROLE_LIST; 164 case WebKit::WebAccessibilityRoleListBox: 165 return AccessibilityNodeData::ROLE_LISTBOX; 166 case WebKit::WebAccessibilityRoleListBoxOption: 167 return AccessibilityNodeData::ROLE_LISTBOX_OPTION; 168 case WebKit::WebAccessibilityRoleListItem: 169 return AccessibilityNodeData::ROLE_LIST_ITEM; 170 case WebKit::WebAccessibilityRoleListMarker: 171 return AccessibilityNodeData::ROLE_LIST_MARKER; 172 case WebKit::WebAccessibilityRoleMatte: 173 return AccessibilityNodeData::ROLE_MATTE; 174 case WebKit::WebAccessibilityRoleMenu: 175 return AccessibilityNodeData::ROLE_MENU; 176 case WebKit::WebAccessibilityRoleMenuBar: 177 return AccessibilityNodeData::ROLE_MENU_BAR; 178 case WebKit::WebAccessibilityRoleMenuButton: 179 return AccessibilityNodeData::ROLE_MENU_BUTTON; 180 case WebKit::WebAccessibilityRoleMenuItem: 181 return AccessibilityNodeData::ROLE_MENU_ITEM; 182 case WebKit::WebAccessibilityRoleMenuListOption: 183 return AccessibilityNodeData::ROLE_MENU_LIST_OPTION; 184 case WebKit::WebAccessibilityRoleMenuListPopup: 185 return AccessibilityNodeData::ROLE_MENU_LIST_POPUP; 186 case WebKit::WebAccessibilityRoleOutline: 187 return AccessibilityNodeData::ROLE_OUTLINE; 188 case WebKit::WebAccessibilityRoleParagraph: 189 return AccessibilityNodeData::ROLE_PARAGRAPH; 190 case WebKit::WebAccessibilityRolePopUpButton: 191 return AccessibilityNodeData::ROLE_POPUP_BUTTON; 192 case WebKit::WebAccessibilityRolePresentational: 193 return AccessibilityNodeData::ROLE_PRESENTATIONAL; 194 case WebKit::WebAccessibilityRoleProgressIndicator: 195 return AccessibilityNodeData::ROLE_PROGRESS_INDICATOR; 196 case WebKit::WebAccessibilityRoleRadioButton: 197 return AccessibilityNodeData::ROLE_RADIO_BUTTON; 198 case WebKit::WebAccessibilityRoleRadioGroup: 199 return AccessibilityNodeData::ROLE_RADIO_GROUP; 200 case WebKit::WebAccessibilityRoleRow: 201 return AccessibilityNodeData::ROLE_ROW; 202 case WebKit::WebAccessibilityRoleRowHeader: 203 return AccessibilityNodeData::ROLE_ROW_HEADER; 204 case WebKit::WebAccessibilityRoleRuler: 205 return AccessibilityNodeData::ROLE_RULER; 206 case WebKit::WebAccessibilityRoleRulerMarker: 207 return AccessibilityNodeData::ROLE_RULER_MARKER; 208 case WebKit::WebAccessibilityRoleScrollArea: 209 return AccessibilityNodeData::ROLE_SCROLLAREA; 210 case WebKit::WebAccessibilityRoleScrollBar: 211 return AccessibilityNodeData::ROLE_SCROLLBAR; 212 case WebKit::WebAccessibilityRoleSheet: 213 return AccessibilityNodeData::ROLE_SHEET; 214 case WebKit::WebAccessibilityRoleSlider: 215 return AccessibilityNodeData::ROLE_SLIDER; 216 case WebKit::WebAccessibilityRoleSliderThumb: 217 return AccessibilityNodeData::ROLE_SLIDER_THUMB; 218 case WebKit::WebAccessibilityRoleSpinButton: 219 return AccessibilityNodeData::ROLE_SPIN_BUTTON; 220 case WebKit::WebAccessibilityRoleSpinButtonPart: 221 return AccessibilityNodeData::ROLE_SPIN_BUTTON_PART; 222 case WebKit::WebAccessibilityRoleSplitGroup: 223 return AccessibilityNodeData::ROLE_SPLIT_GROUP; 224 case WebKit::WebAccessibilityRoleSplitter: 225 return AccessibilityNodeData::ROLE_SPLITTER; 226 case WebKit::WebAccessibilityRoleStaticText: 227 return AccessibilityNodeData::ROLE_STATIC_TEXT; 228 case WebKit::WebAccessibilityRoleSVGRoot: 229 return AccessibilityNodeData::ROLE_SVG_ROOT; 230 case WebKit::WebAccessibilityRoleSystemWide: 231 return AccessibilityNodeData::ROLE_SYSTEM_WIDE; 232 case WebKit::WebAccessibilityRoleTab: 233 return AccessibilityNodeData::ROLE_TAB; 234 case WebKit::WebAccessibilityRoleTabGroup: 235 return AccessibilityNodeData::ROLE_TAB_GROUP_UNUSED; 236 case WebKit::WebAccessibilityRoleTabList: 237 return AccessibilityNodeData::ROLE_TAB_LIST; 238 case WebKit::WebAccessibilityRoleTabPanel: 239 return AccessibilityNodeData::ROLE_TAB_PANEL; 240 case WebKit::WebAccessibilityRoleTable: 241 return AccessibilityNodeData::ROLE_TABLE; 242 case WebKit::WebAccessibilityRoleTableHeaderContainer: 243 return AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER; 244 case WebKit::WebAccessibilityRoleTextArea: 245 return AccessibilityNodeData::ROLE_TEXTAREA; 246 case WebKit::WebAccessibilityRoleTextField: 247 return AccessibilityNodeData::ROLE_TEXT_FIELD; 248 case WebKit::WebAccessibilityRoleToggleButton: 249 return AccessibilityNodeData::ROLE_TOGGLE_BUTTON; 250 case WebKit::WebAccessibilityRoleToolbar: 251 return AccessibilityNodeData::ROLE_TOOLBAR; 252 case WebKit::WebAccessibilityRoleTreeGrid: 253 return AccessibilityNodeData::ROLE_TREE_GRID; 254 case WebKit::WebAccessibilityRoleTreeItemRole: 255 return AccessibilityNodeData::ROLE_TREE_ITEM; 256 case WebKit::WebAccessibilityRoleTreeRole: 257 return AccessibilityNodeData::ROLE_TREE; 258 case WebKit::WebAccessibilityRoleUserInterfaceTooltip: 259 return AccessibilityNodeData::ROLE_TOOLTIP; 260 case WebKit::WebAccessibilityRoleValueIndicator: 261 return AccessibilityNodeData::ROLE_VALUE_INDICATOR; 262 case WebKit::WebAccessibilityRoleWebArea: 263 return AccessibilityNodeData::ROLE_WEB_AREA; 264 case WebKit::WebAccessibilityRoleWebCoreLink: 265 return AccessibilityNodeData::ROLE_WEBCORE_LINK; 266 case WebKit::WebAccessibilityRoleWindow: 267 return AccessibilityNodeData::ROLE_WINDOW; 268 269 default: 270 return AccessibilityNodeData::ROLE_UNKNOWN; 271 } 272 } 273 274 // Provides a conversion between the WebAccessibilityObject state 275 // accessors and a state bitmask that can be serialized and sent to the 276 // Browser process. Rare state are sent as boolean attributes instead. 277 uint32 ConvertState(const WebAccessibilityObject& o) { 278 uint32 state = 0; 279 if (o.isChecked()) 280 state |= (1 << AccessibilityNodeData::STATE_CHECKED); 281 282 if (o.isCollapsed()) 283 state |= (1 << AccessibilityNodeData::STATE_COLLAPSED); 284 285 if (o.canSetFocusAttribute()) 286 state |= (1 << AccessibilityNodeData::STATE_FOCUSABLE); 287 288 if (o.isFocused()) 289 state |= (1 << AccessibilityNodeData::STATE_FOCUSED); 290 291 if (o.roleValue() == WebKit::WebAccessibilityRolePopUpButton || 292 o.ariaHasPopup()) { 293 state |= (1 << AccessibilityNodeData::STATE_HASPOPUP); 294 if (!o.isCollapsed()) 295 state |= (1 << AccessibilityNodeData::STATE_EXPANDED); 296 } 297 298 if (o.isHovered()) 299 state |= (1 << AccessibilityNodeData::STATE_HOTTRACKED); 300 301 if (o.isIndeterminate()) 302 state |= (1 << AccessibilityNodeData::STATE_INDETERMINATE); 303 304 if (!o.isVisible()) 305 state |= (1 << AccessibilityNodeData::STATE_INVISIBLE); 306 307 if (o.isLinked()) 308 state |= (1 << AccessibilityNodeData::STATE_LINKED); 309 310 if (o.isMultiSelectable()) 311 state |= (1 << AccessibilityNodeData::STATE_MULTISELECTABLE); 312 313 if (o.isOffScreen()) 314 state |= (1 << AccessibilityNodeData::STATE_OFFSCREEN); 315 316 if (o.isPressed()) 317 state |= (1 << AccessibilityNodeData::STATE_PRESSED); 318 319 if (o.isPasswordField()) 320 state |= (1 << AccessibilityNodeData::STATE_PROTECTED); 321 322 if (o.isReadOnly()) 323 state |= (1 << AccessibilityNodeData::STATE_READONLY); 324 325 if (o.isRequired()) 326 state |= (1 << AccessibilityNodeData::STATE_REQUIRED); 327 328 if (o.canSetSelectedAttribute()) 329 state |= (1 << AccessibilityNodeData::STATE_SELECTABLE); 330 331 if (o.isSelected()) 332 state |= (1 << AccessibilityNodeData::STATE_SELECTED); 333 334 if (o.isVisited()) 335 state |= (1 << AccessibilityNodeData::STATE_TRAVERSED); 336 337 if (!o.isEnabled()) 338 state |= (1 << AccessibilityNodeData::STATE_UNAVAILABLE); 339 340 if (o.isVertical()) 341 state |= (1 << AccessibilityNodeData::STATE_VERTICAL); 342 343 if (o.isVisited()) 344 state |= (1 << AccessibilityNodeData::STATE_VISITED); 345 346 return state; 347 } 348 349 } // Anonymous namespace 350 351 void SerializeAccessibilityNode( 352 const WebAccessibilityObject& src, 353 AccessibilityNodeData* dst) { 354 dst->name = src.title(); 355 dst->role = ConvertRole(src.roleValue()); 356 dst->state = ConvertState(src); 357 dst->location = src.boundingBoxRect(); 358 dst->id = src.axID(); 359 360 if (src.valueDescription().length()) 361 dst->value = src.valueDescription(); 362 else 363 dst->value = src.stringValue(); 364 365 if (dst->role == AccessibilityNodeData::ROLE_COLOR_WELL) { 366 int r, g, b; 367 src.colorValue(r, g, b); 368 dst->int_attributes[dst->ATTR_COLOR_VALUE_RED] = r; 369 dst->int_attributes[dst->ATTR_COLOR_VALUE_GREEN] = g; 370 dst->int_attributes[dst->ATTR_COLOR_VALUE_BLUE] = b; 371 } 372 373 if (src.accessKey().length()) 374 dst->string_attributes[dst->ATTR_ACCESS_KEY] = src.accessKey(); 375 if (src.actionVerb().length()) 376 dst->string_attributes[dst->ATTR_ACTION] = src.actionVerb(); 377 if (src.isAriaReadOnly()) 378 dst->bool_attributes[dst->ATTR_ARIA_READONLY] = true; 379 if (src.isButtonStateMixed()) 380 dst->bool_attributes[dst->ATTR_BUTTON_MIXED] = true; 381 if (src.canSetValueAttribute()) 382 dst->bool_attributes[dst->ATTR_CAN_SET_VALUE] = true; 383 if (src.accessibilityDescription().length()) 384 dst->string_attributes[dst->ATTR_DESCRIPTION] = 385 src.accessibilityDescription(); 386 if (src.hasComputedStyle()) 387 dst->string_attributes[dst->ATTR_DISPLAY] = src.computedStyleDisplay(); 388 if (src.helpText().length()) 389 dst->string_attributes[dst->ATTR_HELP] = src.helpText(); 390 if (src.keyboardShortcut().length()) 391 dst->string_attributes[dst->ATTR_SHORTCUT] = src.keyboardShortcut(); 392 if (!src.titleUIElement().isDetached()) { 393 dst->int_attributes[dst->ATTR_TITLE_UI_ELEMENT] = 394 src.titleUIElement().axID(); 395 } 396 if (!src.url().isEmpty()) 397 dst->string_attributes[dst->ATTR_URL] = src.url().spec().utf16(); 398 399 if (dst->role == dst->ROLE_HEADING) 400 dst->int_attributes[dst->ATTR_HIERARCHICAL_LEVEL] = src.headingLevel(); 401 else if ((dst->role == dst->ROLE_TREE_ITEM || dst->role == dst->ROLE_ROW) && 402 src.hierarchicalLevel() > 0) { 403 dst->int_attributes[dst->ATTR_HIERARCHICAL_LEVEL] = src.hierarchicalLevel(); 404 } 405 406 // Treat the active list box item as focused. 407 if (dst->role == dst->ROLE_LISTBOX_OPTION && src.isSelectedOptionActive()) 408 dst->state |= (1 << AccessibilityNodeData::STATE_FOCUSED); 409 410 if (src.canvasHasFallbackContent()) 411 dst->role = AccessibilityNodeData::ROLE_CANVAS_WITH_FALLBACK_CONTENT; 412 413 WebNode node = src.node(); 414 bool is_iframe = false; 415 416 if (!node.isNull() && node.isElementNode()) { 417 WebElement element = node.to<WebElement>(); 418 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME")); 419 420 if (LowerCaseEqualsASCII(element.getAttribute("aria-expanded"), "true")) 421 dst->state |= (1 << AccessibilityNodeData::STATE_EXPANDED); 422 423 // TODO(ctguil): The tagName in WebKit is lower cased but 424 // HTMLElement::nodeName calls localNameUpper. Consider adding 425 // a WebElement method that returns the original lower cased tagName. 426 dst->string_attributes[dst->ATTR_HTML_TAG] = 427 StringToLowerASCII(string16(element.tagName())); 428 for (unsigned i = 0; i < element.attributeCount(); ++i) { 429 string16 name = StringToLowerASCII(string16( 430 element.attributeLocalName(i))); 431 string16 value = element.attributeValue(i); 432 dst->html_attributes.push_back( 433 std::pair<string16, string16>(name, value)); 434 } 435 436 if (dst->role == dst->ROLE_EDITABLE_TEXT || 437 dst->role == dst->ROLE_TEXTAREA || 438 dst->role == dst->ROLE_TEXT_FIELD) { 439 dst->int_attributes[dst->ATTR_TEXT_SEL_START] = src.selectionStart(); 440 dst->int_attributes[dst->ATTR_TEXT_SEL_END] = src.selectionEnd(); 441 442 WebVector<int> src_line_breaks; 443 src.lineBreaks(src_line_breaks); 444 dst->line_breaks.reserve(src_line_breaks.size()); 445 for (size_t i = 0; i < src_line_breaks.size(); ++i) 446 dst->line_breaks.push_back(src_line_breaks[i]); 447 } 448 449 // ARIA role. 450 if (element.hasAttribute("role")) { 451 dst->string_attributes[dst->ATTR_ROLE] = element.getAttribute("role"); 452 } 453 454 // Live region attributes 455 if (element.hasAttribute("aria-atomic")) { 456 dst->bool_attributes[dst->ATTR_LIVE_ATOMIC] = 457 LowerCaseEqualsASCII(element.getAttribute("aria-atomic"), "true"); 458 } 459 if (element.hasAttribute("aria-busy")) { 460 dst->bool_attributes[dst->ATTR_LIVE_BUSY] = 461 LowerCaseEqualsASCII(element.getAttribute("aria-busy"), "true"); 462 } 463 if (element.hasAttribute("aria-live")) { 464 dst->string_attributes[dst->ATTR_LIVE_STATUS] = 465 element.getAttribute("aria-live"); 466 } 467 if (element.hasAttribute("aria-relevant")) { 468 dst->string_attributes[dst->ATTR_LIVE_RELEVANT] = 469 element.getAttribute("aria-relevant"); 470 } 471 } 472 473 // Walk up the parent chain to set live region attributes of containers 474 475 WebAccessibilityObject container_accessible = src; 476 while (!container_accessible.isDetached()) { 477 WebNode container_node = container_accessible.node(); 478 if (!container_node.isNull() && container_node.isElementNode()) { 479 WebElement container_elem = 480 container_node.to<WebElement>(); 481 if (container_elem.hasAttribute("aria-atomic") && 482 dst->bool_attributes.find(dst->ATTR_CONTAINER_LIVE_ATOMIC) == 483 dst->bool_attributes.end()) { 484 dst->bool_attributes[dst->ATTR_CONTAINER_LIVE_ATOMIC] = 485 LowerCaseEqualsASCII(container_elem.getAttribute("aria-atomic"), 486 "true"); 487 } 488 if (container_elem.hasAttribute("aria-busy") && 489 dst->bool_attributes.find(dst->ATTR_CONTAINER_LIVE_BUSY) == 490 dst->bool_attributes.end()) { 491 dst->bool_attributes[dst->ATTR_CONTAINER_LIVE_BUSY] = 492 LowerCaseEqualsASCII(container_elem.getAttribute("aria-busy"), 493 "true"); 494 } 495 if (container_elem.hasAttribute("aria-live") && 496 dst->string_attributes.find(dst->ATTR_CONTAINER_LIVE_STATUS) == 497 dst->string_attributes.end()) { 498 dst->string_attributes[dst->ATTR_CONTAINER_LIVE_STATUS] = 499 container_elem.getAttribute("aria-live"); 500 } 501 if (container_elem.hasAttribute("aria-relevant") && 502 dst->string_attributes.find(dst->ATTR_CONTAINER_LIVE_RELEVANT) == 503 dst->string_attributes.end()) { 504 dst->string_attributes[dst->ATTR_CONTAINER_LIVE_RELEVANT] = 505 container_elem.getAttribute("aria-relevant"); 506 } 507 } 508 container_accessible = container_accessible.parentObject(); 509 } 510 511 if (dst->role == dst->ROLE_PROGRESS_INDICATOR || 512 dst->role == dst->ROLE_SCROLLBAR || 513 dst->role == dst->ROLE_SLIDER || 514 dst->role == dst->ROLE_SPIN_BUTTON) { 515 dst->float_attributes[dst->ATTR_VALUE_FOR_RANGE] = src.valueForRange(); 516 dst->float_attributes[dst->ATTR_MAX_VALUE_FOR_RANGE] = 517 src.maxValueForRange(); 518 dst->float_attributes[dst->ATTR_MIN_VALUE_FOR_RANGE] = 519 src.minValueForRange(); 520 } 521 522 if (dst->role == dst->ROLE_DOCUMENT || 523 dst->role == dst->ROLE_WEB_AREA) { 524 dst->string_attributes[dst->ATTR_HTML_TAG] = ASCIIToUTF16("#document"); 525 const WebDocument& document = src.document(); 526 if (dst->name.empty()) 527 dst->name = document.title(); 528 dst->string_attributes[dst->ATTR_DOC_TITLE] = document.title(); 529 dst->string_attributes[dst->ATTR_DOC_URL] = document.url().spec().utf16(); 530 dst->string_attributes[dst->ATTR_DOC_MIMETYPE] = 531 ASCIIToUTF16(document.isXHTMLDocument() ? "text/xhtml" : "text/html"); 532 dst->bool_attributes[dst->ATTR_DOC_LOADED] = src.isLoaded(); 533 dst->float_attributes[dst->ATTR_DOC_LOADING_PROGRESS] = 534 src.estimatedLoadingProgress(); 535 536 const WebDocumentType& doctype = document.doctype(); 537 if (!doctype.isNull()) 538 dst->string_attributes[dst->ATTR_DOC_DOCTYPE] = doctype.name(); 539 540 const gfx::Size& scroll_offset = document.frame()->scrollOffset(); 541 dst->int_attributes[dst->ATTR_SCROLL_X] = scroll_offset.width(); 542 dst->int_attributes[dst->ATTR_SCROLL_Y] = scroll_offset.height(); 543 544 const gfx::Size& min_offset = document.frame()->minimumScrollOffset(); 545 dst->int_attributes[dst->ATTR_SCROLL_X_MIN] = min_offset.width(); 546 dst->int_attributes[dst->ATTR_SCROLL_Y_MIN] = min_offset.height(); 547 548 const gfx::Size& max_offset = document.frame()->maximumScrollOffset(); 549 dst->int_attributes[dst->ATTR_SCROLL_X_MAX] = max_offset.width(); 550 dst->int_attributes[dst->ATTR_SCROLL_Y_MAX] = max_offset.height(); 551 } 552 553 if (dst->role == dst->ROLE_TABLE) { 554 int column_count = src.columnCount(); 555 int row_count = src.rowCount(); 556 if (column_count > 0 && row_count > 0) { 557 std::set<int> unique_cell_id_set; 558 dst->int_attributes[dst->ATTR_TABLE_COLUMN_COUNT] = column_count; 559 dst->int_attributes[dst->ATTR_TABLE_ROW_COUNT] = row_count; 560 WebAccessibilityObject header = src.headerContainerObject(); 561 if (!header.isDetached()) 562 dst->int_attributes[dst->ATTR_TABLE_HEADER_ID] = header.axID(); 563 for (int i = 0; i < column_count * row_count; ++i) { 564 WebAccessibilityObject cell = src.cellForColumnAndRow( 565 i % column_count, i / column_count); 566 int cell_id = -1; 567 if (!cell.isDetached()) { 568 cell_id = cell.axID(); 569 if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) { 570 unique_cell_id_set.insert(cell_id); 571 dst->unique_cell_ids.push_back(cell_id); 572 } 573 } 574 dst->cell_ids.push_back(cell_id); 575 } 576 } 577 } 578 579 if (dst->role == dst->ROLE_ROW) { 580 dst->int_attributes[dst->ATTR_TABLE_ROW_INDEX] = src.rowIndex(); 581 WebAccessibilityObject header = src.rowHeader(); 582 if (!header.isDetached()) 583 dst->int_attributes[dst->ATTR_TABLE_ROW_HEADER_ID] = header.axID(); 584 } 585 586 if (dst->role == dst->ROLE_COLUMN) { 587 dst->int_attributes[dst->ATTR_TABLE_COLUMN_INDEX] = src.columnIndex(); 588 WebAccessibilityObject header = src.columnHeader(); 589 if (!header.isDetached()) 590 dst->int_attributes[dst->ATTR_TABLE_COLUMN_HEADER_ID] = header.axID(); 591 } 592 593 if (dst->role == dst->ROLE_CELL || 594 dst->role == dst->ROLE_ROW_HEADER || 595 dst->role == dst->ROLE_COLUMN_HEADER) { 596 dst->int_attributes[dst->ATTR_TABLE_CELL_COLUMN_INDEX] = 597 src.cellColumnIndex(); 598 dst->int_attributes[dst->ATTR_TABLE_CELL_COLUMN_SPAN] = 599 src.cellColumnSpan(); 600 dst->int_attributes[dst->ATTR_TABLE_CELL_ROW_INDEX] = src.cellRowIndex(); 601 dst->int_attributes[dst->ATTR_TABLE_CELL_ROW_SPAN] = src.cellRowSpan(); 602 } 603 604 // Add the ids of *indirect* children - those who are children of this node, 605 // but whose parent is *not* this node. One example is a table 606 // cell, which is a child of both a row and a column. Because the cell's 607 // parent is the row, the row adds it as a child, and the column adds it 608 // as an indirect child. 609 int child_count = src.childCount(); 610 for (int i = 0; i < child_count; ++i) { 611 WebAccessibilityObject child = src.childAt(i); 612 if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child)) 613 dst->indirect_child_ids.push_back(child.axID()); 614 } 615 } 616 617 bool ShouldIncludeChildNode( 618 const WebAccessibilityObject& parent, 619 const WebAccessibilityObject& child) { 620 switch(parent.roleValue()) { 621 case WebKit::WebAccessibilityRoleSlider: 622 case WebKit::WebAccessibilityRoleEditableText: 623 case WebKit::WebAccessibilityRoleTextArea: 624 case WebKit::WebAccessibilityRoleTextField: 625 return false; 626 default: 627 break; 628 } 629 630 // The child may be invalid due to issues in webkit accessibility code. 631 // Don't add children that are invalid thus preventing a crash. 632 // https://bugs.webkit.org/show_bug.cgi?id=44149 633 // TODO(ctguil): We may want to remove this check as webkit stabilizes. 634 if (child.isDetached()) 635 return false; 636 637 // Skip children whose parent isn't this - see indirect_child_ids, above. 638 // As an exception, include children of an iframe element. 639 bool is_iframe = false; 640 WebNode node = parent.node(); 641 if (!node.isNull() && node.isElementNode()) { 642 WebElement element = node.to<WebElement>(); 643 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME")); 644 } 645 646 return (is_iframe || IsParentUnignoredOf(parent, child)); 647 } 648 649 } // namespace content 650