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