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