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 "webkit/glue/webaccessibility.h" 6 7 #include <set> 8 9 #include "base/string_number_conversions.h" 10 #include "base/string_util.h" 11 #include "base/utf_string_conversions.h" 12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityCache.h" 13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObject.h" 14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityRole.h" 15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAttribute.h" 16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" 17 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocumentType.h" 18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" 19 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement.h" 20 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" 21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" 22 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNamedNodeMap.h" 23 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" 24 #include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" 25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" 26 #include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" 27 28 using WebKit::WebAccessibilityCache; 29 using WebKit::WebAccessibilityRole; 30 using WebKit::WebAccessibilityObject; 31 32 namespace webkit_glue { 33 34 // Provides a conversion between the WebKit::WebAccessibilityRole and a role 35 // supported on the Browser side. Listed alphabetically by the 36 // WebAccessibilityRole (except for default role). 37 WebAccessibility::Role ConvertRole(WebKit::WebAccessibilityRole role) { 38 switch (role) { 39 case WebKit::WebAccessibilityRoleAnnotation: 40 return WebAccessibility::ROLE_ANNOTATION; 41 case WebKit::WebAccessibilityRoleApplication: 42 return WebAccessibility::ROLE_APPLICATION; 43 case WebKit::WebAccessibilityRoleApplicationAlert: 44 return WebAccessibility::ROLE_ALERT; 45 case WebKit::WebAccessibilityRoleApplicationAlertDialog: 46 return WebAccessibility::ROLE_ALERT_DIALOG; 47 case WebKit::WebAccessibilityRoleApplicationDialog: 48 return WebAccessibility::ROLE_DIALOG; 49 case WebKit::WebAccessibilityRoleApplicationLog: 50 return WebAccessibility::ROLE_LOG; 51 case WebKit::WebAccessibilityRoleApplicationMarquee: 52 return WebAccessibility::ROLE_MARQUEE; 53 case WebKit::WebAccessibilityRoleApplicationStatus: 54 return WebAccessibility::ROLE_STATUS; 55 case WebKit::WebAccessibilityRoleApplicationTimer: 56 return WebAccessibility::ROLE_TIMER; 57 case WebKit::WebAccessibilityRoleBrowser: 58 return WebAccessibility::ROLE_BROWSER; 59 case WebKit::WebAccessibilityRoleBusyIndicator: 60 return WebAccessibility::ROLE_BUSY_INDICATOR; 61 case WebKit::WebAccessibilityRoleButton: 62 return WebAccessibility::ROLE_BUTTON; 63 case WebKit::WebAccessibilityRoleCell: 64 return WebAccessibility::ROLE_CELL; 65 case WebKit::WebAccessibilityRoleCheckBox: 66 return WebAccessibility::ROLE_CHECKBOX; 67 case WebKit::WebAccessibilityRoleColorWell: 68 return WebAccessibility::ROLE_COLOR_WELL; 69 case WebKit::WebAccessibilityRoleColumn: 70 return WebAccessibility::ROLE_COLUMN; 71 case WebKit::WebAccessibilityRoleColumnHeader: 72 return WebAccessibility::ROLE_COLUMN_HEADER; 73 case WebKit::WebAccessibilityRoleComboBox: 74 return WebAccessibility::ROLE_COMBO_BOX; 75 case WebKit::WebAccessibilityRoleDefinitionListDefinition: 76 return WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION; 77 case WebKit::WebAccessibilityRoleDefinitionListTerm: 78 return WebAccessibility::ROLE_DEFINITION_LIST_TERM; 79 case WebKit::WebAccessibilityRoleDirectory: 80 return WebAccessibility::ROLE_DIRECTORY; 81 case WebKit::WebAccessibilityRoleDisclosureTriangle: 82 return WebAccessibility::ROLE_DISCLOSURE_TRIANGLE; 83 case WebKit::WebAccessibilityRoleDocument: 84 return WebAccessibility::ROLE_DOCUMENT; 85 case WebKit::WebAccessibilityRoleDocumentArticle: 86 return WebAccessibility::ROLE_ARTICLE; 87 case WebKit::WebAccessibilityRoleDocumentMath: 88 return WebAccessibility::ROLE_MATH; 89 case WebKit::WebAccessibilityRoleDocumentNote: 90 return WebAccessibility::ROLE_NOTE; 91 case WebKit::WebAccessibilityRoleDocumentRegion: 92 return WebAccessibility::ROLE_REGION; 93 case WebKit::WebAccessibilityRoleDrawer: 94 return WebAccessibility::ROLE_DRAWER; 95 case WebKit::WebAccessibilityRoleEditableText: 96 return WebAccessibility::ROLE_EDITABLE_TEXT; 97 case WebKit::WebAccessibilityRoleGrid: 98 return WebAccessibility::ROLE_GRID; 99 case WebKit::WebAccessibilityRoleGroup: 100 return WebAccessibility::ROLE_GROUP; 101 case WebKit::WebAccessibilityRoleGrowArea: 102 return WebAccessibility::ROLE_GROW_AREA; 103 case WebKit::WebAccessibilityRoleHeading: 104 return WebAccessibility::ROLE_HEADING; 105 case WebKit::WebAccessibilityRoleHelpTag: 106 return WebAccessibility::ROLE_HELP_TAG; 107 case WebKit::WebAccessibilityRoleIgnored: 108 return WebAccessibility::ROLE_IGNORED; 109 case WebKit::WebAccessibilityRoleImage: 110 return WebAccessibility::ROLE_IMAGE; 111 case WebKit::WebAccessibilityRoleImageMap: 112 return WebAccessibility::ROLE_IMAGE_MAP; 113 case WebKit::WebAccessibilityRoleImageMapLink: 114 return WebAccessibility::ROLE_IMAGE_MAP_LINK; 115 case WebKit::WebAccessibilityRoleIncrementor: 116 return WebAccessibility::ROLE_INCREMENTOR; 117 case WebKit::WebAccessibilityRoleLandmarkApplication: 118 return WebAccessibility::ROLE_LANDMARK_APPLICATION; 119 case WebKit::WebAccessibilityRoleLandmarkBanner: 120 return WebAccessibility::ROLE_LANDMARK_BANNER; 121 case WebKit::WebAccessibilityRoleLandmarkComplementary: 122 return WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY; 123 case WebKit::WebAccessibilityRoleLandmarkContentInfo: 124 return WebAccessibility::ROLE_LANDMARK_CONTENTINFO; 125 case WebKit::WebAccessibilityRoleLandmarkMain: 126 return WebAccessibility::ROLE_LANDMARK_MAIN; 127 case WebKit::WebAccessibilityRoleLandmarkNavigation: 128 return WebAccessibility::ROLE_LANDMARK_NAVIGATION; 129 case WebKit::WebAccessibilityRoleLandmarkSearch: 130 return WebAccessibility::ROLE_LANDMARK_SEARCH; 131 case WebKit::WebAccessibilityRoleLink: 132 return WebAccessibility::ROLE_LINK; 133 case WebKit::WebAccessibilityRoleList: 134 return WebAccessibility::ROLE_LIST; 135 case WebKit::WebAccessibilityRoleListBox: 136 return WebAccessibility::ROLE_LISTBOX; 137 case WebKit::WebAccessibilityRoleListBoxOption: 138 return WebAccessibility::ROLE_LISTBOX_OPTION; 139 case WebKit::WebAccessibilityRoleListItem: 140 return WebAccessibility::ROLE_LIST_ITEM; 141 case WebKit::WebAccessibilityRoleListMarker: 142 return WebAccessibility::ROLE_LIST_MARKER; 143 case WebKit::WebAccessibilityRoleMatte: 144 return WebAccessibility::ROLE_MATTE; 145 case WebKit::WebAccessibilityRoleMenu: 146 return WebAccessibility::ROLE_MENU; 147 case WebKit::WebAccessibilityRoleMenuBar: 148 return WebAccessibility::ROLE_MENU_BAR; 149 case WebKit::WebAccessibilityRoleMenuButton: 150 return WebAccessibility::ROLE_MENU_BUTTON; 151 case WebKit::WebAccessibilityRoleMenuItem: 152 return WebAccessibility::ROLE_MENU_ITEM; 153 case WebKit::WebAccessibilityRoleMenuListOption: 154 return WebAccessibility::ROLE_MENU_LIST_OPTION; 155 case WebKit::WebAccessibilityRoleMenuListPopup: 156 return WebAccessibility::ROLE_MENU_LIST_POPUP; 157 case WebKit::WebAccessibilityRoleOutline: 158 return WebAccessibility::ROLE_OUTLINE; 159 case WebKit::WebAccessibilityRolePopUpButton: 160 return WebAccessibility::ROLE_POPUP_BUTTON; 161 case WebKit::WebAccessibilityRoleProgressIndicator: 162 return WebAccessibility::ROLE_PROGRESS_INDICATOR; 163 case WebKit::WebAccessibilityRoleRadioButton: 164 return WebAccessibility::ROLE_RADIO_BUTTON; 165 case WebKit::WebAccessibilityRoleRadioGroup: 166 return WebAccessibility::ROLE_RADIO_GROUP; 167 case WebKit::WebAccessibilityRoleRow: 168 return WebAccessibility::ROLE_ROW; 169 case WebKit::WebAccessibilityRoleRowHeader: 170 return WebAccessibility::ROLE_ROW_HEADER; 171 case WebKit::WebAccessibilityRoleRuler: 172 return WebAccessibility::ROLE_RULER; 173 case WebKit::WebAccessibilityRoleRulerMarker: 174 return WebAccessibility::ROLE_RULER_MARKER; 175 case WebKit::WebAccessibilityRoleScrollArea: 176 return WebAccessibility::ROLE_SCROLLAREA; 177 case WebKit::WebAccessibilityRoleScrollBar: 178 return WebAccessibility::ROLE_SCROLLBAR; 179 case WebKit::WebAccessibilityRoleSheet: 180 return WebAccessibility::ROLE_SHEET; 181 case WebKit::WebAccessibilityRoleSlider: 182 return WebAccessibility::ROLE_SLIDER; 183 case WebKit::WebAccessibilityRoleSliderThumb: 184 return WebAccessibility::ROLE_SLIDER_THUMB; 185 case WebKit::WebAccessibilityRoleSplitGroup: 186 return WebAccessibility::ROLE_SPLIT_GROUP; 187 case WebKit::WebAccessibilityRoleSplitter: 188 return WebAccessibility::ROLE_SPLITTER; 189 case WebKit::WebAccessibilityRoleStaticText: 190 return WebAccessibility::ROLE_STATIC_TEXT; 191 case WebKit::WebAccessibilityRoleSystemWide: 192 return WebAccessibility::ROLE_SYSTEM_WIDE; 193 case WebKit::WebAccessibilityRoleTab: 194 return WebAccessibility::ROLE_TAB; 195 case WebKit::WebAccessibilityRoleTabGroup: 196 return WebAccessibility::ROLE_TAB_GROUP; 197 case WebKit::WebAccessibilityRoleTabList: 198 return WebAccessibility::ROLE_TAB_LIST; 199 case WebKit::WebAccessibilityRoleTabPanel: 200 return WebAccessibility::ROLE_TAB_PANEL; 201 case WebKit::WebAccessibilityRoleTable: 202 return WebAccessibility::ROLE_TABLE; 203 case WebKit::WebAccessibilityRoleTableHeaderContainer: 204 return WebAccessibility::ROLE_TABLE_HEADER_CONTAINER; 205 case WebKit::WebAccessibilityRoleTextArea: 206 return WebAccessibility::ROLE_TEXTAREA; 207 case WebKit::WebAccessibilityRoleTextField: 208 return WebAccessibility::ROLE_TEXT_FIELD; 209 case WebKit::WebAccessibilityRoleToolbar: 210 return WebAccessibility::ROLE_TOOLBAR; 211 case WebKit::WebAccessibilityRoleTreeGrid: 212 return WebAccessibility::ROLE_TREE_GRID; 213 case WebKit::WebAccessibilityRoleTreeItemRole: 214 return WebAccessibility::ROLE_TREE_ITEM; 215 case WebKit::WebAccessibilityRoleTreeRole: 216 return WebAccessibility::ROLE_TREE; 217 case WebKit::WebAccessibilityRoleUserInterfaceTooltip: 218 return WebAccessibility::ROLE_TOOLTIP; 219 case WebKit::WebAccessibilityRoleValueIndicator: 220 return WebAccessibility::ROLE_VALUE_INDICATOR; 221 case WebKit::WebAccessibilityRoleWebArea: 222 return WebAccessibility::ROLE_WEB_AREA; 223 case WebKit::WebAccessibilityRoleWebCoreLink: 224 return WebAccessibility::ROLE_WEBCORE_LINK; 225 case WebKit::WebAccessibilityRoleWindow: 226 return WebAccessibility::ROLE_WINDOW; 227 228 default: 229 return WebAccessibility::ROLE_UNKNOWN; 230 } 231 } 232 233 uint32 ConvertState(const WebAccessibilityObject& o) { 234 uint32 state = 0; 235 if (o.isChecked()) 236 state |= (1 << WebAccessibility::STATE_CHECKED); 237 238 if (o.isCollapsed()) 239 state |= (1 << WebAccessibility::STATE_COLLAPSED); 240 241 if (o.canSetFocusAttribute()) 242 state |= (1 << WebAccessibility::STATE_FOCUSABLE); 243 244 if (o.isFocused()) 245 state |= (1 << WebAccessibility::STATE_FOCUSED); 246 247 if (o.roleValue() == WebKit::WebAccessibilityRolePopUpButton) { 248 state |= (1 << WebAccessibility::STATE_HASPOPUP); 249 250 if (!o.isCollapsed()) 251 state |= (1 << WebAccessibility::STATE_EXPANDED); 252 } 253 254 if (o.isHovered()) 255 state |= (1 << WebAccessibility::STATE_HOTTRACKED); 256 257 if (o.isIndeterminate()) 258 state |= (1 << WebAccessibility::STATE_INDETERMINATE); 259 260 if (!o.isVisible()) 261 state |= (1 << WebAccessibility::STATE_INVISIBLE); 262 263 if (o.isLinked()) 264 state |= (1 << WebAccessibility::STATE_LINKED); 265 266 if (o.isMultiSelectable()) 267 state |= (1 << WebAccessibility::STATE_MULTISELECTABLE); 268 269 if (o.isOffScreen()) 270 state |= (1 << WebAccessibility::STATE_OFFSCREEN); 271 272 if (o.isPressed()) 273 state |= (1 << WebAccessibility::STATE_PRESSED); 274 275 if (o.isPasswordField()) 276 state |= (1 << WebAccessibility::STATE_PROTECTED); 277 278 if (o.isReadOnly()) 279 state |= (1 << WebAccessibility::STATE_READONLY); 280 281 if (o.canSetSelectedAttribute()) 282 state |= (1 << WebAccessibility::STATE_SELECTABLE); 283 284 if (o.isSelected()) 285 state |= (1 << WebAccessibility::STATE_SELECTED); 286 287 if (o.isVisited()) 288 state |= (1 << WebAccessibility::STATE_TRAVERSED); 289 290 if (!o.isEnabled()) 291 state |= (1 << WebAccessibility::STATE_UNAVAILABLE); 292 293 return state; 294 } 295 296 WebAccessibility::WebAccessibility() 297 : id(-1), 298 role(ROLE_NONE), 299 state(-1) { 300 } 301 302 WebAccessibility::WebAccessibility(const WebKit::WebAccessibilityObject& src, 303 WebKit::WebAccessibilityCache* cache, 304 bool include_children) { 305 Init(src, cache, include_children); 306 } 307 308 WebAccessibility::~WebAccessibility() { 309 } 310 311 void WebAccessibility::Init(const WebKit::WebAccessibilityObject& src, 312 WebKit::WebAccessibilityCache* cache, 313 bool include_children) { 314 name = src.title(); 315 value = src.stringValue(); 316 role = ConvertRole(src.roleValue()); 317 state = ConvertState(src); 318 location = src.boundingBoxRect(); 319 320 if (src.actionVerb().length()) 321 attributes[ATTR_ACTION] = src.actionVerb(); 322 if (src.accessibilityDescription().length()) 323 attributes[ATTR_DESCRIPTION] = src.accessibilityDescription(); 324 if (src.helpText().length()) 325 attributes[ATTR_HELP] = src.helpText(); 326 if (src.keyboardShortcut().length()) 327 attributes[ATTR_SHORTCUT] = src.keyboardShortcut(); 328 if (src.hasComputedStyle()) 329 attributes[ATTR_DISPLAY] = src.computedStyleDisplay(); 330 if (!src.url().isEmpty()) 331 attributes[ATTR_URL] = src.url().spec().utf16(); 332 333 WebKit::WebNode node = src.node(); 334 bool is_iframe = false; 335 336 if (!node.isNull() && node.isElementNode()) { 337 WebKit::WebElement element = node.to<WebKit::WebElement>(); 338 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME")); 339 340 // TODO(ctguil): The tagName in WebKit is lower cased but 341 // HTMLElement::nodeName calls localNameUpper. Consider adding 342 // a WebElement method that returns the original lower cased tagName. 343 attributes[ATTR_HTML_TAG] = StringToLowerASCII(string16(element.tagName())); 344 for (unsigned i = 0; i < element.attributes().length(); i++) { 345 html_attributes.push_back( 346 std::pair<string16, string16>( 347 element.attributes().attributeItem(i).localName(), 348 element.attributes().attributeItem(i).value())); 349 } 350 351 if (element.isFormControlElement()) { 352 WebKit::WebFormControlElement form_element = 353 element.to<WebKit::WebFormControlElement>(); 354 if (form_element.formControlType() == ASCIIToUTF16("text")) { 355 WebKit::WebInputElement input_element = 356 form_element.to<WebKit::WebInputElement>(); 357 attributes[ATTR_TEXT_SEL_START] = base::IntToString16( 358 input_element.selectionStart()); 359 attributes[ATTR_TEXT_SEL_END] = base::IntToString16( 360 input_element.selectionEnd()); 361 } 362 } 363 } 364 365 if (role == WebAccessibility::ROLE_DOCUMENT || 366 role == WebAccessibility::ROLE_WEB_AREA) { 367 const WebKit::WebDocument& document = src.document(); 368 if (name.empty()) 369 name = document.title(); 370 attributes[ATTR_DOC_TITLE] = document.title(); 371 attributes[ATTR_DOC_URL] = document.frame()->url().spec().utf16(); 372 if (document.isXHTMLDocument()) 373 attributes[ATTR_DOC_MIMETYPE] = WebKit::WebString("text/xhtml"); 374 else 375 attributes[ATTR_DOC_MIMETYPE] = WebKit::WebString("text/html"); 376 377 const WebKit::WebDocumentType& doctype = document.doctype(); 378 if (!doctype.isNull()) 379 attributes[ATTR_DOC_DOCTYPE] = doctype.name(); 380 381 const gfx::Size& scroll_offset = document.frame()->scrollOffset(); 382 attributes[ATTR_DOC_SCROLLX] = base::IntToString16(scroll_offset.width()); 383 attributes[ATTR_DOC_SCROLLY] = base::IntToString16(scroll_offset.height()); 384 } 385 386 // Add the source object to the cache and store its id. 387 id = cache->addOrGetId(src); 388 389 if (include_children) { 390 // Recursively create children. 391 int child_count = src.childCount(); 392 std::set<int32> child_ids; 393 for (int i = 0; i < child_count; i++) { 394 WebAccessibilityObject child = src.childAt(i); 395 int32 child_id = cache->addOrGetId(child); 396 397 // The child may be invalid due to issues in webkit accessibility code. 398 // Don't add children that are invalid thus preventing a crash. 399 // https://bugs.webkit.org/show_bug.cgi?id=44149 400 // TODO(ctguil): We may want to remove this check as webkit stabilizes. 401 if (!child.isValid()) 402 continue; 403 404 // Children may duplicated in the webkit accessibility tree. Only add a 405 // child once for the web accessibility tree. 406 // https://bugs.webkit.org/show_bug.cgi?id=58930 407 if (child_ids.find(child_id) != child_ids.end()) 408 continue; 409 child_ids.insert(child_id); 410 411 // Some nodes appear in the tree in more than one place: for example, 412 // a cell in a table appears as a child of both a row and a column. 413 // Only recursively add child nodes that have this node as its 414 // unignored parent. For child nodes that are actually parented to 415 // somethinng else, store only the ID. 416 // 417 // As an exception, also add children of an iframe element. 418 // https://bugs.webkit.org/show_bug.cgi?id=57066 419 if (is_iframe || IsParentUnignoredOf(src, child)) { 420 children.push_back(WebAccessibility(child, cache, include_children)); 421 } else { 422 indirect_child_ids.push_back(child_id); 423 } 424 } 425 } 426 } 427 428 bool WebAccessibility::IsParentUnignoredOf( 429 const WebKit::WebAccessibilityObject& ancestor, 430 const WebKit::WebAccessibilityObject& child) { 431 WebKit::WebAccessibilityObject parent = child.parentObject(); 432 while (!parent.isNull() && parent.accessibilityIsIgnored()) 433 parent = parent.parentObject(); 434 return parent.equals(ancestor); 435 } 436 437 } // namespace webkit_glue 438