22c66acbdaa4f5d3f08d9d56e11589dae252ea64
[platform/framework/web/crosswalk.git] / src / content / renderer / accessibility / blink_ax_tree_source.cc
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 std::string GetEquivalentAriaRoleString(const ui::AXRole role) {
64   switch (role) {
65     case ui::AX_ROLE_ARTICLE:
66       return "article";
67     case ui::AX_ROLE_BANNER:
68       return "banner";
69     case ui::AX_ROLE_BUTTON:
70       return "button";
71     case ui::AX_ROLE_COMPLEMENTARY:
72       return "complementary";
73     case ui::AX_ROLE_FOOTER:
74       return "contentinfo";
75     case ui::AX_ROLE_HORIZONTAL_RULE:
76       return "separator";
77     case ui::AX_ROLE_IMAGE:
78       return "img";
79     case ui::AX_ROLE_MAIN:
80       return "main";
81     case ui::AX_ROLE_NAVIGATION:
82       return "navigation";
83     case ui::AX_ROLE_RADIO_BUTTON:
84       return "radio";
85     case ui::AX_ROLE_REGION:
86       return "region";
87     case ui::AX_ROLE_SLIDER:
88       return "slider";
89     default:
90       break;
91   }
92
93   return std::string();
94 }
95
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());
102   if (ids.size() > 0)
103     dst->AddIntListAttribute(attr, ids);
104 }
105
106 }  // Anonymous namespace
107
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) {
113 }
114
115 BlinkAXTreeSource::~BlinkAXTreeSource() {
116 }
117
118 bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node) const {
119   const blink::WebAXObject& root = GetRoot();
120   while (IsValid(node)) {
121     if (node.equals(root))
122       return true;
123     node = GetParent(node);
124   }
125   return false;
126 }
127
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;
134 }
135
136 blink::WebAXObject BlinkAXTreeSource::GetRoot() const {
137   return GetMainDocument().accessibilityObject();
138 }
139
140 blink::WebAXObject BlinkAXTreeSource::GetFromId(int32 id) const {
141   return GetMainDocument().accessibilityObjectFromID(id);
142 }
143
144 int32 BlinkAXTreeSource::GetId(blink::WebAXObject node) const {
145   return node.axID();
146 }
147
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();
156         break;
157       }
158       ancestor = ancestor.parentObject();
159     }
160   }
161
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"));
167   }
168
169   for (unsigned i = 0; i < parent.childCount(); i++) {
170     blink::WebAXObject child = parent.childAt(i);
171
172     // The child may be invalid due to issues in blink accessibility code.
173     if (child.isDetached())
174       continue;
175
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))
179       continue;
180
181     out_children->push_back(child);
182   }
183 }
184
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
189   // element.
190   blink::WebAXObject root = GetRoot();
191   do {
192     if (node.equals(root))
193       return blink::WebAXObject();
194     node = node.parentObject();
195   } while (!node.isDetached() && node.accessibilityIsIgnored());
196
197   return node;
198 }
199
200 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node) const {
201   return !node.isDetached();  // This also checks if it's null.
202 }
203
204 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1,
205                                 blink::WebAXObject node2) const {
206   return node1.equals(node2);
207 }
208
209 blink::WebAXObject BlinkAXTreeSource::GetNull() const {
210   return blink::WebAXObject();
211 }
212
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());
220
221   std::string value;
222   if (src.valueDescription().length()) {
223     dst->AddStringAttribute(ui::AX_ATTR_VALUE,
224                             UTF16ToUTF8(src.valueDescription()));
225   } else {
226     dst->AddStringAttribute(ui::AX_ATTR_VALUE, UTF16ToUTF8(src.stringValue()));
227   }
228
229   if (dst->role == ui::AX_ROLE_COLOR_WELL) {
230     int r, g, b;
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);
235   }
236
237   if (dst->role == ui::AX_ROLE_INLINE_TEXT_BOX) {
238     dst->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
239                          AXTextDirectionFromBlink(src.textDirection()));
240
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);
248
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]);
259     }
260     dst->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS, word_starts);
261     dst->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS, word_ends);
262   }
263
264   if (src.accessKey().length()) {
265     dst->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY,
266     UTF16ToUTF8(src.accessKey()));
267   }
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()));
279   }
280   if (src.hasComputedStyle()) {
281     dst->AddStringAttribute(ui::AX_ATTR_DISPLAY,
282                             UTF16ToUTF8(src.computedStyleDisplay()));
283   }
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()));
289   }
290   if (!src.titleUIElement().isDetached()) {
291     dst->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT,
292                          src.titleUIElement().axID());
293   }
294   if (!src.ariaActiveDescendant().isDetached()) {
295     dst->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID,
296                          src.ariaActiveDescendant().axID());
297   }
298
299   if (!src.url().isEmpty())
300     dst->AddStringAttribute(ui::AX_ATTR_URL, src.url().spec());
301
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());
309   }
310
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);
315   }
316
317   if (src.canvasHasFallbackContent())
318     dst->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK, true);
319
320   WebNode node = src.node();
321   bool is_iframe = false;
322
323   if (!node.isNull() && node.isElementNode()) {
324     WebElement element = node.to<WebElement>();
325     is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
326
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));
338     }
339
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());
345
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);
354       }
355
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()));
360       }
361     }
362
363     // ARIA role.
364     if (element.hasAttribute("role")) {
365       dst->AddStringAttribute(ui::AX_ATTR_ROLE,
366                               UTF16ToUTF8(element.getAttribute("role")));
367     } else {
368       std::string role = GetEquivalentAriaRoleString(dst->role);
369       if (!role.empty())
370         dst->AddStringAttribute(ui::AX_ATTR_ROLE, role);
371     }
372
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);
380       }
381     }
382   }
383
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()));
399   }
400
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());
410   }
411
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();
416     if (name.empty())
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());
427
428     const WebDocumentType& doctype = document.doctype();
429     if (!doctype.isNull()) {
430       dst->AddStringAttribute(ui::AX_ATTR_DOC_DOCTYPE,
431                               UTF16ToUTF8(doctype.name()));
432     }
433
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());
437
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());
441
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());
445
446     if (node_to_frame_routing_id_map_ && !src.equals(GetRoot())) {
447       WebLocalFrame* frame = document.frame();
448       RenderFrameImpl* render_frame = RenderFrameImpl::FromWebFrame(frame);
449       if (render_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);
453       } else {
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);
460         }
461       }
462     }
463   }
464
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);
480         int cell_id = -1;
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);
486           }
487         }
488         cell_ids.push_back(cell_id);
489       }
490       dst->AddIntListAttribute(ui::AX_ATTR_CELL_IDS, cell_ids);
491       dst->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS, unique_cell_ids);
492     }
493   }
494
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());
500   }
501
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());
507   }
508
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());
518   }
519
520   dst->AddStringAttribute(ui::AX_ATTR_NAME, name);
521
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);
536     }
537   }
538
539   WebVector<WebAXObject> controls;
540   if (src.ariaControls(controls))
541     AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS, controls, dst);
542
543   WebVector<WebAXObject> describedby;
544   if (src.ariaDescribedby(describedby)) {
545     AddIntListAttributeFromWebObjects(
546         ui::AX_ATTR_DESCRIBEDBY_IDS, describedby, dst);
547   }
548
549   WebVector<WebAXObject> flowTo;
550   if (src.ariaFlowTo(flowTo))
551     AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS, flowTo, dst);
552
553   WebVector<WebAXObject> labelledby;
554   if (src.ariaLabelledby(labelledby)) {
555     AddIntListAttributeFromWebObjects(
556         ui::AX_ATTR_LABELLEDBY_IDS, labelledby, dst);
557   }
558
559   WebVector<WebAXObject> owns;
560   if (src.ariaOwns(owns))
561     AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS, owns, dst);
562 }
563
564 blink::WebDocument BlinkAXTreeSource::GetMainDocument() const {
565   if (render_frame_ && render_frame_->GetWebFrame())
566     return render_frame_->GetWebFrame()->document();
567   return WebDocument();
568 }
569
570 }  // namespace content