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/common/accessibility_messages.h"
13 #include "content/renderer/accessibility/blink_ax_enum_conversion.h"
14 #include "content/renderer/accessibility/renderer_accessibility.h"
15 #include "content/renderer/browser_plugin/browser_plugin.h"
16 #include "content/renderer/render_frame_impl.h"
17 #include "content/renderer/render_frame_proxy.h"
18 #include "content/renderer/render_view_impl.h"
19 #include "third_party/WebKit/public/platform/WebRect.h"
20 #include "third_party/WebKit/public/platform/WebSize.h"
21 #include "third_party/WebKit/public/platform/WebString.h"
22 #include "third_party/WebKit/public/platform/WebVector.h"
23 #include "third_party/WebKit/public/web/WebAXEnums.h"
24 #include "third_party/WebKit/public/web/WebAXObject.h"
25 #include "third_party/WebKit/public/web/WebDocument.h"
26 #include "third_party/WebKit/public/web/WebDocumentType.h"
27 #include "third_party/WebKit/public/web/WebElement.h"
28 #include "third_party/WebKit/public/web/WebFormControlElement.h"
29 #include "third_party/WebKit/public/web/WebFrame.h"
30 #include "third_party/WebKit/public/web/WebLocalFrame.h"
31 #include "third_party/WebKit/public/web/WebNode.h"
32 #include "third_party/WebKit/public/web/WebPlugin.h"
33 #include "third_party/WebKit/public/web/WebPluginContainer.h"
34 #include "third_party/WebKit/public/web/WebView.h"
36 using base::ASCIIToUTF16
;
37 using base::UTF16ToUTF8
;
38 using blink::WebAXObject
;
39 using blink::WebDocument
;
40 using blink::WebDocumentType
;
41 using blink::WebElement
;
42 using blink::WebFrame
;
43 using blink::WebLocalFrame
;
45 using blink::WebPlugin
;
46 using blink::WebPluginContainer
;
47 using blink::WebVector
;
54 // Returns true if |ancestor| is the first unignored parent of |child|,
55 // which means that when walking up the parent chain from |child|,
56 // |ancestor| is the *first* ancestor that isn't marked as
57 // accessibilityIsIgnored().
58 bool IsParentUnignoredOf(WebAXObject ancestor
,
60 WebAXObject parent
= child
.parentObject();
61 while (!parent
.isDetached() && parent
.accessibilityIsIgnored())
62 parent
= parent
.parentObject();
63 return parent
.equals(ancestor
);
66 std::string
GetEquivalentAriaRoleString(const ui::AXRole role
) {
68 case ui::AX_ROLE_ARTICLE
:
70 case ui::AX_ROLE_BANNER
:
72 case ui::AX_ROLE_BUTTON
:
74 case ui::AX_ROLE_COMPLEMENTARY
:
75 return "complementary";
76 case ui::AX_ROLE_FIGURE
:
78 case ui::AX_ROLE_FOOTER
:
80 case ui::AX_ROLE_HEADING
:
82 case ui::AX_ROLE_IMAGE
:
84 case ui::AX_ROLE_MAIN
:
86 case ui::AX_ROLE_NAVIGATION
:
88 case ui::AX_ROLE_RADIO_BUTTON
:
90 case ui::AX_ROLE_REGION
:
92 case ui::AX_ROLE_SLIDER
:
101 void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr
,
102 WebVector
<WebAXObject
> objects
,
103 AXContentNodeData
* dst
) {
104 std::vector
<int32
> ids
;
105 for(size_t i
= 0; i
< objects
.size(); i
++)
106 ids
.push_back(objects
[i
].axID());
108 dst
->AddIntListAttribute(attr
, ids
);
111 } // Anonymous namespace
113 BlinkAXTreeSource::BlinkAXTreeSource(RenderFrameImpl
* render_frame
)
114 : render_frame_(render_frame
),
115 accessibility_focus_id_(-1) {
118 BlinkAXTreeSource::~BlinkAXTreeSource() {
121 void BlinkAXTreeSource::SetRoot(blink::WebAXObject root
) {
125 bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node
) const {
126 const blink::WebAXObject
& root
= GetRoot();
127 while (IsValid(node
)) {
128 if (node
.equals(root
))
130 node
= GetParent(node
);
135 blink::WebAXObject
BlinkAXTreeSource::GetRoot() const {
138 return GetMainDocument().accessibilityObject();
141 blink::WebAXObject
BlinkAXTreeSource::GetFromId(int32 id
) const {
142 return GetMainDocument().accessibilityObjectFromID(id
);
145 int32
BlinkAXTreeSource::GetId(blink::WebAXObject node
) const {
149 void BlinkAXTreeSource::GetChildren(
150 blink::WebAXObject parent
,
151 std::vector
<blink::WebAXObject
>* out_children
) const {
152 if (parent
.role() == blink::WebAXRoleStaticText
) {
153 blink::WebAXObject ancestor
= parent
;
154 while (!ancestor
.isDetached()) {
155 if (ancestor
.axID() == accessibility_focus_id_
) {
156 parent
.loadInlineTextBoxes();
159 ancestor
= ancestor
.parentObject();
163 bool is_iframe
= false;
164 WebNode node
= parent
.node();
165 if (!node
.isNull() && node
.isElementNode()) {
166 WebElement element
= node
.to
<WebElement
>();
167 is_iframe
= (element
.tagName() == ASCIIToUTF16("IFRAME"));
170 for (unsigned i
= 0; i
< parent
.childCount(); i
++) {
171 blink::WebAXObject child
= parent
.childAt(i
);
173 // The child may be invalid due to issues in blink accessibility code.
174 if (child
.isDetached())
177 // Skip children whose parent isn't |parent|.
178 // As an exception, include children of an iframe element.
179 if (!is_iframe
&& !IsParentUnignoredOf(parent
, child
))
182 out_children
->push_back(child
);
186 blink::WebAXObject
BlinkAXTreeSource::GetParent(
187 blink::WebAXObject node
) const {
188 // Blink returns ignored objects when walking up the parent chain,
189 // we have to skip those here. Also, stop when we get to the root
191 blink::WebAXObject root
= GetRoot();
193 if (node
.equals(root
))
194 return blink::WebAXObject();
195 node
= node
.parentObject();
196 } while (!node
.isDetached() && node
.accessibilityIsIgnored());
201 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node
) const {
202 return !node
.isDetached(); // This also checks if it's null.
205 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1
,
206 blink::WebAXObject node2
) const {
207 return node1
.equals(node2
);
210 blink::WebAXObject
BlinkAXTreeSource::GetNull() const {
211 return blink::WebAXObject();
214 void BlinkAXTreeSource::SerializeNode(blink::WebAXObject src
,
215 AXContentNodeData
* dst
) const {
216 dst
->role
= AXRoleFromBlink(src
.role());
217 dst
->state
= AXStateFromBlink(src
);
218 dst
->location
= src
.boundingBoxRect();
219 dst
->id
= src
.axID();
220 std::string name
= UTF16ToUTF8(base::StringPiece16(src
.deprecatedTitle()));
223 if (src
.valueDescription().length()) {
224 dst
->AddStringAttribute(ui::AX_ATTR_VALUE
,
225 UTF16ToUTF8(base::StringPiece16(
226 src
.valueDescription())));
228 dst
->AddStringAttribute(
230 UTF16ToUTF8(base::StringPiece16(src
.stringValue())));
233 if (dst
->role
== ui::AX_ROLE_COLOR_WELL
)
234 dst
->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE
, src
.colorValue());
238 if (src
.backgroundColor())
239 dst
->AddIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR
, src
.backgroundColor());
242 dst
->AddIntAttribute(ui::AX_ATTR_COLOR
, src
.color());
244 // Font size is in pixels.
246 dst
->AddFloatAttribute(ui::AX_ATTR_FONT_SIZE
, src
.fontSize());
248 if (src
.invalidState()) {
249 dst
->AddIntAttribute(ui::AX_ATTR_INVALID_STATE
,
250 AXInvalidStateFromBlink(src
.invalidState()));
252 if (src
.invalidState() == blink::WebAXInvalidStateOther
) {
253 dst
->AddStringAttribute(
254 ui::AX_ATTR_ARIA_INVALID_VALUE
,
255 UTF16ToUTF8(base::StringPiece16(src
.ariaInvalidValue())));
258 if (src
.textDirection()) {
259 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION
,
260 AXTextDirectionFromBlink(src
.textDirection()));
263 if (src
.textStyle()) {
264 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_STYLE
,
265 AXTextStyleFromBlink(src
.textStyle()));
269 if (dst
->role
== ui::AX_ROLE_INLINE_TEXT_BOX
) {
270 WebVector
<int> src_character_offsets
;
271 src
.characterOffsets(src_character_offsets
);
272 std::vector
<int32
> character_offsets
;
273 character_offsets
.reserve(src_character_offsets
.size());
274 for (size_t i
= 0; i
< src_character_offsets
.size(); ++i
)
275 character_offsets
.push_back(src_character_offsets
[i
]);
276 dst
->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS
, character_offsets
);
278 WebVector
<int> src_word_starts
;
279 WebVector
<int> src_word_ends
;
280 src
.wordBoundaries(src_word_starts
, src_word_ends
);
281 std::vector
<int32
> word_starts
;
282 std::vector
<int32
> word_ends
;
283 word_starts
.reserve(src_word_starts
.size());
284 word_ends
.reserve(src_word_starts
.size());
285 for (size_t i
= 0; i
< src_word_starts
.size(); ++i
) {
286 word_starts
.push_back(src_word_starts
[i
]);
287 word_ends
.push_back(src_word_ends
[i
]);
289 dst
->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS
, word_starts
);
290 dst
->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS
, word_ends
);
293 if (src
.accessKey().length()) {
294 dst
->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY
,
295 UTF16ToUTF8(base::StringPiece16(src
.accessKey())));
298 if (src
.actionVerb().length())
299 dst
->AddStringAttribute(
301 UTF16ToUTF8(base::StringPiece16(src
.actionVerb())));
302 if (src
.ariaAutoComplete().length())
303 dst
->AddStringAttribute(
304 ui::AX_ATTR_AUTO_COMPLETE
,
305 UTF16ToUTF8(base::StringPiece16(src
.ariaAutoComplete())));
306 if (src
.isAriaReadOnly())
307 dst
->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY
, true);
308 if (src
.isButtonStateMixed())
309 dst
->AddBoolAttribute(ui::AX_ATTR_BUTTON_MIXED
, true);
310 if (src
.canSetValueAttribute())
311 dst
->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE
, true);
312 if (src
.deprecatedAccessibilityDescription().length()) {
313 dst
->AddStringAttribute(
314 ui::AX_ATTR_DESCRIPTION
,
315 UTF16ToUTF8(base::StringPiece16(
316 src
.deprecatedAccessibilityDescription())));
318 if (src
.hasComputedStyle()) {
319 dst
->AddStringAttribute(
321 UTF16ToUTF8(base::StringPiece16(src
.computedStyleDisplay())));
323 if (src
.deprecatedHelpText().length())
324 dst
->AddStringAttribute(
326 UTF16ToUTF8(base::StringPiece16((src
.deprecatedHelpText()))));
327 if (src
.deprecatedPlaceholder().length()) {
328 dst
->AddStringAttribute(
329 ui::AX_ATTR_PLACEHOLDER
,
330 UTF16ToUTF8(base::StringPiece16(src
.deprecatedPlaceholder())));
332 if (src
.keyboardShortcut().length()) {
333 dst
->AddStringAttribute(
334 ui::AX_ATTR_SHORTCUT
,
335 UTF16ToUTF8(base::StringPiece16(src
.keyboardShortcut())));
337 if (!src
.deprecatedTitleUIElement().isDetached()) {
338 dst
->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT
,
339 src
.deprecatedTitleUIElement().axID());
341 if (!src
.ariaActiveDescendant().isDetached()) {
342 dst
->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID
,
343 src
.ariaActiveDescendant().axID());
346 if (!src
.url().isEmpty())
347 dst
->AddStringAttribute(ui::AX_ATTR_URL
, src
.url().spec());
349 if (dst
->role
== ui::AX_ROLE_HEADING
)
350 dst
->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL
, src
.headingLevel());
351 else if ((dst
->role
== ui::AX_ROLE_TREE_ITEM
||
352 dst
->role
== ui::AX_ROLE_ROW
) &&
353 src
.hierarchicalLevel() > 0) {
354 dst
->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL
,
355 src
.hierarchicalLevel());
359 dst
->AddIntAttribute(ui::AX_ATTR_SET_SIZE
, src
.setSize());
362 dst
->AddIntAttribute(ui::AX_ATTR_POS_IN_SET
, src
.posInSet());
364 // Treat the active list box item as focused.
365 if (dst
->role
== ui::AX_ROLE_LIST_BOX_OPTION
&&
366 src
.isSelectedOptionActive()) {
367 dst
->state
|= (1 << ui::AX_STATE_FOCUSED
);
370 if (src
.canvasHasFallbackContent())
371 dst
->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK
, true);
373 WebNode node
= src
.node();
374 bool is_iframe
= false;
376 if (!node
.isNull() && node
.isElementNode()) {
377 WebElement element
= node
.to
<WebElement
>();
378 is_iframe
= (element
.tagName() == ASCIIToUTF16("IFRAME"));
380 // TODO(ctguil): The tagName in WebKit is lower cased but
381 // HTMLElement::nodeName calls localNameUpper. Consider adding
382 // a WebElement method that returns the original lower cased tagName.
383 dst
->AddStringAttribute(
384 ui::AX_ATTR_HTML_TAG
,
385 base::ToLowerASCII(UTF16ToUTF8(
386 base::StringPiece16(element
.tagName()))));
387 for (unsigned i
= 0; i
< element
.attributeCount(); ++i
) {
388 std::string name
= base::ToLowerASCII(UTF16ToUTF8(
389 base::StringPiece16(element
.attributeLocalName(i
))));
391 UTF16ToUTF8(base::StringPiece16(element
.attributeValue(i
)));
392 dst
->html_attributes
.push_back(std::make_pair(name
, value
));
395 if (src
.isEditable()) {
396 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START
, src
.selectionStart());
397 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END
, src
.selectionEnd());
399 WebVector
<int> src_line_breaks
;
400 src
.lineBreaks(src_line_breaks
);
401 if (src_line_breaks
.size() > 0) {
402 std::vector
<int32
> line_breaks
;
403 line_breaks
.reserve(src_line_breaks
.size());
404 for (size_t i
= 0; i
< src_line_breaks
.size(); ++i
)
405 line_breaks
.push_back(src_line_breaks
[i
]);
406 dst
->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS
, line_breaks
);
411 if (element
.hasAttribute("role")) {
412 dst
->AddStringAttribute(
414 UTF16ToUTF8(base::StringPiece16(element
.getAttribute("role"))));
416 std::string role
= GetEquivalentAriaRoleString(dst
->role
);
418 dst
->AddStringAttribute(ui::AX_ATTR_ROLE
, role
);
419 else if (dst
->role
== ui::AX_ROLE_TIME
)
420 dst
->AddStringAttribute(ui::AX_ATTR_ROLE
, "time");
423 // Browser plugin (used in a <webview>).
424 BrowserPlugin
* browser_plugin
= BrowserPlugin::GetFromNode(element
);
425 if (browser_plugin
) {
426 dst
->AddContentIntAttribute(
427 AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID
,
428 browser_plugin
->browser_plugin_instance_id());
431 // Out-of-process iframe.
433 WebFrame
* frame
= WebFrame::fromFrameOwnerElement(element
);
435 if (frame
&& frame
->isWebRemoteFrame()) {
436 RenderFrameProxy
* render_frame_proxy
=
437 RenderFrameProxy::FromWebFrame(frame
);
438 DCHECK(render_frame_proxy
);
439 dst
->AddContentIntAttribute(
440 AX_CONTENT_ATTR_CHILD_ROUTING_ID
,
441 render_frame_proxy
->routing_id());
446 if (src
.isInLiveRegion()) {
447 dst
->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC
, src
.liveRegionAtomic());
448 dst
->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY
, src
.liveRegionBusy());
449 if (src
.liveRegionBusy())
450 dst
->state
|= (1 << ui::AX_STATE_BUSY
);
451 if (!src
.liveRegionStatus().isEmpty()) {
452 dst
->AddStringAttribute(
453 ui::AX_ATTR_LIVE_STATUS
,
454 UTF16ToUTF8(base::StringPiece16(src
.liveRegionStatus())));
456 dst
->AddStringAttribute(
457 ui::AX_ATTR_LIVE_RELEVANT
,
458 UTF16ToUTF8(base::StringPiece16(src
.liveRegionRelevant())));
459 dst
->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC
,
460 src
.containerLiveRegionAtomic());
461 dst
->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY
,
462 src
.containerLiveRegionBusy());
463 dst
->AddStringAttribute(
464 ui::AX_ATTR_CONTAINER_LIVE_STATUS
,
465 UTF16ToUTF8(base::StringPiece16(src
.containerLiveRegionStatus())));
466 dst
->AddStringAttribute(
467 ui::AX_ATTR_CONTAINER_LIVE_RELEVANT
,
468 UTF16ToUTF8(base::StringPiece16(src
.containerLiveRegionRelevant())));
471 if (dst
->role
== ui::AX_ROLE_PROGRESS_INDICATOR
||
472 dst
->role
== ui::AX_ROLE_METER
||
473 dst
->role
== ui::AX_ROLE_SCROLL_BAR
||
474 dst
->role
== ui::AX_ROLE_SLIDER
||
475 dst
->role
== ui::AX_ROLE_SPIN_BUTTON
) {
476 dst
->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE
, src
.valueForRange());
477 dst
->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE
,
478 src
.maxValueForRange());
479 dst
->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE
,
480 src
.minValueForRange());
483 if (dst
->role
== ui::AX_ROLE_WEB_AREA
) {
484 dst
->AddStringAttribute(ui::AX_ATTR_HTML_TAG
, "#document");
485 const WebDocument
& document
= src
.document();
487 name
= UTF16ToUTF8(base::StringPiece16(document
.title()));
488 dst
->AddStringAttribute(
489 ui::AX_ATTR_DOC_TITLE
,
490 UTF16ToUTF8(base::StringPiece16(document
.title())));
491 dst
->AddStringAttribute(ui::AX_ATTR_DOC_URL
, document
.url().spec());
492 dst
->AddStringAttribute(
493 ui::AX_ATTR_DOC_MIMETYPE
,
494 document
.isXHTMLDocument() ? "text/xhtml" : "text/html");
495 dst
->AddBoolAttribute(ui::AX_ATTR_DOC_LOADED
, src
.isLoaded());
496 dst
->AddFloatAttribute(ui::AX_ATTR_DOC_LOADING_PROGRESS
,
497 src
.estimatedLoadingProgress());
499 const WebDocumentType
& doctype
= document
.doctype();
500 if (!doctype
.isNull()) {
501 dst
->AddStringAttribute(
502 ui::AX_ATTR_DOC_DOCTYPE
,
503 UTF16ToUTF8(base::StringPiece16(doctype
.name())));
506 WebAXObject anchor_object
, focus_object
;
507 int anchor_offset
, focus_offset
;
508 src
.selection(anchor_object
, anchor_offset
, focus_object
, focus_offset
);
509 if (!anchor_object
.isNull() && !focus_object
.isNull() &&
510 anchor_offset
>= 0 && focus_offset
>= 0) {
511 int32 anchor_id
= anchor_object
.axID();
512 int32 focus_id
= focus_object
.axID();
513 dst
->AddIntAttribute(ui::AX_ATTR_ANCHOR_OBJECT_ID
, anchor_id
);
514 dst
->AddIntAttribute(ui::AX_ATTR_ANCHOR_OFFSET
, anchor_offset
);
515 dst
->AddIntAttribute(ui::AX_ATTR_FOCUS_OBJECT_ID
, focus_id
);
516 dst
->AddIntAttribute(ui::AX_ATTR_FOCUS_OFFSET
, focus_offset
);
519 // Get the tree ID for this frame and possibly the parent frame.
520 WebLocalFrame
* web_frame
= document
.frame();
522 RenderFrame
* render_frame
= RenderFrame::FromWebFrame(web_frame
);
523 dst
->AddContentIntAttribute(
524 AX_CONTENT_ATTR_ROUTING_ID
,
525 render_frame
->GetRoutingID());
527 // Get the tree ID for the parent frame, if it's remote.
528 // (If it's local, it's already part of this same tree.)
529 blink::WebFrame
* parent_web_frame
= web_frame
->parent();
530 if (parent_web_frame
&& parent_web_frame
->isWebRemoteFrame()) {
531 RenderFrameProxy
* parent_render_frame_proxy
=
532 RenderFrameProxy::FromWebFrame(parent_web_frame
);
533 dst
->AddContentIntAttribute(
534 AX_CONTENT_ATTR_PARENT_ROUTING_ID
,
535 parent_render_frame_proxy
->routing_id());
540 if (dst
->role
== ui::AX_ROLE_TABLE
) {
541 int column_count
= src
.columnCount();
542 int row_count
= src
.rowCount();
543 if (column_count
> 0 && row_count
> 0) {
544 std::set
<int32
> unique_cell_id_set
;
545 std::vector
<int32
> cell_ids
;
546 std::vector
<int32
> unique_cell_ids
;
547 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT
, column_count
);
548 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT
, row_count
);
549 WebAXObject header
= src
.headerContainerObject();
550 if (!header
.isDetached())
551 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID
, header
.axID());
552 for (int i
= 0; i
< column_count
* row_count
; ++i
) {
553 WebAXObject cell
= src
.cellForColumnAndRow(
554 i
% column_count
, i
/ column_count
);
556 if (!cell
.isDetached()) {
557 cell_id
= cell
.axID();
558 if (unique_cell_id_set
.find(cell_id
) == unique_cell_id_set
.end()) {
559 unique_cell_id_set
.insert(cell_id
);
560 unique_cell_ids
.push_back(cell_id
);
563 cell_ids
.push_back(cell_id
);
565 dst
->AddIntListAttribute(ui::AX_ATTR_CELL_IDS
, cell_ids
);
566 dst
->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS
, unique_cell_ids
);
570 if (dst
->role
== ui::AX_ROLE_ROW
) {
571 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX
, src
.rowIndex());
572 WebAXObject header
= src
.rowHeader();
573 if (!header
.isDetached())
574 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID
, header
.axID());
577 if (dst
->role
== ui::AX_ROLE_COLUMN
) {
578 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX
, src
.columnIndex());
579 WebAXObject header
= src
.columnHeader();
580 if (!header
.isDetached())
581 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID
, header
.axID());
584 if (dst
->role
== ui::AX_ROLE_CELL
||
585 dst
->role
== ui::AX_ROLE_ROW_HEADER
||
586 dst
->role
== ui::AX_ROLE_COLUMN_HEADER
) {
587 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX
,
588 src
.cellColumnIndex());
589 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN
,
590 src
.cellColumnSpan());
591 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX
, src
.cellRowIndex());
592 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN
, src
.cellRowSpan());
595 if ((dst
->role
== ui::AX_ROLE_ROW_HEADER
||
596 dst
->role
== ui::AX_ROLE_COLUMN_HEADER
) && src
.sortDirection()) {
597 dst
->AddIntAttribute(ui::AX_ATTR_SORT_DIRECTION
,
598 AXSortDirectionFromBlink(src
.sortDirection()));
601 dst
->AddStringAttribute(ui::AX_ATTR_NAME
, name
);
603 // Add the ids of *indirect* children - those who are children of this node,
604 // but whose parent is *not* this node. One example is a table
605 // cell, which is a child of both a row and a column. Because the cell's
606 // parent is the row, the row adds it as a child, and the column adds it
607 // as an indirect child.
608 int child_count
= src
.childCount();
609 for (int i
= 0; i
< child_count
; ++i
) {
610 WebAXObject child
= src
.childAt(i
);
611 std::vector
<int32
> indirect_child_ids
;
612 if (!is_iframe
&& !child
.isDetached() && !IsParentUnignoredOf(src
, child
))
613 indirect_child_ids
.push_back(child
.axID());
614 if (indirect_child_ids
.size() > 0) {
615 dst
->AddIntListAttribute(
616 ui::AX_ATTR_INDIRECT_CHILD_IDS
, indirect_child_ids
);
620 WebVector
<WebAXObject
> controls
;
621 if (src
.ariaControls(controls
))
622 AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS
, controls
, dst
);
624 WebVector
<WebAXObject
> describedby
;
625 if (src
.deprecatedAriaDescribedby(describedby
)) {
626 AddIntListAttributeFromWebObjects(
627 ui::AX_ATTR_DESCRIBEDBY_IDS
, describedby
, dst
);
630 WebVector
<WebAXObject
> flowTo
;
631 if (src
.ariaFlowTo(flowTo
))
632 AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS
, flowTo
, dst
);
634 WebVector
<WebAXObject
> labelledby
;
635 if (src
.deprecatedAriaLabelledby(labelledby
)) {
636 AddIntListAttributeFromWebObjects(
637 ui::AX_ATTR_LABELLEDBY_IDS
, labelledby
, dst
);
640 WebVector
<WebAXObject
> owns
;
641 if (src
.ariaOwns(owns
))
642 AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS
, owns
, dst
);
645 if (src
.isScrollableContainer()) {
646 const gfx::Point
& scrollOffset
= src
.scrollOffset();
647 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_X
, scrollOffset
.x());
648 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_Y
, scrollOffset
.y());
650 const gfx::Point
& minScrollOffset
= src
.minimumScrollOffset();
651 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN
, minScrollOffset
.x());
652 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN
, minScrollOffset
.y());
654 const gfx::Point
& maxScrollOffset
= src
.maximumScrollOffset();
655 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX
, maxScrollOffset
.x());
656 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX
, maxScrollOffset
.y());
660 blink::WebDocument
BlinkAXTreeSource::GetMainDocument() const {
661 if (render_frame_
&& render_frame_
->GetWebFrame())
662 return render_frame_
->GetWebFrame()->document();
663 return WebDocument();
666 } // namespace content