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