• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
Create()47 BrowserAccessibility* BrowserAccessibility::Create() {
48   return new BrowserAccessibilityAndroid();
49 }
50 
BrowserAccessibilityAndroid()51 BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
52   first_time_ = true;
53 }
54 
IsNative() const55 bool BrowserAccessibilityAndroid::IsNative() const {
56   return true;
57 }
58 
PlatformIsLeaf() const59 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 
IsCheckable() const90 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 
IsChecked() const105 bool BrowserAccessibilityAndroid::IsChecked() const {
106   return HasState(blink::WebAXStateChecked);
107 }
108 
IsClickable() const109 bool BrowserAccessibilityAndroid::IsClickable() const {
110   return (PlatformIsLeaf() && !GetText().empty());
111 }
112 
IsCollection() const113 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 
IsCollectionItem() const121 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 
IsContentInvalid() const131 bool BrowserAccessibilityAndroid::IsContentInvalid() const {
132   std::string invalid;
133   return GetHtmlAttribute("aria-invalid", &invalid);
134 }
135 
IsDismissable() const136 bool BrowserAccessibilityAndroid::IsDismissable() const {
137   return false;  // No concept of "dismissable" on the web currently.
138 }
139 
IsEnabled() const140 bool BrowserAccessibilityAndroid::IsEnabled() const {
141   return HasState(blink::WebAXStateEnabled);
142 }
143 
IsFocusable() const144 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 
IsFocused() const153 bool BrowserAccessibilityAndroid::IsFocused() const {
154   return manager()->GetFocus(manager()->GetRoot()) == this;
155 }
156 
IsHeading() const157 bool BrowserAccessibilityAndroid::IsHeading() const {
158   return (role() == blink::WebAXRoleColumnHeader ||
159           role() == blink::WebAXRoleHeading ||
160           role() == blink::WebAXRoleRowHeader);
161 }
162 
IsHierarchical() const163 bool BrowserAccessibilityAndroid::IsHierarchical() const {
164   return (role() == blink::WebAXRoleList ||
165           role() == blink::WebAXRoleTree);
166 }
167 
IsMultiLine() const168 bool BrowserAccessibilityAndroid::IsMultiLine() const {
169   return role() == blink::WebAXRoleTextArea;
170 }
171 
IsPassword() const172 bool BrowserAccessibilityAndroid::IsPassword() const {
173   return HasState(blink::WebAXStateProtected);
174 }
175 
IsRangeType() const176 bool BrowserAccessibilityAndroid::IsRangeType() const {
177   return (role() == blink::WebAXRoleProgressIndicator ||
178           role() == blink::WebAXRoleScrollBar ||
179           role() == blink::WebAXRoleSlider);
180 }
181 
IsScrollable() const182 bool BrowserAccessibilityAndroid::IsScrollable() const {
183   int dummy;
184   return GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy);
185 }
186 
IsSelected() const187 bool BrowserAccessibilityAndroid::IsSelected() const {
188   return HasState(blink::WebAXStateSelected);
189 }
190 
IsVisibleToUser() const191 bool BrowserAccessibilityAndroid::IsVisibleToUser() const {
192   return !HasState(blink::WebAXStateInvisible);
193 }
194 
CanOpenPopup() const195 bool BrowserAccessibilityAndroid::CanOpenPopup() const {
196   return HasState(blink::WebAXStateHaspopup);
197 }
198 
GetClassName() const199 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 
GetText() const258 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 
GetItemIndex() const300 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 
GetItemCount() const321 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 
GetScrollX() const341 int BrowserAccessibilityAndroid::GetScrollX() const {
342   int value = 0;
343   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value);
344   return value;
345 }
346 
GetScrollY() const347 int BrowserAccessibilityAndroid::GetScrollY() const {
348   int value = 0;
349   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value);
350   return value;
351 }
352 
GetMaxScrollX() const353 int BrowserAccessibilityAndroid::GetMaxScrollX() const {
354   int value = 0;
355   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value);
356   return value;
357 }
358 
GetMaxScrollY() const359 int BrowserAccessibilityAndroid::GetMaxScrollY() const {
360   int value = 0;
361   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value);
362   return value;
363 }
364 
GetTextChangeFromIndex() const365 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 
GetTextChangeAddedCount() const375 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 
GetTextChangeRemovedCount() const393 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 
GetTextChangeBeforeText() const411 base::string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const {
412   return old_value_;
413 }
414 
GetSelectionStart() const415 int BrowserAccessibilityAndroid::GetSelectionStart() const {
416   int sel_start = 0;
417   GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start);
418   return sel_start;
419 }
420 
GetSelectionEnd() const421 int BrowserAccessibilityAndroid::GetSelectionEnd() const {
422   int sel_end = 0;
423   GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end);
424   return sel_end;
425 }
426 
GetEditableTextLength() const427 int BrowserAccessibilityAndroid::GetEditableTextLength() const {
428   return value().length();
429 }
430 
AndroidInputType() const431 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 
AndroidLiveRegionType() const467 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 
AndroidRangeType() const477 int BrowserAccessibilityAndroid::AndroidRangeType() const {
478   return ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT;
479 }
480 
RowCount() const481 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 
ColumnCount() const496 int BrowserAccessibilityAndroid::ColumnCount() const {
497   if (role() == blink::WebAXRoleGrid ||
498       role() == blink::WebAXRoleTable) {
499     return CountChildrenWithRole(blink::WebAXRoleColumn);
500   }
501   return 0;
502 }
503 
RowIndex() const504 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 
RowSpan() const514 int BrowserAccessibilityAndroid::RowSpan() const {
515   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN);
516 }
517 
ColumnIndex() const518 int BrowserAccessibilityAndroid::ColumnIndex() const {
519   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX);
520 }
521 
ColumnSpan() const522 int BrowserAccessibilityAndroid::ColumnSpan() const {
523   return GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN);
524 }
525 
RangeMin() const526 float BrowserAccessibilityAndroid::RangeMin() const {
527   return GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE);
528 }
529 
RangeMax() const530 float BrowserAccessibilityAndroid::RangeMax() const {
531   return GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE);
532 }
533 
RangeCurrentValue() const534 float BrowserAccessibilityAndroid::RangeCurrentValue() const {
535   return GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE);
536 }
537 
HasFocusableChild() const538 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 
HasOnlyStaticTextChildren() const551 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 
IsIframe() const562 bool BrowserAccessibilityAndroid::IsIframe() const {
563   base::string16 html_tag = GetString16Attribute(
564       AccessibilityNodeData::ATTR_HTML_TAG);
565   return html_tag == ASCIIToUTF16("iframe");
566 }
567 
PostInitialize()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 
NotifyLiveRegionUpdate(base::string16 & aria_live)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 
CountChildrenWithRole(blink::WebAXRole role) const606 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