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/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 bool IsTrue(std::string html_value) {
     64   return LowerCaseEqualsASCII(html_value, "true");
     65 }
     66 
     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 
     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 
    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 
    111 BlinkAXTreeSource::~BlinkAXTreeSource() {
    112 }
    113 
    114 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 
    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 
    132 blink::WebAXObject BlinkAXTreeSource::GetRoot() const {
    133   return GetMainDocument().accessibilityObject();
    134 }
    135 
    136 blink::WebAXObject BlinkAXTreeSource::GetFromId(int32 id) const {
    137   return GetMainDocument().accessibilityObjectFromID(id);
    138 }
    139 
    140 int32 BlinkAXTreeSource::GetId(blink::WebAXObject node) const {
    141   return node.axID();
    142 }
    143 
    144 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 
    170 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 
    185 bool BlinkAXTreeSource::IsValid(blink::WebAXObject node) const {
    186   return !node.isDetached();  // This also checks if it's null.
    187 }
    188 
    189 bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1,
    190                                 blink::WebAXObject node2) const {
    191   return node1.equals(node2);
    192 }
    193 
    194 blink::WebAXObject BlinkAXTreeSource::GetNull() const {
    195   return blink::WebAXObject();
    196 }
    197 
    198 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 
    599 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