Use String16 when computing substrings to handle multibyte correctly.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_cocoa.mm
blobc76d2c4cf20fa52ae6d5b30131c6b975af26b745
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.
5 #include <execinfo.h>
7 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
9 #include <map>
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/app/strings/grit/content_strings.h"
16 #include "content/browser/accessibility/browser_accessibility_manager.h"
17 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
18 #include "content/browser/accessibility/one_shot_accessibility_tree_search.h"
19 #include "content/public/common/content_client.h"
20 #import "ui/accessibility/platform/ax_platform_node_mac.h"
22 // See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5,
23 // 10.6, and 10.7. It allows accessibility clients to observe events posted on
24 // this object.
25 extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
27 using ui::AXNodeData;
28 using content::AccessibilityMatchPredicate;
29 using content::BrowserAccessibility;
30 using content::BrowserAccessibilityDelegate;
31 using content::BrowserAccessibilityManager;
32 using content::BrowserAccessibilityManagerMac;
33 using content::ContentClient;
34 using content::OneShotAccessibilityTreeSearch;
35 typedef ui::AXStringAttribute StringAttribute;
37 namespace {
39 // VoiceOver uses -1 to mean "no limit" for AXResultsLimit.
40 const int kAXResultsLimitNoLimit = -1;
42 // Returns an autoreleased copy of the AXNodeData's attribute.
43 NSString* NSStringForStringAttribute(
44     BrowserAccessibility* browserAccessibility,
45     StringAttribute attribute) {
46   return base::SysUTF8ToNSString(
47       browserAccessibility->GetStringAttribute(attribute));
50 // GetState checks the bitmask used in AXNodeData to check
51 // if the given state was set on the accessibility object.
52 bool GetState(BrowserAccessibility* accessibility, ui::AXState state) {
53   return ((accessibility->GetState() >> state) & 1);
56 // A mapping from an accessibility attribute to its method name.
57 NSDictionary* attributeToMethodNameMap = nil;
59 // Given a search key provided to AXUIElementCountForSearchPredicate or
60 // AXUIElementsForSearchPredicate, return a predicate that can be added
61 // to OneShotAccessibilityTreeSearch.
62 AccessibilityMatchPredicate PredicateForSearchKey(NSString* searchKey) {
63   if ([searchKey isEqualToString:@"AXAnyTypeSearchKey"]) {
64     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
65       return true;
66     };
67   } else if ([searchKey isEqualToString:@"AXBlockquoteSameLevelSearchKey"] ||
68              [searchKey isEqualToString:@"AXBlockquoteSearchKey"]) {
69     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
70       // TODO(dmazzoni): implement the "same level" part.
71       return current->GetRole() == ui::AX_ROLE_BLOCKQUOTE;
72     };
73   } else if ([searchKey isEqualToString:@"AXBoldFontSearchKey"]) {
74     // TODO(dmazzoni): implement this.
75     return nullptr;
76   } else if ([searchKey isEqualToString:@"AXButtonSearchKey"]) {
77     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
78       return (current->GetRole() == ui::AX_ROLE_BUTTON ||
79               current->GetRole() == ui::AX_ROLE_MENU_BUTTON ||
80               current->GetRole() == ui::AX_ROLE_POP_UP_BUTTON ||
81               current->GetRole() == ui::AX_ROLE_SWITCH ||
82               current->GetRole() == ui::AX_ROLE_TOGGLE_BUTTON);
83     };
84   } else if ([searchKey isEqualToString:@"AXCheckBoxSearchKey"]) {
85     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
86       return (current->GetRole() == ui::AX_ROLE_CHECK_BOX ||
87               current->GetRole() == ui::AX_ROLE_MENU_ITEM_CHECK_BOX);
88     };
89   } else if ([searchKey isEqualToString:@"AXControlSearchKey"]) {
90     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
91       if (current->IsControl())
92         return true;
93       if (current->HasState(ui::AX_STATE_FOCUSABLE) &&
94           current->GetRole() != ui::AX_ROLE_IMAGE_MAP_LINK &&
95           current->GetRole() != ui::AX_ROLE_LINK) {
96         return true;
97       }
98       return false;
99     };
100   } else if ([searchKey isEqualToString:@"AXDifferentTypeSearchKey"]) {
101     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
102       return current->GetRole() != start->GetRole();
103     };
104   } else if ([searchKey isEqualToString:@"AXFontChangeSearchKey"]) {
105     // TODO(dmazzoni): implement this.
106     return nullptr;
107   } else if ([searchKey isEqualToString:@"AXFontColorChangeSearchKey"]) {
108     // TODO(dmazzoni): implement this.
109     return nullptr;
110   } else if ([searchKey isEqualToString:@"AXFrameSearchKey"]) {
111     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
112       if (current->IsWebAreaForPresentationalIframe())
113         return false;
114       if (!current->GetParent())
115         return false;
116       return (current->GetRole() == ui::AX_ROLE_WEB_AREA ||
117               current->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA);
118     };
119   } else if ([searchKey isEqualToString:@"AXGraphicSearchKey"]) {
120     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
121       return current->GetRole() == ui::AX_ROLE_IMAGE;
122     };
123   } else if ([searchKey isEqualToString:@"AXHeadingLevel1SearchKey"]) {
124     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
125       return (current->GetRole() == ui::AX_ROLE_HEADING &&
126               current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 1);
127     };
128   } else if ([searchKey isEqualToString:@"AXHeadingLevel2SearchKey"]) {
129     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
130       return (current->GetRole() == ui::AX_ROLE_HEADING &&
131               current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 2);
132     };
133   } else if ([searchKey isEqualToString:@"AXHeadingLevel3SearchKey"]) {
134     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
135       return (current->GetRole() == ui::AX_ROLE_HEADING &&
136               current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 3);
137     };
138   } else if ([searchKey isEqualToString:@"AXHeadingLevel4SearchKey"]) {
139     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
140       return (current->GetRole() == ui::AX_ROLE_HEADING &&
141               current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 4);
142     };
143   } else if ([searchKey isEqualToString:@"AXHeadingLevel5SearchKey"]) {
144     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
145       return (current->GetRole() == ui::AX_ROLE_HEADING &&
146               current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 5);
147     };
148   } else if ([searchKey isEqualToString:@"AXHeadingLevel6SearchKey"]) {
149     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
150       return (current->GetRole() == ui::AX_ROLE_HEADING &&
151               current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 6);
152     };
153   } else if ([searchKey isEqualToString:@"AXHeadingSameLevelSearchKey"]) {
154     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
155       return (current->GetRole() == ui::AX_ROLE_HEADING &&
156               start->GetRole() == ui::AX_ROLE_HEADING &&
157               (current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) ==
158                start->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL)));
159     };
160   } else if ([searchKey isEqualToString:@"AXHeadingSearchKey"]) {
161     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
162       return current->GetRole() == ui::AX_ROLE_HEADING;
163     };
164   } else if ([searchKey isEqualToString:@"AXHighlightedSearchKey"]) {
165     // TODO(dmazzoni): implement this.
166     return nullptr;
167   } else if ([searchKey isEqualToString:@"AXItalicFontSearchKey"]) {
168     // TODO(dmazzoni): implement this.
169     return nullptr;
170   } else if ([searchKey isEqualToString:@"AXLandmarkSearchKey"]) {
171     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
172       return (current->GetRole() == ui::AX_ROLE_APPLICATION ||
173               current->GetRole() == ui::AX_ROLE_BANNER ||
174               current->GetRole() == ui::AX_ROLE_COMPLEMENTARY ||
175               current->GetRole() == ui::AX_ROLE_CONTENT_INFO ||
176               current->GetRole() == ui::AX_ROLE_FORM ||
177               current->GetRole() == ui::AX_ROLE_MAIN ||
178               current->GetRole() == ui::AX_ROLE_NAVIGATION ||
179               current->GetRole() == ui::AX_ROLE_SEARCH);
180     };
181   } else if ([searchKey isEqualToString:@"AXLinkSearchKey"]) {
182     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
183       return current->GetRole() == ui::AX_ROLE_LINK;
184     };
185   } else if ([searchKey isEqualToString:@"AXListSearchKey"]) {
186     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
187       return current->GetRole() == ui::AX_ROLE_LIST;
188     };
189   } else if ([searchKey isEqualToString:@"AXLiveRegionSearchKey"]) {
190     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
191       return current->HasStringAttribute(ui::AX_ATTR_LIVE_STATUS);
192     };
193   } else if ([searchKey isEqualToString:@"AXMisspelledWordSearchKey"]) {
194     // TODO(dmazzoni): implement this.
195     return nullptr;
196   } else if ([searchKey isEqualToString:@"AXOutlineSearchKey"]) {
197     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
198       return current->GetRole() == ui::AX_ROLE_TREE;
199     };
200   } else if ([searchKey isEqualToString:@"AXPlainTextSearchKey"]) {
201     // TODO(dmazzoni): implement this.
202     return nullptr;
203   } else if ([searchKey isEqualToString:@"AXRadioGroupSearchKey"]) {
204     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
205       return current->GetRole() == ui::AX_ROLE_RADIO_GROUP;
206     };
207   } else if ([searchKey isEqualToString:@"AXSameTypeSearchKey"]) {
208     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
209       return current->GetRole() == start->GetRole();
210     };
211   } else if ([searchKey isEqualToString:@"AXStaticTextSearchKey"]) {
212     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
213       return current->GetRole() == ui::AX_ROLE_STATIC_TEXT;
214     };
215   } else if ([searchKey isEqualToString:@"AXStyleChangeSearchKey"]) {
216     // TODO(dmazzoni): implement this.
217     return nullptr;
218   } else if ([searchKey isEqualToString:@"AXTableSameLevelSearchKey"]) {
219     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
220       // TODO(dmazzoni): implement the "same level" part.
221       return current->GetRole() == ui::AX_ROLE_GRID ||
222              current->GetRole() == ui::AX_ROLE_TABLE;
223     };
224   } else if ([searchKey isEqualToString:@"AXTableSearchKey"]) {
225     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
226       return current->GetRole() == ui::AX_ROLE_GRID ||
227              current->GetRole() == ui::AX_ROLE_TABLE;
228     };
229   } else if ([searchKey isEqualToString:@"AXTextFieldSearchKey"]) {
230     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
231       return current->GetRole() == ui::AX_ROLE_TEXT_FIELD;
232     };
233   } else if ([searchKey isEqualToString:@"AXUnderlineSearchKey"]) {
234     // TODO(dmazzoni): implement this.
235     return nullptr;
236   } else if ([searchKey isEqualToString:@"AXUnvisitedLinkSearchKey"]) {
237     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
238       return (current->GetRole() == ui::AX_ROLE_LINK &&
239               !current->HasState(ui::AX_STATE_VISITED));
240     };
241   } else if ([searchKey isEqualToString:@"AXVisitedLinkSearchKey"]) {
242     return [](BrowserAccessibility* start, BrowserAccessibility* current) {
243       return (current->GetRole() == ui::AX_ROLE_LINK &&
244               current->HasState(ui::AX_STATE_VISITED));
245     };
246   }
248   return nullptr;
251 // Initialize a OneShotAccessibilityTreeSearch object given the parameters
252 // passed to AXUIElementCountForSearchPredicate or
253 // AXUIElementsForSearchPredicate. Return true on success.
254 bool InitializeAccessibilityTreeSearch(
255     OneShotAccessibilityTreeSearch* search,
256     id parameter) {
257   if (![parameter isKindOfClass:[NSDictionary class]])
258     return false;
259   NSDictionary* dictionary = parameter;
261   id startElementParameter = [dictionary objectForKey:@"AXStartElement"];
262   BrowserAccessibility* startNode = nullptr;
263   if ([startElementParameter isKindOfClass:[BrowserAccessibilityCocoa class]]) {
264     BrowserAccessibilityCocoa* startNodeCocoa =
265         (BrowserAccessibilityCocoa*)startElementParameter;
266     startNode = [startNodeCocoa browserAccessibility];
267   }
269   bool immediateDescendantsOnly = false;
270   NSNumber *immediateDescendantsOnlyParameter =
271       [dictionary objectForKey:@"AXImmediateDescendantsOnly"];
272   if ([immediateDescendantsOnlyParameter isKindOfClass:[NSNumber class]])
273     immediateDescendantsOnly = [immediateDescendantsOnlyParameter boolValue];
275   bool visibleOnly = false;
276   NSNumber *visibleOnlyParameter = [dictionary objectForKey:@"AXVisibleOnly"];
277   if ([visibleOnlyParameter isKindOfClass:[NSNumber class]])
278     visibleOnly = [visibleOnlyParameter boolValue];
280   content::OneShotAccessibilityTreeSearch::Direction direction =
281       content::OneShotAccessibilityTreeSearch::FORWARDS;
282   NSString* directionParameter = [dictionary objectForKey:@"AXDirection"];
283   if ([directionParameter isKindOfClass:[NSString class]]) {
284     if ([directionParameter isEqualToString:@"AXDirectionNext"])
285       direction = content::OneShotAccessibilityTreeSearch::FORWARDS;
286     else if ([directionParameter isEqualToString:@"AXDirectionPrevious"])
287       direction = content::OneShotAccessibilityTreeSearch::BACKWARDS;
288   }
290   int resultsLimit = kAXResultsLimitNoLimit;
291   NSNumber* resultsLimitParameter = [dictionary objectForKey:@"AXResultsLimit"];
292   if ([resultsLimitParameter isKindOfClass:[NSNumber class]])
293     resultsLimit = [resultsLimitParameter intValue];
295   std::string searchText;
296   NSString* searchTextParameter = [dictionary objectForKey:@"AXSearchText"];
297   if ([searchTextParameter isKindOfClass:[NSString class]])
298     searchText = base::SysNSStringToUTF8(searchTextParameter);
300   search->SetStartNode(startNode);
301   search->SetDirection(direction);
302   search->SetImmediateDescendantsOnly(immediateDescendantsOnly);
303   search->SetVisibleOnly(visibleOnly);
304   search->SetSearchText(searchText);
306   // Mac uses resultsLimit == -1 for unlimited, that that's
307   // the default for OneShotAccessibilityTreeSearch already.
308   // Only set the results limit if it's nonnegative.
309   if (resultsLimit >= 0)
310     search->SetResultLimit(resultsLimit);
312   id searchKey = [dictionary objectForKey:@"AXSearchKey"];
313   if ([searchKey isKindOfClass:[NSString class]]) {
314     AccessibilityMatchPredicate predicate =
315         PredicateForSearchKey((NSString*)searchKey);
316     if (predicate)
317       search->AddPredicate(predicate);
318   } else if ([searchKey isKindOfClass:[NSArray class]]) {
319     size_t searchKeyCount = static_cast<size_t>([searchKey count]);
320     for (size_t i = 0; i < searchKeyCount; ++i) {
321       id key = [searchKey objectAtIndex:i];
322       if ([key isKindOfClass:[NSString class]]) {
323         AccessibilityMatchPredicate predicate =
324             PredicateForSearchKey((NSString*)key);
325         if (predicate)
326           search->AddPredicate(predicate);
327       }
328     }
329   }
331   return true;
334 } // namespace
336 @implementation BrowserAccessibilityCocoa
338 + (void)initialize {
339   const struct {
340     NSString* attribute;
341     NSString* methodName;
342   } attributeToMethodNameContainer[] = {
343     { NSAccessibilityChildrenAttribute, @"children" },
344     { NSAccessibilityColumnsAttribute, @"columns" },
345     { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" },
346     { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" },
347     { NSAccessibilityContentsAttribute, @"contents" },
348     { NSAccessibilityDescriptionAttribute, @"description" },
349     { NSAccessibilityDisclosingAttribute, @"disclosing" },
350     { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" },
351     { NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel" },
352     { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" },
353     { NSAccessibilityEnabledAttribute, @"enabled" },
354     { NSAccessibilityExpandedAttribute, @"expanded" },
355     { NSAccessibilityFocusedAttribute, @"focused" },
356     { NSAccessibilityHeaderAttribute, @"header" },
357     { NSAccessibilityHelpAttribute, @"help" },
358     { NSAccessibilityIndexAttribute, @"index" },
359     { NSAccessibilityLinkedUIElementsAttribute, @"linkedUIElements" },
360     { NSAccessibilityMaxValueAttribute, @"maxValue" },
361     { NSAccessibilityMinValueAttribute, @"minValue" },
362     { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" },
363     { NSAccessibilityOrientationAttribute, @"orientation" },
364     { NSAccessibilityParentAttribute, @"parent" },
365     { NSAccessibilityPositionAttribute, @"position" },
366     { NSAccessibilityRoleAttribute, @"role" },
367     { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" },
368     { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" },
369     { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" },
370     { NSAccessibilityRowsAttribute, @"rows" },
371     // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
372     { NSAccessibilitySelectedChildrenAttribute, @"selectedChildren" },
373     { NSAccessibilitySizeAttribute, @"size" },
374     { NSAccessibilitySubroleAttribute, @"subrole" },
375     { NSAccessibilityTabsAttribute, @"tabs" },
376     { NSAccessibilityTitleAttribute, @"title" },
377     { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" },
378     { NSAccessibilityTopLevelUIElementAttribute, @"window" },
379     { NSAccessibilityURLAttribute, @"url" },
380     { NSAccessibilityValueAttribute, @"value" },
381     { NSAccessibilityValueDescriptionAttribute, @"valueDescription" },
382     { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" },
383     { NSAccessibilityVisibleCellsAttribute, @"visibleCells" },
384     { NSAccessibilityVisibleChildrenAttribute, @"visibleChildren" },
385     { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" },
386     { NSAccessibilityVisibleRowsAttribute, @"visibleRows" },
387     { NSAccessibilityWindowAttribute, @"window" },
388     { @"AXAccessKey", @"accessKey" },
389     { @"AXARIAAtomic", @"ariaAtomic" },
390     { @"AXARIABusy", @"ariaBusy" },
391     { @"AXARIALive", @"ariaLive" },
392     { @"AXARIASetSize", @"ariaSetSize" },
393     { @"AXARIAPosInSet", @"ariaPosInSet" },
394     { @"AXARIARelevant", @"ariaRelevant" },
395     { @"AXDropEffects", @"dropeffect" },
396     { @"AXGrabbed", @"grabbed" },
397     { @"AXInvalid", @"invalid" },
398     { @"AXLoaded", @"loaded" },
399     { @"AXLoadingProgress", @"loadingProgress" },
400     { @"AXPlaceholder", @"placeholder" },
401     { @"AXRequired", @"required" },
402     { @"AXSortDirection", @"sortDirection" },
403     { @"AXVisited", @"visited" },
404   };
406   NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
407   const size_t numAttributes = sizeof(attributeToMethodNameContainer) /
408                                sizeof(attributeToMethodNameContainer[0]);
409   for (size_t i = 0; i < numAttributes; ++i) {
410     [dict setObject:attributeToMethodNameContainer[i].methodName
411              forKey:attributeToMethodNameContainer[i].attribute];
412   }
413   attributeToMethodNameMap = dict;
414   dict = nil;
417 - (id)initWithObject:(BrowserAccessibility*)accessibility {
418   if ((self = [super init]))
419     browserAccessibility_ = accessibility;
420   return self;
423 - (void)detach {
424   if (browserAccessibility_) {
425     NSAccessibilityUnregisterUniqueIdForUIElement(self);
426     browserAccessibility_ = NULL;
427   }
430 - (NSString*)accessKey {
431   return NSStringForStringAttribute(
432       browserAccessibility_, ui::AX_ATTR_ACCESS_KEY);
435 - (NSNumber*)ariaAtomic {
436   bool boolValue = browserAccessibility_->GetBoolAttribute(
437       ui::AX_ATTR_LIVE_ATOMIC);
438   return [NSNumber numberWithBool:boolValue];
441 - (NSNumber*)ariaBusy {
442   return [NSNumber numberWithBool:
443       GetState(browserAccessibility_, ui::AX_STATE_BUSY)];
446 - (NSString*)ariaLive {
447   return NSStringForStringAttribute(
448       browserAccessibility_, ui::AX_ATTR_LIVE_STATUS);
451 - (NSString*)ariaRelevant {
452   return NSStringForStringAttribute(
453       browserAccessibility_, ui::AX_ATTR_LIVE_RELEVANT);
456 - (NSNumber*)ariaPosInSet {
457   return [NSNumber numberWithInt:
458       browserAccessibility_->GetIntAttribute(ui::AX_ATTR_POS_IN_SET)];
461 - (NSNumber*)ariaSetSize {
462   return [NSNumber numberWithInt:
463       browserAccessibility_->GetIntAttribute(ui::AX_ATTR_SET_SIZE)];
466 // Returns an array of BrowserAccessibilityCocoa objects, representing the
467 // accessibility children of this object.
468 - (NSArray*)children {
469   if (!children_) {
470     uint32 childCount = browserAccessibility_->PlatformChildCount();
471     children_.reset([[NSMutableArray alloc] initWithCapacity:childCount]);
472     for (uint32 index = 0; index < childCount; ++index) {
473       BrowserAccessibilityCocoa* child =
474           browserAccessibility_->PlatformGetChild(index)->
475               ToBrowserAccessibilityCocoa();
476       if ([child isIgnored])
477         [children_ addObjectsFromArray:[child children]];
478       else
479         [children_ addObject:child];
480     }
482     // Also, add indirect children (if any).
483     const std::vector<int32>& indirectChildIds =
484         browserAccessibility_->GetIntListAttribute(
485             ui::AX_ATTR_INDIRECT_CHILD_IDS);
486     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
487       int32 child_id = indirectChildIds[i];
488       BrowserAccessibility* child =
489           browserAccessibility_->manager()->GetFromID(child_id);
491       // This only became necessary as a result of crbug.com/93095. It should be
492       // a DCHECK in the future.
493       if (child) {
494         BrowserAccessibilityCocoa* child_cocoa =
495             child->ToBrowserAccessibilityCocoa();
496         [children_ addObject:child_cocoa];
497       }
498     }
499   }
500   return children_;
503 - (void)childrenChanged {
504   if (![self isIgnored]) {
505     children_.reset();
506   } else {
507     [browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa()
508        childrenChanged];
509   }
512 - (NSArray*)columnHeaders {
513   if ([self internalRole] != ui::AX_ROLE_TABLE &&
514       [self internalRole] != ui::AX_ROLE_GRID) {
515     return nil;
516   }
518   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
519   const std::vector<int32>& uniqueCellIds =
520       browserAccessibility_->GetIntListAttribute(
521           ui::AX_ATTR_UNIQUE_CELL_IDS);
522   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
523     int id = uniqueCellIds[i];
524     BrowserAccessibility* cell =
525         browserAccessibility_->manager()->GetFromID(id);
526     if (cell && cell->GetRole() == ui::AX_ROLE_COLUMN_HEADER)
527       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
528   }
529   return ret;
532 - (NSValue*)columnIndexRange {
533   if (!browserAccessibility_->IsCellOrTableHeaderRole())
534     return nil;
536   int column = -1;
537   int colspan = -1;
538   browserAccessibility_->GetIntAttribute(
539       ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, &column);
540   browserAccessibility_->GetIntAttribute(
541       ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN, &colspan);
542   if (column >= 0 && colspan >= 1)
543     return [NSValue valueWithRange:NSMakeRange(column, colspan)];
544   return nil;
547 - (NSArray*)columns {
548   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
549   for (BrowserAccessibilityCocoa* child in [self children]) {
550     if ([[child role] isEqualToString:NSAccessibilityColumnRole])
551       [ret addObject:child];
552   }
553   return ret;
556 - (NSString*)description {
557   std::string description;
558   if (browserAccessibility_->GetStringAttribute(
559           ui::AX_ATTR_DESCRIPTION, &description)) {
560     return base::SysUTF8ToNSString(description);
561   }
563   // If the role is anything other than an image, or if there's
564   // a title or title UI element, just return an empty string.
565   if (![[self role] isEqualToString:NSAccessibilityImageRole])
566     return @"";
567   if (browserAccessibility_->HasStringAttribute(
568           ui::AX_ATTR_NAME)) {
569     return @"";
570   }
571   if ([self titleUIElement])
572     return @"";
574   // The remaining case is an image where there's no other title.
575   // Return the base part of the filename as the description.
576   std::string url;
577   if (browserAccessibility_->GetStringAttribute(
578           ui::AX_ATTR_URL, &url)) {
579     // Given a url like http://foo.com/bar/baz.png, just return the
580     // base name, e.g., "baz.png".
581     size_t leftIndex = url.rfind('/');
582     std::string basename =
583         leftIndex != std::string::npos ? url.substr(leftIndex) : url;
584     return base::SysUTF8ToNSString(basename);
585   }
587   return @"";
590 - (NSNumber*)disclosing {
591   if ([self internalRole] == ui::AX_ROLE_TREE_ITEM) {
592     return [NSNumber numberWithBool:
593         GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
594   } else {
595     return nil;
596   }
599 - (id)disclosedByRow {
600   // The row that contains this row.
601   // It should be the same as the first parent that is a treeitem.
602   return nil;
605 - (NSNumber*)disclosureLevel {
606   ui::AXRole role = [self internalRole];
607   if (role == ui::AX_ROLE_ROW ||
608       role == ui::AX_ROLE_TREE_ITEM) {
609     int level = browserAccessibility_->GetIntAttribute(
610         ui::AX_ATTR_HIERARCHICAL_LEVEL);
611     // Mac disclosureLevel is 0-based, but web levels are 1-based.
612     if (level > 0)
613       level--;
614     return [NSNumber numberWithInt:level];
615   } else {
616     return nil;
617   }
620 - (id)disclosedRows {
621   // The rows that are considered inside this row.
622   return nil;
625 - (NSString*)dropeffect {
626   std::string dropEffect;
627   if (browserAccessibility_->GetHtmlAttribute("aria-dropeffect", &dropEffect))
628     return base::SysUTF8ToNSString(dropEffect);
630   return nil;
633 - (NSNumber*)enabled {
634   return [NSNumber numberWithBool:
635       GetState(browserAccessibility_, ui::AX_STATE_ENABLED)];
638 - (NSNumber*)expanded {
639   return [NSNumber numberWithBool:
640       GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
643 - (NSNumber*)focused {
644   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
645   NSNumber* ret = [NSNumber numberWithBool:
646       manager->GetFocus(NULL) == browserAccessibility_];
647   return ret;
650 - (NSNumber*)grabbed {
651   std::string grabbed;
652   if (browserAccessibility_->GetHtmlAttribute("aria-grabbed", &grabbed) &&
653       grabbed == "true")
654     return [NSNumber numberWithBool:YES];
656   return [NSNumber numberWithBool:NO];
659 - (id)header {
660   int headerElementId = -1;
661   if ([self internalRole] == ui::AX_ROLE_TABLE ||
662       [self internalRole] == ui::AX_ROLE_GRID) {
663     browserAccessibility_->GetIntAttribute(
664         ui::AX_ATTR_TABLE_HEADER_ID, &headerElementId);
665   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
666     browserAccessibility_->GetIntAttribute(
667         ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId);
668   } else if ([self internalRole] == ui::AX_ROLE_ROW) {
669     browserAccessibility_->GetIntAttribute(
670         ui::AX_ATTR_TABLE_ROW_HEADER_ID, &headerElementId);
671   }
673   if (headerElementId > 0) {
674     BrowserAccessibility* headerObject =
675         browserAccessibility_->manager()->GetFromID(headerElementId);
676     if (headerObject)
677       return headerObject->ToBrowserAccessibilityCocoa();
678   }
679   return nil;
682 - (NSString*)help {
683   return NSStringForStringAttribute(
684       browserAccessibility_, ui::AX_ATTR_HELP);
687 - (NSNumber*)index {
688   if ([self internalRole] == ui::AX_ROLE_COLUMN) {
689     int columnIndex = browserAccessibility_->GetIntAttribute(
690           ui::AX_ATTR_TABLE_COLUMN_INDEX);
691     return [NSNumber numberWithInt:columnIndex];
692   } else if ([self internalRole] == ui::AX_ROLE_ROW) {
693     int rowIndex = browserAccessibility_->GetIntAttribute(
694         ui::AX_ATTR_TABLE_ROW_INDEX);
695     return [NSNumber numberWithInt:rowIndex];
696   }
698   return nil;
701 // Returns whether or not this node should be ignored in the
702 // accessibility tree.
703 - (BOOL)isIgnored {
704   return [[self role] isEqualToString:NSAccessibilityUnknownRole];
707 - (NSString*)invalid {
708   int invalidState;
709   if (!browserAccessibility_->GetIntAttribute(
710       ui::AX_ATTR_INVALID_STATE, &invalidState))
711     return @"false";
713   switch (invalidState) {
714   case ui::AX_INVALID_STATE_FALSE:
715     return @"false";
716   case ui::AX_INVALID_STATE_TRUE:
717     return @"true";
718   case ui::AX_INVALID_STATE_SPELLING:
719     return @"spelling";
720   case ui::AX_INVALID_STATE_GRAMMAR:
721     return @"grammar";
722   case ui::AX_INVALID_STATE_OTHER:
723     {
724       std::string ariaInvalidValue;
725       if (browserAccessibility_->GetStringAttribute(
726           ui::AX_ATTR_ARIA_INVALID_VALUE,
727           &ariaInvalidValue))
728         return base::SysUTF8ToNSString(ariaInvalidValue);
729       // Return @"true" since we cannot be more specific about the value.
730       return @"true";
731     }
732   default:
733     NOTREACHED();
734   }
736   return @"false";
739 - (NSString*)placeholder {
740   return NSStringForStringAttribute(
741       browserAccessibility_, ui::AX_ATTR_PLACEHOLDER);
744 - (void)addLinkedUIElementsFromAttribute:(ui::AXIntListAttribute)attribute
745                                    addTo:(NSMutableArray*)outArray {
746   const std::vector<int32>& attributeValues =
747       browserAccessibility_->GetIntListAttribute(attribute);
748   for (size_t i = 0; i < attributeValues.size(); ++i) {
749     BrowserAccessibility* element =
750         browserAccessibility_->manager()->GetFromID(attributeValues[i]);
751     if (element)
752       [outArray addObject:element->ToBrowserAccessibilityCocoa()];
753   }
756 - (NSArray*)linkedUIElements {
757   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
758   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_OWNS_IDS addTo:ret];
759   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_CONTROLS_IDS addTo:ret];
760   [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_FLOWTO_IDS addTo:ret];
761   if ([ret count] == 0)
762     return nil;
763   return ret;
766 - (NSNumber*)loaded {
767   return [NSNumber numberWithBool:YES];
770 - (NSNumber*)loadingProgress {
771   float floatValue = browserAccessibility_->GetFloatAttribute(
772       ui::AX_ATTR_DOC_LOADING_PROGRESS);
773   return [NSNumber numberWithFloat:floatValue];
776 - (NSNumber*)maxValue {
777   float floatValue = browserAccessibility_->GetFloatAttribute(
778       ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
779   return [NSNumber numberWithFloat:floatValue];
782 - (NSNumber*)minValue {
783   float floatValue = browserAccessibility_->GetFloatAttribute(
784       ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
785   return [NSNumber numberWithFloat:floatValue];
788 - (NSString*)orientation {
789   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL))
790     return NSAccessibilityVerticalOrientationValue;
791   else if (GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL))
792     return NSAccessibilityHorizontalOrientationValue;
794   return @"";
797 - (NSNumber*)numberOfCharacters {
798   std::string value = browserAccessibility_->GetStringAttribute(
799       ui::AX_ATTR_VALUE);
800   return [NSNumber numberWithInt:value.size()];
803 // The origin of this accessibility object in the page's document.
804 // This is relative to webkit's top-left origin, not Cocoa's
805 // bottom-left origin.
806 - (NSPoint)origin {
807   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
808   return NSMakePoint(bounds.x(), bounds.y());
811 - (id)parent {
812   // A nil parent means we're the root.
813   if (browserAccessibility_->GetParent()) {
814     return NSAccessibilityUnignoredAncestor(
815         browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa());
816   } else {
817     // Hook back up to RenderWidgetHostViewCocoa.
818     BrowserAccessibilityManagerMac* manager =
819         static_cast<BrowserAccessibilityManagerMac*>(
820             browserAccessibility_->manager());
821     return manager->parent_view();
822   }
825 - (NSValue*)position {
826   NSPoint origin = [self origin];
827   NSSize size = [[self size] sizeValue];
828   NSPoint pointInScreen = [self pointInScreen:origin size:size];
829   return [NSValue valueWithPoint:pointInScreen];
832 - (NSNumber*)required {
833   return [NSNumber numberWithBool:
834       GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)];
837 // Returns an enum indicating the role from browserAccessibility_.
838 - (ui::AXRole)internalRole {
839   return static_cast<ui::AXRole>(browserAccessibility_->GetRole());
842 - (content::BrowserAccessibilityDelegate*)delegate {
843   return browserAccessibility_->manager() ?
844       browserAccessibility_->manager()->delegate() :
845       nil;
848 - (content::BrowserAccessibility*)browserAccessibility {
849   return browserAccessibility_;
852 - (NSPoint)pointInScreen:(NSPoint)origin
853                     size:(NSSize)size {
854   if (!browserAccessibility_)
855     return NSZeroPoint;
857   // Get the delegate for the topmost BrowserAccessibilityManager, because
858   // that's the only one that can convert points to their origin in the screen.
859   BrowserAccessibilityDelegate* delegate =
860       browserAccessibility_->manager()->GetDelegateFromRootManager();
861   if (delegate) {
862     gfx::Rect bounds(origin.x, origin.y, size.width, size.height);
863     gfx::Point point = delegate->AccessibilityOriginInScreen(bounds);
864     return NSMakePoint(point.x(), point.y());
865   } else {
866     return NSZeroPoint;
867   }
870 // Returns a string indicating the NSAccessibility role of this object.
871 - (NSString*)role {
872   ui::AXRole role = [self internalRole];
873   if (role == ui::AX_ROLE_CANVAS &&
874       browserAccessibility_->GetBoolAttribute(
875           ui::AX_ATTR_CANVAS_HAS_FALLBACK)) {
876     return NSAccessibilityGroupRole;
877   }
878   if (role == ui::AX_ROLE_BUTTON || role == ui::AX_ROLE_TOGGLE_BUTTON) {
879     bool isAriaPressedDefined;
880     bool isMixed;
881     browserAccessibility_->GetAriaTristate("aria-pressed",
882                                            &isAriaPressedDefined,
883                                            &isMixed);
884     if (isAriaPressedDefined)
885       return NSAccessibilityCheckBoxRole;
886     else
887       return NSAccessibilityButtonRole;
888   }
889   if (role == ui::AX_ROLE_TEXT_FIELD &&
890       browserAccessibility_->HasState(ui::AX_STATE_MULTILINE)) {
891     return NSAccessibilityTextAreaRole;
892   }
894   // If this is a web area for a presentational iframe, give it a role of
895   // something other than WebArea so that the fact that it's a separate doc
896   // is not exposed to AT.
897   if (browserAccessibility_->IsWebAreaForPresentationalIframe())
898     return NSAccessibilityGroupRole;
900   return [AXPlatformNodeCocoa nativeRoleFromAXRole:role];
903 // Returns a string indicating the role description of this object.
904 - (NSString*)roleDescription {
905   NSString* role = [self role];
907   ContentClient* content_client = content::GetContentClient();
909   // The following descriptions are specific to webkit.
910   if ([role isEqualToString:@"AXWebArea"]) {
911     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
912         IDS_AX_ROLE_WEB_AREA));
913   }
915   if ([role isEqualToString:@"NSAccessibilityLinkRole"]) {
916     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
917         IDS_AX_ROLE_LINK));
918   }
920   if ([role isEqualToString:@"AXHeading"]) {
921     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
922         IDS_AX_ROLE_HEADING));
923   }
925   if (([role isEqualToString:NSAccessibilityGroupRole] ||
926        [role isEqualToString:NSAccessibilityRadioButtonRole]) &&
927       !browserAccessibility_->IsWebAreaForPresentationalIframe()) {
928     std::string role;
929     if (browserAccessibility_->GetHtmlAttribute("role", &role)) {
930       ui::AXRole internalRole = [self internalRole];
931       if ((internalRole != ui::AX_ROLE_GROUP &&
932            internalRole != ui::AX_ROLE_LIST_ITEM) ||
933           internalRole == ui::AX_ROLE_TAB) {
934         // TODO(dtseng): This is not localized; see crbug/84814.
935         return base::SysUTF8ToNSString(role);
936       }
937     }
938   }
940   switch([self internalRole]) {
941   case ui::AX_ROLE_ARTICLE:
942     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
943         IDS_AX_ROLE_ARTICLE));
944   case ui::AX_ROLE_BANNER:
945     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
946         IDS_AX_ROLE_BANNER));
947   case ui::AX_ROLE_COMPLEMENTARY:
948     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
949         IDS_AX_ROLE_COMPLEMENTARY));
950   case ui::AX_ROLE_CONTENT_INFO:
951     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
952         IDS_AX_ROLE_ADDRESS));
953   case ui::AX_ROLE_DESCRIPTION_LIST:
954     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
955         IDS_AX_ROLE_DESCRIPTION_LIST));
956   case ui::AX_ROLE_DESCRIPTION_LIST_DETAIL:
957     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
958         IDS_AX_ROLE_DESCRIPTION_DETAIL));
959   case ui::AX_ROLE_DESCRIPTION_LIST_TERM:
960     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
961         IDS_AX_ROLE_DESCRIPTION_TERM));
962   case ui::AX_ROLE_FIGURE:
963     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
964         IDS_AX_ROLE_FIGURE));
965   case ui::AX_ROLE_FOOTER:
966     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
967         IDS_AX_ROLE_FOOTER));
968   case ui::AX_ROLE_FORM:
969     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
970         IDS_AX_ROLE_FORM));
971   case ui::AX_ROLE_MAIN:
972     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
973         IDS_AX_ROLE_MAIN_CONTENT));
974   case ui::AX_ROLE_MARK:
975     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
976         IDS_AX_ROLE_MARK));
977   case ui::AX_ROLE_MATH:
978     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
979         IDS_AX_ROLE_MATH));
980   case ui::AX_ROLE_NAVIGATION:
981     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
982         IDS_AX_ROLE_NAVIGATIONAL_LINK));
983   case ui::AX_ROLE_REGION:
984     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
985         IDS_AX_ROLE_REGION));
986   case ui::AX_ROLE_SPIN_BUTTON:
987     // This control is similar to what VoiceOver calls a "stepper".
988     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
989         IDS_AX_ROLE_STEPPER));
990   case ui::AX_ROLE_STATUS:
991     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
992         IDS_AX_ROLE_STATUS));
993   case ui::AX_ROLE_SEARCH_BOX:
994     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
995         IDS_AX_ROLE_SEARCH_BOX));
996   case ui::AX_ROLE_SWITCH:
997     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
998         IDS_AX_ROLE_SWITCH));
999   case ui::AX_ROLE_TOGGLE_BUTTON:
1000     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
1001         IDS_AX_ROLE_TOGGLE_BUTTON));
1002   default:
1003     break;
1004   }
1006   return NSAccessibilityRoleDescription(role, nil);
1009 - (NSArray*)rowHeaders {
1010   if ([self internalRole] != ui::AX_ROLE_TABLE &&
1011       [self internalRole] != ui::AX_ROLE_GRID) {
1012     return nil;
1013   }
1015   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1016   const std::vector<int32>& uniqueCellIds =
1017       browserAccessibility_->GetIntListAttribute(
1018           ui::AX_ATTR_UNIQUE_CELL_IDS);
1019   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
1020     int id = uniqueCellIds[i];
1021     BrowserAccessibility* cell =
1022         browserAccessibility_->manager()->GetFromID(id);
1023     if (cell && cell->GetRole() == ui::AX_ROLE_ROW_HEADER)
1024       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
1025   }
1026   return ret;
1029 - (NSValue*)rowIndexRange {
1030   if (!browserAccessibility_->IsCellOrTableHeaderRole())
1031     return nil;
1033   int row = -1;
1034   int rowspan = -1;
1035   browserAccessibility_->GetIntAttribute(
1036       ui::AX_ATTR_TABLE_CELL_ROW_INDEX, &row);
1037   browserAccessibility_->GetIntAttribute(
1038       ui::AX_ATTR_TABLE_CELL_ROW_SPAN, &rowspan);
1039   if (row >= 0 && rowspan >= 1)
1040     return [NSValue valueWithRange:NSMakeRange(row, rowspan)];
1041   return nil;
1044 - (NSArray*)rows {
1045   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1047   if ([self internalRole] == ui::AX_ROLE_TABLE||
1048       [self internalRole] == ui::AX_ROLE_GRID) {
1049     for (BrowserAccessibilityCocoa* child in [self children]) {
1050       if ([[child role] isEqualToString:NSAccessibilityRowRole])
1051         [ret addObject:child];
1052     }
1053   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
1054     const std::vector<int32>& indirectChildIds =
1055         browserAccessibility_->GetIntListAttribute(
1056             ui::AX_ATTR_INDIRECT_CHILD_IDS);
1057     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
1058       int id = indirectChildIds[i];
1059       BrowserAccessibility* rowElement =
1060           browserAccessibility_->manager()->GetFromID(id);
1061       if (rowElement)
1062         [ret addObject:rowElement->ToBrowserAccessibilityCocoa()];
1063     }
1064   }
1066   return ret;
1069 - (NSArray*)selectedChildren {
1070   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1071   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1072   BrowserAccessibility* focusedChild = manager->GetFocus(browserAccessibility_);
1074   // If it's not multiselectable, try to skip iterating over the
1075   // children.
1076   if (!GetState(browserAccessibility_, ui::AX_STATE_MULTISELECTABLE)) {
1077     // First try the focused child.
1078     if (focusedChild && focusedChild != browserAccessibility_) {
1079       [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
1080       return ret;
1081     }
1083     // Next try the active descendant.
1084     int activeDescendantId;
1085     if (browserAccessibility_->GetIntAttribute(
1086             ui::AX_ATTR_ACTIVEDESCENDANT_ID, &activeDescendantId)) {
1087       BrowserAccessibility* activeDescendant =
1088           manager->GetFromID(activeDescendantId);
1089       if (activeDescendant) {
1090         [ret addObject:activeDescendant->ToBrowserAccessibilityCocoa()];
1091         return ret;
1092       }
1093     }
1094   }
1096   // If it's multiselectable or if the previous attempts failed,
1097   // return any children with the "selected" state, which may
1098   // come from aria-selected.
1099   uint32 childCount = browserAccessibility_->PlatformChildCount();
1100   for (uint32 index = 0; index < childCount; ++index) {
1101     BrowserAccessibility* child =
1102       browserAccessibility_->PlatformGetChild(index);
1103     if (child->HasState(ui::AX_STATE_SELECTED))
1104       [ret addObject:child->ToBrowserAccessibilityCocoa()];
1105   }
1107   // And if nothing's selected but one has focus, use the focused one.
1108   if ([ret count] == 0 &&
1109       focusedChild &&
1110       focusedChild != browserAccessibility_) {
1111     [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
1112   }
1114   return ret;
1117 // Returns the size of this object.
1118 - (NSValue*)size {
1119   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
1120   return  [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())];
1123 - (NSString*)sortDirection {
1124   int sortDirection;
1125   if (!browserAccessibility_->GetIntAttribute(
1126       ui::AX_ATTR_SORT_DIRECTION, &sortDirection))
1127     return @"";
1129   switch (sortDirection) {
1130   case ui::AX_SORT_DIRECTION_UNSORTED:
1131     return @"";
1132   case ui::AX_SORT_DIRECTION_ASCENDING:
1133     return @"AXSortDirectionAscending";
1134   case ui::AX_SORT_DIRECTION_DESCENDING:
1135     return @"AXSortDirectionDescending";
1136   case ui::AX_SORT_DIRECTION_OTHER:
1137     return @"AXSortDirectionUnknown";
1138   default:
1139     NOTREACHED();
1140   }
1142   return @"";
1145 // Returns a subrole based upon the role.
1146 - (NSString*) subrole {
1147   ui::AXRole browserAccessibilityRole = [self internalRole];
1148   if (browserAccessibilityRole == ui::AX_ROLE_TEXT_FIELD &&
1149       GetState(browserAccessibility_, ui::AX_STATE_PROTECTED)) {
1150     return @"AXSecureTextField";
1151   }
1153   if (browserAccessibilityRole == ui::AX_ROLE_DESCRIPTION_LIST)
1154     return @"AXDefinitionList";
1156   if (browserAccessibilityRole == ui::AX_ROLE_LIST)
1157     return @"AXContentList";
1159   return [AXPlatformNodeCocoa nativeSubroleFromAXRole:browserAccessibilityRole];
1162 // Returns all tabs in this subtree.
1163 - (NSArray*)tabs {
1164   NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
1166   if ([self internalRole] == ui::AX_ROLE_TAB)
1167     [tabSubtree addObject:self];
1169   for (uint i=0; i < [[self children] count]; ++i) {
1170     NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
1171     if ([tabChildren count] > 0)
1172       [tabSubtree addObjectsFromArray:tabChildren];
1173   }
1175   return tabSubtree;
1178 - (NSString*)title {
1179   return NSStringForStringAttribute(
1180       browserAccessibility_, ui::AX_ATTR_NAME);
1183 - (id)titleUIElement {
1184   int titleElementId;
1185   if (browserAccessibility_->GetIntAttribute(
1186           ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
1187     BrowserAccessibility* titleElement =
1188         browserAccessibility_->manager()->GetFromID(titleElementId);
1189     if (titleElement)
1190       return titleElement->ToBrowserAccessibilityCocoa();
1191   }
1192   std::vector<int32> labelledby_ids =
1193       browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
1194   if (labelledby_ids.size() == 1) {
1195     BrowserAccessibility* titleElement =
1196         browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
1197     if (titleElement)
1198       return titleElement->ToBrowserAccessibilityCocoa();
1199   }
1201   return nil;
1204 - (NSURL*)url {
1205   StringAttribute urlAttribute =
1206       [[self role] isEqualToString:@"AXWebArea"] ?
1207           ui::AX_ATTR_DOC_URL :
1208           ui::AX_ATTR_URL;
1210   std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute);
1211   if (urlStr.empty())
1212     return nil;
1214   return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))];
1217 - (id)value {
1218   // WebCore uses an attachmentView to get the below behavior.
1219   // We do not have any native views backing this object, so need
1220   // to approximate Cocoa ax behavior best as we can.
1221   NSString* role = [self role];
1222   if ([role isEqualToString:@"AXHeading"]) {
1223     int level = 0;
1224     if (browserAccessibility_->GetIntAttribute(
1225             ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) {
1226       return [NSNumber numberWithInt:level];
1227     }
1228   } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
1229     // AXValue does not make sense for pure buttons.
1230     return @"";
1231   } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) {
1232     int value = 0;
1233     bool isAriaPressedDefined;
1234     bool isMixed;
1235     value = browserAccessibility_->GetAriaTristate(
1236         "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0;
1238     if (isMixed)
1239       value = 2;
1241     return [NSNumber numberWithInt:value];
1243   } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
1244              [role isEqualToString:NSAccessibilityRadioButtonRole]) {
1245     int value = 0;
1246     value = GetState(
1247         browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0;
1248     value = GetState(
1249         browserAccessibility_, ui::AX_STATE_SELECTED) ?
1250             1 :
1251             value;
1253     if (browserAccessibility_->GetBoolAttribute(
1254         ui::AX_ATTR_BUTTON_MIXED)) {
1255       value = 2;
1256     }
1257     return [NSNumber numberWithInt:value];
1258   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1259              [role isEqualToString:NSAccessibilitySliderRole] ||
1260              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1261              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1262     float floatValue;
1263     if (browserAccessibility_->GetFloatAttribute(
1264             ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) {
1265       return [NSNumber numberWithFloat:floatValue];
1266     }
1267   } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
1268     int color = browserAccessibility_->GetIntAttribute(
1269         ui::AX_ATTR_COLOR_VALUE);
1270     int red = (color >> 16) & 0xFF;
1271     int green = (color >> 8) & 0xFF;
1272     int blue = color & 0xFF;
1273     // This string matches the one returned by a native Mac color well.
1274     return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
1275                 red / 255., green / 255., blue / 255.];
1276   }
1278   return NSStringForStringAttribute(
1279       browserAccessibility_, ui::AX_ATTR_VALUE);
1282 - (NSString*)valueDescription {
1283   return NSStringForStringAttribute(
1284       browserAccessibility_, ui::AX_ATTR_VALUE);
1287 - (NSValue*)visibleCharacterRange {
1288   std::string value = browserAccessibility_->GetStringAttribute(
1289       ui::AX_ATTR_VALUE);
1290   return [NSValue valueWithRange:NSMakeRange(0, value.size())];
1293 - (NSArray*)visibleCells {
1294   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1295   const std::vector<int32>& uniqueCellIds =
1296       browserAccessibility_->GetIntListAttribute(
1297           ui::AX_ATTR_UNIQUE_CELL_IDS);
1298   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
1299     int id = uniqueCellIds[i];
1300     BrowserAccessibility* cell =
1301         browserAccessibility_->manager()->GetFromID(id);
1302     if (cell)
1303       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
1304   }
1305   return ret;
1308 - (NSArray*)visibleChildren {
1309   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1310   uint32 childCount = browserAccessibility_->PlatformChildCount();
1311   for (uint32 index = 0; index < childCount; ++index) {
1312     BrowserAccessibilityCocoa* child =
1313         browserAccessibility_->PlatformGetChild(index)->
1314             ToBrowserAccessibilityCocoa();
1315     [ret addObject:child];
1316   }
1317   return ret;
1320 - (NSArray*)visibleColumns {
1321   return [self columns];
1324 - (NSArray*)visibleRows {
1325   return [self rows];
1328 - (NSNumber*)visited {
1329   return [NSNumber numberWithBool:
1330       GetState(browserAccessibility_, ui::AX_STATE_VISITED)];
1333 - (id)window {
1334   if (!browserAccessibility_)
1335     return nil;
1337   BrowserAccessibilityManagerMac* manager =
1338       static_cast<BrowserAccessibilityManagerMac*>(
1339           browserAccessibility_->manager());
1340   if (!manager || !manager->parent_view())
1341     return nil;
1343   return [manager->parent_view() window];
1346 - (NSString*)methodNameForAttribute:(NSString*)attribute {
1347   return [attributeToMethodNameMap objectForKey:attribute];
1350 - (void)swapChildren:(base::scoped_nsobject<NSMutableArray>*)other {
1351   children_.swap(*other);
1354 // Returns the accessibility value for the given attribute.  If the value isn't
1355 // supported this will return nil.
1356 - (id)accessibilityAttributeValue:(NSString*)attribute {
1357   if (!browserAccessibility_)
1358     return nil;
1360   SEL selector =
1361       NSSelectorFromString([self methodNameForAttribute:attribute]);
1362   if (selector)
1363     return [self performSelector:selector];
1365   // TODO(dtseng): refactor remaining attributes.
1366   int selStart, selEnd;
1367   if (browserAccessibility_->GetIntAttribute(
1368           ui::AX_ATTR_TEXT_SEL_START, &selStart) &&
1369       browserAccessibility_->
1370           GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) {
1371     if (selStart > selEnd)
1372       std::swap(selStart, selEnd);
1373     int selLength = selEnd - selStart;
1374     if ([attribute isEqualToString:
1375         NSAccessibilityInsertionPointLineNumberAttribute]) {
1376       const std::vector<int32>& line_breaks =
1377           browserAccessibility_->GetIntListAttribute(
1378               ui::AX_ATTR_LINE_BREAKS);
1379       for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1380         if (line_breaks[i] > selStart)
1381           return [NSNumber numberWithInt:i];
1382       }
1383       return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1384     }
1385     if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
1386       base::string16 value = browserAccessibility_->GetString16Attribute(
1387           ui::AX_ATTR_VALUE);
1388       return base::SysUTF16ToNSString(value.substr(selStart, selLength));
1389     }
1390     if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1391       return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
1392     }
1393   }
1394   return nil;
1397 // Returns the accessibility value for the given attribute and parameter. If the
1398 // value isn't supported this will return nil.
1399 - (id)accessibilityAttributeValue:(NSString*)attribute
1400                      forParameter:(id)parameter {
1401   if (!browserAccessibility_)
1402     return nil;
1404   const std::vector<int32>& line_breaks =
1405       browserAccessibility_->GetIntListAttribute(
1406           ui::AX_ATTR_LINE_BREAKS);
1407   std::string value = browserAccessibility_->GetStringAttribute(
1408       ui::AX_ATTR_VALUE);
1409   int len = static_cast<int>(value.size());
1411   if ([attribute isEqualToString:
1412       NSAccessibilityStringForRangeParameterizedAttribute]) {
1413     NSRange range = [(NSValue*)parameter rangeValue];
1414     base::string16 value = browserAccessibility_->GetString16Attribute(
1415         ui::AX_ATTR_VALUE);
1416     return base::SysUTF16ToNSString(value.substr(range.location, range.length));
1417   }
1419   if ([attribute isEqualToString:
1420       NSAccessibilityLineForIndexParameterizedAttribute]) {
1421     int index = [(NSNumber*)parameter intValue];
1422     for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1423       if (line_breaks[i] > index)
1424         return [NSNumber numberWithInt:i];
1425     }
1426     return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1427   }
1429   if ([attribute isEqualToString:
1430       NSAccessibilityRangeForLineParameterizedAttribute]) {
1431     int line_index = [(NSNumber*)parameter intValue];
1432     int line_count = static_cast<int>(line_breaks.size()) + 1;
1433     if (line_index < 0 || line_index >= line_count)
1434       return nil;
1435     int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
1436     int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
1437     return [NSValue valueWithRange:
1438         NSMakeRange(start, end - start)];
1439   }
1441   if ([attribute isEqualToString:
1442       NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
1443     if ([self internalRole] != ui::AX_ROLE_TABLE &&
1444         [self internalRole] != ui::AX_ROLE_GRID) {
1445       return nil;
1446     }
1447     if (![parameter isKindOfClass:[NSArray self]])
1448       return nil;
1449     NSArray* array = parameter;
1450     int column = [[array objectAtIndex:0] intValue];
1451     int row = [[array objectAtIndex:1] intValue];
1452     int num_columns = browserAccessibility_->GetIntAttribute(
1453         ui::AX_ATTR_TABLE_COLUMN_COUNT);
1454     int num_rows = browserAccessibility_->GetIntAttribute(
1455         ui::AX_ATTR_TABLE_ROW_COUNT);
1456     if (column < 0 || column >= num_columns ||
1457         row < 0 || row >= num_rows) {
1458       return nil;
1459     }
1460     for (size_t i = 0;
1461          i < browserAccessibility_->PlatformChildCount();
1462          ++i) {
1463       BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i);
1464       if (child->GetRole() != ui::AX_ROLE_ROW)
1465         continue;
1466       int rowIndex;
1467       if (!child->GetIntAttribute(
1468               ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) {
1469         continue;
1470       }
1471       if (rowIndex < row)
1472         continue;
1473       if (rowIndex > row)
1474         break;
1475       for (size_t j = 0;
1476            j < child->PlatformChildCount();
1477            ++j) {
1478         BrowserAccessibility* cell = child->PlatformGetChild(j);
1479         if (!cell->IsCellOrTableHeaderRole())
1480           continue;
1481         int colIndex;
1482         if (!cell->GetIntAttribute(
1483                 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
1484                 &colIndex)) {
1485           continue;
1486         }
1487         if (colIndex == column)
1488           return cell->ToBrowserAccessibilityCocoa();
1489         if (colIndex > column)
1490           break;
1491       }
1492     }
1493     return nil;
1494   }
1496   if ([attribute isEqualToString:
1497       NSAccessibilityBoundsForRangeParameterizedAttribute]) {
1498     if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT)
1499       return nil;
1500     NSRange range = [(NSValue*)parameter rangeValue];
1501     gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange(
1502         range.location, range.length);
1503     NSPoint origin = NSMakePoint(rect.x(), rect.y());
1504     NSSize size = NSMakeSize(rect.width(), rect.height());
1505     NSPoint pointInScreen = [self pointInScreen:origin size:size];
1506     NSRect nsrect = NSMakeRect(
1507         pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
1508     return [NSValue valueWithRect:nsrect];
1509   }
1510   if ([attribute isEqualToString:@"AXUIElementCountForSearchPredicate"]) {
1511     OneShotAccessibilityTreeSearch search(browserAccessibility_->manager());
1512     if (InitializeAccessibilityTreeSearch(&search, parameter))
1513       return [NSNumber numberWithInt:search.CountMatches()];
1514     return nil;
1515   }
1517   if ([attribute isEqualToString:@"AXUIElementsForSearchPredicate"]) {
1518     OneShotAccessibilityTreeSearch search(browserAccessibility_->manager());
1519     if (InitializeAccessibilityTreeSearch(&search, parameter)) {
1520       size_t count = search.CountMatches();
1521       NSMutableArray* result = [NSMutableArray arrayWithCapacity:count];
1522       for (size_t i = 0; i < count; ++i) {
1523         BrowserAccessibility* match = search.GetMatchAtIndex(i);
1524         [result addObject:match->ToBrowserAccessibilityCocoa()];
1525       }
1526       return result;
1527     }
1528     return nil;
1529   }
1531   // TODO(dtseng): support the following attributes.
1532   if ([attribute isEqualTo:
1533           NSAccessibilityRangeForPositionParameterizedAttribute] ||
1534       [attribute isEqualTo:
1535           NSAccessibilityRangeForIndexParameterizedAttribute] ||
1536       [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
1537       [attribute isEqualTo:
1538           NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
1539     return nil;
1540   }
1541   return nil;
1544 // Returns an array of parameterized attributes names that this object will
1545 // respond to.
1546 - (NSArray*)accessibilityParameterizedAttributeNames {
1547   if (!browserAccessibility_)
1548     return nil;
1550   // General attributes.
1551   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1552       @"AXUIElementCountForSearchPredicate",
1553       @"AXUIElementsForSearchPredicate",
1554       nil];
1556   if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
1557       [[self role] isEqualToString:NSAccessibilityGridRole]) {
1558     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1559         NSAccessibilityCellForColumnAndRowParameterizedAttribute,
1560         nil]];
1561   }
1562   if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1563       [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
1564     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1565         NSAccessibilityLineForIndexParameterizedAttribute,
1566         NSAccessibilityRangeForLineParameterizedAttribute,
1567         NSAccessibilityStringForRangeParameterizedAttribute,
1568         NSAccessibilityRangeForPositionParameterizedAttribute,
1569         NSAccessibilityRangeForIndexParameterizedAttribute,
1570         NSAccessibilityBoundsForRangeParameterizedAttribute,
1571         NSAccessibilityRTFForRangeParameterizedAttribute,
1572         NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1573         NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1574         nil]];
1575   }
1576   if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1577     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1578         NSAccessibilityBoundsForRangeParameterizedAttribute,
1579         nil]];
1580   }
1581   return ret;
1584 // Returns an array of action names that this object will respond to.
1585 - (NSArray*)accessibilityActionNames {
1586   if (!browserAccessibility_)
1587     return nil;
1589   NSMutableArray* ret =
1590       [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1591   NSString* role = [self role];
1592   // TODO(dtseng): this should only get set when there's a default action.
1593   if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1594       ![role isEqualToString:NSAccessibilityTextFieldRole] &&
1595       ![role isEqualToString:NSAccessibilityTextAreaRole]) {
1596     [ret addObject:NSAccessibilityPressAction];
1597   }
1599   return ret;
1602 // Returns a sub-array of values for the given attribute value, starting at
1603 // index, with up to maxCount items.  If the given index is out of bounds,
1604 // or there are no values for the given attribute, it will return nil.
1605 // This method is used for querying subsets of values, without having to
1606 // return a large set of data, such as elements with a large number of
1607 // children.
1608 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1609                                         index:(NSUInteger)index
1610                                      maxCount:(NSUInteger)maxCount {
1611   if (!browserAccessibility_)
1612     return nil;
1614   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1615   if (!fullArray)
1616     return nil;
1617   NSUInteger arrayCount = [fullArray count];
1618   if (index >= arrayCount)
1619     return nil;
1620   NSRange subRange;
1621   if ((index + maxCount) > arrayCount) {
1622     subRange = NSMakeRange(index, arrayCount - index);
1623   } else {
1624     subRange = NSMakeRange(index, maxCount);
1625   }
1626   return [fullArray subarrayWithRange:subRange];
1629 // Returns the count of the specified accessibility array attribute.
1630 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1631   if (!browserAccessibility_)
1632     return 0;
1634   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1635   return [fullArray count];
1638 // Returns the list of accessibility attributes that this object supports.
1639 - (NSArray*)accessibilityAttributeNames {
1640   if (!browserAccessibility_)
1641     return nil;
1643   // General attributes.
1644   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1645       NSAccessibilityChildrenAttribute,
1646       NSAccessibilityDescriptionAttribute,
1647       NSAccessibilityEnabledAttribute,
1648       NSAccessibilityFocusedAttribute,
1649       NSAccessibilityHelpAttribute,
1650       NSAccessibilityLinkedUIElementsAttribute,
1651       NSAccessibilityParentAttribute,
1652       NSAccessibilityPositionAttribute,
1653       NSAccessibilityRoleAttribute,
1654       NSAccessibilityRoleDescriptionAttribute,
1655       NSAccessibilitySizeAttribute,
1656       NSAccessibilitySubroleAttribute,
1657       NSAccessibilityTitleAttribute,
1658       NSAccessibilityTopLevelUIElementAttribute,
1659       NSAccessibilityValueAttribute,
1660       NSAccessibilityWindowAttribute,
1661       @"AXAccessKey",
1662       @"AXInvalid",
1663       @"AXVisited",
1664       nil];
1666   // Specific role attributes.
1667   NSString* role = [self role];
1668   NSString* subrole = [self subrole];
1669   if ([role isEqualToString:NSAccessibilityTableRole] ||
1670       [role isEqualToString:NSAccessibilityGridRole]) {
1671     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1672         NSAccessibilityColumnsAttribute,
1673         NSAccessibilityVisibleColumnsAttribute,
1674         NSAccessibilityRowsAttribute,
1675         NSAccessibilityVisibleRowsAttribute,
1676         NSAccessibilityVisibleCellsAttribute,
1677         NSAccessibilityHeaderAttribute,
1678         NSAccessibilityColumnHeaderUIElementsAttribute,
1679         NSAccessibilityRowHeaderUIElementsAttribute,
1680         nil]];
1681   } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1682     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1683         NSAccessibilityIndexAttribute,
1684         NSAccessibilityHeaderAttribute,
1685         NSAccessibilityRowsAttribute,
1686         NSAccessibilityVisibleRowsAttribute,
1687         nil]];
1688   } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1689     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1690         NSAccessibilityColumnIndexRangeAttribute,
1691         NSAccessibilityRowIndexRangeAttribute,
1692         @"AXSortDirection",
1693         nil]];
1694   } else if ([role isEqualToString:@"AXWebArea"]) {
1695     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1696         @"AXLoaded",
1697         @"AXLoadingProgress",
1698         nil]];
1699   } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
1700              [role isEqualToString:NSAccessibilityTextAreaRole]) {
1701     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1702         NSAccessibilityInsertionPointLineNumberAttribute,
1703         NSAccessibilityNumberOfCharactersAttribute,
1704         NSAccessibilitySelectedTextAttribute,
1705         NSAccessibilitySelectedTextRangeAttribute,
1706         NSAccessibilityVisibleCharacterRangeAttribute,
1707         nil]];
1708   } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1709     [ret addObject:NSAccessibilityTabsAttribute];
1710   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1711              [role isEqualToString:NSAccessibilitySliderRole] ||
1712              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1713              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1714     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1715         NSAccessibilityMaxValueAttribute,
1716         NSAccessibilityMinValueAttribute,
1717         NSAccessibilityValueDescriptionAttribute,
1718         nil]];
1719   } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1720     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1721         NSAccessibilityDisclosingAttribute,
1722         NSAccessibilityDisclosedByRowAttribute,
1723         NSAccessibilityDisclosureLevelAttribute,
1724         NSAccessibilityDisclosedRowsAttribute,
1725         nil]];
1726   } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1727     if (browserAccessibility_->GetParent()) {
1728       base::string16 parentRole;
1729       browserAccessibility_->GetParent()->GetHtmlAttribute(
1730           "role", &parentRole);
1731       const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1732       if (parentRole == treegridRole) {
1733         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1734             NSAccessibilityDisclosingAttribute,
1735             NSAccessibilityDisclosedByRowAttribute,
1736             NSAccessibilityDisclosureLevelAttribute,
1737             NSAccessibilityDisclosedRowsAttribute,
1738             nil]];
1739       } else {
1740         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1741             NSAccessibilityIndexAttribute,
1742             nil]];
1743       }
1744     }
1745   } else if ([role isEqualToString:NSAccessibilityListRole]) {
1746     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1747         NSAccessibilitySelectedChildrenAttribute,
1748         NSAccessibilityVisibleChildrenAttribute,
1749         nil]];
1750   }
1752   // Add the url attribute only if it has a valid url.
1753   if ([self url] != nil) {
1754     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1755         NSAccessibilityURLAttribute,
1756         nil]];
1757   }
1759   // Position in set and Set size
1760   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_POS_IN_SET)) {
1761     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1762          @"AXARIAPosInSet",
1763          nil]];
1764   }
1765   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_SET_SIZE)) {
1766     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1767          @"AXARIASetSize",
1768          nil]];
1769   }
1771   // Live regions.
1772   if (browserAccessibility_->HasStringAttribute(
1773           ui::AX_ATTR_LIVE_STATUS)) {
1774     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1775         @"AXARIALive",
1776         nil]];
1777   }
1778   if (browserAccessibility_->HasStringAttribute(
1779           ui::AX_ATTR_LIVE_RELEVANT)) {
1780     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1781         @"AXARIARelevant",
1782         nil]];
1783   }
1784   if (browserAccessibility_->HasBoolAttribute(
1785           ui::AX_ATTR_LIVE_ATOMIC)) {
1786     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1787         @"AXARIAAtomic",
1788         nil]];
1789   }
1790   if (browserAccessibility_->HasBoolAttribute(
1791           ui::AX_ATTR_LIVE_BUSY)) {
1792     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1793         @"AXARIABusy",
1794         nil]];
1795   }
1797   std::string dropEffect;
1798   if (browserAccessibility_->GetHtmlAttribute("aria-dropeffect", &dropEffect)) {
1799     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1800         @"AXDropEffects",
1801         nil]];
1802   }
1804   std::string grabbed;
1805   if (browserAccessibility_->GetHtmlAttribute("aria-grabbed", &grabbed)) {
1806     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1807         @"AXGrabbed",
1808         nil]];
1809   }
1811   // Add expanded attribute only if it has expanded or collapsed state.
1812   if (GetState(browserAccessibility_, ui::AX_STATE_EXPANDED) ||
1813         GetState(browserAccessibility_, ui::AX_STATE_COLLAPSED)) {
1814     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1815         NSAccessibilityExpandedAttribute,
1816         nil]];
1817   }
1819   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)
1820       || GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL)) {
1821     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1822         NSAccessibilityOrientationAttribute, nil]];
1823   }
1825   if (browserAccessibility_->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER)) {
1826     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1827         @"AXPlaceholder", nil]];
1828   }
1830   if (GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)) {
1831     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1832         @"AXRequired", nil]];
1833   }
1835   // Title UI Element.
1836   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1837       (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1838        browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1839                             .size() == 1)) {
1840     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1841          NSAccessibilityTitleUIElementAttribute,
1842          nil]];
1843   }
1844   // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1845   // for elements which are referred to by labelledby or are labels
1847   return ret;
1850 // Returns the index of the child in this objects array of children.
1851 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1852   if (!browserAccessibility_)
1853     return 0;
1855   NSUInteger index = 0;
1856   for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1857     if ([child isEqual:childToCheck])
1858       return index;
1859     ++index;
1860   }
1861   return NSNotFound;
1864 // Returns whether or not the specified attribute can be set by the
1865 // accessibility API via |accessibilitySetValue:forAttribute:|.
1866 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1867   if (!browserAccessibility_)
1868     return NO;
1870   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1871     return GetState(browserAccessibility_,
1872         ui::AX_STATE_FOCUSABLE);
1873   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1874     return browserAccessibility_->GetBoolAttribute(
1875         ui::AX_ATTR_CAN_SET_VALUE);
1876   }
1877   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1878       ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1879        [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1880     return YES;
1882   return NO;
1885 // Returns whether or not this object should be ignored in the accessibility
1886 // tree.
1887 - (BOOL)accessibilityIsIgnored {
1888   if (!browserAccessibility_)
1889     return true;
1891   return [self isIgnored];
1894 // Performs the given accessibility action on the webkit accessibility object
1895 // that backs this object.
1896 - (void)accessibilityPerformAction:(NSString*)action {
1897   if (!browserAccessibility_)
1898     return;
1900   // TODO(dmazzoni): Support more actions.
1901   if ([action isEqualToString:NSAccessibilityPressAction]) {
1902     [self delegate]->AccessibilityDoDefaultAction(
1903         browserAccessibility_->GetId());
1904   } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1905     [self delegate]->AccessibilityShowContextMenu(
1906         browserAccessibility_->GetId());
1907   }
1910 // Returns the description of the given action.
1911 - (NSString*)accessibilityActionDescription:(NSString*)action {
1912   if (!browserAccessibility_)
1913     return nil;
1915   return NSAccessibilityActionDescription(action);
1918 // Sets an override value for a specific accessibility attribute.
1919 // This class does not support this.
1920 - (BOOL)accessibilitySetOverrideValue:(id)value
1921                          forAttribute:(NSString*)attribute {
1922   return NO;
1925 // Sets the value for an accessibility attribute via the accessibility API.
1926 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1927   if (!browserAccessibility_)
1928     return;
1930   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1931     BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1932     NSNumber* focusedNumber = value;
1933     BOOL focused = [focusedNumber intValue];
1934     if (focused)
1935       manager->SetFocus(browserAccessibility_, true);
1936   }
1937   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1938     NSRange range = [(NSValue*)value rangeValue];
1939     [self delegate]->AccessibilitySetTextSelection(
1940         browserAccessibility_->GetId(),
1941         range.location, range.location + range.length);
1942   }
1945 // Returns the deepest accessibility child that should not be ignored.
1946 // It is assumed that the hit test has been narrowed down to this object
1947 // or one of its children, so this will never return nil unless this
1948 // object is invalid.
1949 - (id)accessibilityHitTest:(NSPoint)point {
1950   if (!browserAccessibility_)
1951     return nil;
1953   BrowserAccessibilityCocoa* hit = self;
1954   for (BrowserAccessibilityCocoa* child in [self children]) {
1955     if (!child->browserAccessibility_)
1956       continue;
1957     NSPoint origin = [child origin];
1958     NSSize size = [[child size] sizeValue];
1959     NSRect rect;
1960     rect.origin = origin;
1961     rect.size = size;
1962     if (NSPointInRect(point, rect)) {
1963       hit = child;
1964       id childResult = [child accessibilityHitTest:point];
1965       if (![childResult accessibilityIsIgnored]) {
1966         hit = childResult;
1967         break;
1968       }
1969     }
1970   }
1971   return NSAccessibilityUnignoredAncestor(hit);
1974 - (BOOL)isEqual:(id)object {
1975   if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1976     return NO;
1977   return ([self hash] == [object hash]);
1980 - (NSUInteger)hash {
1981   // Potentially called during dealloc.
1982   if (!browserAccessibility_)
1983     return [super hash];
1984   return browserAccessibility_->GetId();
1987 - (BOOL)accessibilityShouldUseUniqueId {
1988   return YES;
1991 @end