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