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.
5 #include "content/renderer/accessibility/blink_ax_tree_source.h"
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"
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;
42 using blink::WebPlugin;
43 using blink::WebPluginContainer;
44 using blink::WebVector;
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,
57 WebAXObject parent = child.parentObject();
58 while (!parent.isDetached() && parent.accessibilityIsIgnored())
59 parent = parent.parentObject();
60 return parent.equals(ancestor);
63 std::string GetEquivalentAriaRoleString(const ui::AXRole role) {
65 case ui::AX_ROLE_ARTICLE:
67 case ui::AX_ROLE_BANNER:
69 case ui::AX_ROLE_BUTTON:
71 case ui::AX_ROLE_COMPLEMENTARY:
72 return "complementary";
73 case ui::AX_ROLE_FOOTER:
75 case ui::AX_ROLE_HORIZONTAL_RULE:
77 case ui::AX_ROLE_IMAGE:
79 case ui::AX_ROLE_MAIN:
81 case ui::AX_ROLE_NAVIGATION:
83 case ui::AX_ROLE_RADIO_BUTTON:
85 case ui::AX_ROLE_REGION:
87 case ui::AX_ROLE_SLIDER:
96 void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr,
97 WebVector<WebAXObject> objects,
98 ui::AXNodeData* dst) {
99 std::vector<int32> ids;
100 for(size_t i = 0; i < objects.size(); i++)
101 ids.push_back(objects[i].axID());
103 dst->AddIntListAttribute(attr, ids);
106 } // Anonymous namespace
108 BlinkAXTreeSource::BlinkAXTreeSource(RenderFrameImpl* render_frame)
109 : render_frame_(render_frame),
110 node_to_frame_routing_id_map_(NULL),
111 node_to_browser_plugin_instance_id_map_(NULL),
112 accessibility_focus_id_(-1) {
115 BlinkAXTreeSource::~BlinkAXTreeSource() {
118 bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node) const {
119 const blink::WebAXObject& root = GetRoot();
120 while (IsValid(node)) {
121 if (node.equals(root))
123 node = GetParent(node);
128 void BlinkAXTreeSource::CollectChildFrameIdMapping(
129 std::map<int32, int>* node_to_frame_routing_id_map,
130 std::map<int32, int>* node_to_browser_plugin_instance_id_map) {
131 node_to_frame_routing_id_map_ = node_to_frame_routing_id_map;
132 node_to_browser_plugin_instance_id_map_ =
133 node_to_browser_plugin_instance_id_map;
136 blink::WebAXObject BlinkAXTreeSource::GetRoot() const {
137 return GetMainDocument().accessibilityObject();
140 blink::WebAXObject BlinkAXTreeSource::GetFromId(int32 id) const {
141 return GetMainDocument().accessibilityObjectFromID(id);
144 int32 BlinkAXTreeSource::GetId(blink::WebAXObject node) const {
148 void BlinkAXTreeSource::GetChildren(
149 blink::WebAXObject parent,
150 std::vector<blink::WebAXObject>* out_children) const {
151 if (parent.role() == blink::WebAXRoleStaticText) {
152 blink::WebAXObject ancestor = parent;
153 while (!ancestor.isDetached()) {
154 if (ancestor.axID() == accessibility_focus_id_) {
155 parent.loadInlineTextBoxes();
158 ancestor = ancestor.parentObject();
162 bool is_iframe = false;
163 WebNode node = parent.node();
164 if (!node.isNull() && node.isElementNode()) {
165 WebElement element = node.to<WebElement>();
166 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
169 for (unsigned i = 0; i < parent.childCount(); i++) {
170 blink::WebAXObject child = parent.childAt(i);
172 // The child may be invalid due to issues in blink accessibility code.
173 if (child.isDetached())
176 // Skip children whose parent isn't |parent|.
177 // As an exception, include children of an iframe element.
178 if (!is_iframe && !IsParentUnignoredOf(parent, child))
181 out_children->push_back(child);
185 blink::WebAXObject BlinkAXTreeSource::GetParent(
186 blink::WebAXObject node) const {
187 // Blink returns ignored objects when walking up the parent chain,
188 // we have to skip those here. Also, stop when we get to the root
190 blink::WebAXObject root = GetRoot();
192 if (node.equals(root))
193 return blink::WebAXObject();
194 node = node.parentObject();
195 } while (!node.isDetached() && node.accessibilityIsIgnored());
200 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node) const {
201 return !node.isDetached(); // This also checks if it's null.
204 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1,
205 blink::WebAXObject node2) const {
206 return node1.equals(node2);
209 blink::WebAXObject BlinkAXTreeSource::GetNull() const {
210 return blink::WebAXObject();
213 void BlinkAXTreeSource::SerializeNode(blink::WebAXObject src,
214 ui::AXNodeData* dst) const {
215 dst->role = AXRoleFromBlink(src.role());
216 dst->state = AXStateFromBlink(src);
217 dst->location = src.boundingBoxRect();
218 dst->id = src.axID();
219 std::string name = UTF16ToUTF8(src.title());
222 if (src.valueDescription().length()) {
223 dst->AddStringAttribute(ui::AX_ATTR_VALUE,
224 UTF16ToUTF8(src.valueDescription()));
226 dst->AddStringAttribute(ui::AX_ATTR_VALUE, UTF16ToUTF8(src.stringValue()));
229 if (dst->role == ui::AX_ROLE_COLOR_WELL) {
231 src.colorValue(r, g, b);
232 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_RED, r);
233 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_GREEN, g);
234 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_BLUE, b);
237 if (dst->role == ui::AX_ROLE_INLINE_TEXT_BOX) {
238 dst->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
239 AXTextDirectionFromBlink(src.textDirection()));
241 WebVector<int> src_character_offsets;
242 src.characterOffsets(src_character_offsets);
243 std::vector<int32> character_offsets;
244 character_offsets.reserve(src_character_offsets.size());
245 for (size_t i = 0; i < src_character_offsets.size(); ++i)
246 character_offsets.push_back(src_character_offsets[i]);
247 dst->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets);
249 WebVector<int> src_word_starts;
250 WebVector<int> src_word_ends;
251 src.wordBoundaries(src_word_starts, src_word_ends);
252 std::vector<int32> word_starts;
253 std::vector<int32> word_ends;
254 word_starts.reserve(src_word_starts.size());
255 word_ends.reserve(src_word_starts.size());
256 for (size_t i = 0; i < src_word_starts.size(); ++i) {
257 word_starts.push_back(src_word_starts[i]);
258 word_ends.push_back(src_word_ends[i]);
260 dst->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS, word_starts);
261 dst->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS, word_ends);
264 if (src.accessKey().length()) {
265 dst->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY,
266 UTF16ToUTF8(src.accessKey()));
268 if (src.actionVerb().length())
269 dst->AddStringAttribute(ui::AX_ATTR_ACTION, UTF16ToUTF8(src.actionVerb()));
270 if (src.isAriaReadOnly())
271 dst->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY, true);
272 if (src.isButtonStateMixed())
273 dst->AddBoolAttribute(ui::AX_ATTR_BUTTON_MIXED, true);
274 if (src.canSetValueAttribute())
275 dst->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE, true);
276 if (src.accessibilityDescription().length()) {
277 dst->AddStringAttribute(ui::AX_ATTR_DESCRIPTION,
278 UTF16ToUTF8(src.accessibilityDescription()));
280 if (src.hasComputedStyle()) {
281 dst->AddStringAttribute(ui::AX_ATTR_DISPLAY,
282 UTF16ToUTF8(src.computedStyleDisplay()));
284 if (src.helpText().length())
285 dst->AddStringAttribute(ui::AX_ATTR_HELP, UTF16ToUTF8(src.helpText()));
286 if (src.keyboardShortcut().length()) {
287 dst->AddStringAttribute(ui::AX_ATTR_SHORTCUT,
288 UTF16ToUTF8(src.keyboardShortcut()));
290 if (!src.titleUIElement().isDetached()) {
291 dst->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT,
292 src.titleUIElement().axID());
294 if (!src.ariaActiveDescendant().isDetached()) {
295 dst->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID,
296 src.ariaActiveDescendant().axID());
299 if (!src.url().isEmpty())
300 dst->AddStringAttribute(ui::AX_ATTR_URL, src.url().spec());
302 if (dst->role == ui::AX_ROLE_HEADING)
303 dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL, src.headingLevel());
304 else if ((dst->role == ui::AX_ROLE_TREE_ITEM ||
305 dst->role == ui::AX_ROLE_ROW) &&
306 src.hierarchicalLevel() > 0) {
307 dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL,
308 src.hierarchicalLevel());
311 // Treat the active list box item as focused.
312 if (dst->role == ui::AX_ROLE_LIST_BOX_OPTION &&
313 src.isSelectedOptionActive()) {
314 dst->state |= (1 << ui::AX_STATE_FOCUSED);
317 if (src.canvasHasFallbackContent())
318 dst->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK, true);
320 WebNode node = src.node();
321 bool is_iframe = false;
323 if (!node.isNull() && node.isElementNode()) {
324 WebElement element = node.to<WebElement>();
325 is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
327 // TODO(ctguil): The tagName in WebKit is lower cased but
328 // HTMLElement::nodeName calls localNameUpper. Consider adding
329 // a WebElement method that returns the original lower cased tagName.
330 dst->AddStringAttribute(
331 ui::AX_ATTR_HTML_TAG,
332 base::StringToLowerASCII(UTF16ToUTF8(element.tagName())));
333 for (unsigned i = 0; i < element.attributeCount(); ++i) {
334 std::string name = base::StringToLowerASCII(UTF16ToUTF8(
335 element.attributeLocalName(i)));
336 std::string value = UTF16ToUTF8(element.attributeValue(i));
337 dst->html_attributes.push_back(std::make_pair(name, value));
340 if (dst->role == ui::AX_ROLE_EDITABLE_TEXT ||
341 dst->role == ui::AX_ROLE_TEXT_AREA ||
342 dst->role == ui::AX_ROLE_TEXT_FIELD) {
343 dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, src.selectionStart());
344 dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, src.selectionEnd());
346 WebVector<int> src_line_breaks;
347 src.lineBreaks(src_line_breaks);
348 if (src_line_breaks.size() > 0) {
349 std::vector<int32> line_breaks;
350 line_breaks.reserve(src_line_breaks.size());
351 for (size_t i = 0; i < src_line_breaks.size(); ++i)
352 line_breaks.push_back(src_line_breaks[i]);
353 dst->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS, line_breaks);
356 if (dst->role == ui::AX_ROLE_TEXT_FIELD &&
357 src.textInputType().length()) {
358 dst->AddStringAttribute(ui::AX_ATTR_TEXT_INPUT_TYPE,
359 UTF16ToUTF8(src.textInputType()));
364 if (element.hasAttribute("role")) {
365 dst->AddStringAttribute(ui::AX_ATTR_ROLE,
366 UTF16ToUTF8(element.getAttribute("role")));
368 std::string role = GetEquivalentAriaRoleString(dst->role);
370 dst->AddStringAttribute(ui::AX_ATTR_ROLE, role);
373 // Browser plugin (used in a <webview>).
374 if (node_to_browser_plugin_instance_id_map_) {
375 BrowserPlugin* browser_plugin = BrowserPlugin::GetFromNode(element);
376 if (browser_plugin) {
377 (*node_to_browser_plugin_instance_id_map_)[dst->id] =
378 browser_plugin->browser_plugin_instance_id();
379 dst->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST, true);
384 if (src.isInLiveRegion()) {
385 dst->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC, src.liveRegionAtomic());
386 dst->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY, src.liveRegionBusy());
387 dst->AddStringAttribute(ui::AX_ATTR_LIVE_STATUS,
388 UTF16ToUTF8(src.liveRegionStatus()));
389 dst->AddStringAttribute(ui::AX_ATTR_LIVE_RELEVANT,
390 UTF16ToUTF8(src.liveRegionRelevant()));
391 dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC,
392 src.containerLiveRegionAtomic());
393 dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY,
394 src.containerLiveRegionBusy());
395 dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_STATUS,
396 UTF16ToUTF8(src.containerLiveRegionStatus()));
397 dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_RELEVANT,
398 UTF16ToUTF8(src.containerLiveRegionRelevant()));
401 if (dst->role == ui::AX_ROLE_PROGRESS_INDICATOR ||
402 dst->role == ui::AX_ROLE_SCROLL_BAR ||
403 dst->role == ui::AX_ROLE_SLIDER ||
404 dst->role == ui::AX_ROLE_SPIN_BUTTON) {
405 dst->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, src.valueForRange());
406 dst->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE,
407 src.maxValueForRange());
408 dst->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE,
409 src.minValueForRange());
412 if (dst->role == ui::AX_ROLE_DOCUMENT ||
413 dst->role == ui::AX_ROLE_WEB_AREA) {
414 dst->AddStringAttribute(ui::AX_ATTR_HTML_TAG, "#document");
415 const WebDocument& document = src.document();
417 name = UTF16ToUTF8(document.title());
418 dst->AddStringAttribute(ui::AX_ATTR_DOC_TITLE,
419 UTF16ToUTF8(document.title()));
420 dst->AddStringAttribute(ui::AX_ATTR_DOC_URL, document.url().spec());
421 dst->AddStringAttribute(
422 ui::AX_ATTR_DOC_MIMETYPE,
423 document.isXHTMLDocument() ? "text/xhtml" : "text/html");
424 dst->AddBoolAttribute(ui::AX_ATTR_DOC_LOADED, src.isLoaded());
425 dst->AddFloatAttribute(ui::AX_ATTR_DOC_LOADING_PROGRESS,
426 src.estimatedLoadingProgress());
428 const WebDocumentType& doctype = document.doctype();
429 if (!doctype.isNull()) {
430 dst->AddStringAttribute(ui::AX_ATTR_DOC_DOCTYPE,
431 UTF16ToUTF8(doctype.name()));
434 const gfx::Size& scroll_offset = document.scrollOffset();
435 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X, scroll_offset.width());
436 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y, scroll_offset.height());
438 const gfx::Size& min_offset = document.minimumScrollOffset();
439 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN, min_offset.width());
440 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN, min_offset.height());
442 const gfx::Size& max_offset = document.maximumScrollOffset();
443 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, max_offset.width());
444 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, max_offset.height());
446 if (node_to_frame_routing_id_map_ && !src.equals(GetRoot())) {
447 WebLocalFrame* frame = document.frame();
448 RenderFrameImpl* render_frame = RenderFrameImpl::FromWebFrame(frame);
450 (*node_to_frame_routing_id_map_)[dst->id] =
451 render_frame->GetRoutingID();
452 dst->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST, true);
454 RenderFrameProxy* render_frame_proxy =
455 RenderFrameProxy::FromWebFrame(frame);
456 if (render_frame_proxy) {
457 (*node_to_frame_routing_id_map_)[dst->id] =
458 render_frame_proxy->routing_id();
459 dst->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST, true);
465 if (dst->role == ui::AX_ROLE_TABLE) {
466 int column_count = src.columnCount();
467 int row_count = src.rowCount();
468 if (column_count > 0 && row_count > 0) {
469 std::set<int32> unique_cell_id_set;
470 std::vector<int32> cell_ids;
471 std::vector<int32> unique_cell_ids;
472 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT, column_count);
473 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT, row_count);
474 WebAXObject header = src.headerContainerObject();
475 if (!header.isDetached())
476 dst->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID, header.axID());
477 for (int i = 0; i < column_count * row_count; ++i) {
478 WebAXObject cell = src.cellForColumnAndRow(
479 i % column_count, i / column_count);
481 if (!cell.isDetached()) {
482 cell_id = cell.axID();
483 if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) {
484 unique_cell_id_set.insert(cell_id);
485 unique_cell_ids.push_back(cell_id);
488 cell_ids.push_back(cell_id);
490 dst->AddIntListAttribute(ui::AX_ATTR_CELL_IDS, cell_ids);
491 dst->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS, unique_cell_ids);
495 if (dst->role == ui::AX_ROLE_ROW) {
496 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX, src.rowIndex());
497 WebAXObject header = src.rowHeader();
498 if (!header.isDetached())
499 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID, header.axID());
502 if (dst->role == ui::AX_ROLE_COLUMN) {
503 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX, src.columnIndex());
504 WebAXObject header = src.columnHeader();
505 if (!header.isDetached())
506 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, header.axID());
509 if (dst->role == ui::AX_ROLE_CELL ||
510 dst->role == ui::AX_ROLE_ROW_HEADER ||
511 dst->role == ui::AX_ROLE_COLUMN_HEADER) {
512 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
513 src.cellColumnIndex());
514 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN,
515 src.cellColumnSpan());
516 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX, src.cellRowIndex());
517 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN, src.cellRowSpan());
520 dst->AddStringAttribute(ui::AX_ATTR_NAME, name);
522 // Add the ids of *indirect* children - those who are children of this node,
523 // but whose parent is *not* this node. One example is a table
524 // cell, which is a child of both a row and a column. Because the cell's
525 // parent is the row, the row adds it as a child, and the column adds it
526 // as an indirect child.
527 int child_count = src.childCount();
528 for (int i = 0; i < child_count; ++i) {
529 WebAXObject child = src.childAt(i);
530 std::vector<int32> indirect_child_ids;
531 if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child))
532 indirect_child_ids.push_back(child.axID());
533 if (indirect_child_ids.size() > 0) {
534 dst->AddIntListAttribute(
535 ui::AX_ATTR_INDIRECT_CHILD_IDS, indirect_child_ids);
539 WebVector<WebAXObject> controls;
540 if (src.ariaControls(controls))
541 AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS, controls, dst);
543 WebVector<WebAXObject> describedby;
544 if (src.ariaDescribedby(describedby)) {
545 AddIntListAttributeFromWebObjects(
546 ui::AX_ATTR_DESCRIBEDBY_IDS, describedby, dst);
549 WebVector<WebAXObject> flowTo;
550 if (src.ariaFlowTo(flowTo))
551 AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS, flowTo, dst);
553 WebVector<WebAXObject> labelledby;
554 if (src.ariaLabelledby(labelledby)) {
555 AddIntListAttributeFromWebObjects(
556 ui::AX_ATTR_LABELLEDBY_IDS, labelledby, dst);
559 WebVector<WebAXObject> owns;
560 if (src.ariaOwns(owns))
561 AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS, owns, dst);
564 blink::WebDocument BlinkAXTreeSource::GetMainDocument() const {
565 if (render_frame_ && render_frame_->GetWebFrame())
566 return render_frame_->GetWebFrame()->document();
567 return WebDocument();
570 } // namespace content