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