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