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