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