• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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().
IsParentUnignoredOf(WebAXObject ancestor,WebAXObject child)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 
IsTrue(std::string html_value)63 bool IsTrue(std::string html_value) {
64   return LowerCaseEqualsASCII(html_value, "true");
65 }
66 
GetEquivalentAriaRoleString(const ui::AXRole role)67 std::string GetEquivalentAriaRoleString(const ui::AXRole role) {
68   switch (role) {
69     case ui::AX_ROLE_ARTICLE:
70       return "article";
71     case ui::AX_ROLE_BANNER:
72       return "banner";
73     case ui::AX_ROLE_COMPLEMENTARY:
74       return "complementary";
75     case ui::AX_ROLE_CONTENT_INFO:
76     case ui::AX_ROLE_FOOTER:
77       return "contentinfo";
78     case ui::AX_ROLE_IMAGE:
79       return "img";
80     case ui::AX_ROLE_MAIN:
81       return "main";
82     case ui::AX_ROLE_NAVIGATION:
83       return "navigation";
84     case ui::AX_ROLE_REGION:
85       return "region";
86     default:
87       break;
88   }
89 
90   return std::string();
91 }
92 
AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr,WebVector<WebAXObject> objects,ui::AXNodeData * dst)93 void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr,
94                                        WebVector<WebAXObject> objects,
95                                        ui::AXNodeData* dst) {
96   std::vector<int32> ids;
97   for(size_t i = 0; i < objects.size(); i++)
98     ids.push_back(objects[i].axID());
99   if (ids.size() > 0)
100     dst->AddIntListAttribute(attr, ids);
101 }
102 
103 }  // Anonymous namespace
104 
BlinkAXTreeSource(RenderFrameImpl * render_frame)105 BlinkAXTreeSource::BlinkAXTreeSource(RenderFrameImpl* render_frame)
106     : render_frame_(render_frame),
107       node_to_frame_routing_id_map_(NULL),
108       node_to_browser_plugin_instance_id_map_(NULL) {
109 }
110 
~BlinkAXTreeSource()111 BlinkAXTreeSource::~BlinkAXTreeSource() {
112 }
113 
IsInTree(blink::WebAXObject node) const114 bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node) const {
115   const blink::WebAXObject& root = GetRoot();
116   while (IsValid(node)) {
117     if (node.equals(root))
118       return true;
119     node = GetParent(node);
120   }
121   return false;
122 }
123 
CollectChildFrameIdMapping(std::map<int32,int> * node_to_frame_routing_id_map,std::map<int32,int> * node_to_browser_plugin_instance_id_map)124 void BlinkAXTreeSource::CollectChildFrameIdMapping(
125     std::map<int32, int>* node_to_frame_routing_id_map,
126     std::map<int32, int>* node_to_browser_plugin_instance_id_map) {
127   node_to_frame_routing_id_map_ = node_to_frame_routing_id_map;
128   node_to_browser_plugin_instance_id_map_ =
129       node_to_browser_plugin_instance_id_map;
130 }
131 
GetRoot() const132 blink::WebAXObject BlinkAXTreeSource::GetRoot() const {
133   return GetMainDocument().accessibilityObject();
134 }
135 
GetFromId(int32 id) const136 blink::WebAXObject BlinkAXTreeSource::GetFromId(int32 id) const {
137   return GetMainDocument().accessibilityObjectFromID(id);
138 }
139 
GetId(blink::WebAXObject node) const140 int32 BlinkAXTreeSource::GetId(blink::WebAXObject node) const {
141   return node.axID();
142 }
143 
GetChildren(blink::WebAXObject parent,std::vector<blink::WebAXObject> * out_children) const144 void BlinkAXTreeSource::GetChildren(
145     blink::WebAXObject parent,
146     std::vector<blink::WebAXObject>* out_children) const {
147   bool is_iframe = false;
148   WebNode node = parent.node();
149   if (!node.isNull() && node.isElementNode()) {
150     WebElement element = node.to<WebElement>();
151     is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
152   }
153 
154   for (unsigned i = 0; i < parent.childCount(); i++) {
155     blink::WebAXObject child = parent.childAt(i);
156 
157     // The child may be invalid due to issues in blink accessibility code.
158     if (child.isDetached())
159       continue;
160 
161     // Skip children whose parent isn't |parent|.
162     // As an exception, include children of an iframe element.
163     if (!is_iframe && !IsParentUnignoredOf(parent, child))
164       continue;
165 
166     out_children->push_back(child);
167   }
168 }
169 
GetParent(blink::WebAXObject node) const170 blink::WebAXObject BlinkAXTreeSource::GetParent(
171     blink::WebAXObject node) const {
172   // Blink returns ignored objects when walking up the parent chain,
173   // we have to skip those here. Also, stop when we get to the root
174   // element.
175   blink::WebAXObject root = GetRoot();
176   do {
177     if (node.equals(root))
178       return blink::WebAXObject();
179     node = node.parentObject();
180   } while (!node.isDetached() && node.accessibilityIsIgnored());
181 
182   return node;
183 }
184 
IsValid(blink::WebAXObject node) const185 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node) const {
186   return !node.isDetached();  // This also checks if it's null.
187 }
188 
IsEqual(blink::WebAXObject node1,blink::WebAXObject node2) const189 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1,
190                                 blink::WebAXObject node2) const {
191   return node1.equals(node2);
192 }
193 
GetNull() const194 blink::WebAXObject BlinkAXTreeSource::GetNull() const {
195   return blink::WebAXObject();
196 }
197 
SerializeNode(blink::WebAXObject src,ui::AXNodeData * dst) const198 void BlinkAXTreeSource::SerializeNode(blink::WebAXObject src,
199                                       ui::AXNodeData* dst) const {
200   dst->role = AXRoleFromBlink(src.role());
201   dst->state = AXStateFromBlink(src);
202   dst->location = src.boundingBoxRect();
203   dst->id = src.axID();
204   std::string name = UTF16ToUTF8(src.title());
205 
206   std::string value;
207   if (src.valueDescription().length()) {
208     dst->AddStringAttribute(ui::AX_ATTR_VALUE,
209                             UTF16ToUTF8(src.valueDescription()));
210   } else {
211     dst->AddStringAttribute(ui::AX_ATTR_VALUE, UTF16ToUTF8(src.stringValue()));
212   }
213 
214   if (dst->role == ui::AX_ROLE_COLOR_WELL) {
215     int r, g, b;
216     src.colorValue(r, g, b);
217     dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_RED, r);
218     dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_GREEN, g);
219     dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_BLUE, b);
220   }
221 
222   if (dst->role == ui::AX_ROLE_INLINE_TEXT_BOX) {
223     dst->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
224                          AXTextDirectionFromBlink(src.textDirection()));
225 
226     WebVector<int> src_character_offsets;
227     src.characterOffsets(src_character_offsets);
228     std::vector<int32> character_offsets;
229     character_offsets.reserve(src_character_offsets.size());
230     for (size_t i = 0; i < src_character_offsets.size(); ++i)
231       character_offsets.push_back(src_character_offsets[i]);
232     dst->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets);
233 
234     WebVector<int> src_word_starts;
235     WebVector<int> src_word_ends;
236     src.wordBoundaries(src_word_starts, src_word_ends);
237     std::vector<int32> word_starts;
238     std::vector<int32> word_ends;
239     word_starts.reserve(src_word_starts.size());
240     word_ends.reserve(src_word_starts.size());
241     for (size_t i = 0; i < src_word_starts.size(); ++i) {
242       word_starts.push_back(src_word_starts[i]);
243       word_ends.push_back(src_word_ends[i]);
244     }
245     dst->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS, word_starts);
246     dst->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS, word_ends);
247   }
248 
249   if (src.accessKey().length()) {
250     dst->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY,
251     UTF16ToUTF8(src.accessKey()));
252   }
253   if (src.actionVerb().length())
254     dst->AddStringAttribute(ui::AX_ATTR_ACTION, UTF16ToUTF8(src.actionVerb()));
255   if (src.isAriaReadOnly())
256     dst->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY, true);
257   if (src.isButtonStateMixed())
258     dst->AddBoolAttribute(ui::AX_ATTR_BUTTON_MIXED, true);
259   if (src.canSetValueAttribute())
260     dst->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE, true);
261   if (src.accessibilityDescription().length()) {
262     dst->AddStringAttribute(ui::AX_ATTR_DESCRIPTION,
263                             UTF16ToUTF8(src.accessibilityDescription()));
264   }
265   if (src.hasComputedStyle()) {
266     dst->AddStringAttribute(ui::AX_ATTR_DISPLAY,
267                             UTF16ToUTF8(src.computedStyleDisplay()));
268   }
269   if (src.helpText().length())
270     dst->AddStringAttribute(ui::AX_ATTR_HELP, UTF16ToUTF8(src.helpText()));
271   if (src.keyboardShortcut().length()) {
272     dst->AddStringAttribute(ui::AX_ATTR_SHORTCUT,
273                             UTF16ToUTF8(src.keyboardShortcut()));
274   }
275   if (!src.titleUIElement().isDetached()) {
276     dst->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT,
277                          src.titleUIElement().axID());
278   }
279   if (!src.ariaActiveDescendant().isDetached()) {
280     dst->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID,
281                          src.ariaActiveDescendant().axID());
282   }
283 
284   if (!src.url().isEmpty())
285     dst->AddStringAttribute(ui::AX_ATTR_URL, src.url().spec());
286 
287   if (dst->role == ui::AX_ROLE_HEADING)
288     dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL, src.headingLevel());
289   else if ((dst->role == ui::AX_ROLE_TREE_ITEM ||
290             dst->role == ui::AX_ROLE_ROW) &&
291            src.hierarchicalLevel() > 0) {
292     dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL,
293                          src.hierarchicalLevel());
294   }
295 
296   // Treat the active list box item as focused.
297   if (dst->role == ui::AX_ROLE_LIST_BOX_OPTION &&
298       src.isSelectedOptionActive()) {
299     dst->state |= (1 << ui::AX_STATE_FOCUSED);
300   }
301 
302   if (src.canvasHasFallbackContent())
303     dst->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK, true);
304 
305   WebNode node = src.node();
306   bool is_iframe = false;
307   std::string live_atomic;
308   std::string live_busy;
309   std::string live_status;
310   std::string live_relevant;
311 
312   if (!node.isNull() && node.isElementNode()) {
313     WebElement element = node.to<WebElement>();
314     is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
315 
316     if (LowerCaseEqualsASCII(element.getAttribute("aria-expanded"), "true"))
317       dst->state |= (1 << ui::AX_STATE_EXPANDED);
318 
319     // TODO(ctguil): The tagName in WebKit is lower cased but
320     // HTMLElement::nodeName calls localNameUpper. Consider adding
321     // a WebElement method that returns the original lower cased tagName.
322     dst->AddStringAttribute(
323         ui::AX_ATTR_HTML_TAG,
324         base::StringToLowerASCII(UTF16ToUTF8(element.tagName())));
325     for (unsigned i = 0; i < element.attributeCount(); ++i) {
326       std::string name = base::StringToLowerASCII(UTF16ToUTF8(
327           element.attributeLocalName(i)));
328       std::string value = UTF16ToUTF8(element.attributeValue(i));
329       dst->html_attributes.push_back(std::make_pair(name, value));
330     }
331 
332     if (dst->role == ui::AX_ROLE_EDITABLE_TEXT ||
333         dst->role == ui::AX_ROLE_TEXT_AREA ||
334         dst->role == ui::AX_ROLE_TEXT_FIELD) {
335       dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, src.selectionStart());
336       dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, src.selectionEnd());
337 
338       WebVector<int> src_line_breaks;
339       src.lineBreaks(src_line_breaks);
340       if (src_line_breaks.size() > 0) {
341         std::vector<int32> line_breaks;
342         line_breaks.reserve(src_line_breaks.size());
343         for (size_t i = 0; i < src_line_breaks.size(); ++i)
344           line_breaks.push_back(src_line_breaks[i]);
345         dst->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS, line_breaks);
346       }
347     }
348 
349     // ARIA role.
350     if (element.hasAttribute("role")) {
351       dst->AddStringAttribute(ui::AX_ATTR_ROLE,
352                               UTF16ToUTF8(element.getAttribute("role")));
353     } else {
354       std::string role = GetEquivalentAriaRoleString(dst->role);
355       if (!role.empty())
356         dst->AddStringAttribute(ui::AX_ATTR_ROLE, role);
357     }
358 
359     // Live region attributes
360     live_atomic = UTF16ToUTF8(element.getAttribute("aria-atomic"));
361     live_busy = UTF16ToUTF8(element.getAttribute("aria-busy"));
362     live_status = UTF16ToUTF8(element.getAttribute("aria-live"));
363     live_relevant = UTF16ToUTF8(element.getAttribute("aria-relevant"));
364 
365     // Browser plugin (used in a <webview>).
366     if (node_to_browser_plugin_instance_id_map_) {
367       BrowserPlugin* browser_plugin = BrowserPlugin::GetFromNode(element);
368       if (browser_plugin) {
369         (*node_to_browser_plugin_instance_id_map_)[dst->id] =
370             browser_plugin->browser_plugin_instance_id();
371         dst->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST, true);
372       }
373     }
374   }
375 
376   // Walk up the parent chain to set live region attributes of containers
377   std::string container_live_atomic;
378   std::string container_live_busy;
379   std::string container_live_status;
380   std::string container_live_relevant;
381   WebAXObject container_accessible = src;
382   while (!container_accessible.isDetached()) {
383     WebNode container_node = container_accessible.node();
384     if (!container_node.isNull() && container_node.isElementNode()) {
385       WebElement container_elem = container_node.to<WebElement>();
386       if (container_elem.hasAttribute("aria-atomic") &&
387           container_live_atomic.empty()) {
388         container_live_atomic =
389             UTF16ToUTF8(container_elem.getAttribute("aria-atomic"));
390       }
391       if (container_elem.hasAttribute("aria-busy") &&
392           container_live_busy.empty()) {
393         container_live_busy =
394             UTF16ToUTF8(container_elem.getAttribute("aria-busy"));
395       }
396       if (container_elem.hasAttribute("aria-live") &&
397           container_live_status.empty()) {
398         container_live_status =
399             UTF16ToUTF8(container_elem.getAttribute("aria-live"));
400       }
401       if (container_elem.hasAttribute("aria-relevant") &&
402           container_live_relevant.empty()) {
403         container_live_relevant =
404             UTF16ToUTF8(container_elem.getAttribute("aria-relevant"));
405       }
406     }
407     container_accessible = container_accessible.parentObject();
408   }
409 
410   if (!live_atomic.empty())
411     dst->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC, IsTrue(live_atomic));
412   if (!live_busy.empty())
413     dst->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY, IsTrue(live_busy));
414   if (!live_status.empty())
415     dst->AddStringAttribute(ui::AX_ATTR_LIVE_STATUS, live_status);
416   if (!live_relevant.empty())
417     dst->AddStringAttribute(ui::AX_ATTR_LIVE_RELEVANT, live_relevant);
418 
419   if (!container_live_atomic.empty()) {
420     dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC,
421                           IsTrue(container_live_atomic));
422   }
423   if (!container_live_busy.empty()) {
424     dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY,
425                           IsTrue(container_live_busy));
426   }
427   if (!container_live_status.empty()) {
428     dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_STATUS,
429                             container_live_status);
430   }
431   if (!container_live_relevant.empty()) {
432     dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_RELEVANT,
433                             container_live_relevant);
434   }
435 
436   if (dst->role == ui::AX_ROLE_PROGRESS_INDICATOR ||
437       dst->role == ui::AX_ROLE_SCROLL_BAR ||
438       dst->role == ui::AX_ROLE_SLIDER ||
439       dst->role == ui::AX_ROLE_SPIN_BUTTON) {
440     dst->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, src.valueForRange());
441     dst->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE,
442                            src.maxValueForRange());
443     dst->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE,
444                            src.minValueForRange());
445   }
446 
447   if (dst->role == ui::AX_ROLE_DOCUMENT ||
448       dst->role == ui::AX_ROLE_WEB_AREA) {
449     dst->AddStringAttribute(ui::AX_ATTR_HTML_TAG, "#document");
450     const WebDocument& document = src.document();
451     if (name.empty())
452       name = UTF16ToUTF8(document.title());
453     dst->AddStringAttribute(ui::AX_ATTR_DOC_TITLE,
454                             UTF16ToUTF8(document.title()));
455     dst->AddStringAttribute(ui::AX_ATTR_DOC_URL, document.url().spec());
456     dst->AddStringAttribute(
457         ui::AX_ATTR_DOC_MIMETYPE,
458         document.isXHTMLDocument() ? "text/xhtml" : "text/html");
459     dst->AddBoolAttribute(ui::AX_ATTR_DOC_LOADED, src.isLoaded());
460     dst->AddFloatAttribute(ui::AX_ATTR_DOC_LOADING_PROGRESS,
461                            src.estimatedLoadingProgress());
462 
463     const WebDocumentType& doctype = document.doctype();
464     if (!doctype.isNull()) {
465       dst->AddStringAttribute(ui::AX_ATTR_DOC_DOCTYPE,
466                               UTF16ToUTF8(doctype.name()));
467     }
468 
469     const gfx::Size& scroll_offset = document.scrollOffset();
470     dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X, scroll_offset.width());
471     dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y, scroll_offset.height());
472 
473     const gfx::Size& min_offset = document.minimumScrollOffset();
474     dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN, min_offset.width());
475     dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN, min_offset.height());
476 
477     const gfx::Size& max_offset = document.maximumScrollOffset();
478     dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, max_offset.width());
479     dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, max_offset.height());
480 
481     if (node_to_frame_routing_id_map_ && !src.equals(GetRoot())) {
482       WebLocalFrame* frame = document.frame();
483       RenderFrameImpl* render_frame = RenderFrameImpl::FromWebFrame(frame);
484       if (render_frame) {
485         (*node_to_frame_routing_id_map_)[dst->id] =
486             render_frame->GetRoutingID();
487         dst->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST, true);
488       } else {
489         RenderFrameProxy* render_frame_proxy =
490             RenderFrameProxy::FromWebFrame(frame);
491         if (render_frame_proxy) {
492           (*node_to_frame_routing_id_map_)[dst->id] =
493               render_frame_proxy->routing_id();
494           dst->AddBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST, true);
495         }
496       }
497     }
498   }
499 
500   if (dst->role == ui::AX_ROLE_TABLE) {
501     int column_count = src.columnCount();
502     int row_count = src.rowCount();
503     if (column_count > 0 && row_count > 0) {
504       std::set<int32> unique_cell_id_set;
505       std::vector<int32> cell_ids;
506       std::vector<int32> unique_cell_ids;
507       dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT, column_count);
508       dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT, row_count);
509       WebAXObject header = src.headerContainerObject();
510       if (!header.isDetached())
511         dst->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID, header.axID());
512       for (int i = 0; i < column_count * row_count; ++i) {
513         WebAXObject cell = src.cellForColumnAndRow(
514             i % column_count, i / column_count);
515         int cell_id = -1;
516         if (!cell.isDetached()) {
517           cell_id = cell.axID();
518           if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) {
519             unique_cell_id_set.insert(cell_id);
520             unique_cell_ids.push_back(cell_id);
521           }
522         }
523         cell_ids.push_back(cell_id);
524       }
525       dst->AddIntListAttribute(ui::AX_ATTR_CELL_IDS, cell_ids);
526       dst->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS, unique_cell_ids);
527     }
528   }
529 
530   if (dst->role == ui::AX_ROLE_ROW) {
531     dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX, src.rowIndex());
532     WebAXObject header = src.rowHeader();
533     if (!header.isDetached())
534       dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID, header.axID());
535   }
536 
537   if (dst->role == ui::AX_ROLE_COLUMN) {
538     dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX, src.columnIndex());
539     WebAXObject header = src.columnHeader();
540     if (!header.isDetached())
541       dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, header.axID());
542   }
543 
544   if (dst->role == ui::AX_ROLE_CELL ||
545       dst->role == ui::AX_ROLE_ROW_HEADER ||
546       dst->role == ui::AX_ROLE_COLUMN_HEADER) {
547     dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
548                          src.cellColumnIndex());
549     dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN,
550                          src.cellColumnSpan());
551     dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX, src.cellRowIndex());
552     dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN, src.cellRowSpan());
553   }
554 
555   dst->AddStringAttribute(ui::AX_ATTR_NAME, name);
556 
557   // Add the ids of *indirect* children - those who are children of this node,
558   // but whose parent is *not* this node. One example is a table
559   // cell, which is a child of both a row and a column. Because the cell's
560   // parent is the row, the row adds it as a child, and the column adds it
561   // as an indirect child.
562   int child_count = src.childCount();
563   for (int i = 0; i < child_count; ++i) {
564     WebAXObject child = src.childAt(i);
565     std::vector<int32> indirect_child_ids;
566     if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child))
567       indirect_child_ids.push_back(child.axID());
568     if (indirect_child_ids.size() > 0) {
569       dst->AddIntListAttribute(
570           ui::AX_ATTR_INDIRECT_CHILD_IDS, indirect_child_ids);
571     }
572   }
573 
574   WebVector<WebAXObject> controls;
575   if (src.ariaControls(controls))
576     AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS, controls, dst);
577 
578   WebVector<WebAXObject> describedby;
579   if (src.ariaDescribedby(describedby)) {
580     AddIntListAttributeFromWebObjects(
581         ui::AX_ATTR_DESCRIBEDBY_IDS, describedby, dst);
582   }
583 
584   WebVector<WebAXObject> flowTo;
585   if (src.ariaFlowTo(flowTo))
586     AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS, flowTo, dst);
587 
588   WebVector<WebAXObject> labelledby;
589   if (src.ariaLabelledby(labelledby)) {
590     AddIntListAttributeFromWebObjects(
591         ui::AX_ATTR_LABELLEDBY_IDS, labelledby, dst);
592   }
593 
594   WebVector<WebAXObject> owns;
595   if (src.ariaOwns(owns))
596     AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS, owns, dst);
597 }
598 
GetMainDocument() const599 blink::WebDocument BlinkAXTreeSource::GetMainDocument() const {
600   if (render_frame_ && render_frame_->GetWebFrame())
601     return render_frame_->GetWebFrame()->document();
602   return WebDocument();
603 }
604 
605 }  // namespace content
606