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