Fix flakiness in DumpAccessibilityEvent* tests.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_cocoa.mm
blobb09e4d6b5d81bef64cac70da14030ec94443ba73
1 // Copyright (c) 2012 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.
5 #include <execinfo.h>
7 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
9 #include <map>
11 #include "base/basictypes.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "content/app/strings/grit/content_strings.h"
16 #include "content/browser/accessibility/browser_accessibility_manager.h"
17 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
18 #include "content/public/common/content_client.h"
19 #import "ui/accessibility/platform/ax_platform_node_mac.h"
21 // See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5,
22 // 10.6, and 10.7. It allows accessibility clients to observe events posted on
23 // this object.
24 extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
26 using ui::AXNodeData;
27 using content::BrowserAccessibility;
28 using content::BrowserAccessibilityDelegate;
29 using content::BrowserAccessibilityManager;
30 using content::BrowserAccessibilityManagerMac;
31 using content::ContentClient;
32 typedef ui::AXStringAttribute StringAttribute;
34 namespace {
36 // Returns an autoreleased copy of the AXNodeData's attribute.
37 NSString* NSStringForStringAttribute(
38     BrowserAccessibility* browserAccessibility,
39     StringAttribute attribute) {
40   return base::SysUTF8ToNSString(
41       browserAccessibility->GetStringAttribute(attribute));
44 // GetState checks the bitmask used in AXNodeData to check
45 // if the given state was set on the accessibility object.
46 bool GetState(BrowserAccessibility* accessibility, ui::AXState state) {
47   return ((accessibility->GetState() >> state) & 1);
50 // A mapping from an accessibility attribute to its method name.
51 NSDictionary* attributeToMethodNameMap = nil;
53 } // namespace
55 @implementation BrowserAccessibilityCocoa
57 + (void)initialize {
58   const struct {
59     NSString* attribute;
60     NSString* methodName;
61   } attributeToMethodNameContainer[] = {
62     { NSAccessibilityChildrenAttribute, @"children" },
63     { NSAccessibilityColumnsAttribute, @"columns" },
64     { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" },
65     { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" },
66     { NSAccessibilityContentsAttribute, @"contents" },
67     { NSAccessibilityDescriptionAttribute, @"description" },
68     { NSAccessibilityDisclosingAttribute, @"disclosing" },
69     { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" },
70     { NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel" },
71     { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" },
72     { NSAccessibilityEnabledAttribute, @"enabled" },
73     { NSAccessibilityExpandedAttribute, @"expanded" },
74     { NSAccessibilityFocusedAttribute, @"focused" },
75     { NSAccessibilityHeaderAttribute, @"header" },
76     { NSAccessibilityHelpAttribute, @"help" },
77     { NSAccessibilityIndexAttribute, @"index" },
78     { NSAccessibilityLinkedUIElementsAttribute, @"linkedUIElements" },
79     { NSAccessibilityMaxValueAttribute, @"maxValue" },
80     { NSAccessibilityMinValueAttribute, @"minValue" },
81     { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" },
82     { NSAccessibilityOrientationAttribute, @"orientation" },
83     { NSAccessibilityParentAttribute, @"parent" },
84     { NSAccessibilityPositionAttribute, @"position" },
85     { NSAccessibilityRoleAttribute, @"role" },
86     { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" },
87     { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" },
88     { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" },
89     { NSAccessibilityRowsAttribute, @"rows" },
90     // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
91     { NSAccessibilitySelectedChildrenAttribute, @"selectedChildren" },
92     { NSAccessibilitySizeAttribute, @"size" },
93     { NSAccessibilitySubroleAttribute, @"subrole" },
94     { NSAccessibilityTabsAttribute, @"tabs" },
95     { NSAccessibilityTitleAttribute, @"title" },
96     { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" },
97     { NSAccessibilityTopLevelUIElementAttribute, @"window" },
98     { NSAccessibilityURLAttribute, @"url" },
99     { NSAccessibilityValueAttribute, @"value" },
100     { NSAccessibilityValueDescriptionAttribute, @"valueDescription" },
101     { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" },
102     { NSAccessibilityVisibleCellsAttribute, @"visibleCells" },
103     { NSAccessibilityVisibleChildrenAttribute, @"visibleChildren" },
104     { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" },
105     { NSAccessibilityVisibleRowsAttribute, @"visibleRows" },
106     { NSAccessibilityWindowAttribute, @"window" },
107     { @"AXAccessKey", @"accessKey" },
108     { @"AXARIAAtomic", @"ariaAtomic" },
109     { @"AXARIABusy", @"ariaBusy" },
110     { @"AXARIALive", @"ariaLive" },
111     { @"AXARIARelevant", @"ariaRelevant" },
112     { @"AXGrabbed", @"grabbed" },
113     { @"AXInvalid", @"invalid" },
114     { @"AXLoaded", @"loaded" },
115     { @"AXLoadingProgress", @"loadingProgress" },
116     { @"AXPlaceholder", @"placeholder" },
117     { @"AXRequired", @"required" },
118     { @"AXVisited", @"visited" },
119   };
121   NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
122   const size_t numAttributes = sizeof(attributeToMethodNameContainer) /
123                                sizeof(attributeToMethodNameContainer[0]);
124   for (size_t i = 0; i < numAttributes; ++i) {
125     [dict setObject:attributeToMethodNameContainer[i].methodName
126              forKey:attributeToMethodNameContainer[i].attribute];
127   }
128   attributeToMethodNameMap = dict;
129   dict = nil;
132 - (id)initWithObject:(BrowserAccessibility*)accessibility {
133   if ((self = [super init]))
134     browserAccessibility_ = accessibility;
135   return self;
138 - (void)detach {
139   if (browserAccessibility_) {
140     NSAccessibilityUnregisterUniqueIdForUIElement(self);
141     browserAccessibility_ = NULL;
142   }
145 - (NSString*)accessKey {
146   return NSStringForStringAttribute(
147       browserAccessibility_, ui::AX_ATTR_ACCESS_KEY);
150 - (NSNumber*)ariaAtomic {
151   bool boolValue = browserAccessibility_->GetBoolAttribute(
152       ui::AX_ATTR_LIVE_ATOMIC);
153   return [NSNumber numberWithBool:boolValue];
156 - (NSNumber*)ariaBusy {
157   return [NSNumber numberWithBool:
158       GetState(browserAccessibility_, ui::AX_STATE_BUSY)];
161 - (NSString*)ariaLive {
162   return NSStringForStringAttribute(
163       browserAccessibility_, ui::AX_ATTR_LIVE_STATUS);
166 - (NSString*)ariaRelevant {
167   return NSStringForStringAttribute(
168       browserAccessibility_, ui::AX_ATTR_LIVE_RELEVANT);
171 // Returns an array of BrowserAccessibilityCocoa objects, representing the
172 // accessibility children of this object.
173 - (NSArray*)children {
174   if (!children_) {
175     uint32 childCount = browserAccessibility_->PlatformChildCount();
176     children_.reset([[NSMutableArray alloc] initWithCapacity:childCount]);
177     for (uint32 index = 0; index < childCount; ++index) {
178       BrowserAccessibilityCocoa* child =
179           browserAccessibility_->PlatformGetChild(index)->
180               ToBrowserAccessibilityCocoa();
181       if ([child isIgnored])
182         [children_ addObjectsFromArray:[child children]];
183       else
184         [children_ addObject:child];
185     }
187     // Also, add indirect children (if any).
188     const std::vector<int32>& indirectChildIds =
189         browserAccessibility_->GetIntListAttribute(
190             ui::AX_ATTR_INDIRECT_CHILD_IDS);
191     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
192       int32 child_id = indirectChildIds[i];
193       BrowserAccessibility* child =
194           browserAccessibility_->manager()->GetFromID(child_id);
196       // This only became necessary as a result of crbug.com/93095. It should be
197       // a DCHECK in the future.
198       if (child) {
199         BrowserAccessibilityCocoa* child_cocoa =
200             child->ToBrowserAccessibilityCocoa();
201         [children_ addObject:child_cocoa];
202       }
203     }
204   }
205   return children_;
208 - (void)childrenChanged {
209   if (![self isIgnored]) {
210     children_.reset();
211   } else {
212     [browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa()
213        childrenChanged];
214   }
217 - (NSArray*)columnHeaders {
218   if ([self internalRole] != ui::AX_ROLE_TABLE &&
219       [self internalRole] != ui::AX_ROLE_GRID) {
220     return nil;
221   }
223   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
224   const std::vector<int32>& uniqueCellIds =
225       browserAccessibility_->GetIntListAttribute(
226           ui::AX_ATTR_UNIQUE_CELL_IDS);
227   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
228     int id = uniqueCellIds[i];
229     BrowserAccessibility* cell =
230         browserAccessibility_->manager()->GetFromID(id);
231     if (cell && cell->GetRole() == ui::AX_ROLE_COLUMN_HEADER)
232       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
233   }
234   return ret;
237 - (NSValue*)columnIndexRange {
238   if (!browserAccessibility_->IsCellOrTableHeaderRole())
239     return nil;
241   int column = -1;
242   int colspan = -1;
243   browserAccessibility_->GetIntAttribute(
244       ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, &column);
245   browserAccessibility_->GetIntAttribute(
246       ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN, &colspan);
247   if (column >= 0 && colspan >= 1)
248     return [NSValue valueWithRange:NSMakeRange(column, colspan)];
249   return nil;
252 - (NSArray*)columns {
253   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
254   for (BrowserAccessibilityCocoa* child in [self children]) {
255     if ([[child role] isEqualToString:NSAccessibilityColumnRole])
256       [ret addObject:child];
257   }
258   return ret;
261 - (NSString*)description {
262   std::string description;
263   if (browserAccessibility_->GetStringAttribute(
264           ui::AX_ATTR_DESCRIPTION, &description)) {
265     return base::SysUTF8ToNSString(description);
266   }
268   // If the role is anything other than an image, or if there's
269   // a title or title UI element, just return an empty string.
270   if (![[self role] isEqualToString:NSAccessibilityImageRole])
271     return @"";
272   if (browserAccessibility_->HasStringAttribute(
273           ui::AX_ATTR_NAME)) {
274     return @"";
275   }
276   if ([self titleUIElement])
277     return @"";
279   // The remaining case is an image where there's no other title.
280   // Return the base part of the filename as the description.
281   std::string url;
282   if (browserAccessibility_->GetStringAttribute(
283           ui::AX_ATTR_URL, &url)) {
284     // Given a url like http://foo.com/bar/baz.png, just return the
285     // base name, e.g., "baz.png".
286     size_t leftIndex = url.rfind('/');
287     std::string basename =
288         leftIndex != std::string::npos ? url.substr(leftIndex) : url;
289     return base::SysUTF8ToNSString(basename);
290   }
292   return @"";
295 - (NSNumber*)disclosing {
296   if ([self internalRole] == ui::AX_ROLE_TREE_ITEM) {
297     return [NSNumber numberWithBool:
298         GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
299   } else {
300     return nil;
301   }
304 - (id)disclosedByRow {
305   // The row that contains this row.
306   // It should be the same as the first parent that is a treeitem.
307   return nil;
310 - (NSNumber*)disclosureLevel {
311   ui::AXRole role = [self internalRole];
312   if (role == ui::AX_ROLE_ROW ||
313       role == ui::AX_ROLE_TREE_ITEM) {
314     int level = browserAccessibility_->GetIntAttribute(
315         ui::AX_ATTR_HIERARCHICAL_LEVEL);
316     // Mac disclosureLevel is 0-based, but web levels are 1-based.
317     if (level > 0)
318       level--;
319     return [NSNumber numberWithInt:level];
320   } else {
321     return nil;
322   }
325 - (id)disclosedRows {
326   // The rows that are considered inside this row.
327   return nil;
330 - (NSNumber*)enabled {
331   return [NSNumber numberWithBool:
332       GetState(browserAccessibility_, ui::AX_STATE_ENABLED)];
335 - (NSNumber*)expanded {
336   return [NSNumber numberWithBool:
337       GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
340 - (NSNumber*)focused {
341   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
342   NSNumber* ret = [NSNumber numberWithBool:
343       manager->GetFocus(NULL) == browserAccessibility_];
344   return ret;
347 - (NSNumber*)grabbed {
348   bool boolValue = browserAccessibility_->GetBoolAttribute(ui::AX_ATTR_GRABBED);
349   return [NSNumber numberWithBool:boolValue];
352 - (id)header {
353   int headerElementId = -1;
354   if ([self internalRole] == ui::AX_ROLE_TABLE ||
355       [self internalRole] == ui::AX_ROLE_GRID) {
356     browserAccessibility_->GetIntAttribute(
357         ui::AX_ATTR_TABLE_HEADER_ID, &headerElementId);
358   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
359     browserAccessibility_->GetIntAttribute(
360         ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId);
361   } else if ([self internalRole] == ui::AX_ROLE_ROW) {
362     browserAccessibility_->GetIntAttribute(
363         ui::AX_ATTR_TABLE_ROW_HEADER_ID, &headerElementId);
364   }
366   if (headerElementId > 0) {
367     BrowserAccessibility* headerObject =
368         browserAccessibility_->manager()->GetFromID(headerElementId);
369     if (headerObject)
370       return headerObject->ToBrowserAccessibilityCocoa();
371   }
372   return nil;
375 - (NSString*)help {
376   return NSStringForStringAttribute(
377       browserAccessibility_, ui::AX_ATTR_HELP);
380 - (NSNumber*)index {
381   if ([self internalRole] == ui::AX_ROLE_COLUMN) {
382     int columnIndex = browserAccessibility_->GetIntAttribute(
383           ui::AX_ATTR_TABLE_COLUMN_INDEX);
384     return [NSNumber numberWithInt:columnIndex];
385   } else if ([self internalRole] == ui::AX_ROLE_ROW) {
386     int rowIndex = browserAccessibility_->GetIntAttribute(
387         ui::AX_ATTR_TABLE_ROW_INDEX);
388     return [NSNumber numberWithInt:rowIndex];
389   }
391   return nil;
394 // Returns whether or not this node should be ignored in the
395 // accessibility tree.
396 - (BOOL)isIgnored {
397   return [[self role] isEqualToString:NSAccessibilityUnknownRole];
400 - (NSString*)invalid {
401   int invalidState;
402   if (!browserAccessibility_->GetIntAttribute(
403       ui::AX_ATTR_INVALID_STATE, &invalidState))
404     return @"false";
406   switch (invalidState) {
407   case ui::AX_INVALID_STATE_FALSE:
408     return @"false";
409   case ui::AX_INVALID_STATE_TRUE:
410     return @"true";
411   case ui::AX_INVALID_STATE_SPELLING:
412     return @"spelling";
413   case ui::AX_INVALID_STATE_GRAMMAR:
414     return @"grammar";
415   case ui::AX_INVALID_STATE_OTHER:
416     {
417       std::string ariaInvalidValue;
418       if (browserAccessibility_->GetStringAttribute(
419           ui::AX_ATTR_ARIA_INVALID_VALUE,
420           &ariaInvalidValue))
421         return base::SysUTF8ToNSString(ariaInvalidValue);
422       // Return @"true" since we cannot be more specific about the value.
423       return @"true";
424     }
425   default:
426     NOTREACHED();
427   }
429   return @"false";
432 - (NSString*)placeholder {
433   return NSStringForStringAttribute(
434       browserAccessibility_, ui::AX_ATTR_PLACEHOLDER);
437 - (void)addLinkedUIElementsFromAttribute:(ui::AXIntListAttribute)attribute
438                                    addTo:(NSMutableArray*)outArray {
439   const std::vector<int32>& attributeValues =
440       browserAccessibility_->GetIntListAttribute(attribute);
441   for (size_t i = 0; i < attributeValues.size(); ++i) {
442     BrowserAccessibility* element =
443         browserAccessibility_->manager()->GetFromID(attributeValues[i]);
444     if (element)
445       [outArray addObject:element->ToBrowserAccessibilityCocoa()];
446   }
449 - (NSArray*)linkedUIElements {
450   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
451   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_OWNS_IDS addTo:ret];
452   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_CONTROLS_IDS addTo:ret];
453   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_FLOWTO_IDS addTo:ret];
454   if ([ret count] == 0)
455     return nil;
456   return ret;
459 - (NSNumber*)loaded {
460   return [NSNumber numberWithBool:YES];
463 - (NSNumber*)loadingProgress {
464   float floatValue = browserAccessibility_->GetFloatAttribute(
465       ui::AX_ATTR_DOC_LOADING_PROGRESS);
466   return [NSNumber numberWithFloat:floatValue];
469 - (NSNumber*)maxValue {
470   float floatValue = browserAccessibility_->GetFloatAttribute(
471       ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
472   return [NSNumber numberWithFloat:floatValue];
475 - (NSNumber*)minValue {
476   float floatValue = browserAccessibility_->GetFloatAttribute(
477       ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
478   return [NSNumber numberWithFloat:floatValue];
481 - (NSString*)orientation {
482   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL))
483     return NSAccessibilityVerticalOrientationValue;
484   else if (GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL))
485     return NSAccessibilityHorizontalOrientationValue;
487   return @"";
490 - (NSNumber*)numberOfCharacters {
491   std::string value = browserAccessibility_->GetStringAttribute(
492       ui::AX_ATTR_VALUE);
493   return [NSNumber numberWithInt:value.size()];
496 // The origin of this accessibility object in the page's document.
497 // This is relative to webkit's top-left origin, not Cocoa's
498 // bottom-left origin.
499 - (NSPoint)origin {
500   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
501   return NSMakePoint(bounds.x(), bounds.y());
504 - (id)parent {
505   // A nil parent means we're the root.
506   if (browserAccessibility_->GetParent()) {
507     return NSAccessibilityUnignoredAncestor(
508         browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa());
509   } else {
510     // Hook back up to RenderWidgetHostViewCocoa.
511     BrowserAccessibilityManagerMac* manager =
512         static_cast<BrowserAccessibilityManagerMac*>(
513             browserAccessibility_->manager());
514     return manager->parent_view();
515   }
518 - (NSValue*)position {
519   NSPoint origin = [self origin];
520   NSSize size = [[self size] sizeValue];
521   NSPoint pointInScreen = [self pointInScreen:origin size:size];
522   return [NSValue valueWithPoint:pointInScreen];
525 - (NSNumber*)required {
526   return [NSNumber numberWithBool:
527       GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)];
530 // Returns an enum indicating the role from browserAccessibility_.
531 - (ui::AXRole)internalRole {
532   return static_cast<ui::AXRole>(browserAccessibility_->GetRole());
535 - (content::BrowserAccessibilityDelegate*)delegate {
536   return browserAccessibility_->manager() ?
537       browserAccessibility_->manager()->delegate() :
538       nil;
541 - (NSPoint)pointInScreen:(NSPoint)origin
542                     size:(NSSize)size {
543   if (!browserAccessibility_)
544     return NSZeroPoint;
546   // Get the delegate for the topmost BrowserAccessibilityManager, because
547   // that's the only one that can convert points to their origin in the screen.
548   BrowserAccessibilityDelegate* delegate =
549       browserAccessibility_->manager()->GetDelegateFromRootManager();
550   if (delegate) {
551     gfx::Rect bounds(origin.x, origin.y, size.width, size.height);
552     gfx::Point point = delegate->AccessibilityOriginInScreen(bounds);
553     return NSMakePoint(point.x(), point.y());
554   } else {
555     return NSZeroPoint;
556   }
559 // Returns a string indicating the NSAccessibility role of this object.
560 - (NSString*)role {
561   ui::AXRole role = [self internalRole];
562   if (role == ui::AX_ROLE_CANVAS &&
563       browserAccessibility_->GetBoolAttribute(
564           ui::AX_ATTR_CANVAS_HAS_FALLBACK)) {
565     return NSAccessibilityGroupRole;
566   }
567   if (role == ui::AX_ROLE_BUTTON || role == ui::AX_ROLE_TOGGLE_BUTTON) {
568     bool isAriaPressedDefined;
569     bool isMixed;
570     browserAccessibility_->GetAriaTristate("aria-pressed",
571                                            &isAriaPressedDefined,
572                                            &isMixed);
573     if (isAriaPressedDefined)
574       return NSAccessibilityCheckBoxRole;
575     else
576       return NSAccessibilityButtonRole;
577   }
579   // If this is a web area for a presentational iframe, give it a role of
580   // something other than WebArea so that the fact that it's a separate doc
581   // is not exposed to AT.
582   if (browserAccessibility_->IsWebAreaForPresentationalIframe())
583     return NSAccessibilityGroupRole;
585   return [AXPlatformNodeCocoa nativeRoleFromAXRole:role];
588 // Returns a string indicating the role description of this object.
589 - (NSString*)roleDescription {
590   NSString* role = [self role];
592   ContentClient* content_client = content::GetContentClient();
594   // The following descriptions are specific to webkit.
595   if ([role isEqualToString:@"AXWebArea"]) {
596     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
597         IDS_AX_ROLE_WEB_AREA));
598   }
600   if ([role isEqualToString:@"NSAccessibilityLinkRole"]) {
601     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
602         IDS_AX_ROLE_LINK));
603   }
605   if ([role isEqualToString:@"AXHeading"]) {
606     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
607         IDS_AX_ROLE_HEADING));
608   }
610   if (([role isEqualToString:NSAccessibilityGroupRole] ||
611        [role isEqualToString:NSAccessibilityRadioButtonRole]) &&
612       !browserAccessibility_->IsWebAreaForPresentationalIframe()) {
613     std::string role;
614     if (browserAccessibility_->GetHtmlAttribute("role", &role)) {
615       ui::AXRole internalRole = [self internalRole];
616       if ((internalRole != ui::AX_ROLE_GROUP &&
617            internalRole != ui::AX_ROLE_LIST_ITEM) ||
618           internalRole == ui::AX_ROLE_TAB) {
619         // TODO(dtseng): This is not localized; see crbug/84814.
620         return base::SysUTF8ToNSString(role);
621       }
622     }
623   }
625   switch([self internalRole]) {
626   case ui::AX_ROLE_ARTICLE:
627     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
628         IDS_AX_ROLE_ARTICLE));
629   case ui::AX_ROLE_BANNER:
630     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
631         IDS_AX_ROLE_BANNER));
632   case ui::AX_ROLE_COMPLEMENTARY:
633     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
634         IDS_AX_ROLE_COMPLEMENTARY));
635   case ui::AX_ROLE_CONTENT_INFO:
636     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
637         IDS_AX_ROLE_ADDRESS));
638   case ui::AX_ROLE_DESCRIPTION_LIST:
639     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
640         IDS_AX_ROLE_DESCRIPTION_LIST));
641   case ui::AX_ROLE_DESCRIPTION_LIST_DETAIL:
642     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
643         IDS_AX_ROLE_DESCRIPTION_DETAIL));
644   case ui::AX_ROLE_DESCRIPTION_LIST_TERM:
645     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
646         IDS_AX_ROLE_DESCRIPTION_TERM));
647   case ui::AX_ROLE_FIGURE:
648     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
649         IDS_AX_ROLE_FIGURE));
650   case ui::AX_ROLE_FOOTER:
651     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
652         IDS_AX_ROLE_FOOTER));
653   case ui::AX_ROLE_FORM:
654     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
655         IDS_AX_ROLE_FORM));
656   case ui::AX_ROLE_MAIN:
657     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
658         IDS_AX_ROLE_MAIN_CONTENT));
659   case ui::AX_ROLE_MATH:
660     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
661         IDS_AX_ROLE_MATH));
662   case ui::AX_ROLE_NAVIGATION:
663     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
664         IDS_AX_ROLE_NAVIGATIONAL_LINK));
665   case ui::AX_ROLE_REGION:
666     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
667         IDS_AX_ROLE_REGION));
668   case ui::AX_ROLE_SPIN_BUTTON:
669     // This control is similar to what VoiceOver calls a "stepper".
670     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
671         IDS_AX_ROLE_STEPPER));
672   case ui::AX_ROLE_STATUS:
673     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
674         IDS_AX_ROLE_STATUS));
675   case ui::AX_ROLE_TOGGLE_BUTTON:
676     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
677         IDS_AX_ROLE_TOGGLE_BUTTON));
678   default:
679     break;
680   }
682   return NSAccessibilityRoleDescription(role, nil);
685 - (NSArray*)rowHeaders {
686   if ([self internalRole] != ui::AX_ROLE_TABLE &&
687       [self internalRole] != ui::AX_ROLE_GRID) {
688     return nil;
689   }
691   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
692   const std::vector<int32>& uniqueCellIds =
693       browserAccessibility_->GetIntListAttribute(
694           ui::AX_ATTR_UNIQUE_CELL_IDS);
695   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
696     int id = uniqueCellIds[i];
697     BrowserAccessibility* cell =
698         browserAccessibility_->manager()->GetFromID(id);
699     if (cell && cell->GetRole() == ui::AX_ROLE_ROW_HEADER)
700       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
701   }
702   return ret;
705 - (NSValue*)rowIndexRange {
706   if (!browserAccessibility_->IsCellOrTableHeaderRole())
707     return nil;
709   int row = -1;
710   int rowspan = -1;
711   browserAccessibility_->GetIntAttribute(
712       ui::AX_ATTR_TABLE_CELL_ROW_INDEX, &row);
713   browserAccessibility_->GetIntAttribute(
714       ui::AX_ATTR_TABLE_CELL_ROW_SPAN, &rowspan);
715   if (row >= 0 && rowspan >= 1)
716     return [NSValue valueWithRange:NSMakeRange(row, rowspan)];
717   return nil;
720 - (NSArray*)rows {
721   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
723   if ([self internalRole] == ui::AX_ROLE_TABLE||
724       [self internalRole] == ui::AX_ROLE_GRID) {
725     for (BrowserAccessibilityCocoa* child in [self children]) {
726       if ([[child role] isEqualToString:NSAccessibilityRowRole])
727         [ret addObject:child];
728     }
729   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
730     const std::vector<int32>& indirectChildIds =
731         browserAccessibility_->GetIntListAttribute(
732             ui::AX_ATTR_INDIRECT_CHILD_IDS);
733     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
734       int id = indirectChildIds[i];
735       BrowserAccessibility* rowElement =
736           browserAccessibility_->manager()->GetFromID(id);
737       if (rowElement)
738         [ret addObject:rowElement->ToBrowserAccessibilityCocoa()];
739     }
740   }
742   return ret;
745 - (NSArray*)selectedChildren {
746   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
748   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
749   BrowserAccessibility* focusedChild =
750       manager->GetFocus(browserAccessibility_);
751   if (focusedChild && focusedChild != browserAccessibility_) {
752     // First try the focused child.
753     [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
754   } else {
755     // Next try the active descendant.
756     int activeDescendantId;
757     if (browserAccessibility_->GetIntAttribute(
758             ui::AX_ATTR_ACTIVEDESCENDANT_ID, &activeDescendantId)) {
759       BrowserAccessibility* activeDescendant =
760           manager->GetFromID(activeDescendantId);
761       if (activeDescendant)
762         [ret addObject:activeDescendant->ToBrowserAccessibilityCocoa()];
763     } else {
764       // Otherwise return any children with the "selected" state, which
765       // may come from aria-selected.
766       uint32 childCount = browserAccessibility_->PlatformChildCount();
767       for (uint32 index = 0; index < childCount; ++index) {
768         BrowserAccessibility* child =
769             browserAccessibility_->PlatformGetChild(index);
770         if (child->HasState(ui::AX_STATE_SELECTED))
771           [ret addObject:child->ToBrowserAccessibilityCocoa()];
772       }
773     }
774   }
776   return ret;
779 // Returns the size of this object.
780 - (NSValue*)size {
781   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
782   return  [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())];
785 // Returns a subrole based upon the role.
786 - (NSString*) subrole {
787   ui::AXRole browserAccessibilityRole = [self internalRole];
788   if (browserAccessibilityRole == ui::AX_ROLE_TEXT_FIELD &&
789       GetState(browserAccessibility_, ui::AX_STATE_PROTECTED)) {
790     return @"AXSecureTextField";
791   }
793   if (browserAccessibilityRole == ui::AX_ROLE_DESCRIPTION_LIST)
794     return @"AXDefinitionList";
796   if (browserAccessibilityRole == ui::AX_ROLE_LIST)
797     return @"AXContentList";
799   return [AXPlatformNodeCocoa nativeSubroleFromAXRole:browserAccessibilityRole];
802 // Returns all tabs in this subtree.
803 - (NSArray*)tabs {
804   NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
806   if ([self internalRole] == ui::AX_ROLE_TAB)
807     [tabSubtree addObject:self];
809   for (uint i=0; i < [[self children] count]; ++i) {
810     NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
811     if ([tabChildren count] > 0)
812       [tabSubtree addObjectsFromArray:tabChildren];
813   }
815   return tabSubtree;
818 - (NSString*)title {
819   return NSStringForStringAttribute(
820       browserAccessibility_, ui::AX_ATTR_NAME);
823 - (id)titleUIElement {
824   int titleElementId;
825   if (browserAccessibility_->GetIntAttribute(
826           ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
827     BrowserAccessibility* titleElement =
828         browserAccessibility_->manager()->GetFromID(titleElementId);
829     if (titleElement)
830       return titleElement->ToBrowserAccessibilityCocoa();
831   }
832   std::vector<int32> labelledby_ids =
833       browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
834   if (labelledby_ids.size() == 1) {
835     BrowserAccessibility* titleElement =
836         browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
837     if (titleElement)
838       return titleElement->ToBrowserAccessibilityCocoa();
839   }
841   return nil;
844 - (NSURL*)url {
845   StringAttribute urlAttribute =
846       [[self role] isEqualToString:@"AXWebArea"] ?
847           ui::AX_ATTR_DOC_URL :
848           ui::AX_ATTR_URL;
850   std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute);
851   if (urlStr.empty())
852     return nil;
854   return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))];
857 - (id)value {
858   // WebCore uses an attachmentView to get the below behavior.
859   // We do not have any native views backing this object, so need
860   // to approximate Cocoa ax behavior best as we can.
861   NSString* role = [self role];
862   if ([role isEqualToString:@"AXHeading"]) {
863     int level = 0;
864     if (browserAccessibility_->GetIntAttribute(
865             ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) {
866       return [NSNumber numberWithInt:level];
867     }
868   } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
869     // AXValue does not make sense for pure buttons.
870     return @"";
871   } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) {
872     int value = 0;
873     bool isAriaPressedDefined;
874     bool isMixed;
875     value = browserAccessibility_->GetAriaTristate(
876         "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0;
878     if (isMixed)
879       value = 2;
881     return [NSNumber numberWithInt:value];
883   } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
884              [role isEqualToString:NSAccessibilityRadioButtonRole]) {
885     int value = 0;
886     value = GetState(
887         browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0;
888     value = GetState(
889         browserAccessibility_, ui::AX_STATE_SELECTED) ?
890             1 :
891             value;
893     if (browserAccessibility_->GetBoolAttribute(
894         ui::AX_ATTR_BUTTON_MIXED)) {
895       value = 2;
896     }
897     return [NSNumber numberWithInt:value];
898   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
899              [role isEqualToString:NSAccessibilitySliderRole] ||
900              [role isEqualToString:NSAccessibilityIncrementorRole] ||
901              [role isEqualToString:NSAccessibilityScrollBarRole]) {
902     float floatValue;
903     if (browserAccessibility_->GetFloatAttribute(
904             ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) {
905       return [NSNumber numberWithFloat:floatValue];
906     }
907   } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
908     int r = browserAccessibility_->GetIntAttribute(
909         ui::AX_ATTR_COLOR_VALUE_RED);
910     int g = browserAccessibility_->GetIntAttribute(
911         ui::AX_ATTR_COLOR_VALUE_GREEN);
912     int b = browserAccessibility_->GetIntAttribute(
913         ui::AX_ATTR_COLOR_VALUE_BLUE);
914     // This string matches the one returned by a native Mac color well.
915     return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
916                 r / 255., g / 255., b / 255.];
917   }
919   return NSStringForStringAttribute(
920       browserAccessibility_, ui::AX_ATTR_VALUE);
923 - (NSString*)valueDescription {
924   return NSStringForStringAttribute(
925       browserAccessibility_, ui::AX_ATTR_VALUE);
928 - (NSValue*)visibleCharacterRange {
929   std::string value = browserAccessibility_->GetStringAttribute(
930       ui::AX_ATTR_VALUE);
931   return [NSValue valueWithRange:NSMakeRange(0, value.size())];
934 - (NSArray*)visibleCells {
935   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
936   const std::vector<int32>& uniqueCellIds =
937       browserAccessibility_->GetIntListAttribute(
938           ui::AX_ATTR_UNIQUE_CELL_IDS);
939   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
940     int id = uniqueCellIds[i];
941     BrowserAccessibility* cell =
942         browserAccessibility_->manager()->GetFromID(id);
943     if (cell)
944       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
945   }
946   return ret;
949 - (NSArray*)visibleChildren {
950   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
951   uint32 childCount = browserAccessibility_->PlatformChildCount();
952   for (uint32 index = 0; index < childCount; ++index) {
953     BrowserAccessibilityCocoa* child =
954         browserAccessibility_->PlatformGetChild(index)->
955             ToBrowserAccessibilityCocoa();
956     [ret addObject:child];
957   }
958   return ret;
961 - (NSArray*)visibleColumns {
962   return [self columns];
965 - (NSArray*)visibleRows {
966   return [self rows];
969 - (NSNumber*)visited {
970   return [NSNumber numberWithBool:
971       GetState(browserAccessibility_, ui::AX_STATE_VISITED)];
974 - (id)window {
975   if (!browserAccessibility_)
976     return nil;
978   BrowserAccessibilityManagerMac* manager =
979       static_cast<BrowserAccessibilityManagerMac*>(
980           browserAccessibility_->manager());
981   return [manager->parent_view() window];
984 - (NSString*)methodNameForAttribute:(NSString*)attribute {
985   return [attributeToMethodNameMap objectForKey:attribute];
988 - (void)swapChildren:(base::scoped_nsobject<NSMutableArray>*)other {
989   children_.swap(*other);
992 // Returns the accessibility value for the given attribute.  If the value isn't
993 // supported this will return nil.
994 - (id)accessibilityAttributeValue:(NSString*)attribute {
995   if (!browserAccessibility_)
996     return nil;
998   SEL selector =
999       NSSelectorFromString([self methodNameForAttribute:attribute]);
1000   if (selector)
1001     return [self performSelector:selector];
1003   // TODO(dtseng): refactor remaining attributes.
1004   int selStart, selEnd;
1005   if (browserAccessibility_->GetIntAttribute(
1006           ui::AX_ATTR_TEXT_SEL_START, &selStart) &&
1007       browserAccessibility_->
1008           GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) {
1009     if (selStart > selEnd)
1010       std::swap(selStart, selEnd);
1011     int selLength = selEnd - selStart;
1012     if ([attribute isEqualToString:
1013         NSAccessibilityInsertionPointLineNumberAttribute]) {
1014       const std::vector<int32>& line_breaks =
1015           browserAccessibility_->GetIntListAttribute(
1016               ui::AX_ATTR_LINE_BREAKS);
1017       for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1018         if (line_breaks[i] > selStart)
1019           return [NSNumber numberWithInt:i];
1020       }
1021       return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1022     }
1023     if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
1024       std::string value = browserAccessibility_->GetStringAttribute(
1025           ui::AX_ATTR_VALUE);
1026       return base::SysUTF8ToNSString(value.substr(selStart, selLength));
1027     }
1028     if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1029       return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
1030     }
1031   }
1032   return nil;
1035 // Returns the accessibility value for the given attribute and parameter. If the
1036 // value isn't supported this will return nil.
1037 - (id)accessibilityAttributeValue:(NSString*)attribute
1038                      forParameter:(id)parameter {
1039   if (!browserAccessibility_)
1040     return nil;
1042   const std::vector<int32>& line_breaks =
1043       browserAccessibility_->GetIntListAttribute(
1044           ui::AX_ATTR_LINE_BREAKS);
1045   std::string value = browserAccessibility_->GetStringAttribute(
1046       ui::AX_ATTR_VALUE);
1047   int len = static_cast<int>(value.size());
1049   if ([attribute isEqualToString:
1050       NSAccessibilityStringForRangeParameterizedAttribute]) {
1051     NSRange range = [(NSValue*)parameter rangeValue];
1052     std::string value = browserAccessibility_->GetStringAttribute(
1053         ui::AX_ATTR_VALUE);
1054     return base::SysUTF8ToNSString(value.substr(range.location, range.length));
1055   }
1057   if ([attribute isEqualToString:
1058       NSAccessibilityLineForIndexParameterizedAttribute]) {
1059     int index = [(NSNumber*)parameter intValue];
1060     for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1061       if (line_breaks[i] > index)
1062         return [NSNumber numberWithInt:i];
1063     }
1064     return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1065   }
1067   if ([attribute isEqualToString:
1068       NSAccessibilityRangeForLineParameterizedAttribute]) {
1069     int line_index = [(NSNumber*)parameter intValue];
1070     int line_count = static_cast<int>(line_breaks.size()) + 1;
1071     if (line_index < 0 || line_index >= line_count)
1072       return nil;
1073     int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
1074     int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
1075     return [NSValue valueWithRange:
1076         NSMakeRange(start, end - start)];
1077   }
1079   if ([attribute isEqualToString:
1080       NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
1081     if ([self internalRole] != ui::AX_ROLE_TABLE &&
1082         [self internalRole] != ui::AX_ROLE_GRID) {
1083       return nil;
1084     }
1085     if (![parameter isKindOfClass:[NSArray self]])
1086       return nil;
1087     NSArray* array = parameter;
1088     int column = [[array objectAtIndex:0] intValue];
1089     int row = [[array objectAtIndex:1] intValue];
1090     int num_columns = browserAccessibility_->GetIntAttribute(
1091         ui::AX_ATTR_TABLE_COLUMN_COUNT);
1092     int num_rows = browserAccessibility_->GetIntAttribute(
1093         ui::AX_ATTR_TABLE_ROW_COUNT);
1094     if (column < 0 || column >= num_columns ||
1095         row < 0 || row >= num_rows) {
1096       return nil;
1097     }
1098     for (size_t i = 0;
1099          i < browserAccessibility_->PlatformChildCount();
1100          ++i) {
1101       BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i);
1102       if (child->GetRole() != ui::AX_ROLE_ROW)
1103         continue;
1104       int rowIndex;
1105       if (!child->GetIntAttribute(
1106               ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) {
1107         continue;
1108       }
1109       if (rowIndex < row)
1110         continue;
1111       if (rowIndex > row)
1112         break;
1113       for (size_t j = 0;
1114            j < child->PlatformChildCount();
1115            ++j) {
1116         BrowserAccessibility* cell = child->PlatformGetChild(j);
1117         if (!browserAccessibility_->IsCellOrTableHeaderRole())
1118           continue;
1119         int colIndex;
1120         if (!cell->GetIntAttribute(
1121                 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
1122                 &colIndex)) {
1123           continue;
1124         }
1125         if (colIndex == column)
1126           return cell->ToBrowserAccessibilityCocoa();
1127         if (colIndex > column)
1128           break;
1129       }
1130     }
1131     return nil;
1132   }
1134   if ([attribute isEqualToString:
1135       NSAccessibilityBoundsForRangeParameterizedAttribute]) {
1136     if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT)
1137       return nil;
1138     NSRange range = [(NSValue*)parameter rangeValue];
1139     gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange(
1140         range.location, range.length);
1141     NSPoint origin = NSMakePoint(rect.x(), rect.y());
1142     NSSize size = NSMakeSize(rect.width(), rect.height());
1143     NSPoint pointInScreen = [self pointInScreen:origin size:size];
1144     NSRect nsrect = NSMakeRect(
1145         pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
1146     return [NSValue valueWithRect:nsrect];
1147   }
1149   // TODO(dtseng): support the following attributes.
1150   if ([attribute isEqualTo:
1151           NSAccessibilityRangeForPositionParameterizedAttribute] ||
1152       [attribute isEqualTo:
1153           NSAccessibilityRangeForIndexParameterizedAttribute] ||
1154       [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
1155       [attribute isEqualTo:
1156           NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
1157     return nil;
1158   }
1159   return nil;
1162 // Returns an array of parameterized attributes names that this object will
1163 // respond to.
1164 - (NSArray*)accessibilityParameterizedAttributeNames {
1165   if (!browserAccessibility_)
1166     return nil;
1168   if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
1169       [[self role] isEqualToString:NSAccessibilityGridRole]) {
1170     return [NSArray arrayWithObjects:
1171         NSAccessibilityCellForColumnAndRowParameterizedAttribute,
1172         nil];
1173   }
1174   if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1175       [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
1176     return [NSArray arrayWithObjects:
1177         NSAccessibilityLineForIndexParameterizedAttribute,
1178         NSAccessibilityRangeForLineParameterizedAttribute,
1179         NSAccessibilityStringForRangeParameterizedAttribute,
1180         NSAccessibilityRangeForPositionParameterizedAttribute,
1181         NSAccessibilityRangeForIndexParameterizedAttribute,
1182         NSAccessibilityBoundsForRangeParameterizedAttribute,
1183         NSAccessibilityRTFForRangeParameterizedAttribute,
1184         NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1185         NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1186         nil];
1187   }
1188   if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1189     return [NSArray arrayWithObjects:
1190         NSAccessibilityBoundsForRangeParameterizedAttribute,
1191         nil];
1192   }
1193   return nil;
1196 // Returns an array of action names that this object will respond to.
1197 - (NSArray*)accessibilityActionNames {
1198   if (!browserAccessibility_)
1199     return nil;
1201   NSMutableArray* ret =
1202       [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1203   NSString* role = [self role];
1204   // TODO(dtseng): this should only get set when there's a default action.
1205   if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1206       ![role isEqualToString:NSAccessibilityTextAreaRole] &&
1207       ![role isEqualToString:NSAccessibilityTextFieldRole]) {
1208     [ret addObject:NSAccessibilityPressAction];
1209   }
1211   return ret;
1214 // Returns a sub-array of values for the given attribute value, starting at
1215 // index, with up to maxCount items.  If the given index is out of bounds,
1216 // or there are no values for the given attribute, it will return nil.
1217 // This method is used for querying subsets of values, without having to
1218 // return a large set of data, such as elements with a large number of
1219 // children.
1220 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1221                                         index:(NSUInteger)index
1222                                      maxCount:(NSUInteger)maxCount {
1223   if (!browserAccessibility_)
1224     return nil;
1226   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1227   if (!fullArray)
1228     return nil;
1229   NSUInteger arrayCount = [fullArray count];
1230   if (index >= arrayCount)
1231     return nil;
1232   NSRange subRange;
1233   if ((index + maxCount) > arrayCount) {
1234     subRange = NSMakeRange(index, arrayCount - index);
1235   } else {
1236     subRange = NSMakeRange(index, maxCount);
1237   }
1238   return [fullArray subarrayWithRange:subRange];
1241 // Returns the count of the specified accessibility array attribute.
1242 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1243   if (!browserAccessibility_)
1244     return 0;
1246   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1247   return [fullArray count];
1250 // Returns the list of accessibility attributes that this object supports.
1251 - (NSArray*)accessibilityAttributeNames {
1252   if (!browserAccessibility_)
1253     return nil;
1255   // General attributes.
1256   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1257       NSAccessibilityChildrenAttribute,
1258       NSAccessibilityDescriptionAttribute,
1259       NSAccessibilityEnabledAttribute,
1260       NSAccessibilityFocusedAttribute,
1261       NSAccessibilityHelpAttribute,
1262       NSAccessibilityLinkedUIElementsAttribute,
1263       NSAccessibilityParentAttribute,
1264       NSAccessibilityPositionAttribute,
1265       NSAccessibilityRoleAttribute,
1266       NSAccessibilityRoleDescriptionAttribute,
1267       NSAccessibilitySizeAttribute,
1268       NSAccessibilitySubroleAttribute,
1269       NSAccessibilityTitleAttribute,
1270       NSAccessibilityTopLevelUIElementAttribute,
1271       NSAccessibilityValueAttribute,
1272       NSAccessibilityWindowAttribute,
1273       @"AXAccessKey",
1274       @"AXInvalid",
1275       @"AXVisited",
1276       nil];
1278   // Specific role attributes.
1279   NSString* role = [self role];
1280   NSString* subrole = [self subrole];
1281   if ([role isEqualToString:NSAccessibilityTableRole] ||
1282       [role isEqualToString:NSAccessibilityGridRole]) {
1283     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1284         NSAccessibilityColumnsAttribute,
1285         NSAccessibilityVisibleColumnsAttribute,
1286         NSAccessibilityRowsAttribute,
1287         NSAccessibilityVisibleRowsAttribute,
1288         NSAccessibilityVisibleCellsAttribute,
1289         NSAccessibilityHeaderAttribute,
1290         NSAccessibilityColumnHeaderUIElementsAttribute,
1291         NSAccessibilityRowHeaderUIElementsAttribute,
1292         nil]];
1293   } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1294     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1295         NSAccessibilityIndexAttribute,
1296         NSAccessibilityHeaderAttribute,
1297         NSAccessibilityRowsAttribute,
1298         NSAccessibilityVisibleRowsAttribute,
1299         nil]];
1300   } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1301     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1302         NSAccessibilityColumnIndexRangeAttribute,
1303         NSAccessibilityRowIndexRangeAttribute,
1304         nil]];
1305   } else if ([role isEqualToString:@"AXWebArea"]) {
1306     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1307         @"AXLoaded",
1308         @"AXLoadingProgress",
1309         nil]];
1310   } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
1311              [role isEqualToString:NSAccessibilityTextAreaRole]) {
1312     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1313         NSAccessibilityInsertionPointLineNumberAttribute,
1314         NSAccessibilityNumberOfCharactersAttribute,
1315         NSAccessibilitySelectedTextAttribute,
1316         NSAccessibilitySelectedTextRangeAttribute,
1317         NSAccessibilityVisibleCharacterRangeAttribute,
1318         nil]];
1319   } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1320     [ret addObject:NSAccessibilityTabsAttribute];
1321   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1322              [role isEqualToString:NSAccessibilitySliderRole] ||
1323              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1324              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1325     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1326         NSAccessibilityMaxValueAttribute,
1327         NSAccessibilityMinValueAttribute,
1328         NSAccessibilityValueDescriptionAttribute,
1329         nil]];
1330   } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1331     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1332         NSAccessibilityDisclosingAttribute,
1333         NSAccessibilityDisclosedByRowAttribute,
1334         NSAccessibilityDisclosureLevelAttribute,
1335         NSAccessibilityDisclosedRowsAttribute,
1336         nil]];
1337   } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1338     if (browserAccessibility_->GetParent()) {
1339       base::string16 parentRole;
1340       browserAccessibility_->GetParent()->GetHtmlAttribute(
1341           "role", &parentRole);
1342       const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1343       if (parentRole == treegridRole) {
1344         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1345             NSAccessibilityDisclosingAttribute,
1346             NSAccessibilityDisclosedByRowAttribute,
1347             NSAccessibilityDisclosureLevelAttribute,
1348             NSAccessibilityDisclosedRowsAttribute,
1349             nil]];
1350       } else {
1351         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1352             NSAccessibilityIndexAttribute,
1353             nil]];
1354       }
1355     }
1356   } else if ([role isEqualToString:NSAccessibilityListRole]) {
1357     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1358         NSAccessibilitySelectedChildrenAttribute,
1359         NSAccessibilityVisibleChildrenAttribute,
1360         nil]];
1361   }
1363   // Add the url attribute only if it has a valid url.
1364   if ([self url] != nil) {
1365     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1366         NSAccessibilityURLAttribute,
1367         nil]];
1368   }
1370   // Live regions.
1371   if (browserAccessibility_->HasStringAttribute(
1372           ui::AX_ATTR_LIVE_STATUS)) {
1373     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1374         @"AXARIALive",
1375         nil]];
1376   }
1377   if (browserAccessibility_->HasStringAttribute(
1378           ui::AX_ATTR_LIVE_RELEVANT)) {
1379     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1380         @"AXARIARelevant",
1381         nil]];
1382   }
1383   if (browserAccessibility_->HasBoolAttribute(
1384           ui::AX_ATTR_LIVE_ATOMIC)) {
1385     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1386         @"AXARIAAtomic",
1387         nil]];
1388   }
1389   if (browserAccessibility_->HasBoolAttribute(
1390           ui::AX_ATTR_LIVE_BUSY)) {
1391     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1392         @"AXARIABusy",
1393         nil]];
1394   }
1395   // Add aria-grabbed attribute only if it has true.
1396   if (browserAccessibility_->HasBoolAttribute(ui::AX_ATTR_GRABBED)) {
1397     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1398         @"AXGrabbed",
1399         nil]];
1400   }
1402   // Add expanded attribute only if it has expanded or collapsed state.
1403   if (GetState(browserAccessibility_, ui::AX_STATE_EXPANDED) ||
1404         GetState(browserAccessibility_, ui::AX_STATE_COLLAPSED)) {
1405     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1406         NSAccessibilityExpandedAttribute,
1407         nil]];
1408   }
1410   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)
1411       || GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL)) {
1412     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1413         NSAccessibilityOrientationAttribute, nil]];
1414   }
1416   if (browserAccessibility_->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER)) {
1417     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1418         @"AXPlaceholder", nil]];
1419   }
1421   if (GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)) {
1422     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1423         @"AXRequired", nil]];
1424   }
1426   // Title UI Element.
1427   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1428       (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1429        browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1430                             .size() == 1)) {
1431     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1432          NSAccessibilityTitleUIElementAttribute,
1433          nil]];
1434   }
1435   // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1436   // for elements which are referred to by labelledby or are labels
1438   return ret;
1441 // Returns the index of the child in this objects array of children.
1442 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1443   if (!browserAccessibility_)
1444     return 0;
1446   NSUInteger index = 0;
1447   for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1448     if ([child isEqual:childToCheck])
1449       return index;
1450     ++index;
1451   }
1452   return NSNotFound;
1455 // Returns whether or not the specified attribute can be set by the
1456 // accessibility API via |accessibilitySetValue:forAttribute:|.
1457 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1458   if (!browserAccessibility_)
1459     return NO;
1461   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1462     return GetState(browserAccessibility_,
1463         ui::AX_STATE_FOCUSABLE);
1464   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1465     return browserAccessibility_->GetBoolAttribute(
1466         ui::AX_ATTR_CAN_SET_VALUE);
1467   }
1468   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1469       ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1470        [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1471     return YES;
1473   return NO;
1476 // Returns whether or not this object should be ignored in the accessibility
1477 // tree.
1478 - (BOOL)accessibilityIsIgnored {
1479   if (!browserAccessibility_)
1480     return true;
1482   return [self isIgnored];
1485 // Performs the given accessibility action on the webkit accessibility object
1486 // that backs this object.
1487 - (void)accessibilityPerformAction:(NSString*)action {
1488   if (!browserAccessibility_)
1489     return;
1491   // TODO(dmazzoni): Support more actions.
1492   if ([action isEqualToString:NSAccessibilityPressAction]) {
1493     [self delegate]->AccessibilityDoDefaultAction(
1494         browserAccessibility_->GetId());
1495   } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1496     NSPoint objOrigin = [self origin];
1497     NSSize size = [[self size] sizeValue];
1498     gfx::Point origin = [self delegate]->AccessibilityOriginInScreen(
1499         gfx::Rect(objOrigin.x, objOrigin.y, size.width, size.height));
1500     origin.Offset(size.width / 2, size.height / 2);
1501     [self delegate]->AccessibilityShowMenu(origin);
1502   }
1505 // Returns the description of the given action.
1506 - (NSString*)accessibilityActionDescription:(NSString*)action {
1507   if (!browserAccessibility_)
1508     return nil;
1510   return NSAccessibilityActionDescription(action);
1513 // Sets an override value for a specific accessibility attribute.
1514 // This class does not support this.
1515 - (BOOL)accessibilitySetOverrideValue:(id)value
1516                          forAttribute:(NSString*)attribute {
1517   return NO;
1520 // Sets the value for an accessibility attribute via the accessibility API.
1521 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1522   if (!browserAccessibility_)
1523     return;
1525   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1526     BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1527     NSNumber* focusedNumber = value;
1528     BOOL focused = [focusedNumber intValue];
1529     if (focused)
1530       manager->SetFocus(browserAccessibility_, true);
1531   }
1532   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1533     NSRange range = [(NSValue*)value rangeValue];
1534     [self delegate]->AccessibilitySetTextSelection(
1535         browserAccessibility_->GetId(),
1536         range.location, range.location + range.length);
1537   }
1540 // Returns the deepest accessibility child that should not be ignored.
1541 // It is assumed that the hit test has been narrowed down to this object
1542 // or one of its children, so this will never return nil unless this
1543 // object is invalid.
1544 - (id)accessibilityHitTest:(NSPoint)point {
1545   if (!browserAccessibility_)
1546     return nil;
1548   BrowserAccessibilityCocoa* hit = self;
1549   for (BrowserAccessibilityCocoa* child in [self children]) {
1550     if (!child->browserAccessibility_)
1551       continue;
1552     NSPoint origin = [child origin];
1553     NSSize size = [[child size] sizeValue];
1554     NSRect rect;
1555     rect.origin = origin;
1556     rect.size = size;
1557     if (NSPointInRect(point, rect)) {
1558       hit = child;
1559       id childResult = [child accessibilityHitTest:point];
1560       if (![childResult accessibilityIsIgnored]) {
1561         hit = childResult;
1562         break;
1563       }
1564     }
1565   }
1566   return NSAccessibilityUnignoredAncestor(hit);
1569 - (BOOL)isEqual:(id)object {
1570   if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1571     return NO;
1572   return ([self hash] == [object hash]);
1575 - (NSUInteger)hash {
1576   // Potentially called during dealloc.
1577   if (!browserAccessibility_)
1578     return [super hash];
1579   return browserAccessibility_->GetId();
1582 - (BOOL)accessibilityShouldUseUniqueId {
1583   return YES;
1586 @end