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