Home | History | Annotate | Download | only in accessibility
      1 // Copyright 2013 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/browser/accessibility/browser_accessibility_android.h"
      6 
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
      9 #include "content/common/accessibility_messages.h"
     10 #include "content/common/accessibility_node_data.h"
     11 
     12 namespace {
     13 
     14 // These are enums from android.text.InputType in Java:
     15 enum {
     16   ANDROID_TEXT_INPUTTYPE_TYPE_NULL = 0,
     17   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME = 0x4,
     18   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE = 0x14,
     19   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_TIME = 0x24,
     20   ANDROID_TEXT_INPUTTYPE_TYPE_NUMBER = 0x2,
     21   ANDROID_TEXT_INPUTTYPE_TYPE_PHONE = 0x3,
     22   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT = 0x1,
     23   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_URI = 0x11,
     24   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EDIT_TEXT = 0xa1,
     25   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EMAIL = 0xd1,
     26   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_PASSWORD = 0xe1
     27 };
     28 
     29 // These are enums from android.view.View in Java:
     30 enum {
     31   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_NONE = 0,
     32   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_POLITE = 1,
     33   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2
     34 };
     35 
     36 // These are enums from
     37 // android.view.accessibility.AccessibilityNodeInfo.RangeInfo in Java:
     38 enum {
     39   ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT = 1
     40 };
     41 
     42 }  // namespace
     43 
     44 namespace content {
     45 
     46 // static
     47 BrowserAccessibility* BrowserAccessibility::Create() {
     48   return new BrowserAccessibilityAndroid();
     49 }
     50 
     51 BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
     52   first_time_ = true;
     53 }
     54 
     55 bool BrowserAccessibilityAndroid::IsNative() const {
     56   return true;
     57 }
     58 
     59 bool BrowserAccessibilityAndroid::PlatformIsLeaf() const {
     60   if (child_count() == 0)
     61     return true;
     62 
     63   // Iframes are always allowed to contain children.
     64   if (IsIframe() ||
     65       role() == blink::WebAXRoleRootWebArea ||
     66       role() == blink::WebAXRoleWebArea) {
     67     return false;
     68   }
     69 
     70   // If it has a focusable child, we definitely can't leave out children.
     71   if (HasFocusableChild())
     72     return false;
     73 
     74   // Headings with text can drop their children.
     75   base::string16 name = GetText();
     76   if (role() == blink::WebAXRoleHeading && !name.empty())
     77     return true;
     78 
     79   // Focusable nodes with text can drop their children.
     80   if (HasState(blink::WebAXStateFocusable) && !name.empty())
     81     return true;
     82 
     83   // Nodes with only static text as children can drop their children.
     84   if (HasOnlyStaticTextChildren())
     85     return true;
     86 
     87   return BrowserAccessibility::PlatformIsLeaf();
     88 }
     89 
     90 bool BrowserAccessibilityAndroid::IsCheckable() const {
     91   bool checkable = false;
     92   bool is_aria_pressed_defined;
     93   bool is_mixed;
     94   GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
     95   if (role() == blink::WebAXRoleCheckBox ||
     96       role() == blink::WebAXRoleRadioButton ||
     97       is_aria_pressed_defined) {
     98     checkable = true;
     99   }
    100   if (HasState(blink::WebAXStateChecked))
    101     checkable = true;
    102   return checkable;
    103 }
    104 
    105 bool BrowserAccessibilityAndroid::IsChecked() const {
    106   return HasState(blink::WebAXStateChecked);
    107 }
    108 
    109 bool BrowserAccessibilityAndroid::IsClickable() const {
    110   return (PlatformIsLeaf() && !GetText().empty());
    111 }
    112 
    113 bool BrowserAccessibilityAndroid::IsCollection() const {
    114   return (role() == blink::WebAXRoleGrid ||
    115           role() == blink::WebAXRoleList ||
    116           role() == blink::WebAXRoleListBox ||
    117           role() == blink::WebAXRoleTable ||
    118           role() == blink::WebAXRoleTree);
    119 }
    120 
    121 bool BrowserAccessibilityAndroid::IsCollectionItem() const {
    122   return (role() == blink::WebAXRoleCell ||
    123           role() == blink::WebAXRoleColumnHeader ||
    124           role() == blink::WebAXRoleDescriptionListTerm ||
    125           role() == blink::WebAXRoleListBoxOption ||
    126           role() == blink::WebAXRoleListItem ||
    127           role() == blink::WebAXRoleRowHeader ||
    128           role() == blink::WebAXRoleTreeItem);
    129 }
    130 
    131 bool BrowserAccessibilityAndroid::IsContentInvalid() const {
    132   std::string invalid;
    133   return GetHtmlAttribute("aria-invalid", &invalid);
    134 }
    135 
    136 bool BrowserAccessibilityAndroid::IsDismissable() const {
    137   return false;  // No concept of "dismissable" on the web currently.
    138 }
    139 
    140 bool BrowserAccessibilityAndroid::IsEnabled() const {
    141   return HasState(blink::WebAXStateEnabled);
    142 }
    143 
    144 bool BrowserAccessibilityAndroid::IsFocusable() const {
    145   bool focusable = HasState(blink::WebAXStateFocusable);
    146   if (IsIframe() ||
    147       role() == blink::WebAXRoleWebArea) {
    148     focusable = false;
    149   }
    150   return focusable;
    151 }
    152 
    153 bool BrowserAccessibilityAndroid::IsFocused() const {
    154   return manager()->GetFocus(manager()->GetRoot()) == this;
    155 }
    156 
    157 bool BrowserAccessibilityAndroid::IsHeading() const {
    158   return (role() == blink::WebAXRoleColumnHeader ||
    159           role() == blink::WebAXRoleHeading ||
    160           role() == blink::WebAXRoleRowHeader);
    161 }
    162 
    163 bool BrowserAccessibilityAndroid::IsHierarchical() const {
    164   return (role() == blink::WebAXRoleList ||
    165           role() == blink::WebAXRoleTree);
    166 }
    167 
    168 bool BrowserAccessibilityAndroid::IsMultiLine() const {
    169   return role() == blink::WebAXRoleTextArea;
    170 }
    171 
    172 bool BrowserAccessibilityAndroid::IsPassword() const {
    173   return HasState(blink::WebAXStateProtected);
    174 }
    175 
    176 bool BrowserAccessibilityAndroid::IsRangeType() const {
    177   return (role() == blink::WebAXRoleProgressIndicator ||
    178           role() == blink::WebAXRoleScrollBar ||
    179           role() == blink::WebAXRoleSlider);
    180 }
    181 
    182 bool BrowserAccessibilityAndroid::IsScrollable() const {
    183   int dummy;
    184   return GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy);
    185 }
    186 
    187 bool BrowserAccessibilityAndroid::IsSelected() const {
    188   return HasState(blink::WebAXStateSelected);
    189 }
    190 
    191 bool BrowserAccessibilityAndroid::IsVisibleToUser() const {
    192   return !HasState(blink::WebAXStateInvisible);
    193 }
    194 
    195 bool BrowserAccessibilityAndroid::CanOpenPopup() const {
    196   return HasState(blink::WebAXStateHaspopup);
    197 }
    198 
    199 const char* BrowserAccessibilityAndroid::GetClassName() const {
    200   const char* class_name = NULL;
    201 
    202   switch(role()) {
    203     case blink::WebAXRoleEditableText:
    204     case blink::WebAXRoleSpinButton:
    205     case blink::WebAXRoleTextArea:
    206     case blink::WebAXRoleTextField:
    207       class_name = "android.widget.EditText";
    208       break;
    209     case blink::WebAXRoleSlider:
    210       class_name = "android.widget.SeekBar";
    211       break;
    212     case blink::WebAXRoleComboBox:
    213       class_name = "android.widget.Spinner";
    214       break;
    215     case blink::WebAXRoleButton:
    216     case blink::WebAXRoleMenuButton:
    217     case blink::WebAXRolePopUpButton:
    218       class_name = "android.widget.Button";
    219       break;
    220     case blink::WebAXRoleCheckBox:
    221       class_name = "android.widget.CheckBox";
    222       break;
    223     case blink::WebAXRoleRadioButton:
    224       class_name = "android.widget.RadioButton";
    225       break;
    226     case blink::WebAXRoleToggleButton:
    227       class_name = "android.widget.ToggleButton";
    228       break;
    229     case blink::WebAXRoleCanvas:
    230     case blink::WebAXRoleImage:
    231       class_name = "android.widget.Image";
    232       break;
    233     case blink::WebAXRoleProgressIndicator:
    234       class_name = "android.widget.ProgressBar";
    235       break;
    236     case blink::WebAXRoleTabList:
    237       class_name = "android.widget.TabWidget";
    238       break;
    239     case blink::WebAXRoleGrid:
    240     case blink::WebAXRoleTable:
    241       class_name = "android.widget.GridView";
    242       break;
    243     case blink::WebAXRoleList:
    244     case blink::WebAXRoleListBox:
    245       class_name = "android.widget.ListView";
    246       break;
    247     case blink::WebAXRoleDialog:
    248       class_name = "android.app.Dialog";
    249       break;
    250     default:
    251       class_name = "android.view.View";
    252       break;
    253   }
    254 
    255   return class_name;
    256 }
    257 
    258 base::string16 BrowserAccessibilityAndroid::GetText() const {
    259   if (IsIframe() ||
    260       role() == blink::WebAXRoleWebArea) {
    261     return base::string16();
    262   }
    263 
    264   base::string16 description = GetString16Attribute(
    265       AccessibilityNodeData::ATTR_DESCRIPTION);
    266   base::string16 text;
    267   if (!name().empty())
    268     text = base::UTF8ToUTF16(name());
    269   else if (!description.empty())
    270     text = description;
    271   else if (!value().empty())
    272     text = base::UTF8ToUTF16(value());
    273 
    274   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
    275   // from within this!
    276   if (text.empty() && HasOnlyStaticTextChildren()) {
    277     for (uint32 i = 0; i < child_count(); i++) {
    278       BrowserAccessibility* child = children()[i];
    279       text += static_cast<BrowserAccessibilityAndroid*>(child)->GetText();
    280     }
    281   }
    282 
    283   switch(role()) {
    284     case blink::WebAXRoleImageMapLink:
    285     case blink::WebAXRoleLink:
    286       if (!text.empty())
    287         text += ASCIIToUTF16(" ");
    288       text += ASCIIToUTF16("Link");
    289       break;
    290     case blink::WebAXRoleHeading:
    291       // Only append "heading" if this node already has text.
    292       if (!text.empty())
    293         text += ASCIIToUTF16(" Heading");
    294       break;
    295   }
    296 
    297   return text;
    298 }
    299 
    300 int BrowserAccessibilityAndroid::GetItemIndex() const {
    301   int index = 0;
    302   switch(role()) {
    303     case blink::WebAXRoleListItem:
    304     case blink::WebAXRoleListBoxOption:
    305     case blink::WebAXRoleTreeItem:
    306       index = index_in_parent();
    307       break;
    308     case blink::WebAXRoleSlider:
    309     case blink::WebAXRoleProgressIndicator: {
    310       float value_for_range;
    311       if (GetFloatAttribute(
    312               AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &value_for_range)) {
    313         index = static_cast<int>(value_for_range);
    314       }
    315       break;
    316     }
    317   }
    318   return index;
    319 }
    320 
    321 int BrowserAccessibilityAndroid::GetItemCount() const {
    322   int count = 0;
    323   switch(role()) {
    324     case blink::WebAXRoleList:
    325     case blink::WebAXRoleListBox:
    326       count = PlatformChildCount();
    327       break;
    328     case blink::WebAXRoleSlider:
    329     case blink::WebAXRoleProgressIndicator: {
    330       float max_value_for_range;
    331       if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
    332                             &max_value_for_range)) {
    333         count = static_cast<int>(max_value_for_range);
    334       }
    335       break;
    336     }
    337   }
    338   return count;
    339 }
    340 
    341 int BrowserAccessibilityAndroid::GetScrollX() const {
    342   int value = 0;
    343   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value);
    344   return value;
    345 }
    346 
    347 int BrowserAccessibilityAndroid::GetScrollY() const {
    348   int value = 0;
    349   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value);
    350   return value;
    351 }
    352 
    353 int BrowserAccessibilityAndroid::GetMaxScrollX() const {
    354   int value = 0;
    355   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value);
    356   return value;
    357 }
    358 
    359 int BrowserAccessibilityAndroid::GetMaxScrollY() const {
    360   int value = 0;
    361   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value);
    362   return value;
    363 }
    364 
    365 int BrowserAccessibilityAndroid::GetTextChangeFromIndex() const {
    366   size_t index = 0;
    367   while (index < old_value_.length() &&
    368          index < new_value_.length() &&
    369          old_value_[index] == new_value_[index]) {
    370     index++;
    371   }
    372   return index;
    373 }
    374 
    375 int BrowserAccessibilityAndroid::GetTextChangeAddedCount() const {
    376   size_t old_len = old_value_.length();
    377   size_t new_len = new_value_.length();
    378   size_t left = 0;
    379   while (left < old_len &&
    380          left < new_len &&
    381          old_value_[left] == new_value_[left]) {
    382     left++;
    383   }
    384   size_t right = 0;
    385   while (right < old_len &&
    386          right < new_len &&
    387          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
    388     right++;
    389   }
    390   return (new_len - left - right);
    391 }
    392 
    393 int BrowserAccessibilityAndroid::GetTextChangeRemovedCount() const {
    394   size_t old_len = old_value_.length();
    395   size_t new_len = new_value_.length();
    396   size_t left = 0;
    397   while (left < old_len &&
    398          left < new_len &&
    399          old_value_[left] == new_value_[left]) {
    400     left++;
    401   }
    402   size_t right = 0;
    403   while (right < old_len &&
    404          right < new_len &&
    405          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
    406     right++;
    407   }
    408   return (old_len - left - right);
    409 }
    410 
    411 base::string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const {
    412   return old_value_;
    413 }
    414 
    415 int BrowserAccessibilityAndroid::GetSelectionStart() const {
    416   int sel_start = 0;
    417   GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start);
    418   return sel_start;
    419 }
    420 
    421 int BrowserAccessibilityAndroid::GetSelectionEnd() const {
    422   int sel_end = 0;
    423   GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end);
    424   return sel_end;
    425 }
    426 
    427 int BrowserAccessibilityAndroid::GetEditableTextLength() const {
    428   return value().length();
    429 }
    430 
    431 int BrowserAccessibilityAndroid::AndroidInputType() const {
    432   std::string html_tag = GetStringAttribute(
    433       AccessibilityNodeData::ATTR_HTML_TAG);
    434   if (html_tag != "input")
    435     return ANDROID_TEXT_INPUTTYPE_TYPE_NULL;
    436 
    437   std::string type;
    438   if (!GetHtmlAttribute("type", &type))
    439     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT;
    440 
    441   if (type == "" || type == "text" || type == "search")
    442     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT;
    443   else if (type == "date")
    444     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE;
    445   else if (type == "datetime" || type == "datetime-local")
    446     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME;
    447   else if (type == "email")
    448     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EMAIL;
    449   else if (type == "month")
    450     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE;
    451   else if (type == "number")
    452     return ANDROID_TEXT_INPUTTYPE_TYPE_NUMBER;
    453   else if (type == "password")
    454     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_PASSWORD;
    455   else if (type == "tel")
    456     return ANDROID_TEXT_INPUTTYPE_TYPE_PHONE;
    457   else if (type == "time")
    458     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_TIME;
    459   else if (type == "url")
    460     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_URI;
    461   else if (type == "week")
    462     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME;
    463 
    464   return ANDROID_TEXT_INPUTTYPE_TYPE_NULL;
    465 }
    466 
    467 int BrowserAccessibilityAndroid::AndroidLiveRegionType() const {
    468   std::string live = GetStringAttribute(
    469       AccessibilityNodeData::ATTR_LIVE_STATUS);
    470   if (live == "polite")
    471     return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_POLITE;
    472   else if (live == "assertive")
    473     return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_ASSERTIVE;
    474   return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_NONE;
    475 }
    476 
    477 int BrowserAccessibilityAndroid::AndroidRangeType() const {
    478   return ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT;
    479 }
    480 
    481 int BrowserAccessibilityAndroid::RowCount() const {
    482   if (role() == blink::WebAXRoleGrid ||
    483       role() == blink::WebAXRoleTable) {
    484     return CountChildrenWithRole(blink::WebAXRoleRow);
    485   }
    486 
    487   if (role() == blink::WebAXRoleList ||
    488       role() == blink::WebAXRoleListBox ||
    489       role() == blink::WebAXRoleTree) {
    490     return PlatformChildCount();
    491   }
    492 
    493   return 0;
    494 }
    495 
    496 int BrowserAccessibilityAndroid::ColumnCount() const {
    497   if (role() == blink::WebAXRoleGrid ||
    498       role() == blink::WebAXRoleTable) {
    499     return CountChildrenWithRole(blink::WebAXRoleColumn);
    500   }
    501   return 0;
    502 }
    503 
    504 int BrowserAccessibilityAndroid::RowIndex() const {
    505   if (role() == blink::WebAXRoleListItem ||
    506       role() == blink::WebAXRoleListBoxOption ||
    507       role() == blink::WebAXRoleTreeItem) {
    508     return index_in_parent();
    509   }
    510 
    511   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX);
    512 }
    513 
    514 int BrowserAccessibilityAndroid::RowSpan() const {
    515   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN);
    516 }
    517 
    518 int BrowserAccessibilityAndroid::ColumnIndex() const {
    519   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX);
    520 }
    521 
    522 int BrowserAccessibilityAndroid::ColumnSpan() const {
    523   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN);
    524 }
    525 
    526 float BrowserAccessibilityAndroid::RangeMin() const {
    527   return GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE);
    528 }
    529 
    530 float BrowserAccessibilityAndroid::RangeMax() const {
    531   return GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE);
    532 }
    533 
    534 float BrowserAccessibilityAndroid::RangeCurrentValue() const {
    535   return GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE);
    536 }
    537 
    538 bool BrowserAccessibilityAndroid::HasFocusableChild() const {
    539   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
    540   // from within this!
    541   for (uint32 i = 0; i < child_count(); i++) {
    542     BrowserAccessibility* child = children()[i];
    543     if (child->HasState(blink::WebAXStateFocusable))
    544       return true;
    545     if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
    546       return true;
    547   }
    548   return false;
    549 }
    550 
    551 bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
    552   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
    553   // from within this!
    554   for (uint32 i = 0; i < child_count(); i++) {
    555     BrowserAccessibility* child = children()[i];
    556     if (child->role() != blink::WebAXRoleStaticText)
    557       return false;
    558   }
    559   return true;
    560 }
    561 
    562 bool BrowserAccessibilityAndroid::IsIframe() const {
    563   base::string16 html_tag = GetString16Attribute(
    564       AccessibilityNodeData::ATTR_HTML_TAG);
    565   return html_tag == ASCIIToUTF16("iframe");
    566 }
    567 
    568 void BrowserAccessibilityAndroid::PostInitialize() {
    569   BrowserAccessibility::PostInitialize();
    570 
    571   if (IsEditableText()) {
    572     if (base::UTF8ToUTF16(value()) != new_value_) {
    573       old_value_ = new_value_;
    574       new_value_ = base::UTF8ToUTF16(value());
    575     }
    576   }
    577 
    578   if (role() == blink::WebAXRoleAlert && first_time_)
    579     manager()->NotifyAccessibilityEvent(blink::WebAXEventAlert, this);
    580 
    581   base::string16 live;
    582   if (GetString16Attribute(
    583       AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS, &live)) {
    584     NotifyLiveRegionUpdate(live);
    585   }
    586 
    587   first_time_ = false;
    588 }
    589 
    590 void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(
    591     base::string16& aria_live) {
    592   if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
    593       !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
    594     return;
    595 
    596   base::string16 text = GetText();
    597   if (cached_text_ != text) {
    598     if (!text.empty()) {
    599       manager()->NotifyAccessibilityEvent(blink::WebAXEventShow,
    600                                          this);
    601     }
    602     cached_text_ = text;
    603   }
    604 }
    605 
    606 int BrowserAccessibilityAndroid::CountChildrenWithRole(
    607     blink::WebAXRole role) const {
    608   int count = 0;
    609   for (uint32 i = 0; i < PlatformChildCount(); i++) {
    610     if (PlatformGetChild(i)->role() == role)
    611       count++;
    612   }
    613   return count;
    614 }
    615 
    616 }  // namespace content
    617