Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / browser / accessibility / browser_accessibility_cocoa.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <execinfo.h>
6
7 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
8
9 #include <map>
10
11 #include "base/basictypes.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "content/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.
23 extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
24
25 using ui::AXNodeData;
26 using content::BrowserAccessibility;
27 using content::BrowserAccessibilityManager;
28 using content::BrowserAccessibilityManagerMac;
29 using content::ContentClient;
30 typedef ui::AXStringAttribute StringAttribute;
31
32 namespace {
33
34 // Returns an autoreleased copy of the AXNodeData's attribute.
35 NSString* NSStringForStringAttribute(
36     BrowserAccessibility* browserAccessibility,
37     StringAttribute attribute) {
38   return base::SysUTF8ToNSString(
39       browserAccessibility->GetStringAttribute(attribute));
40 }
41
42 struct MapEntry {
43   ui::AXRole webKitValue;
44   NSString* nativeValue;
45 };
46
47 typedef 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.
51 bool GetState(BrowserAccessibility* accessibility, ui::AXState state) {
52   return ((accessibility->GetState() >> state) & 1);
53 }
54
55 RoleMap 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.
173 NSString* 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
184 RoleMap 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.
222 NSString* 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.
234 NSDictionary* 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:@"ul"] ||
851         [htmlTag isEqualToString:@"ol"]) {
852       return @"AXContentList";
853     } else if ([htmlTag isEqualToString:@"dl"]) {
854       return @"AXDescriptionList";
855     }
856   }
857
858   return NativeSubroleFromAXRole(browserAccessibilityRole);
859 }
860
861 // Returns all tabs in this subtree.
862 - (NSArray*)tabs {
863   NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
864
865   if ([self internalRole] == ui::AX_ROLE_TAB)
866     [tabSubtree addObject:self];
867
868   for (uint i=0; i < [[self children] count]; ++i) {
869     NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
870     if ([tabChildren count] > 0)
871       [tabSubtree addObjectsFromArray:tabChildren];
872   }
873
874   return tabSubtree;
875 }
876
877 - (NSString*)title {
878   return NSStringForStringAttribute(
879       browserAccessibility_, ui::AX_ATTR_NAME);
880 }
881
882 - (id)titleUIElement {
883   int titleElementId;
884   if (browserAccessibility_->GetIntAttribute(
885           ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
886     BrowserAccessibility* titleElement =
887         browserAccessibility_->manager()->GetFromID(titleElementId);
888     if (titleElement)
889       return titleElement->ToBrowserAccessibilityCocoa();
890   }
891   std::vector<int32> labelledby_ids =
892       browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
893   if (labelledby_ids.size() == 1) {
894     BrowserAccessibility* titleElement =
895         browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
896     if (titleElement)
897       return titleElement->ToBrowserAccessibilityCocoa();
898   }
899
900   return nil;
901 }
902
903 - (NSURL*)url {
904   StringAttribute urlAttribute =
905       [[self role] isEqualToString:@"AXWebArea"] ?
906           ui::AX_ATTR_DOC_URL :
907           ui::AX_ATTR_URL;
908
909   std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute);
910   if (urlStr.empty())
911     return nil;
912
913   return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))];
914 }
915
916 - (id)value {
917   // WebCore uses an attachmentView to get the below behavior.
918   // We do not have any native views backing this object, so need
919   // to approximate Cocoa ax behavior best as we can.
920   NSString* role = [self role];
921   if ([role isEqualToString:@"AXHeading"]) {
922     int level = 0;
923     if (browserAccessibility_->GetIntAttribute(
924             ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) {
925       return [NSNumber numberWithInt:level];
926     }
927   } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
928     // AXValue does not make sense for pure buttons.
929     return @"";
930   } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) {
931     int value = 0;
932     bool isAriaPressedDefined;
933     bool isMixed;
934     value = browserAccessibility_->GetAriaTristate(
935         "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0;
936
937     if (isMixed)
938       value = 2;
939
940     return [NSNumber numberWithInt:value];
941
942   } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
943              [role isEqualToString:NSAccessibilityRadioButtonRole]) {
944     int value = 0;
945     value = GetState(
946         browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0;
947     value = GetState(
948         browserAccessibility_, ui::AX_STATE_SELECTED) ?
949             1 :
950             value;
951
952     if (browserAccessibility_->GetBoolAttribute(
953         ui::AX_ATTR_BUTTON_MIXED)) {
954       value = 2;
955     }
956     return [NSNumber numberWithInt:value];
957   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
958              [role isEqualToString:NSAccessibilitySliderRole] ||
959              [role isEqualToString:NSAccessibilityScrollBarRole]) {
960     float floatValue;
961     if (browserAccessibility_->GetFloatAttribute(
962             ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) {
963       return [NSNumber numberWithFloat:floatValue];
964     }
965   } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
966     int r = browserAccessibility_->GetIntAttribute(
967         ui::AX_ATTR_COLOR_VALUE_RED);
968     int g = browserAccessibility_->GetIntAttribute(
969         ui::AX_ATTR_COLOR_VALUE_GREEN);
970     int b = browserAccessibility_->GetIntAttribute(
971         ui::AX_ATTR_COLOR_VALUE_BLUE);
972     // This string matches the one returned by a native Mac color well.
973     return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
974                 r / 255., g / 255., b / 255.];
975   }
976
977   return NSStringForStringAttribute(
978       browserAccessibility_, ui::AX_ATTR_VALUE);
979 }
980
981 - (NSString*)valueDescription {
982   return NSStringForStringAttribute(
983       browserAccessibility_, ui::AX_ATTR_VALUE);
984 }
985
986 - (NSValue*)visibleCharacterRange {
987   return [NSValue valueWithRange:
988       NSMakeRange(0, browserAccessibility_->value().length())];
989 }
990
991 - (NSArray*)visibleCells {
992   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
993   const std::vector<int32>& uniqueCellIds =
994       browserAccessibility_->GetIntListAttribute(
995           ui::AX_ATTR_UNIQUE_CELL_IDS);
996   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
997     int id = uniqueCellIds[i];
998     BrowserAccessibility* cell =
999         browserAccessibility_->manager()->GetFromID(id);
1000     if (cell)
1001       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
1002   }
1003   return ret;
1004 }
1005
1006 - (NSArray*)visibleColumns {
1007   return [self columns];
1008 }
1009
1010 - (NSArray*)visibleRows {
1011   return [self rows];
1012 }
1013
1014 - (NSNumber*)visited {
1015   return [NSNumber numberWithBool:
1016       GetState(browserAccessibility_, ui::AX_STATE_VISITED)];
1017 }
1018
1019 - (id)window {
1020   if (!browserAccessibility_)
1021     return nil;
1022
1023   BrowserAccessibilityManagerMac* manager =
1024       static_cast<BrowserAccessibilityManagerMac*>(
1025           browserAccessibility_->manager());
1026   return [manager->parent_view() window];
1027 }
1028
1029 - (NSString*)methodNameForAttribute:(NSString*)attribute {
1030   return [attributeToMethodNameMap objectForKey:attribute];
1031 }
1032
1033 // Returns the accessibility value for the given attribute.  If the value isn't
1034 // supported this will return nil.
1035 - (id)accessibilityAttributeValue:(NSString*)attribute {
1036   if (!browserAccessibility_)
1037     return nil;
1038
1039   SEL selector =
1040       NSSelectorFromString([self methodNameForAttribute:attribute]);
1041   if (selector)
1042     return [self performSelector:selector];
1043
1044   // TODO(dtseng): refactor remaining attributes.
1045   int selStart, selEnd;
1046   if (browserAccessibility_->GetIntAttribute(
1047           ui::AX_ATTR_TEXT_SEL_START, &selStart) &&
1048       browserAccessibility_->
1049           GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) {
1050     if (selStart > selEnd)
1051       std::swap(selStart, selEnd);
1052     int selLength = selEnd - selStart;
1053     if ([attribute isEqualToString:
1054         NSAccessibilityInsertionPointLineNumberAttribute]) {
1055       const std::vector<int32>& line_breaks =
1056           browserAccessibility_->GetIntListAttribute(
1057               ui::AX_ATTR_LINE_BREAKS);
1058       for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1059         if (line_breaks[i] > selStart)
1060           return [NSNumber numberWithInt:i];
1061       }
1062       return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1063     }
1064     if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
1065       std::string value = browserAccessibility_->GetStringAttribute(
1066           ui::AX_ATTR_VALUE);
1067       return base::SysUTF8ToNSString(value.substr(selStart, selLength));
1068     }
1069     if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1070       return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
1071     }
1072   }
1073   return nil;
1074 }
1075
1076 // Returns the accessibility value for the given attribute and parameter. If the
1077 // value isn't supported this will return nil.
1078 - (id)accessibilityAttributeValue:(NSString*)attribute
1079                      forParameter:(id)parameter {
1080   if (!browserAccessibility_)
1081     return nil;
1082
1083   const std::vector<int32>& line_breaks =
1084       browserAccessibility_->GetIntListAttribute(
1085           ui::AX_ATTR_LINE_BREAKS);
1086   int len = static_cast<int>(browserAccessibility_->value().size());
1087
1088   if ([attribute isEqualToString:
1089       NSAccessibilityStringForRangeParameterizedAttribute]) {
1090     NSRange range = [(NSValue*)parameter rangeValue];
1091     std::string value = browserAccessibility_->GetStringAttribute(
1092         ui::AX_ATTR_VALUE);
1093     return base::SysUTF8ToNSString(value.substr(range.location, range.length));
1094   }
1095
1096   if ([attribute isEqualToString:
1097       NSAccessibilityLineForIndexParameterizedAttribute]) {
1098     int index = [(NSNumber*)parameter intValue];
1099     for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1100       if (line_breaks[i] > index)
1101         return [NSNumber numberWithInt:i];
1102     }
1103     return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1104   }
1105
1106   if ([attribute isEqualToString:
1107       NSAccessibilityRangeForLineParameterizedAttribute]) {
1108     int line_index = [(NSNumber*)parameter intValue];
1109     int line_count = static_cast<int>(line_breaks.size()) + 1;
1110     if (line_index < 0 || line_index >= line_count)
1111       return nil;
1112     int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
1113     int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
1114     return [NSValue valueWithRange:
1115         NSMakeRange(start, end - start)];
1116   }
1117
1118   if ([attribute isEqualToString:
1119       NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
1120     if ([self internalRole] != ui::AX_ROLE_TABLE &&
1121         [self internalRole] != ui::AX_ROLE_GRID) {
1122       return nil;
1123     }
1124     if (![parameter isKindOfClass:[NSArray self]])
1125       return nil;
1126     NSArray* array = parameter;
1127     int column = [[array objectAtIndex:0] intValue];
1128     int row = [[array objectAtIndex:1] intValue];
1129     int num_columns = browserAccessibility_->GetIntAttribute(
1130         ui::AX_ATTR_TABLE_COLUMN_COUNT);
1131     int num_rows = browserAccessibility_->GetIntAttribute(
1132         ui::AX_ATTR_TABLE_ROW_COUNT);
1133     if (column < 0 || column >= num_columns ||
1134         row < 0 || row >= num_rows) {
1135       return nil;
1136     }
1137     for (size_t i = 0;
1138          i < browserAccessibility_->PlatformChildCount();
1139          ++i) {
1140       BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i);
1141       if (child->GetRole() != ui::AX_ROLE_ROW)
1142         continue;
1143       int rowIndex;
1144       if (!child->GetIntAttribute(
1145               ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) {
1146         continue;
1147       }
1148       if (rowIndex < row)
1149         continue;
1150       if (rowIndex > row)
1151         break;
1152       for (size_t j = 0;
1153            j < child->PlatformChildCount();
1154            ++j) {
1155         BrowserAccessibility* cell = child->PlatformGetChild(j);
1156         if (cell->GetRole() != ui::AX_ROLE_CELL)
1157           continue;
1158         int colIndex;
1159         if (!cell->GetIntAttribute(
1160                 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
1161                 &colIndex)) {
1162           continue;
1163         }
1164         if (colIndex == column)
1165           return cell->ToBrowserAccessibilityCocoa();
1166         if (colIndex > column)
1167           break;
1168       }
1169     }
1170     return nil;
1171   }
1172
1173   if ([attribute isEqualToString:
1174       NSAccessibilityBoundsForRangeParameterizedAttribute]) {
1175     if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT)
1176       return nil;
1177     NSRange range = [(NSValue*)parameter rangeValue];
1178     gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange(
1179         range.location, range.length);
1180     NSPoint origin = NSMakePoint(rect.x(), rect.y());
1181     NSSize size = NSMakeSize(rect.width(), rect.height());
1182     NSPoint pointInScreen = [self pointInScreen:origin size:size];
1183     NSRect nsrect = NSMakeRect(
1184         pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
1185     return [NSValue valueWithRect:nsrect];
1186   }
1187
1188   // TODO(dtseng): support the following attributes.
1189   if ([attribute isEqualTo:
1190           NSAccessibilityRangeForPositionParameterizedAttribute] ||
1191       [attribute isEqualTo:
1192           NSAccessibilityRangeForIndexParameterizedAttribute] ||
1193       [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
1194       [attribute isEqualTo:
1195           NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
1196     return nil;
1197   }
1198   return nil;
1199 }
1200
1201 // Returns an array of parameterized attributes names that this object will
1202 // respond to.
1203 - (NSArray*)accessibilityParameterizedAttributeNames {
1204   if (!browserAccessibility_)
1205     return nil;
1206
1207   if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
1208       [[self role] isEqualToString:NSAccessibilityGridRole]) {
1209     return [NSArray arrayWithObjects:
1210         NSAccessibilityCellForColumnAndRowParameterizedAttribute,
1211         nil];
1212   }
1213   if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1214       [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
1215     return [NSArray arrayWithObjects:
1216         NSAccessibilityLineForIndexParameterizedAttribute,
1217         NSAccessibilityRangeForLineParameterizedAttribute,
1218         NSAccessibilityStringForRangeParameterizedAttribute,
1219         NSAccessibilityRangeForPositionParameterizedAttribute,
1220         NSAccessibilityRangeForIndexParameterizedAttribute,
1221         NSAccessibilityBoundsForRangeParameterizedAttribute,
1222         NSAccessibilityRTFForRangeParameterizedAttribute,
1223         NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1224         NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1225         nil];
1226   }
1227   if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1228     return [NSArray arrayWithObjects:
1229         NSAccessibilityBoundsForRangeParameterizedAttribute,
1230         nil];
1231   }
1232   return nil;
1233 }
1234
1235 // Returns an array of action names that this object will respond to.
1236 - (NSArray*)accessibilityActionNames {
1237   if (!browserAccessibility_)
1238     return nil;
1239
1240   NSMutableArray* ret =
1241       [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1242   NSString* role = [self role];
1243   // TODO(dtseng): this should only get set when there's a default action.
1244   if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1245       ![role isEqualToString:NSAccessibilityTextAreaRole] &&
1246       ![role isEqualToString:NSAccessibilityTextFieldRole]) {
1247     [ret addObject:NSAccessibilityPressAction];
1248   }
1249
1250   return ret;
1251 }
1252
1253 // Returns a sub-array of values for the given attribute value, starting at
1254 // index, with up to maxCount items.  If the given index is out of bounds,
1255 // or there are no values for the given attribute, it will return nil.
1256 // This method is used for querying subsets of values, without having to
1257 // return a large set of data, such as elements with a large number of
1258 // children.
1259 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1260                                         index:(NSUInteger)index
1261                                      maxCount:(NSUInteger)maxCount {
1262   if (!browserAccessibility_)
1263     return nil;
1264
1265   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1266   if (!fullArray)
1267     return nil;
1268   NSUInteger arrayCount = [fullArray count];
1269   if (index >= arrayCount)
1270     return nil;
1271   NSRange subRange;
1272   if ((index + maxCount) > arrayCount) {
1273     subRange = NSMakeRange(index, arrayCount - index);
1274   } else {
1275     subRange = NSMakeRange(index, maxCount);
1276   }
1277   return [fullArray subarrayWithRange:subRange];
1278 }
1279
1280 // Returns the count of the specified accessibility array attribute.
1281 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1282   if (!browserAccessibility_)
1283     return nil;
1284
1285   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1286   return [fullArray count];
1287 }
1288
1289 // Returns the list of accessibility attributes that this object supports.
1290 - (NSArray*)accessibilityAttributeNames {
1291   if (!browserAccessibility_)
1292     return nil;
1293
1294   // General attributes.
1295   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1296       NSAccessibilityChildrenAttribute,
1297       NSAccessibilityDescriptionAttribute,
1298       NSAccessibilityEnabledAttribute,
1299       NSAccessibilityFocusedAttribute,
1300       NSAccessibilityHelpAttribute,
1301       NSAccessibilityLinkedUIElementsAttribute,
1302       NSAccessibilityParentAttribute,
1303       NSAccessibilityPositionAttribute,
1304       NSAccessibilityRoleAttribute,
1305       NSAccessibilityRoleDescriptionAttribute,
1306       NSAccessibilitySizeAttribute,
1307       NSAccessibilitySubroleAttribute,
1308       NSAccessibilityTitleAttribute,
1309       NSAccessibilityTopLevelUIElementAttribute,
1310       NSAccessibilityValueAttribute,
1311       NSAccessibilityWindowAttribute,
1312       @"AXAccessKey",
1313       @"AXInvalid",
1314       @"AXRequired",
1315       @"AXVisited",
1316       nil];
1317
1318   // Specific role attributes.
1319   NSString* role = [self role];
1320   NSString* subrole = [self subrole];
1321   if ([role isEqualToString:NSAccessibilityTableRole] ||
1322       [role isEqualToString:NSAccessibilityGridRole]) {
1323     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1324         NSAccessibilityColumnsAttribute,
1325         NSAccessibilityVisibleColumnsAttribute,
1326         NSAccessibilityRowsAttribute,
1327         NSAccessibilityVisibleRowsAttribute,
1328         NSAccessibilityVisibleCellsAttribute,
1329         NSAccessibilityHeaderAttribute,
1330         NSAccessibilityColumnHeaderUIElementsAttribute,
1331         NSAccessibilityRowHeaderUIElementsAttribute,
1332         nil]];
1333   } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1334     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1335         NSAccessibilityIndexAttribute,
1336         NSAccessibilityHeaderAttribute,
1337         NSAccessibilityRowsAttribute,
1338         NSAccessibilityVisibleRowsAttribute,
1339         nil]];
1340   } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1341     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1342         NSAccessibilityColumnIndexRangeAttribute,
1343         NSAccessibilityRowIndexRangeAttribute,
1344         nil]];
1345   } else if ([role isEqualToString:@"AXWebArea"]) {
1346     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1347         @"AXLoaded",
1348         @"AXLoadingProgress",
1349         nil]];
1350   } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
1351              [role isEqualToString:NSAccessibilityTextAreaRole]) {
1352     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1353         NSAccessibilityInsertionPointLineNumberAttribute,
1354         NSAccessibilityNumberOfCharactersAttribute,
1355         NSAccessibilitySelectedTextAttribute,
1356         NSAccessibilitySelectedTextRangeAttribute,
1357         NSAccessibilityVisibleCharacterRangeAttribute,
1358         nil]];
1359   } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1360     [ret addObject:NSAccessibilityTabsAttribute];
1361   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1362              [role isEqualToString:NSAccessibilitySliderRole] ||
1363              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1364     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1365         NSAccessibilityMaxValueAttribute,
1366         NSAccessibilityMinValueAttribute,
1367         NSAccessibilityOrientationAttribute,
1368         NSAccessibilityValueDescriptionAttribute,
1369         nil]];
1370   } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1371     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1372         NSAccessibilityDisclosingAttribute,
1373         NSAccessibilityDisclosedByRowAttribute,
1374         NSAccessibilityDisclosureLevelAttribute,
1375         NSAccessibilityDisclosedRowsAttribute,
1376         nil]];
1377   } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1378     if (browserAccessibility_->GetParent()) {
1379       base::string16 parentRole;
1380       browserAccessibility_->GetParent()->GetHtmlAttribute(
1381           "role", &parentRole);
1382       const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1383       if (parentRole == treegridRole) {
1384         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1385             NSAccessibilityDisclosingAttribute,
1386             NSAccessibilityDisclosedByRowAttribute,
1387             NSAccessibilityDisclosureLevelAttribute,
1388             NSAccessibilityDisclosedRowsAttribute,
1389             nil]];
1390       } else {
1391         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1392             NSAccessibilityIndexAttribute,
1393             nil]];
1394       }
1395     }
1396   }
1397
1398   // Add the url attribute only if it has a valid url.
1399   if ([self url] != nil) {
1400     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1401         NSAccessibilityURLAttribute,
1402         nil]];
1403   }
1404
1405   // Live regions.
1406   if (browserAccessibility_->HasStringAttribute(
1407           ui::AX_ATTR_LIVE_STATUS)) {
1408     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1409         @"AXARIALive",
1410         @"AXARIARelevant",
1411         nil]];
1412   }
1413   if (browserAccessibility_->HasStringAttribute(
1414           ui::AX_ATTR_CONTAINER_LIVE_STATUS)) {
1415     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1416         @"AXARIAAtomic",
1417         @"AXARIABusy",
1418         nil]];
1419   }
1420
1421   // Title UI Element.
1422   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1423       (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1424        browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1425                             .size() == 1)) {
1426     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1427          NSAccessibilityTitleUIElementAttribute,
1428          nil]];
1429   }
1430   // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1431   // for elements which are referred to by labelledby or are labels
1432
1433   return ret;
1434 }
1435
1436 // Returns the index of the child in this objects array of children.
1437 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1438   if (!browserAccessibility_)
1439     return nil;
1440
1441   NSUInteger index = 0;
1442   for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1443     if ([child isEqual:childToCheck])
1444       return index;
1445     ++index;
1446   }
1447   return NSNotFound;
1448 }
1449
1450 // Returns whether or not the specified attribute can be set by the
1451 // accessibility API via |accessibilitySetValue:forAttribute:|.
1452 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1453   if (!browserAccessibility_)
1454     return nil;
1455
1456   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1457     return GetState(browserAccessibility_,
1458         ui::AX_STATE_FOCUSABLE);
1459   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1460     return browserAccessibility_->GetBoolAttribute(
1461         ui::AX_ATTR_CAN_SET_VALUE);
1462   }
1463   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1464       ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1465        [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1466     return YES;
1467
1468   return NO;
1469 }
1470
1471 // Returns whether or not this object should be ignored in the accessibilty
1472 // tree.
1473 - (BOOL)accessibilityIsIgnored {
1474   if (!browserAccessibility_)
1475     return true;
1476
1477   return [self isIgnored];
1478 }
1479
1480 // Performs the given accessibilty action on the webkit accessibility object
1481 // that backs this object.
1482 - (void)accessibilityPerformAction:(NSString*)action {
1483   if (!browserAccessibility_)
1484     return;
1485
1486   // TODO(dmazzoni): Support more actions.
1487   if ([action isEqualToString:NSAccessibilityPressAction]) {
1488     [self delegate]->AccessibilityDoDefaultAction(
1489         browserAccessibility_->GetId());
1490   } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1491     [self delegate]->AccessibilityShowMenu(browserAccessibility_->GetId());
1492   }
1493 }
1494
1495 // Returns the description of the given action.
1496 - (NSString*)accessibilityActionDescription:(NSString*)action {
1497   if (!browserAccessibility_)
1498     return nil;
1499
1500   return NSAccessibilityActionDescription(action);
1501 }
1502
1503 // Sets an override value for a specific accessibility attribute.
1504 // This class does not support this.
1505 - (BOOL)accessibilitySetOverrideValue:(id)value
1506                          forAttribute:(NSString*)attribute {
1507   return NO;
1508 }
1509
1510 // Sets the value for an accessibility attribute via the accessibility API.
1511 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1512   if (!browserAccessibility_)
1513     return;
1514
1515   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1516     NSNumber* focusedNumber = value;
1517     BOOL focused = [focusedNumber intValue];
1518     if (focused)
1519       [self delegate]->AccessibilitySetFocus(browserAccessibility_->GetId());
1520   }
1521   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1522     NSRange range = [(NSValue*)value rangeValue];
1523     [self delegate]->AccessibilitySetTextSelection(
1524         browserAccessibility_->GetId(),
1525         range.location, range.location + range.length);
1526   }
1527 }
1528
1529 // Returns the deepest accessibility child that should not be ignored.
1530 // It is assumed that the hit test has been narrowed down to this object
1531 // or one of its children, so this will never return nil unless this
1532 // object is invalid.
1533 - (id)accessibilityHitTest:(NSPoint)point {
1534   if (!browserAccessibility_)
1535     return nil;
1536
1537   BrowserAccessibilityCocoa* hit = self;
1538   for (BrowserAccessibilityCocoa* child in [self children]) {
1539     if (!child->browserAccessibility_)
1540       continue;
1541     NSPoint origin = [child origin];
1542     NSSize size = [[child size] sizeValue];
1543     NSRect rect;
1544     rect.origin = origin;
1545     rect.size = size;
1546     if (NSPointInRect(point, rect)) {
1547       hit = child;
1548       id childResult = [child accessibilityHitTest:point];
1549       if (![childResult accessibilityIsIgnored]) {
1550         hit = childResult;
1551         break;
1552       }
1553     }
1554   }
1555   return NSAccessibilityUnignoredAncestor(hit);
1556 }
1557
1558 - (BOOL)isEqual:(id)object {
1559   if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1560     return NO;
1561   return ([self hash] == [object hash]);
1562 }
1563
1564 - (NSUInteger)hash {
1565   // Potentially called during dealloc.
1566   if (!browserAccessibility_)
1567     return [super hash];
1568   return browserAccessibility_->GetId();
1569 }
1570
1571 - (BOOL)accessibilityShouldUseUniqueId {
1572   return YES;
1573 }
1574
1575 @end
1576