Disable PDF tests that are failing after combining PDF plugin into chrome.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_cocoa.mm
blobdc1c35fbad4b4c41242e3070a750aa89786beddf
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     { @"AXDropEffects", @"dropeffect" },
113     { @"AXGrabbed", @"grabbed" },
114     { @"AXInvalid", @"invalid" },
115     { @"AXLoaded", @"loaded" },
116     { @"AXLoadingProgress", @"loadingProgress" },
117     { @"AXPlaceholder", @"placeholder" },
118     { @"AXRequired", @"required" },
119     { @"AXSortDirection", @"sortDirection" },
120     { @"AXVisited", @"visited" },
121   };
123   NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
124   const size_t numAttributes = sizeof(attributeToMethodNameContainer) /
125                                sizeof(attributeToMethodNameContainer[0]);
126   for (size_t i = 0; i < numAttributes; ++i) {
127     [dict setObject:attributeToMethodNameContainer[i].methodName
128              forKey:attributeToMethodNameContainer[i].attribute];
129   }
130   attributeToMethodNameMap = dict;
131   dict = nil;
134 - (id)initWithObject:(BrowserAccessibility*)accessibility {
135   if ((self = [super init]))
136     browserAccessibility_ = accessibility;
137   return self;
140 - (void)detach {
141   if (browserAccessibility_) {
142     NSAccessibilityUnregisterUniqueIdForUIElement(self);
143     browserAccessibility_ = NULL;
144   }
147 - (NSString*)accessKey {
148   return NSStringForStringAttribute(
149       browserAccessibility_, ui::AX_ATTR_ACCESS_KEY);
152 - (NSNumber*)ariaAtomic {
153   bool boolValue = browserAccessibility_->GetBoolAttribute(
154       ui::AX_ATTR_LIVE_ATOMIC);
155   return [NSNumber numberWithBool:boolValue];
158 - (NSNumber*)ariaBusy {
159   return [NSNumber numberWithBool:
160       GetState(browserAccessibility_, ui::AX_STATE_BUSY)];
163 - (NSString*)ariaLive {
164   return NSStringForStringAttribute(
165       browserAccessibility_, ui::AX_ATTR_LIVE_STATUS);
168 - (NSString*)ariaRelevant {
169   return NSStringForStringAttribute(
170       browserAccessibility_, ui::AX_ATTR_LIVE_RELEVANT);
173 // Returns an array of BrowserAccessibilityCocoa objects, representing the
174 // accessibility children of this object.
175 - (NSArray*)children {
176   if (!children_) {
177     uint32 childCount = browserAccessibility_->PlatformChildCount();
178     children_.reset([[NSMutableArray alloc] initWithCapacity:childCount]);
179     for (uint32 index = 0; index < childCount; ++index) {
180       BrowserAccessibilityCocoa* child =
181           browserAccessibility_->PlatformGetChild(index)->
182               ToBrowserAccessibilityCocoa();
183       if ([child isIgnored])
184         [children_ addObjectsFromArray:[child children]];
185       else
186         [children_ addObject:child];
187     }
189     // Also, add indirect children (if any).
190     const std::vector<int32>& indirectChildIds =
191         browserAccessibility_->GetIntListAttribute(
192             ui::AX_ATTR_INDIRECT_CHILD_IDS);
193     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
194       int32 child_id = indirectChildIds[i];
195       BrowserAccessibility* child =
196           browserAccessibility_->manager()->GetFromID(child_id);
198       // This only became necessary as a result of crbug.com/93095. It should be
199       // a DCHECK in the future.
200       if (child) {
201         BrowserAccessibilityCocoa* child_cocoa =
202             child->ToBrowserAccessibilityCocoa();
203         [children_ addObject:child_cocoa];
204       }
205     }
206   }
207   return children_;
210 - (void)childrenChanged {
211   if (![self isIgnored]) {
212     children_.reset();
213   } else {
214     [browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa()
215        childrenChanged];
216   }
219 - (NSArray*)columnHeaders {
220   if ([self internalRole] != ui::AX_ROLE_TABLE &&
221       [self internalRole] != ui::AX_ROLE_GRID) {
222     return nil;
223   }
225   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
226   const std::vector<int32>& uniqueCellIds =
227       browserAccessibility_->GetIntListAttribute(
228           ui::AX_ATTR_UNIQUE_CELL_IDS);
229   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
230     int id = uniqueCellIds[i];
231     BrowserAccessibility* cell =
232         browserAccessibility_->manager()->GetFromID(id);
233     if (cell && cell->GetRole() == ui::AX_ROLE_COLUMN_HEADER)
234       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
235   }
236   return ret;
239 - (NSValue*)columnIndexRange {
240   if (!browserAccessibility_->IsCellOrTableHeaderRole())
241     return nil;
243   int column = -1;
244   int colspan = -1;
245   browserAccessibility_->GetIntAttribute(
246       ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, &column);
247   browserAccessibility_->GetIntAttribute(
248       ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN, &colspan);
249   if (column >= 0 && colspan >= 1)
250     return [NSValue valueWithRange:NSMakeRange(column, colspan)];
251   return nil;
254 - (NSArray*)columns {
255   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
256   for (BrowserAccessibilityCocoa* child in [self children]) {
257     if ([[child role] isEqualToString:NSAccessibilityColumnRole])
258       [ret addObject:child];
259   }
260   return ret;
263 - (NSString*)description {
264   std::string description;
265   if (browserAccessibility_->GetStringAttribute(
266           ui::AX_ATTR_DESCRIPTION, &description)) {
267     return base::SysUTF8ToNSString(description);
268   }
270   // If the role is anything other than an image, or if there's
271   // a title or title UI element, just return an empty string.
272   if (![[self role] isEqualToString:NSAccessibilityImageRole])
273     return @"";
274   if (browserAccessibility_->HasStringAttribute(
275           ui::AX_ATTR_NAME)) {
276     return @"";
277   }
278   if ([self titleUIElement])
279     return @"";
281   // The remaining case is an image where there's no other title.
282   // Return the base part of the filename as the description.
283   std::string url;
284   if (browserAccessibility_->GetStringAttribute(
285           ui::AX_ATTR_URL, &url)) {
286     // Given a url like http://foo.com/bar/baz.png, just return the
287     // base name, e.g., "baz.png".
288     size_t leftIndex = url.rfind('/');
289     std::string basename =
290         leftIndex != std::string::npos ? url.substr(leftIndex) : url;
291     return base::SysUTF8ToNSString(basename);
292   }
294   return @"";
297 - (NSNumber*)disclosing {
298   if ([self internalRole] == ui::AX_ROLE_TREE_ITEM) {
299     return [NSNumber numberWithBool:
300         GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
301   } else {
302     return nil;
303   }
306 - (id)disclosedByRow {
307   // The row that contains this row.
308   // It should be the same as the first parent that is a treeitem.
309   return nil;
312 - (NSNumber*)disclosureLevel {
313   ui::AXRole role = [self internalRole];
314   if (role == ui::AX_ROLE_ROW ||
315       role == ui::AX_ROLE_TREE_ITEM) {
316     int level = browserAccessibility_->GetIntAttribute(
317         ui::AX_ATTR_HIERARCHICAL_LEVEL);
318     // Mac disclosureLevel is 0-based, but web levels are 1-based.
319     if (level > 0)
320       level--;
321     return [NSNumber numberWithInt:level];
322   } else {
323     return nil;
324   }
327 - (id)disclosedRows {
328   // The rows that are considered inside this row.
329   return nil;
332 - (NSString*)dropeffect {
333   return NSStringForStringAttribute(
334       browserAccessibility_, ui::AX_ATTR_DROPEFFECT);
337 - (NSNumber*)enabled {
338   return [NSNumber numberWithBool:
339       GetState(browserAccessibility_, ui::AX_STATE_ENABLED)];
342 - (NSNumber*)expanded {
343   return [NSNumber numberWithBool:
344       GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
347 - (NSNumber*)focused {
348   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
349   NSNumber* ret = [NSNumber numberWithBool:
350       manager->GetFocus(NULL) == browserAccessibility_];
351   return ret;
354 - (NSNumber*)grabbed {
355   bool boolValue = browserAccessibility_->GetBoolAttribute(ui::AX_ATTR_GRABBED);
356   return [NSNumber numberWithBool:boolValue];
359 - (id)header {
360   int headerElementId = -1;
361   if ([self internalRole] == ui::AX_ROLE_TABLE ||
362       [self internalRole] == ui::AX_ROLE_GRID) {
363     browserAccessibility_->GetIntAttribute(
364         ui::AX_ATTR_TABLE_HEADER_ID, &headerElementId);
365   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
366     browserAccessibility_->GetIntAttribute(
367         ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId);
368   } else if ([self internalRole] == ui::AX_ROLE_ROW) {
369     browserAccessibility_->GetIntAttribute(
370         ui::AX_ATTR_TABLE_ROW_HEADER_ID, &headerElementId);
371   }
373   if (headerElementId > 0) {
374     BrowserAccessibility* headerObject =
375         browserAccessibility_->manager()->GetFromID(headerElementId);
376     if (headerObject)
377       return headerObject->ToBrowserAccessibilityCocoa();
378   }
379   return nil;
382 - (NSString*)help {
383   return NSStringForStringAttribute(
384       browserAccessibility_, ui::AX_ATTR_HELP);
387 - (NSNumber*)index {
388   if ([self internalRole] == ui::AX_ROLE_COLUMN) {
389     int columnIndex = browserAccessibility_->GetIntAttribute(
390           ui::AX_ATTR_TABLE_COLUMN_INDEX);
391     return [NSNumber numberWithInt:columnIndex];
392   } else if ([self internalRole] == ui::AX_ROLE_ROW) {
393     int rowIndex = browserAccessibility_->GetIntAttribute(
394         ui::AX_ATTR_TABLE_ROW_INDEX);
395     return [NSNumber numberWithInt:rowIndex];
396   }
398   return nil;
401 // Returns whether or not this node should be ignored in the
402 // accessibility tree.
403 - (BOOL)isIgnored {
404   return [[self role] isEqualToString:NSAccessibilityUnknownRole];
407 - (NSString*)invalid {
408   int invalidState;
409   if (!browserAccessibility_->GetIntAttribute(
410       ui::AX_ATTR_INVALID_STATE, &invalidState))
411     return @"false";
413   switch (invalidState) {
414   case ui::AX_INVALID_STATE_FALSE:
415     return @"false";
416   case ui::AX_INVALID_STATE_TRUE:
417     return @"true";
418   case ui::AX_INVALID_STATE_SPELLING:
419     return @"spelling";
420   case ui::AX_INVALID_STATE_GRAMMAR:
421     return @"grammar";
422   case ui::AX_INVALID_STATE_OTHER:
423     {
424       std::string ariaInvalidValue;
425       if (browserAccessibility_->GetStringAttribute(
426           ui::AX_ATTR_ARIA_INVALID_VALUE,
427           &ariaInvalidValue))
428         return base::SysUTF8ToNSString(ariaInvalidValue);
429       // Return @"true" since we cannot be more specific about the value.
430       return @"true";
431     }
432   default:
433     NOTREACHED();
434   }
436   return @"false";
439 - (NSString*)placeholder {
440   return NSStringForStringAttribute(
441       browserAccessibility_, ui::AX_ATTR_PLACEHOLDER);
444 - (void)addLinkedUIElementsFromAttribute:(ui::AXIntListAttribute)attribute
445                                    addTo:(NSMutableArray*)outArray {
446   const std::vector<int32>& attributeValues =
447       browserAccessibility_->GetIntListAttribute(attribute);
448   for (size_t i = 0; i < attributeValues.size(); ++i) {
449     BrowserAccessibility* element =
450         browserAccessibility_->manager()->GetFromID(attributeValues[i]);
451     if (element)
452       [outArray addObject:element->ToBrowserAccessibilityCocoa()];
453   }
456 - (NSArray*)linkedUIElements {
457   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
458   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_OWNS_IDS addTo:ret];
459   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_CONTROLS_IDS addTo:ret];
460   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_FLOWTO_IDS addTo:ret];
461   if ([ret count] == 0)
462     return nil;
463   return ret;
466 - (NSNumber*)loaded {
467   return [NSNumber numberWithBool:YES];
470 - (NSNumber*)loadingProgress {
471   float floatValue = browserAccessibility_->GetFloatAttribute(
472       ui::AX_ATTR_DOC_LOADING_PROGRESS);
473   return [NSNumber numberWithFloat:floatValue];
476 - (NSNumber*)maxValue {
477   float floatValue = browserAccessibility_->GetFloatAttribute(
478       ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
479   return [NSNumber numberWithFloat:floatValue];
482 - (NSNumber*)minValue {
483   float floatValue = browserAccessibility_->GetFloatAttribute(
484       ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
485   return [NSNumber numberWithFloat:floatValue];
488 - (NSString*)orientation {
489   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL))
490     return NSAccessibilityVerticalOrientationValue;
491   else if (GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL))
492     return NSAccessibilityHorizontalOrientationValue;
494   return @"";
497 - (NSNumber*)numberOfCharacters {
498   std::string value = browserAccessibility_->GetStringAttribute(
499       ui::AX_ATTR_VALUE);
500   return [NSNumber numberWithInt:value.size()];
503 // The origin of this accessibility object in the page's document.
504 // This is relative to webkit's top-left origin, not Cocoa's
505 // bottom-left origin.
506 - (NSPoint)origin {
507   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
508   return NSMakePoint(bounds.x(), bounds.y());
511 - (id)parent {
512   // A nil parent means we're the root.
513   if (browserAccessibility_->GetParent()) {
514     return NSAccessibilityUnignoredAncestor(
515         browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa());
516   } else {
517     // Hook back up to RenderWidgetHostViewCocoa.
518     BrowserAccessibilityManagerMac* manager =
519         static_cast<BrowserAccessibilityManagerMac*>(
520             browserAccessibility_->manager());
521     return manager->parent_view();
522   }
525 - (NSValue*)position {
526   NSPoint origin = [self origin];
527   NSSize size = [[self size] sizeValue];
528   NSPoint pointInScreen = [self pointInScreen:origin size:size];
529   return [NSValue valueWithPoint:pointInScreen];
532 - (NSNumber*)required {
533   return [NSNumber numberWithBool:
534       GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)];
537 // Returns an enum indicating the role from browserAccessibility_.
538 - (ui::AXRole)internalRole {
539   return static_cast<ui::AXRole>(browserAccessibility_->GetRole());
542 - (content::BrowserAccessibilityDelegate*)delegate {
543   return browserAccessibility_->manager() ?
544       browserAccessibility_->manager()->delegate() :
545       nil;
548 - (NSPoint)pointInScreen:(NSPoint)origin
549                     size:(NSSize)size {
550   if (!browserAccessibility_)
551     return NSZeroPoint;
553   // Get the delegate for the topmost BrowserAccessibilityManager, because
554   // that's the only one that can convert points to their origin in the screen.
555   BrowserAccessibilityDelegate* delegate =
556       browserAccessibility_->manager()->GetDelegateFromRootManager();
557   if (delegate) {
558     gfx::Rect bounds(origin.x, origin.y, size.width, size.height);
559     gfx::Point point = delegate->AccessibilityOriginInScreen(bounds);
560     return NSMakePoint(point.x(), point.y());
561   } else {
562     return NSZeroPoint;
563   }
566 // Returns a string indicating the NSAccessibility role of this object.
567 - (NSString*)role {
568   ui::AXRole role = [self internalRole];
569   if (role == ui::AX_ROLE_CANVAS &&
570       browserAccessibility_->GetBoolAttribute(
571           ui::AX_ATTR_CANVAS_HAS_FALLBACK)) {
572     return NSAccessibilityGroupRole;
573   }
574   if (role == ui::AX_ROLE_BUTTON || role == ui::AX_ROLE_TOGGLE_BUTTON) {
575     bool isAriaPressedDefined;
576     bool isMixed;
577     browserAccessibility_->GetAriaTristate("aria-pressed",
578                                            &isAriaPressedDefined,
579                                            &isMixed);
580     if (isAriaPressedDefined)
581       return NSAccessibilityCheckBoxRole;
582     else
583       return NSAccessibilityButtonRole;
584   }
586   // If this is a web area for a presentational iframe, give it a role of
587   // something other than WebArea so that the fact that it's a separate doc
588   // is not exposed to AT.
589   if (browserAccessibility_->IsWebAreaForPresentationalIframe())
590     return NSAccessibilityGroupRole;
592   return [AXPlatformNodeCocoa nativeRoleFromAXRole:role];
595 // Returns a string indicating the role description of this object.
596 - (NSString*)roleDescription {
597   NSString* role = [self role];
599   ContentClient* content_client = content::GetContentClient();
601   // The following descriptions are specific to webkit.
602   if ([role isEqualToString:@"AXWebArea"]) {
603     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
604         IDS_AX_ROLE_WEB_AREA));
605   }
607   if ([role isEqualToString:@"NSAccessibilityLinkRole"]) {
608     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
609         IDS_AX_ROLE_LINK));
610   }
612   if ([role isEqualToString:@"AXHeading"]) {
613     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
614         IDS_AX_ROLE_HEADING));
615   }
617   if (([role isEqualToString:NSAccessibilityGroupRole] ||
618        [role isEqualToString:NSAccessibilityRadioButtonRole]) &&
619       !browserAccessibility_->IsWebAreaForPresentationalIframe()) {
620     std::string role;
621     if (browserAccessibility_->GetHtmlAttribute("role", &role)) {
622       ui::AXRole internalRole = [self internalRole];
623       if ((internalRole != ui::AX_ROLE_GROUP &&
624            internalRole != ui::AX_ROLE_LIST_ITEM) ||
625           internalRole == ui::AX_ROLE_TAB) {
626         // TODO(dtseng): This is not localized; see crbug/84814.
627         return base::SysUTF8ToNSString(role);
628       }
629     }
630   }
632   switch([self internalRole]) {
633   case ui::AX_ROLE_ARTICLE:
634     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
635         IDS_AX_ROLE_ARTICLE));
636   case ui::AX_ROLE_BANNER:
637     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
638         IDS_AX_ROLE_BANNER));
639   case ui::AX_ROLE_COMPLEMENTARY:
640     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
641         IDS_AX_ROLE_COMPLEMENTARY));
642   case ui::AX_ROLE_CONTENT_INFO:
643     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
644         IDS_AX_ROLE_ADDRESS));
645   case ui::AX_ROLE_DESCRIPTION_LIST:
646     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
647         IDS_AX_ROLE_DESCRIPTION_LIST));
648   case ui::AX_ROLE_DESCRIPTION_LIST_DETAIL:
649     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
650         IDS_AX_ROLE_DESCRIPTION_DETAIL));
651   case ui::AX_ROLE_DESCRIPTION_LIST_TERM:
652     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
653         IDS_AX_ROLE_DESCRIPTION_TERM));
654   case ui::AX_ROLE_FIGURE:
655     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
656         IDS_AX_ROLE_FIGURE));
657   case ui::AX_ROLE_FOOTER:
658     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
659         IDS_AX_ROLE_FOOTER));
660   case ui::AX_ROLE_FORM:
661     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
662         IDS_AX_ROLE_FORM));
663   case ui::AX_ROLE_MAIN:
664     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
665         IDS_AX_ROLE_MAIN_CONTENT));
666   case ui::AX_ROLE_MATH:
667     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
668         IDS_AX_ROLE_MATH));
669   case ui::AX_ROLE_NAVIGATION:
670     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
671         IDS_AX_ROLE_NAVIGATIONAL_LINK));
672   case ui::AX_ROLE_REGION:
673     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
674         IDS_AX_ROLE_REGION));
675   case ui::AX_ROLE_SPIN_BUTTON:
676     // This control is similar to what VoiceOver calls a "stepper".
677     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
678         IDS_AX_ROLE_STEPPER));
679   case ui::AX_ROLE_STATUS:
680     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
681         IDS_AX_ROLE_STATUS));
682   case ui::AX_ROLE_TOGGLE_BUTTON:
683     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
684         IDS_AX_ROLE_TOGGLE_BUTTON));
685   default:
686     break;
687   }
689   return NSAccessibilityRoleDescription(role, nil);
692 - (NSArray*)rowHeaders {
693   if ([self internalRole] != ui::AX_ROLE_TABLE &&
694       [self internalRole] != ui::AX_ROLE_GRID) {
695     return nil;
696   }
698   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
699   const std::vector<int32>& uniqueCellIds =
700       browserAccessibility_->GetIntListAttribute(
701           ui::AX_ATTR_UNIQUE_CELL_IDS);
702   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
703     int id = uniqueCellIds[i];
704     BrowserAccessibility* cell =
705         browserAccessibility_->manager()->GetFromID(id);
706     if (cell && cell->GetRole() == ui::AX_ROLE_ROW_HEADER)
707       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
708   }
709   return ret;
712 - (NSValue*)rowIndexRange {
713   if (!browserAccessibility_->IsCellOrTableHeaderRole())
714     return nil;
716   int row = -1;
717   int rowspan = -1;
718   browserAccessibility_->GetIntAttribute(
719       ui::AX_ATTR_TABLE_CELL_ROW_INDEX, &row);
720   browserAccessibility_->GetIntAttribute(
721       ui::AX_ATTR_TABLE_CELL_ROW_SPAN, &rowspan);
722   if (row >= 0 && rowspan >= 1)
723     return [NSValue valueWithRange:NSMakeRange(row, rowspan)];
724   return nil;
727 - (NSArray*)rows {
728   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
730   if ([self internalRole] == ui::AX_ROLE_TABLE||
731       [self internalRole] == ui::AX_ROLE_GRID) {
732     for (BrowserAccessibilityCocoa* child in [self children]) {
733       if ([[child role] isEqualToString:NSAccessibilityRowRole])
734         [ret addObject:child];
735     }
736   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
737     const std::vector<int32>& indirectChildIds =
738         browserAccessibility_->GetIntListAttribute(
739             ui::AX_ATTR_INDIRECT_CHILD_IDS);
740     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
741       int id = indirectChildIds[i];
742       BrowserAccessibility* rowElement =
743           browserAccessibility_->manager()->GetFromID(id);
744       if (rowElement)
745         [ret addObject:rowElement->ToBrowserAccessibilityCocoa()];
746     }
747   }
749   return ret;
752 - (NSArray*)selectedChildren {
753   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
755   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
756   BrowserAccessibility* focusedChild =
757       manager->GetFocus(browserAccessibility_);
758   if (focusedChild && focusedChild != browserAccessibility_) {
759     // First try the focused child.
760     [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
761   } else {
762     // Next try the active descendant.
763     int activeDescendantId;
764     if (browserAccessibility_->GetIntAttribute(
765             ui::AX_ATTR_ACTIVEDESCENDANT_ID, &activeDescendantId)) {
766       BrowserAccessibility* activeDescendant =
767           manager->GetFromID(activeDescendantId);
768       if (activeDescendant)
769         [ret addObject:activeDescendant->ToBrowserAccessibilityCocoa()];
770     } else {
771       // Otherwise return any children with the "selected" state, which
772       // may come from aria-selected.
773       uint32 childCount = browserAccessibility_->PlatformChildCount();
774       for (uint32 index = 0; index < childCount; ++index) {
775         BrowserAccessibility* child =
776             browserAccessibility_->PlatformGetChild(index);
777         if (child->HasState(ui::AX_STATE_SELECTED))
778           [ret addObject:child->ToBrowserAccessibilityCocoa()];
779       }
780     }
781   }
783   return ret;
786 // Returns the size of this object.
787 - (NSValue*)size {
788   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
789   return  [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())];
792 - (NSString*)sortDirection {
793   int sortDirection;
794   if (!browserAccessibility_->GetIntAttribute(
795       ui::AX_ATTR_SORT_DIRECTION, &sortDirection))
796     return @"";
798   switch (sortDirection) {
799   case ui::AX_SORT_DIRECTION_UNSORTED:
800     return @"";
801   case ui::AX_SORT_DIRECTION_ASCENDING:
802     return @"AXSortDirectionAscending";
803   case ui::AX_SORT_DIRECTION_DESCENDING:
804     return @"AXSortDirectionDescending";
805   case ui::AX_SORT_DIRECTION_OTHER:
806     return @"AXSortDirectionUnknown";
807   default:
808     NOTREACHED();
809   }
811   return @"";
814 // Returns a subrole based upon the role.
815 - (NSString*) subrole {
816   ui::AXRole browserAccessibilityRole = [self internalRole];
817   if (browserAccessibilityRole == ui::AX_ROLE_TEXT_FIELD &&
818       GetState(browserAccessibility_, ui::AX_STATE_PROTECTED)) {
819     return @"AXSecureTextField";
820   }
822   if (browserAccessibilityRole == ui::AX_ROLE_DESCRIPTION_LIST)
823     return @"AXDefinitionList";
825   if (browserAccessibilityRole == ui::AX_ROLE_LIST)
826     return @"AXContentList";
828   return [AXPlatformNodeCocoa nativeSubroleFromAXRole:browserAccessibilityRole];
831 // Returns all tabs in this subtree.
832 - (NSArray*)tabs {
833   NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
835   if ([self internalRole] == ui::AX_ROLE_TAB)
836     [tabSubtree addObject:self];
838   for (uint i=0; i < [[self children] count]; ++i) {
839     NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
840     if ([tabChildren count] > 0)
841       [tabSubtree addObjectsFromArray:tabChildren];
842   }
844   return tabSubtree;
847 - (NSString*)title {
848   return NSStringForStringAttribute(
849       browserAccessibility_, ui::AX_ATTR_NAME);
852 - (id)titleUIElement {
853   int titleElementId;
854   if (browserAccessibility_->GetIntAttribute(
855           ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
856     BrowserAccessibility* titleElement =
857         browserAccessibility_->manager()->GetFromID(titleElementId);
858     if (titleElement)
859       return titleElement->ToBrowserAccessibilityCocoa();
860   }
861   std::vector<int32> labelledby_ids =
862       browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
863   if (labelledby_ids.size() == 1) {
864     BrowserAccessibility* titleElement =
865         browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
866     if (titleElement)
867       return titleElement->ToBrowserAccessibilityCocoa();
868   }
870   return nil;
873 - (NSURL*)url {
874   StringAttribute urlAttribute =
875       [[self role] isEqualToString:@"AXWebArea"] ?
876           ui::AX_ATTR_DOC_URL :
877           ui::AX_ATTR_URL;
879   std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute);
880   if (urlStr.empty())
881     return nil;
883   return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))];
886 - (id)value {
887   // WebCore uses an attachmentView to get the below behavior.
888   // We do not have any native views backing this object, so need
889   // to approximate Cocoa ax behavior best as we can.
890   NSString* role = [self role];
891   if ([role isEqualToString:@"AXHeading"]) {
892     int level = 0;
893     if (browserAccessibility_->GetIntAttribute(
894             ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) {
895       return [NSNumber numberWithInt:level];
896     }
897   } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
898     // AXValue does not make sense for pure buttons.
899     return @"";
900   } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) {
901     int value = 0;
902     bool isAriaPressedDefined;
903     bool isMixed;
904     value = browserAccessibility_->GetAriaTristate(
905         "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0;
907     if (isMixed)
908       value = 2;
910     return [NSNumber numberWithInt:value];
912   } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
913              [role isEqualToString:NSAccessibilityRadioButtonRole]) {
914     int value = 0;
915     value = GetState(
916         browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0;
917     value = GetState(
918         browserAccessibility_, ui::AX_STATE_SELECTED) ?
919             1 :
920             value;
922     if (browserAccessibility_->GetBoolAttribute(
923         ui::AX_ATTR_BUTTON_MIXED)) {
924       value = 2;
925     }
926     return [NSNumber numberWithInt:value];
927   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
928              [role isEqualToString:NSAccessibilitySliderRole] ||
929              [role isEqualToString:NSAccessibilityIncrementorRole] ||
930              [role isEqualToString:NSAccessibilityScrollBarRole]) {
931     float floatValue;
932     if (browserAccessibility_->GetFloatAttribute(
933             ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) {
934       return [NSNumber numberWithFloat:floatValue];
935     }
936   } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
937     int r = browserAccessibility_->GetIntAttribute(
938         ui::AX_ATTR_COLOR_VALUE_RED);
939     int g = browserAccessibility_->GetIntAttribute(
940         ui::AX_ATTR_COLOR_VALUE_GREEN);
941     int b = browserAccessibility_->GetIntAttribute(
942         ui::AX_ATTR_COLOR_VALUE_BLUE);
943     // This string matches the one returned by a native Mac color well.
944     return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
945                 r / 255., g / 255., b / 255.];
946   }
948   return NSStringForStringAttribute(
949       browserAccessibility_, ui::AX_ATTR_VALUE);
952 - (NSString*)valueDescription {
953   return NSStringForStringAttribute(
954       browserAccessibility_, ui::AX_ATTR_VALUE);
957 - (NSValue*)visibleCharacterRange {
958   std::string value = browserAccessibility_->GetStringAttribute(
959       ui::AX_ATTR_VALUE);
960   return [NSValue valueWithRange:NSMakeRange(0, value.size())];
963 - (NSArray*)visibleCells {
964   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
965   const std::vector<int32>& uniqueCellIds =
966       browserAccessibility_->GetIntListAttribute(
967           ui::AX_ATTR_UNIQUE_CELL_IDS);
968   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
969     int id = uniqueCellIds[i];
970     BrowserAccessibility* cell =
971         browserAccessibility_->manager()->GetFromID(id);
972     if (cell)
973       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
974   }
975   return ret;
978 - (NSArray*)visibleChildren {
979   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
980   uint32 childCount = browserAccessibility_->PlatformChildCount();
981   for (uint32 index = 0; index < childCount; ++index) {
982     BrowserAccessibilityCocoa* child =
983         browserAccessibility_->PlatformGetChild(index)->
984             ToBrowserAccessibilityCocoa();
985     [ret addObject:child];
986   }
987   return ret;
990 - (NSArray*)visibleColumns {
991   return [self columns];
994 - (NSArray*)visibleRows {
995   return [self rows];
998 - (NSNumber*)visited {
999   return [NSNumber numberWithBool:
1000       GetState(browserAccessibility_, ui::AX_STATE_VISITED)];
1003 - (id)window {
1004   if (!browserAccessibility_)
1005     return nil;
1007   BrowserAccessibilityManagerMac* manager =
1008       static_cast<BrowserAccessibilityManagerMac*>(
1009           browserAccessibility_->manager());
1010   return [manager->parent_view() window];
1013 - (NSString*)methodNameForAttribute:(NSString*)attribute {
1014   return [attributeToMethodNameMap objectForKey:attribute];
1017 - (void)swapChildren:(base::scoped_nsobject<NSMutableArray>*)other {
1018   children_.swap(*other);
1021 // Returns the accessibility value for the given attribute.  If the value isn't
1022 // supported this will return nil.
1023 - (id)accessibilityAttributeValue:(NSString*)attribute {
1024   if (!browserAccessibility_)
1025     return nil;
1027   SEL selector =
1028       NSSelectorFromString([self methodNameForAttribute:attribute]);
1029   if (selector)
1030     return [self performSelector:selector];
1032   // TODO(dtseng): refactor remaining attributes.
1033   int selStart, selEnd;
1034   if (browserAccessibility_->GetIntAttribute(
1035           ui::AX_ATTR_TEXT_SEL_START, &selStart) &&
1036       browserAccessibility_->
1037           GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) {
1038     if (selStart > selEnd)
1039       std::swap(selStart, selEnd);
1040     int selLength = selEnd - selStart;
1041     if ([attribute isEqualToString:
1042         NSAccessibilityInsertionPointLineNumberAttribute]) {
1043       const std::vector<int32>& line_breaks =
1044           browserAccessibility_->GetIntListAttribute(
1045               ui::AX_ATTR_LINE_BREAKS);
1046       for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1047         if (line_breaks[i] > selStart)
1048           return [NSNumber numberWithInt:i];
1049       }
1050       return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1051     }
1052     if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
1053       std::string value = browserAccessibility_->GetStringAttribute(
1054           ui::AX_ATTR_VALUE);
1055       return base::SysUTF8ToNSString(value.substr(selStart, selLength));
1056     }
1057     if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1058       return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
1059     }
1060   }
1061   return nil;
1064 // Returns the accessibility value for the given attribute and parameter. If the
1065 // value isn't supported this will return nil.
1066 - (id)accessibilityAttributeValue:(NSString*)attribute
1067                      forParameter:(id)parameter {
1068   if (!browserAccessibility_)
1069     return nil;
1071   const std::vector<int32>& line_breaks =
1072       browserAccessibility_->GetIntListAttribute(
1073           ui::AX_ATTR_LINE_BREAKS);
1074   std::string value = browserAccessibility_->GetStringAttribute(
1075       ui::AX_ATTR_VALUE);
1076   int len = static_cast<int>(value.size());
1078   if ([attribute isEqualToString:
1079       NSAccessibilityStringForRangeParameterizedAttribute]) {
1080     NSRange range = [(NSValue*)parameter rangeValue];
1081     std::string value = browserAccessibility_->GetStringAttribute(
1082         ui::AX_ATTR_VALUE);
1083     return base::SysUTF8ToNSString(value.substr(range.location, range.length));
1084   }
1086   if ([attribute isEqualToString:
1087       NSAccessibilityLineForIndexParameterizedAttribute]) {
1088     int index = [(NSNumber*)parameter intValue];
1089     for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1090       if (line_breaks[i] > index)
1091         return [NSNumber numberWithInt:i];
1092     }
1093     return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1094   }
1096   if ([attribute isEqualToString:
1097       NSAccessibilityRangeForLineParameterizedAttribute]) {
1098     int line_index = [(NSNumber*)parameter intValue];
1099     int line_count = static_cast<int>(line_breaks.size()) + 1;
1100     if (line_index < 0 || line_index >= line_count)
1101       return nil;
1102     int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
1103     int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
1104     return [NSValue valueWithRange:
1105         NSMakeRange(start, end - start)];
1106   }
1108   if ([attribute isEqualToString:
1109       NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
1110     if ([self internalRole] != ui::AX_ROLE_TABLE &&
1111         [self internalRole] != ui::AX_ROLE_GRID) {
1112       return nil;
1113     }
1114     if (![parameter isKindOfClass:[NSArray self]])
1115       return nil;
1116     NSArray* array = parameter;
1117     int column = [[array objectAtIndex:0] intValue];
1118     int row = [[array objectAtIndex:1] intValue];
1119     int num_columns = browserAccessibility_->GetIntAttribute(
1120         ui::AX_ATTR_TABLE_COLUMN_COUNT);
1121     int num_rows = browserAccessibility_->GetIntAttribute(
1122         ui::AX_ATTR_TABLE_ROW_COUNT);
1123     if (column < 0 || column >= num_columns ||
1124         row < 0 || row >= num_rows) {
1125       return nil;
1126     }
1127     for (size_t i = 0;
1128          i < browserAccessibility_->PlatformChildCount();
1129          ++i) {
1130       BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i);
1131       if (child->GetRole() != ui::AX_ROLE_ROW)
1132         continue;
1133       int rowIndex;
1134       if (!child->GetIntAttribute(
1135               ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) {
1136         continue;
1137       }
1138       if (rowIndex < row)
1139         continue;
1140       if (rowIndex > row)
1141         break;
1142       for (size_t j = 0;
1143            j < child->PlatformChildCount();
1144            ++j) {
1145         BrowserAccessibility* cell = child->PlatformGetChild(j);
1146         if (!browserAccessibility_->IsCellOrTableHeaderRole())
1147           continue;
1148         int colIndex;
1149         if (!cell->GetIntAttribute(
1150                 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
1151                 &colIndex)) {
1152           continue;
1153         }
1154         if (colIndex == column)
1155           return cell->ToBrowserAccessibilityCocoa();
1156         if (colIndex > column)
1157           break;
1158       }
1159     }
1160     return nil;
1161   }
1163   if ([attribute isEqualToString:
1164       NSAccessibilityBoundsForRangeParameterizedAttribute]) {
1165     if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT)
1166       return nil;
1167     NSRange range = [(NSValue*)parameter rangeValue];
1168     gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange(
1169         range.location, range.length);
1170     NSPoint origin = NSMakePoint(rect.x(), rect.y());
1171     NSSize size = NSMakeSize(rect.width(), rect.height());
1172     NSPoint pointInScreen = [self pointInScreen:origin size:size];
1173     NSRect nsrect = NSMakeRect(
1174         pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
1175     return [NSValue valueWithRect:nsrect];
1176   }
1178   // TODO(dtseng): support the following attributes.
1179   if ([attribute isEqualTo:
1180           NSAccessibilityRangeForPositionParameterizedAttribute] ||
1181       [attribute isEqualTo:
1182           NSAccessibilityRangeForIndexParameterizedAttribute] ||
1183       [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
1184       [attribute isEqualTo:
1185           NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
1186     return nil;
1187   }
1188   return nil;
1191 // Returns an array of parameterized attributes names that this object will
1192 // respond to.
1193 - (NSArray*)accessibilityParameterizedAttributeNames {
1194   if (!browserAccessibility_)
1195     return nil;
1197   if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
1198       [[self role] isEqualToString:NSAccessibilityGridRole]) {
1199     return [NSArray arrayWithObjects:
1200         NSAccessibilityCellForColumnAndRowParameterizedAttribute,
1201         nil];
1202   }
1203   if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1204       [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
1205     return [NSArray arrayWithObjects:
1206         NSAccessibilityLineForIndexParameterizedAttribute,
1207         NSAccessibilityRangeForLineParameterizedAttribute,
1208         NSAccessibilityStringForRangeParameterizedAttribute,
1209         NSAccessibilityRangeForPositionParameterizedAttribute,
1210         NSAccessibilityRangeForIndexParameterizedAttribute,
1211         NSAccessibilityBoundsForRangeParameterizedAttribute,
1212         NSAccessibilityRTFForRangeParameterizedAttribute,
1213         NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1214         NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1215         nil];
1216   }
1217   if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1218     return [NSArray arrayWithObjects:
1219         NSAccessibilityBoundsForRangeParameterizedAttribute,
1220         nil];
1221   }
1222   return nil;
1225 // Returns an array of action names that this object will respond to.
1226 - (NSArray*)accessibilityActionNames {
1227   if (!browserAccessibility_)
1228     return nil;
1230   NSMutableArray* ret =
1231       [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1232   NSString* role = [self role];
1233   // TODO(dtseng): this should only get set when there's a default action.
1234   if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1235       ![role isEqualToString:NSAccessibilityTextAreaRole] &&
1236       ![role isEqualToString:NSAccessibilityTextFieldRole]) {
1237     [ret addObject:NSAccessibilityPressAction];
1238   }
1240   return ret;
1243 // Returns a sub-array of values for the given attribute value, starting at
1244 // index, with up to maxCount items.  If the given index is out of bounds,
1245 // or there are no values for the given attribute, it will return nil.
1246 // This method is used for querying subsets of values, without having to
1247 // return a large set of data, such as elements with a large number of
1248 // children.
1249 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1250                                         index:(NSUInteger)index
1251                                      maxCount:(NSUInteger)maxCount {
1252   if (!browserAccessibility_)
1253     return nil;
1255   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1256   if (!fullArray)
1257     return nil;
1258   NSUInteger arrayCount = [fullArray count];
1259   if (index >= arrayCount)
1260     return nil;
1261   NSRange subRange;
1262   if ((index + maxCount) > arrayCount) {
1263     subRange = NSMakeRange(index, arrayCount - index);
1264   } else {
1265     subRange = NSMakeRange(index, maxCount);
1266   }
1267   return [fullArray subarrayWithRange:subRange];
1270 // Returns the count of the specified accessibility array attribute.
1271 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1272   if (!browserAccessibility_)
1273     return 0;
1275   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1276   return [fullArray count];
1279 // Returns the list of accessibility attributes that this object supports.
1280 - (NSArray*)accessibilityAttributeNames {
1281   if (!browserAccessibility_)
1282     return nil;
1284   // General attributes.
1285   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1286       NSAccessibilityChildrenAttribute,
1287       NSAccessibilityDescriptionAttribute,
1288       NSAccessibilityEnabledAttribute,
1289       NSAccessibilityFocusedAttribute,
1290       NSAccessibilityHelpAttribute,
1291       NSAccessibilityLinkedUIElementsAttribute,
1292       NSAccessibilityParentAttribute,
1293       NSAccessibilityPositionAttribute,
1294       NSAccessibilityRoleAttribute,
1295       NSAccessibilityRoleDescriptionAttribute,
1296       NSAccessibilitySizeAttribute,
1297       NSAccessibilitySubroleAttribute,
1298       NSAccessibilityTitleAttribute,
1299       NSAccessibilityTopLevelUIElementAttribute,
1300       NSAccessibilityValueAttribute,
1301       NSAccessibilityWindowAttribute,
1302       @"AXAccessKey",
1303       @"AXInvalid",
1304       @"AXVisited",
1305       nil];
1307   // Specific role attributes.
1308   NSString* role = [self role];
1309   NSString* subrole = [self subrole];
1310   if ([role isEqualToString:NSAccessibilityTableRole] ||
1311       [role isEqualToString:NSAccessibilityGridRole]) {
1312     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1313         NSAccessibilityColumnsAttribute,
1314         NSAccessibilityVisibleColumnsAttribute,
1315         NSAccessibilityRowsAttribute,
1316         NSAccessibilityVisibleRowsAttribute,
1317         NSAccessibilityVisibleCellsAttribute,
1318         NSAccessibilityHeaderAttribute,
1319         NSAccessibilityColumnHeaderUIElementsAttribute,
1320         NSAccessibilityRowHeaderUIElementsAttribute,
1321         nil]];
1322   } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1323     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1324         NSAccessibilityIndexAttribute,
1325         NSAccessibilityHeaderAttribute,
1326         NSAccessibilityRowsAttribute,
1327         NSAccessibilityVisibleRowsAttribute,
1328         nil]];
1329   } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1330     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1331         NSAccessibilityColumnIndexRangeAttribute,
1332         NSAccessibilityRowIndexRangeAttribute,
1333         @"AXSortDirection",
1334         nil]];
1335   } else if ([role isEqualToString:@"AXWebArea"]) {
1336     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1337         @"AXLoaded",
1338         @"AXLoadingProgress",
1339         nil]];
1340   } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
1341              [role isEqualToString:NSAccessibilityTextAreaRole]) {
1342     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1343         NSAccessibilityInsertionPointLineNumberAttribute,
1344         NSAccessibilityNumberOfCharactersAttribute,
1345         NSAccessibilitySelectedTextAttribute,
1346         NSAccessibilitySelectedTextRangeAttribute,
1347         NSAccessibilityVisibleCharacterRangeAttribute,
1348         nil]];
1349   } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1350     [ret addObject:NSAccessibilityTabsAttribute];
1351   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1352              [role isEqualToString:NSAccessibilitySliderRole] ||
1353              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1354              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1355     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1356         NSAccessibilityMaxValueAttribute,
1357         NSAccessibilityMinValueAttribute,
1358         NSAccessibilityValueDescriptionAttribute,
1359         nil]];
1360   } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1361     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1362         NSAccessibilityDisclosingAttribute,
1363         NSAccessibilityDisclosedByRowAttribute,
1364         NSAccessibilityDisclosureLevelAttribute,
1365         NSAccessibilityDisclosedRowsAttribute,
1366         nil]];
1367   } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1368     if (browserAccessibility_->GetParent()) {
1369       base::string16 parentRole;
1370       browserAccessibility_->GetParent()->GetHtmlAttribute(
1371           "role", &parentRole);
1372       const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1373       if (parentRole == treegridRole) {
1374         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1375             NSAccessibilityDisclosingAttribute,
1376             NSAccessibilityDisclosedByRowAttribute,
1377             NSAccessibilityDisclosureLevelAttribute,
1378             NSAccessibilityDisclosedRowsAttribute,
1379             nil]];
1380       } else {
1381         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1382             NSAccessibilityIndexAttribute,
1383             nil]];
1384       }
1385     }
1386   } else if ([role isEqualToString:NSAccessibilityListRole]) {
1387     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1388         NSAccessibilitySelectedChildrenAttribute,
1389         NSAccessibilityVisibleChildrenAttribute,
1390         nil]];
1391   }
1393   // Add the url attribute only if it has a valid url.
1394   if ([self url] != nil) {
1395     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1396         NSAccessibilityURLAttribute,
1397         nil]];
1398   }
1400   // Live regions.
1401   if (browserAccessibility_->HasStringAttribute(
1402           ui::AX_ATTR_LIVE_STATUS)) {
1403     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1404         @"AXARIALive",
1405         nil]];
1406   }
1407   if (browserAccessibility_->HasStringAttribute(
1408           ui::AX_ATTR_LIVE_RELEVANT)) {
1409     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1410         @"AXARIARelevant",
1411         nil]];
1412   }
1413   if (browserAccessibility_->HasBoolAttribute(
1414           ui::AX_ATTR_LIVE_ATOMIC)) {
1415     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1416         @"AXARIAAtomic",
1417         nil]];
1418   }
1419   if (browserAccessibility_->HasBoolAttribute(
1420           ui::AX_ATTR_LIVE_BUSY)) {
1421     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1422         @"AXARIABusy",
1423         nil]];
1424   }
1426   if (browserAccessibility_->HasStringAttribute(
1427           ui::AX_ATTR_DROPEFFECT)) {
1428     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1429         @"AXDropEffects",
1430         nil]];
1431   }
1433   // Add aria-grabbed attribute only if it has true.
1434   if (browserAccessibility_->HasBoolAttribute(ui::AX_ATTR_GRABBED)) {
1435     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1436         @"AXGrabbed",
1437         nil]];
1438   }
1440   // Add expanded attribute only if it has expanded or collapsed state.
1441   if (GetState(browserAccessibility_, ui::AX_STATE_EXPANDED) ||
1442         GetState(browserAccessibility_, ui::AX_STATE_COLLAPSED)) {
1443     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1444         NSAccessibilityExpandedAttribute,
1445         nil]];
1446   }
1448   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)
1449       || GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL)) {
1450     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1451         NSAccessibilityOrientationAttribute, nil]];
1452   }
1454   if (browserAccessibility_->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER)) {
1455     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1456         @"AXPlaceholder", nil]];
1457   }
1459   if (GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)) {
1460     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1461         @"AXRequired", nil]];
1462   }
1464   // Title UI Element.
1465   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1466       (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1467        browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1468                             .size() == 1)) {
1469     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1470          NSAccessibilityTitleUIElementAttribute,
1471          nil]];
1472   }
1473   // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1474   // for elements which are referred to by labelledby or are labels
1476   return ret;
1479 // Returns the index of the child in this objects array of children.
1480 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1481   if (!browserAccessibility_)
1482     return 0;
1484   NSUInteger index = 0;
1485   for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1486     if ([child isEqual:childToCheck])
1487       return index;
1488     ++index;
1489   }
1490   return NSNotFound;
1493 // Returns whether or not the specified attribute can be set by the
1494 // accessibility API via |accessibilitySetValue:forAttribute:|.
1495 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1496   if (!browserAccessibility_)
1497     return NO;
1499   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1500     return GetState(browserAccessibility_,
1501         ui::AX_STATE_FOCUSABLE);
1502   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1503     return browserAccessibility_->GetBoolAttribute(
1504         ui::AX_ATTR_CAN_SET_VALUE);
1505   }
1506   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1507       ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1508        [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1509     return YES;
1511   return NO;
1514 // Returns whether or not this object should be ignored in the accessibility
1515 // tree.
1516 - (BOOL)accessibilityIsIgnored {
1517   if (!browserAccessibility_)
1518     return true;
1520   return [self isIgnored];
1523 // Performs the given accessibility action on the webkit accessibility object
1524 // that backs this object.
1525 - (void)accessibilityPerformAction:(NSString*)action {
1526   if (!browserAccessibility_)
1527     return;
1529   // TODO(dmazzoni): Support more actions.
1530   if ([action isEqualToString:NSAccessibilityPressAction]) {
1531     [self delegate]->AccessibilityDoDefaultAction(
1532         browserAccessibility_->GetId());
1533   } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1534     NSPoint objOrigin = [self origin];
1535     NSSize size = [[self size] sizeValue];
1536     gfx::Point origin = [self delegate]->AccessibilityOriginInScreen(
1537         gfx::Rect(objOrigin.x, objOrigin.y, size.width, size.height));
1538     origin.Offset(size.width / 2, size.height / 2);
1539     [self delegate]->AccessibilityShowMenu(origin);
1540   }
1543 // Returns the description of the given action.
1544 - (NSString*)accessibilityActionDescription:(NSString*)action {
1545   if (!browserAccessibility_)
1546     return nil;
1548   return NSAccessibilityActionDescription(action);
1551 // Sets an override value for a specific accessibility attribute.
1552 // This class does not support this.
1553 - (BOOL)accessibilitySetOverrideValue:(id)value
1554                          forAttribute:(NSString*)attribute {
1555   return NO;
1558 // Sets the value for an accessibility attribute via the accessibility API.
1559 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1560   if (!browserAccessibility_)
1561     return;
1563   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1564     BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1565     NSNumber* focusedNumber = value;
1566     BOOL focused = [focusedNumber intValue];
1567     if (focused)
1568       manager->SetFocus(browserAccessibility_, true);
1569   }
1570   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1571     NSRange range = [(NSValue*)value rangeValue];
1572     [self delegate]->AccessibilitySetTextSelection(
1573         browserAccessibility_->GetId(),
1574         range.location, range.location + range.length);
1575   }
1578 // Returns the deepest accessibility child that should not be ignored.
1579 // It is assumed that the hit test has been narrowed down to this object
1580 // or one of its children, so this will never return nil unless this
1581 // object is invalid.
1582 - (id)accessibilityHitTest:(NSPoint)point {
1583   if (!browserAccessibility_)
1584     return nil;
1586   BrowserAccessibilityCocoa* hit = self;
1587   for (BrowserAccessibilityCocoa* child in [self children]) {
1588     if (!child->browserAccessibility_)
1589       continue;
1590     NSPoint origin = [child origin];
1591     NSSize size = [[child size] sizeValue];
1592     NSRect rect;
1593     rect.origin = origin;
1594     rect.size = size;
1595     if (NSPointInRect(point, rect)) {
1596       hit = child;
1597       id childResult = [child accessibilityHitTest:point];
1598       if (![childResult accessibilityIsIgnored]) {
1599         hit = childResult;
1600         break;
1601       }
1602     }
1603   }
1604   return NSAccessibilityUnignoredAncestor(hit);
1607 - (BOOL)isEqual:(id)object {
1608   if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1609     return NO;
1610   return ([self hash] == [object hash]);
1613 - (NSUInteger)hash {
1614   // Potentially called during dealloc.
1615   if (!browserAccessibility_)
1616     return [super hash];
1617   return browserAccessibility_->GetId();
1620 - (BOOL)accessibilityShouldUseUniqueId {
1621   return YES;
1624 @end