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.
24extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
25
26using ui::AXNodeData;
27using content::BrowserAccessibility;
28using content::BrowserAccessibilityManager;
29using content::BrowserAccessibilityManagerMac;
30using content::ContentClient;
31typedef ui::AXStringAttribute StringAttribute;
32
33namespace {
34
35// Returns an autoreleased copy of the AXNodeData's attribute.
36NSString* 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.
45bool 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.
50NSDictionary* 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
1461