1 // Copyright 2014 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/blink_ax_tree_source.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 "content/renderer/accessibility/blink_ax_enum_conversion.h" 13 #include "content/renderer/render_view_impl.h" 14 #include "third_party/WebKit/public/platform/WebRect.h" 15 #include "third_party/WebKit/public/platform/WebSize.h" 16 #include "third_party/WebKit/public/platform/WebString.h" 17 #include "third_party/WebKit/public/platform/WebVector.h" 18 #include "third_party/WebKit/public/web/WebAXEnums.h" 19 #include "third_party/WebKit/public/web/WebAXObject.h" 20 #include "third_party/WebKit/public/web/WebDocument.h" 21 #include "third_party/WebKit/public/web/WebDocumentType.h" 22 #include "third_party/WebKit/public/web/WebElement.h" 23 #include "third_party/WebKit/public/web/WebFormControlElement.h" 24 #include "third_party/WebKit/public/web/WebInputElement.h" 25 #include "third_party/WebKit/public/web/WebLocalFrame.h" 26 #include "third_party/WebKit/public/web/WebNode.h" 27 #include "third_party/WebKit/public/web/WebView.h" 28 29 using base::ASCIIToUTF16; 30 using base::UTF16ToUTF8; 31 using blink::WebAXObject; 32 using blink::WebDocument; 33 using blink::WebDocumentType; 34 using blink::WebElement; 35 using blink::WebLocalFrame; 36 using blink::WebNode; 37 using blink::WebVector; 38 using blink::WebView; 39 40 namespace content { 41 42 namespace { 43 44 // Returns true if |ancestor| is the first unignored parent of |child|, 45 // which means that when walking up the parent chain from |child|, 46 // |ancestor| is the *first* ancestor that isn't marked as 47 // accessibilityIsIgnored(). 48 bool IsParentUnignoredOf(WebAXObject ancestor, 49 WebAXObject child) { 50 WebAXObject parent = child.parentObject(); 51 while (!parent.isDetached() && parent.accessibilityIsIgnored()) 52 parent = parent.parentObject(); 53 return parent.equals(ancestor); 54 } 55 56 bool IsTrue(std::string html_value) { 57 return LowerCaseEqualsASCII(html_value, "true"); 58 } 59 60 std::string GetEquivalentAriaRoleString(const ui::AXRole role) { 61 switch (role) { 62 case ui::AX_ROLE_ARTICLE: 63 return "article"; 64 case ui::AX_ROLE_BANNER: 65 return "banner"; 66 case ui::AX_ROLE_COMPLEMENTARY: 67 return "complementary"; 68 case ui::AX_ROLE_CONTENT_INFO: 69 case ui::AX_ROLE_FOOTER: 70 return "contentinfo"; 71 case ui::AX_ROLE_MAIN: 72 return "main"; 73 case ui::AX_ROLE_NAVIGATION: 74 return "navigation"; 75 case ui::AX_ROLE_REGION: 76 return "region"; 77 default: 78 break; 79 } 80 81 return std::string(); 82 } 83 84 void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr, 85 WebVector<WebAXObject> objects, 86 ui::AXNodeData* dst) { 87 std::vector<int32> ids; 88 for(size_t i = 0; i < objects.size(); i++) 89 ids.push_back(objects[i].axID()); 90 if (ids.size() > 0) 91 dst->AddIntListAttribute(attr, ids); 92 } 93 94 } // Anonymous namespace 95 96 BlinkAXTreeSource::BlinkAXTreeSource(RenderViewImpl* render_view) 97 : render_view_(render_view) { 98 } 99 100 BlinkAXTreeSource::~BlinkAXTreeSource() { 101 } 102 103 bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node) const { 104 const blink::WebAXObject& root = GetRoot(); 105 while (IsValid(node)) { 106 if (node.equals(root)) 107 return true; 108 node = GetParent(node); 109 } 110 return false; 111 } 112 113 blink::WebAXObject BlinkAXTreeSource::GetRoot() const { 114 return GetMainDocument().accessibilityObject(); 115 } 116 117 blink::WebAXObject BlinkAXTreeSource::GetFromId(int32 id) const { 118 return GetMainDocument().accessibilityObjectFromID(id); 119 } 120 121 int32 BlinkAXTreeSource::GetId(blink::WebAXObject node) const { 122 return node.axID(); 123 } 124 125 void BlinkAXTreeSource::GetChildren( 126 blink::WebAXObject parent, 127 std::vector<blink::WebAXObject>* out_children) const { 128 bool is_iframe = false; 129 WebNode node = parent.node(); 130 if (!node.isNull() && node.isElementNode()) { 131 WebElement element = node.to<WebElement>(); 132 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME")); 133 } 134 135 for (unsigned i = 0; i < parent.childCount(); i++) { 136 blink::WebAXObject child = parent.childAt(i); 137 138 // The child may be invalid due to issues in blink accessibility code. 139 if (child.isDetached()) 140 continue; 141 142 // Skip children whose parent isn't |parent|. 143 // As an exception, include children of an iframe element. 144 if (!is_iframe && !IsParentUnignoredOf(parent, child)) 145 continue; 146 147 out_children->push_back(child); 148 } 149 } 150 151 blink::WebAXObject BlinkAXTreeSource::GetParent( 152 blink::WebAXObject node) const { 153 // Blink returns ignored objects when walking up the parent chain, 154 // we have to skip those here. Also, stop when we get to the root 155 // element. 156 blink::WebAXObject root = GetRoot(); 157 do { 158 if (node.equals(root)) 159 return blink::WebAXObject(); 160 node = node.parentObject(); 161 } while (!node.isDetached() && node.accessibilityIsIgnored()); 162 163 return node; 164 } 165 166 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node) const { 167 return !node.isDetached(); // This also checks if it's null. 168 } 169 170 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1, 171 blink::WebAXObject node2) const { 172 return node1.equals(node2); 173 } 174 175 blink::WebAXObject BlinkAXTreeSource::GetNull() const { 176 return blink::WebAXObject(); 177 } 178 179 void BlinkAXTreeSource::SerializeNode(blink::WebAXObject src, 180 ui::AXNodeData* dst) const { 181 dst->role = AXRoleFromBlink(src.role()); 182 dst->state = AXStateFromBlink(src); 183 dst->location = src.boundingBoxRect(); 184 dst->id = src.axID(); 185 std::string name = UTF16ToUTF8(src.title()); 186 187 std::string value; 188 if (src.valueDescription().length()) { 189 dst->AddStringAttribute(ui::AX_ATTR_VALUE, 190 UTF16ToUTF8(src.valueDescription())); 191 } else { 192 dst->AddStringAttribute(ui::AX_ATTR_VALUE, UTF16ToUTF8(src.stringValue())); 193 } 194 195 if (dst->role == ui::AX_ROLE_COLOR_WELL) { 196 int r, g, b; 197 src.colorValue(r, g, b); 198 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_RED, r); 199 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_GREEN, g); 200 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_BLUE, b); 201 } 202 203 if (dst->role == ui::AX_ROLE_INLINE_TEXT_BOX) { 204 dst->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, 205 AXTextDirectionFromBlink(src.textDirection())); 206 207 WebVector<int> src_character_offsets; 208 src.characterOffsets(src_character_offsets); 209 std::vector<int32> character_offsets; 210 character_offsets.reserve(src_character_offsets.size()); 211 for (size_t i = 0; i < src_character_offsets.size(); ++i) 212 character_offsets.push_back(src_character_offsets[i]); 213 dst->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets); 214 215 WebVector<int> src_word_starts; 216 WebVector<int> src_word_ends; 217 src.wordBoundaries(src_word_starts, src_word_ends); 218 std::vector<int32> word_starts; 219 std::vector<int32> word_ends; 220 word_starts.reserve(src_word_starts.size()); 221 word_ends.reserve(src_word_starts.size()); 222 for (size_t i = 0; i < src_word_starts.size(); ++i) { 223 word_starts.push_back(src_word_starts[i]); 224 word_ends.push_back(src_word_ends[i]); 225 } 226 dst->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS, word_starts); 227 dst->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS, word_ends); 228 } 229 230 if (src.accessKey().length()) { 231 dst->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY, 232 UTF16ToUTF8(src.accessKey())); 233 } 234 if (src.actionVerb().length()) 235 dst->AddStringAttribute(ui::AX_ATTR_ACTION, UTF16ToUTF8(src.actionVerb())); 236 if (src.isAriaReadOnly()) 237 dst->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY, true); 238 if (src.isButtonStateMixed()) 239 dst->AddBoolAttribute(ui::AX_ATTR_BUTTON_MIXED, true); 240 if (src.canSetValueAttribute()) 241 dst->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE, true); 242 if (src.accessibilityDescription().length()) { 243 dst->AddStringAttribute(ui::AX_ATTR_DESCRIPTION, 244 UTF16ToUTF8(src.accessibilityDescription())); 245 } 246 if (src.hasComputedStyle()) { 247 dst->AddStringAttribute(ui::AX_ATTR_DISPLAY, 248 UTF16ToUTF8(src.computedStyleDisplay())); 249 } 250 if (src.helpText().length()) 251 dst->AddStringAttribute(ui::AX_ATTR_HELP, UTF16ToUTF8(src.helpText())); 252 if (src.keyboardShortcut().length()) { 253 dst->AddStringAttribute(ui::AX_ATTR_SHORTCUT, 254 UTF16ToUTF8(src.keyboardShortcut())); 255 } 256 if (!src.titleUIElement().isDetached()) { 257 dst->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT, 258 src.titleUIElement().axID()); 259 } 260 if (!src.ariaActiveDescendant().isDetached()) { 261 dst->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID, 262 src.ariaActiveDescendant().axID()); 263 } 264 265 if (!src.url().isEmpty()) 266 dst->AddStringAttribute(ui::AX_ATTR_URL, src.url().spec()); 267 268 if (dst->role == ui::AX_ROLE_HEADING) 269 dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL, src.headingLevel()); 270 else if ((dst->role == ui::AX_ROLE_TREE_ITEM || 271 dst->role == ui::AX_ROLE_ROW) && 272 src.hierarchicalLevel() > 0) { 273 dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL, 274 src.hierarchicalLevel()); 275 } 276 277 // Treat the active list box item as focused. 278 if (dst->role == ui::AX_ROLE_LIST_BOX_OPTION && 279 src.isSelectedOptionActive()) { 280 dst->state |= (1 << ui::AX_STATE_FOCUSED); 281 } 282 283 if (src.canvasHasFallbackContent()) 284 dst->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK, true); 285 286 WebNode node = src.node(); 287 bool is_iframe = false; 288 std::string live_atomic; 289 std::string live_busy; 290 std::string live_status; 291 std::string live_relevant; 292 293 if (!node.isNull() && node.isElementNode()) { 294 WebElement element = node.to<WebElement>(); 295 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME")); 296 297 if (LowerCaseEqualsASCII(element.getAttribute("aria-expanded"), "true")) 298 dst->state |= (1 << ui::AX_STATE_EXPANDED); 299 300 // TODO(ctguil): The tagName in WebKit is lower cased but 301 // HTMLElement::nodeName calls localNameUpper. Consider adding 302 // a WebElement method that returns the original lower cased tagName. 303 dst->AddStringAttribute( 304 ui::AX_ATTR_HTML_TAG, 305 StringToLowerASCII(UTF16ToUTF8(element.tagName()))); 306 for (unsigned i = 0; i < element.attributeCount(); ++i) { 307 std::string name = StringToLowerASCII(UTF16ToUTF8( 308 element.attributeLocalName(i))); 309 std::string value = UTF16ToUTF8(element.attributeValue(i)); 310 dst->html_attributes.push_back(std::make_pair(name, value)); 311 } 312 313 if (dst->role == ui::AX_ROLE_EDITABLE_TEXT || 314 dst->role == ui::AX_ROLE_TEXT_AREA || 315 dst->role == ui::AX_ROLE_TEXT_FIELD) { 316 dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, src.selectionStart()); 317 dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, src.selectionEnd()); 318 319 WebVector<int> src_line_breaks; 320 src.lineBreaks(src_line_breaks); 321 if (src_line_breaks.size() > 0) { 322 std::vector<int32> line_breaks; 323 line_breaks.reserve(src_line_breaks.size()); 324 for (size_t i = 0; i < src_line_breaks.size(); ++i) 325 line_breaks.push_back(src_line_breaks[i]); 326 dst->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS, line_breaks); 327 } 328 } 329 330 // ARIA role. 331 if (element.hasAttribute("role")) { 332 dst->AddStringAttribute(ui::AX_ATTR_ROLE, 333 UTF16ToUTF8(element.getAttribute("role"))); 334 } else { 335 std::string role = GetEquivalentAriaRoleString(dst->role); 336 if (!role.empty()) 337 dst->AddStringAttribute(ui::AX_ATTR_ROLE, role); 338 } 339 340 // Live region attributes 341 live_atomic = UTF16ToUTF8(element.getAttribute("aria-atomic")); 342 live_busy = UTF16ToUTF8(element.getAttribute("aria-busy")); 343 live_status = UTF16ToUTF8(element.getAttribute("aria-live")); 344 live_relevant = UTF16ToUTF8(element.getAttribute("aria-relevant")); 345 } 346 347 // Walk up the parent chain to set live region attributes of containers 348 std::string container_live_atomic; 349 std::string container_live_busy; 350 std::string container_live_status; 351 std::string container_live_relevant; 352 WebAXObject container_accessible = src; 353 while (!container_accessible.isDetached()) { 354 WebNode container_node = container_accessible.node(); 355 if (!container_node.isNull() && container_node.isElementNode()) { 356 WebElement container_elem = container_node.to<WebElement>(); 357 if (container_elem.hasAttribute("aria-atomic") && 358 container_live_atomic.empty()) { 359 container_live_atomic = 360 UTF16ToUTF8(container_elem.getAttribute("aria-atomic")); 361 } 362 if (container_elem.hasAttribute("aria-busy") && 363 container_live_busy.empty()) { 364 container_live_busy = 365 UTF16ToUTF8(container_elem.getAttribute("aria-busy")); 366 } 367 if (container_elem.hasAttribute("aria-live") && 368 container_live_status.empty()) { 369 container_live_status = 370 UTF16ToUTF8(container_elem.getAttribute("aria-live")); 371 } 372 if (container_elem.hasAttribute("aria-relevant") && 373 container_live_relevant.empty()) { 374 container_live_relevant = 375 UTF16ToUTF8(container_elem.getAttribute("aria-relevant")); 376 } 377 } 378 container_accessible = container_accessible.parentObject(); 379 } 380 381 if (!live_atomic.empty()) 382 dst->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC, IsTrue(live_atomic)); 383 if (!live_busy.empty()) 384 dst->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY, IsTrue(live_busy)); 385 if (!live_status.empty()) 386 dst->AddStringAttribute(ui::AX_ATTR_LIVE_STATUS, live_status); 387 if (!live_relevant.empty()) 388 dst->AddStringAttribute(ui::AX_ATTR_LIVE_RELEVANT, live_relevant); 389 390 if (!container_live_atomic.empty()) { 391 dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC, 392 IsTrue(container_live_atomic)); 393 } 394 if (!container_live_busy.empty()) { 395 dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY, 396 IsTrue(container_live_busy)); 397 } 398 if (!container_live_status.empty()) { 399 dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_STATUS, 400 container_live_status); 401 } 402 if (!container_live_relevant.empty()) { 403 dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_RELEVANT, 404 container_live_relevant); 405 } 406 407 if (dst->role == ui::AX_ROLE_PROGRESS_INDICATOR || 408 dst->role == ui::AX_ROLE_SCROLL_BAR || 409 dst->role == ui::AX_ROLE_SLIDER || 410 dst->role == ui::AX_ROLE_SPIN_BUTTON) { 411 dst->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, src.valueForRange()); 412 dst->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE, 413 src.maxValueForRange()); 414 dst->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE, 415 src.minValueForRange()); 416 } 417 418 if (dst->role == ui::AX_ROLE_DOCUMENT || 419 dst->role == ui::AX_ROLE_WEB_AREA) { 420 dst->AddStringAttribute(ui::AX_ATTR_HTML_TAG, "#document"); 421 const WebDocument& document = src.document(); 422 if (name.empty()) 423 name = UTF16ToUTF8(document.title()); 424 dst->AddStringAttribute(ui::AX_ATTR_DOC_TITLE, 425 UTF16ToUTF8(document.title())); 426 dst->AddStringAttribute(ui::AX_ATTR_DOC_URL, document.url().spec()); 427 dst->AddStringAttribute( 428 ui::AX_ATTR_DOC_MIMETYPE, 429 document.isXHTMLDocument() ? "text/xhtml" : "text/html"); 430 dst->AddBoolAttribute(ui::AX_ATTR_DOC_LOADED, src.isLoaded()); 431 dst->AddFloatAttribute(ui::AX_ATTR_DOC_LOADING_PROGRESS, 432 src.estimatedLoadingProgress()); 433 434 const WebDocumentType& doctype = document.doctype(); 435 if (!doctype.isNull()) { 436 dst->AddStringAttribute(ui::AX_ATTR_DOC_DOCTYPE, 437 UTF16ToUTF8(doctype.name())); 438 } 439 440 const gfx::Size& scroll_offset = document.frame()->scrollOffset(); 441 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X, scroll_offset.width()); 442 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y, scroll_offset.height()); 443 444 const gfx::Size& min_offset = document.frame()->minimumScrollOffset(); 445 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN, min_offset.width()); 446 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN, min_offset.height()); 447 448 const gfx::Size& max_offset = document.frame()->maximumScrollOffset(); 449 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, max_offset.width()); 450 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, max_offset.height()); 451 } 452 453 if (dst->role == ui::AX_ROLE_TABLE) { 454 int column_count = src.columnCount(); 455 int row_count = src.rowCount(); 456 if (column_count > 0 && row_count > 0) { 457 std::set<int32> unique_cell_id_set; 458 std::vector<int32> cell_ids; 459 std::vector<int32> unique_cell_ids; 460 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT, column_count); 461 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT, row_count); 462 WebAXObject header = src.headerContainerObject(); 463 if (!header.isDetached()) 464 dst->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID, header.axID()); 465 for (int i = 0; i < column_count * row_count; ++i) { 466 WebAXObject cell = src.cellForColumnAndRow( 467 i % column_count, i / column_count); 468 int cell_id = -1; 469 if (!cell.isDetached()) { 470 cell_id = cell.axID(); 471 if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) { 472 unique_cell_id_set.insert(cell_id); 473 unique_cell_ids.push_back(cell_id); 474 } 475 } 476 cell_ids.push_back(cell_id); 477 } 478 dst->AddIntListAttribute(ui::AX_ATTR_CELL_IDS, cell_ids); 479 dst->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS, unique_cell_ids); 480 } 481 } 482 483 if (dst->role == ui::AX_ROLE_ROW) { 484 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX, src.rowIndex()); 485 WebAXObject header = src.rowHeader(); 486 if (!header.isDetached()) 487 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID, header.axID()); 488 } 489 490 if (dst->role == ui::AX_ROLE_COLUMN) { 491 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX, src.columnIndex()); 492 WebAXObject header = src.columnHeader(); 493 if (!header.isDetached()) 494 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, header.axID()); 495 } 496 497 if (dst->role == ui::AX_ROLE_CELL || 498 dst->role == ui::AX_ROLE_ROW_HEADER || 499 dst->role == ui::AX_ROLE_COLUMN_HEADER) { 500 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, 501 src.cellColumnIndex()); 502 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN, 503 src.cellColumnSpan()); 504 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX, src.cellRowIndex()); 505 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN, src.cellRowSpan()); 506 } 507 508 dst->AddStringAttribute(ui::AX_ATTR_NAME, name); 509 510 // Add the ids of *indirect* children - those who are children of this node, 511 // but whose parent is *not* this node. One example is a table 512 // cell, which is a child of both a row and a column. Because the cell's 513 // parent is the row, the row adds it as a child, and the column adds it 514 // as an indirect child. 515 int child_count = src.childCount(); 516 for (int i = 0; i < child_count; ++i) { 517 WebAXObject child = src.childAt(i); 518 std::vector<int32> indirect_child_ids; 519 if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child)) 520 indirect_child_ids.push_back(child.axID()); 521 if (indirect_child_ids.size() > 0) { 522 dst->AddIntListAttribute( 523 ui::AX_ATTR_INDIRECT_CHILD_IDS, indirect_child_ids); 524 } 525 } 526 527 WebVector<WebAXObject> controls; 528 if (src.ariaControls(controls)) 529 AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS, controls, dst); 530 531 WebVector<WebAXObject> describedby; 532 if (src.ariaDescribedby(describedby)) { 533 AddIntListAttributeFromWebObjects( 534 ui::AX_ATTR_DESCRIBEDBY_IDS, describedby, dst); 535 } 536 537 WebVector<WebAXObject> flowTo; 538 if (src.ariaFlowTo(flowTo)) 539 AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS, flowTo, dst); 540 541 WebVector<WebAXObject> labelledby; 542 if (src.ariaLabelledby(labelledby)) { 543 AddIntListAttributeFromWebObjects( 544 ui::AX_ATTR_LABELLEDBY_IDS, labelledby, dst); 545 } 546 547 WebVector<WebAXObject> owns; 548 if (src.ariaOwns(owns)) 549 AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS, owns, dst); 550 } 551 552 blink::WebDocument BlinkAXTreeSource::GetMainDocument() const { 553 WebView* view = render_view_->GetWebView(); 554 WebLocalFrame* main_frame = 555 view ? view->mainFrame()->toWebLocalFrame() : NULL; 556 557 if (main_frame) 558 return main_frame->document(); 559 560 return WebDocument(); 561 } 562 563 } // namespace content 564