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::WebFrame
;
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_FIGURE
:
75 case ui::AX_ROLE_FOOTER
:
77 case ui::AX_ROLE_HEADING
:
79 case ui::AX_ROLE_IMAGE
:
81 case ui::AX_ROLE_MAIN
:
83 case ui::AX_ROLE_NAVIGATION
:
85 case ui::AX_ROLE_RADIO_BUTTON
:
87 case ui::AX_ROLE_REGION
:
89 case ui::AX_ROLE_SLIDER
:
98 void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr
,
99 WebVector
<WebAXObject
> objects
,
100 ui::AXNodeData
* dst
) {
101 std::vector
<int32
> ids
;
102 for(size_t i
= 0; i
< objects
.size(); i
++)
103 ids
.push_back(objects
[i
].axID());
105 dst
->AddIntListAttribute(attr
, ids
);
108 } // Anonymous namespace
110 BlinkAXTreeSource::BlinkAXTreeSource(RenderFrameImpl
* render_frame
)
111 : render_frame_(render_frame
),
112 node_to_frame_routing_id_map_(NULL
),
113 node_to_browser_plugin_instance_id_map_(NULL
),
114 accessibility_focus_id_(-1) {
117 BlinkAXTreeSource::~BlinkAXTreeSource() {
120 void BlinkAXTreeSource::SetRoot(blink::WebAXObject root
) {
124 bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node
) const {
125 const blink::WebAXObject
& root
= GetRoot();
126 while (IsValid(node
)) {
127 if (node
.equals(root
))
129 node
= GetParent(node
);
134 void BlinkAXTreeSource::CollectChildFrameIdMapping(
135 std::map
<int32
, int>* node_to_frame_routing_id_map
,
136 std::map
<int32
, int>* node_to_browser_plugin_instance_id_map
) {
137 node_to_frame_routing_id_map_
= node_to_frame_routing_id_map
;
138 node_to_browser_plugin_instance_id_map_
=
139 node_to_browser_plugin_instance_id_map
;
142 blink::WebAXObject
BlinkAXTreeSource::GetRoot() const {
145 return GetMainDocument().accessibilityObject();
148 blink::WebAXObject
BlinkAXTreeSource::GetFromId(int32 id
) const {
149 return GetMainDocument().accessibilityObjectFromID(id
);
152 int32
BlinkAXTreeSource::GetId(blink::WebAXObject node
) const {
156 void BlinkAXTreeSource::GetChildren(
157 blink::WebAXObject parent
,
158 std::vector
<blink::WebAXObject
>* out_children
) const {
159 if (parent
.role() == blink::WebAXRoleStaticText
) {
160 blink::WebAXObject ancestor
= parent
;
161 while (!ancestor
.isDetached()) {
162 if (ancestor
.axID() == accessibility_focus_id_
) {
163 parent
.loadInlineTextBoxes();
166 ancestor
= ancestor
.parentObject();
170 bool is_iframe
= false;
171 WebNode node
= parent
.node();
172 if (!node
.isNull() && node
.isElementNode()) {
173 WebElement element
= node
.to
<WebElement
>();
174 is_iframe
= (element
.tagName() == ASCIIToUTF16("IFRAME"));
177 for (unsigned i
= 0; i
< parent
.childCount(); i
++) {
178 blink::WebAXObject child
= parent
.childAt(i
);
180 // The child may be invalid due to issues in blink accessibility code.
181 if (child
.isDetached())
184 // Skip children whose parent isn't |parent|.
185 // As an exception, include children of an iframe element.
186 if (!is_iframe
&& !IsParentUnignoredOf(parent
, child
))
189 out_children
->push_back(child
);
193 blink::WebAXObject
BlinkAXTreeSource::GetParent(
194 blink::WebAXObject node
) const {
195 // Blink returns ignored objects when walking up the parent chain,
196 // we have to skip those here. Also, stop when we get to the root
198 blink::WebAXObject root
= GetRoot();
200 if (node
.equals(root
))
201 return blink::WebAXObject();
202 node
= node
.parentObject();
203 } while (!node
.isDetached() && node
.accessibilityIsIgnored());
208 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node
) const {
209 return !node
.isDetached(); // This also checks if it's null.
212 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1
,
213 blink::WebAXObject node2
) const {
214 return node1
.equals(node2
);
217 blink::WebAXObject
BlinkAXTreeSource::GetNull() const {
218 return blink::WebAXObject();
221 void BlinkAXTreeSource::SerializeNode(blink::WebAXObject src
,
222 ui::AXNodeData
* dst
) const {
223 dst
->role
= AXRoleFromBlink(src
.role());
224 dst
->state
= AXStateFromBlink(src
);
225 dst
->location
= src
.boundingBoxRect();
226 dst
->id
= src
.axID();
227 std::string name
= UTF16ToUTF8(base::StringPiece16(src
.deprecatedTitle()));
230 if (src
.valueDescription().length()) {
231 dst
->AddStringAttribute(ui::AX_ATTR_VALUE
,
232 UTF16ToUTF8(base::StringPiece16(
233 src
.valueDescription())));
235 dst
->AddStringAttribute(
237 UTF16ToUTF8(base::StringPiece16(src
.stringValue())));
240 if (dst
->role
== ui::AX_ROLE_COLOR_WELL
)
241 dst
->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE
, src
.colorValue());
245 if (src
.backgroundColor())
246 dst
->AddIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR
, src
.backgroundColor());
249 dst
->AddIntAttribute(ui::AX_ATTR_COLOR
, src
.color());
251 // Font size is in pixels.
253 dst
->AddFloatAttribute(ui::AX_ATTR_FONT_SIZE
, src
.fontSize());
255 if (src
.invalidState()) {
256 dst
->AddIntAttribute(ui::AX_ATTR_INVALID_STATE
,
257 AXInvalidStateFromBlink(src
.invalidState()));
259 if (src
.invalidState() == blink::WebAXInvalidStateOther
) {
260 dst
->AddStringAttribute(
261 ui::AX_ATTR_ARIA_INVALID_VALUE
,
262 UTF16ToUTF8(base::StringPiece16(src
.ariaInvalidValue())));
265 if (src
.textDirection()) {
266 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION
,
267 AXTextDirectionFromBlink(src
.textDirection()));
270 if (src
.textStyle()) {
271 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_STYLE
,
272 AXTextStyleFromBlink(src
.textStyle()));
276 if (dst
->role
== ui::AX_ROLE_INLINE_TEXT_BOX
) {
277 WebVector
<int> src_character_offsets
;
278 src
.characterOffsets(src_character_offsets
);
279 std::vector
<int32
> character_offsets
;
280 character_offsets
.reserve(src_character_offsets
.size());
281 for (size_t i
= 0; i
< src_character_offsets
.size(); ++i
)
282 character_offsets
.push_back(src_character_offsets
[i
]);
283 dst
->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS
, character_offsets
);
285 WebVector
<int> src_word_starts
;
286 WebVector
<int> src_word_ends
;
287 src
.wordBoundaries(src_word_starts
, src_word_ends
);
288 std::vector
<int32
> word_starts
;
289 std::vector
<int32
> word_ends
;
290 word_starts
.reserve(src_word_starts
.size());
291 word_ends
.reserve(src_word_starts
.size());
292 for (size_t i
= 0; i
< src_word_starts
.size(); ++i
) {
293 word_starts
.push_back(src_word_starts
[i
]);
294 word_ends
.push_back(src_word_ends
[i
]);
296 dst
->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS
, word_starts
);
297 dst
->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS
, word_ends
);
300 if (src
.accessKey().length()) {
301 dst
->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY
,
302 UTF16ToUTF8(base::StringPiece16(src
.accessKey())));
305 if (src
.actionVerb().length())
306 dst
->AddStringAttribute(
308 UTF16ToUTF8(base::StringPiece16(src
.actionVerb())));
309 if (src
.ariaAutoComplete().length())
310 dst
->AddStringAttribute(
311 ui::AX_ATTR_AUTO_COMPLETE
,
312 UTF16ToUTF8(base::StringPiece16(src
.ariaAutoComplete())));
313 if (src
.isAriaReadOnly())
314 dst
->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY
, true);
315 if (src
.isButtonStateMixed())
316 dst
->AddBoolAttribute(ui::AX_ATTR_BUTTON_MIXED
, true);
317 if (src
.canSetValueAttribute())
318 dst
->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE
, true);
319 if (src
.deprecatedAccessibilityDescription().length()) {
320 dst
->AddStringAttribute(
321 ui::AX_ATTR_DESCRIPTION
,
322 UTF16ToUTF8(base::StringPiece16(
323 src
.deprecatedAccessibilityDescription())));
325 if (src
.hasComputedStyle()) {
326 dst
->AddStringAttribute(
328 UTF16ToUTF8(base::StringPiece16(src
.computedStyleDisplay())));
330 if (src
.deprecatedHelpText().length())
331 dst
->AddStringAttribute(
333 UTF16ToUTF8(base::StringPiece16((src
.deprecatedHelpText()))));
334 if (src
.deprecatedPlaceholder().length()) {
335 dst
->AddStringAttribute(
336 ui::AX_ATTR_PLACEHOLDER
,
337 UTF16ToUTF8(base::StringPiece16(src
.deprecatedPlaceholder())));
339 if (src
.keyboardShortcut().length()) {
340 dst
->AddStringAttribute(
341 ui::AX_ATTR_SHORTCUT
,
342 UTF16ToUTF8(base::StringPiece16(src
.keyboardShortcut())));
344 if (!src
.deprecatedTitleUIElement().isDetached()) {
345 dst
->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT
,
346 src
.deprecatedTitleUIElement().axID());
348 if (!src
.ariaActiveDescendant().isDetached()) {
349 dst
->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID
,
350 src
.ariaActiveDescendant().axID());
353 if (!src
.url().isEmpty())
354 dst
->AddStringAttribute(ui::AX_ATTR_URL
, src
.url().spec());
356 if (dst
->role
== ui::AX_ROLE_HEADING
)
357 dst
->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL
, src
.headingLevel());
358 else if ((dst
->role
== ui::AX_ROLE_TREE_ITEM
||
359 dst
->role
== ui::AX_ROLE_ROW
) &&
360 src
.hierarchicalLevel() > 0) {
361 dst
->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL
,
362 src
.hierarchicalLevel());
366 dst
->AddIntAttribute(ui::AX_ATTR_SET_SIZE
, src
.setSize());
369 dst
->AddIntAttribute(ui::AX_ATTR_POS_IN_SET
, src
.posInSet());
371 // Treat the active list box item as focused.
372 if (dst
->role
== ui::AX_ROLE_LIST_BOX_OPTION
&&
373 src
.isSelectedOptionActive()) {
374 dst
->state
|= (1 << ui::AX_STATE_FOCUSED
);
377 if (src
.canvasHasFallbackContent())
378 dst
->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK
, true);
380 WebNode node
= src
.node();
381 bool is_iframe
= false;
383 if (!node
.isNull() && node
.isElementNode()) {
384 WebElement element
= node
.to
<WebElement
>();
385 is_iframe
= (element
.tagName() == ASCIIToUTF16("IFRAME"));
387 // TODO(ctguil): The tagName in WebKit is lower cased but
388 // HTMLElement::nodeName calls localNameUpper. Consider adding
389 // a WebElement method that returns the original lower cased tagName.
390 dst
->AddStringAttribute(
391 ui::AX_ATTR_HTML_TAG
,
392 base::StringToLowerASCII(UTF16ToUTF8(
393 base::StringPiece16(element
.tagName()))));
394 for (unsigned i
= 0; i
< element
.attributeCount(); ++i
) {
395 std::string name
= base::StringToLowerASCII(UTF16ToUTF8(
396 base::StringPiece16(element
.attributeLocalName(i
))));
398 UTF16ToUTF8(base::StringPiece16(element
.attributeValue(i
)));
399 dst
->html_attributes
.push_back(std::make_pair(name
, value
));
402 if (!src
.isReadOnly() || dst
->role
== ui::AX_ROLE_TEXT_FIELD
) {
403 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START
, src
.selectionStart());
404 dst
->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END
, src
.selectionEnd());
406 WebVector
<int> src_line_breaks
;
407 src
.lineBreaks(src_line_breaks
);
408 if (src_line_breaks
.size() > 0) {
409 std::vector
<int32
> line_breaks
;
410 line_breaks
.reserve(src_line_breaks
.size());
411 for (size_t i
= 0; i
< src_line_breaks
.size(); ++i
)
412 line_breaks
.push_back(src_line_breaks
[i
]);
413 dst
->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS
, line_breaks
);
418 if (element
.hasAttribute("role")) {
419 dst
->AddStringAttribute(
421 UTF16ToUTF8(base::StringPiece16(element
.getAttribute("role"))));
423 std::string role
= GetEquivalentAriaRoleString(dst
->role
);
425 dst
->AddStringAttribute(ui::AX_ATTR_ROLE
, role
);
426 else if (dst
->role
== ui::AX_ROLE_TIME
)
427 dst
->AddStringAttribute(ui::AX_ATTR_ROLE
, "time");
430 // Browser plugin (used in a <webview>).
431 if (node_to_browser_plugin_instance_id_map_
) {
432 BrowserPlugin
* browser_plugin
= BrowserPlugin::GetFromNode(element
);
433 if (browser_plugin
) {
434 (*node_to_browser_plugin_instance_id_map_
)[dst
->id
] =
435 browser_plugin
->browser_plugin_instance_id();
436 dst
->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST
, true);
440 // Out-of-process iframe.
441 if (is_iframe
&& node_to_frame_routing_id_map_
) {
442 WebFrame
* frame
= WebFrame::fromFrameOwnerElement(element
);
444 if (frame
&& frame
->isWebRemoteFrame()) {
445 RenderFrameProxy
* render_frame_proxy
=
446 RenderFrameProxy::FromWebFrame(frame
);
448 DCHECK(render_frame_proxy
);
449 (*node_to_frame_routing_id_map_
)[dst
->id
] =
450 render_frame_proxy
->routing_id();
451 dst
->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST
, true);
456 if (src
.isInLiveRegion()) {
457 dst
->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC
, src
.liveRegionAtomic());
458 dst
->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY
, src
.liveRegionBusy());
459 if (src
.liveRegionBusy())
460 dst
->state
|= (1 << ui::AX_STATE_BUSY
);
461 if (!src
.liveRegionStatus().isEmpty()) {
462 dst
->AddStringAttribute(
463 ui::AX_ATTR_LIVE_STATUS
,
464 UTF16ToUTF8(base::StringPiece16(src
.liveRegionStatus())));
466 dst
->AddStringAttribute(
467 ui::AX_ATTR_LIVE_RELEVANT
,
468 UTF16ToUTF8(base::StringPiece16(src
.liveRegionRelevant())));
469 dst
->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC
,
470 src
.containerLiveRegionAtomic());
471 dst
->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY
,
472 src
.containerLiveRegionBusy());
473 dst
->AddStringAttribute(
474 ui::AX_ATTR_CONTAINER_LIVE_STATUS
,
475 UTF16ToUTF8(base::StringPiece16(src
.containerLiveRegionStatus())));
476 dst
->AddStringAttribute(
477 ui::AX_ATTR_CONTAINER_LIVE_RELEVANT
,
478 UTF16ToUTF8(base::StringPiece16(src
.containerLiveRegionRelevant())));
481 if (dst
->role
== ui::AX_ROLE_PROGRESS_INDICATOR
||
482 dst
->role
== ui::AX_ROLE_METER
||
483 dst
->role
== ui::AX_ROLE_SCROLL_BAR
||
484 dst
->role
== ui::AX_ROLE_SLIDER
||
485 dst
->role
== ui::AX_ROLE_SPIN_BUTTON
) {
486 dst
->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE
, src
.valueForRange());
487 dst
->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE
,
488 src
.maxValueForRange());
489 dst
->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE
,
490 src
.minValueForRange());
493 if (dst
->role
== ui::AX_ROLE_WEB_AREA
) {
494 dst
->AddStringAttribute(ui::AX_ATTR_HTML_TAG
, "#document");
495 const WebDocument
& document
= src
.document();
497 name
= UTF16ToUTF8(base::StringPiece16(document
.title()));
498 dst
->AddStringAttribute(
499 ui::AX_ATTR_DOC_TITLE
,
500 UTF16ToUTF8(base::StringPiece16(document
.title())));
501 dst
->AddStringAttribute(ui::AX_ATTR_DOC_URL
, document
.url().spec());
502 dst
->AddStringAttribute(
503 ui::AX_ATTR_DOC_MIMETYPE
,
504 document
.isXHTMLDocument() ? "text/xhtml" : "text/html");
505 dst
->AddBoolAttribute(ui::AX_ATTR_DOC_LOADED
, src
.isLoaded());
506 dst
->AddFloatAttribute(ui::AX_ATTR_DOC_LOADING_PROGRESS
,
507 src
.estimatedLoadingProgress());
509 const WebDocumentType
& doctype
= document
.doctype();
510 if (!doctype
.isNull()) {
511 dst
->AddStringAttribute(
512 ui::AX_ATTR_DOC_DOCTYPE
,
513 UTF16ToUTF8(base::StringPiece16(doctype
.name())));
518 if (dst
->role
== ui::AX_ROLE_TABLE
) {
519 int column_count
= src
.columnCount();
520 int row_count
= src
.rowCount();
521 if (column_count
> 0 && row_count
> 0) {
522 std::set
<int32
> unique_cell_id_set
;
523 std::vector
<int32
> cell_ids
;
524 std::vector
<int32
> unique_cell_ids
;
525 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT
, column_count
);
526 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT
, row_count
);
527 WebAXObject header
= src
.headerContainerObject();
528 if (!header
.isDetached())
529 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID
, header
.axID());
530 for (int i
= 0; i
< column_count
* row_count
; ++i
) {
531 WebAXObject cell
= src
.cellForColumnAndRow(
532 i
% column_count
, i
/ column_count
);
534 if (!cell
.isDetached()) {
535 cell_id
= cell
.axID();
536 if (unique_cell_id_set
.find(cell_id
) == unique_cell_id_set
.end()) {
537 unique_cell_id_set
.insert(cell_id
);
538 unique_cell_ids
.push_back(cell_id
);
541 cell_ids
.push_back(cell_id
);
543 dst
->AddIntListAttribute(ui::AX_ATTR_CELL_IDS
, cell_ids
);
544 dst
->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS
, unique_cell_ids
);
548 if (dst
->role
== ui::AX_ROLE_ROW
) {
549 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX
, src
.rowIndex());
550 WebAXObject header
= src
.rowHeader();
551 if (!header
.isDetached())
552 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID
, header
.axID());
555 if (dst
->role
== ui::AX_ROLE_COLUMN
) {
556 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX
, src
.columnIndex());
557 WebAXObject header
= src
.columnHeader();
558 if (!header
.isDetached())
559 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID
, header
.axID());
562 if (dst
->role
== ui::AX_ROLE_CELL
||
563 dst
->role
== ui::AX_ROLE_ROW_HEADER
||
564 dst
->role
== ui::AX_ROLE_COLUMN_HEADER
) {
565 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX
,
566 src
.cellColumnIndex());
567 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN
,
568 src
.cellColumnSpan());
569 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX
, src
.cellRowIndex());
570 dst
->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN
, src
.cellRowSpan());
573 if ((dst
->role
== ui::AX_ROLE_ROW_HEADER
||
574 dst
->role
== ui::AX_ROLE_COLUMN_HEADER
) && src
.sortDirection()) {
575 dst
->AddIntAttribute(ui::AX_ATTR_SORT_DIRECTION
,
576 AXSortDirectionFromBlink(src
.sortDirection()));
579 dst
->AddStringAttribute(ui::AX_ATTR_NAME
, name
);
581 // Add the ids of *indirect* children - those who are children of this node,
582 // but whose parent is *not* this node. One example is a table
583 // cell, which is a child of both a row and a column. Because the cell's
584 // parent is the row, the row adds it as a child, and the column adds it
585 // as an indirect child.
586 int child_count
= src
.childCount();
587 for (int i
= 0; i
< child_count
; ++i
) {
588 WebAXObject child
= src
.childAt(i
);
589 std::vector
<int32
> indirect_child_ids
;
590 if (!is_iframe
&& !child
.isDetached() && !IsParentUnignoredOf(src
, child
))
591 indirect_child_ids
.push_back(child
.axID());
592 if (indirect_child_ids
.size() > 0) {
593 dst
->AddIntListAttribute(
594 ui::AX_ATTR_INDIRECT_CHILD_IDS
, indirect_child_ids
);
598 WebVector
<WebAXObject
> controls
;
599 if (src
.ariaControls(controls
))
600 AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS
, controls
, dst
);
602 WebVector
<WebAXObject
> describedby
;
603 if (src
.deprecatedAriaDescribedby(describedby
)) {
604 AddIntListAttributeFromWebObjects(
605 ui::AX_ATTR_DESCRIBEDBY_IDS
, describedby
, dst
);
608 WebVector
<WebAXObject
> flowTo
;
609 if (src
.ariaFlowTo(flowTo
))
610 AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS
, flowTo
, dst
);
612 WebVector
<WebAXObject
> labelledby
;
613 if (src
.deprecatedAriaLabelledby(labelledby
)) {
614 AddIntListAttributeFromWebObjects(
615 ui::AX_ATTR_LABELLEDBY_IDS
, labelledby
, dst
);
618 WebVector
<WebAXObject
> owns
;
619 if (src
.ariaOwns(owns
))
620 AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS
, owns
, dst
);
623 if (src
.isScrollableContainer()) {
624 const gfx::Point
& scrollOffset
= src
.scrollOffset();
625 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_X
, scrollOffset
.x());
626 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_Y
, scrollOffset
.y());
628 const gfx::Point
& minScrollOffset
= src
.minimumScrollOffset();
629 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN
, minScrollOffset
.x());
630 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN
, minScrollOffset
.y());
632 const gfx::Point
& maxScrollOffset
= src
.maximumScrollOffset();
633 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX
, maxScrollOffset
.x());
634 dst
->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX
, maxScrollOffset
.y());
638 blink::WebDocument
BlinkAXTreeSource::GetMainDocument() const {
639 if (render_frame_
&& render_frame_
->GetWebFrame())
640 return render_frame_
->GetWebFrame()->document();
641 return WebDocument();
644 } // namespace content