Permission messages: Add a bunch of missing combinations/suppressions.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_cocoa.mm
blob7afcb30426c6481756c7d3c20af6d4bca173ef30
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     { NSAccessibilityPlaceholderValueAttribute, @"placeholderValue" },
366     { NSAccessibilityPositionAttribute, @"position" },
367     { NSAccessibilityRoleAttribute, @"role" },
368     { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" },
369     { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" },
370     { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" },
371     { NSAccessibilityRowsAttribute, @"rows" },
372     // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
373     { NSAccessibilitySelectedChildrenAttribute, @"selectedChildren" },
374     { NSAccessibilitySizeAttribute, @"size" },
375     { NSAccessibilitySubroleAttribute, @"subrole" },
376     { NSAccessibilityTabsAttribute, @"tabs" },
377     { NSAccessibilityTitleAttribute, @"title" },
378     { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" },
379     { NSAccessibilityTopLevelUIElementAttribute, @"window" },
380     { NSAccessibilityURLAttribute, @"url" },
381     { NSAccessibilityValueAttribute, @"value" },
382     { NSAccessibilityValueDescriptionAttribute, @"valueDescription" },
383     { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" },
384     { NSAccessibilityVisibleCellsAttribute, @"visibleCells" },
385     { NSAccessibilityVisibleChildrenAttribute, @"visibleChildren" },
386     { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" },
387     { NSAccessibilityVisibleRowsAttribute, @"visibleRows" },
388     { NSAccessibilityWindowAttribute, @"window" },
389     { @"AXAccessKey", @"accessKey" },
390     { @"AXARIAAtomic", @"ariaAtomic" },
391     { @"AXARIABusy", @"ariaBusy" },
392     { @"AXARIALive", @"ariaLive" },
393     { @"AXARIASetSize", @"ariaSetSize" },
394     { @"AXARIAPosInSet", @"ariaPosInSet" },
395     { @"AXARIARelevant", @"ariaRelevant" },
396     { @"AXDropEffects", @"dropeffect" },
397     { @"AXGrabbed", @"grabbed" },
398     { @"AXInvalid", @"invalid" },
399     { @"AXLoaded", @"loaded" },
400     { @"AXLoadingProgress", @"loadingProgress" },
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*)placeholderValue {
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_CHECK_BOX:
948     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
949         IDS_AX_ROLE_CHECK_BOX));
950   case ui::AX_ROLE_COMPLEMENTARY:
951     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
952         IDS_AX_ROLE_COMPLEMENTARY));
953   case ui::AX_ROLE_CONTENT_INFO:
954     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
955         IDS_AX_ROLE_ADDRESS));
956   case ui::AX_ROLE_DESCRIPTION_LIST:
957     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
958         IDS_AX_ROLE_DESCRIPTION_LIST));
959   case ui::AX_ROLE_DESCRIPTION_LIST_DETAIL:
960     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
961         IDS_AX_ROLE_DESCRIPTION_DETAIL));
962   case ui::AX_ROLE_DESCRIPTION_LIST_TERM:
963     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
964         IDS_AX_ROLE_DESCRIPTION_TERM));
965   case ui::AX_ROLE_FIGURE:
966     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
967         IDS_AX_ROLE_FIGURE));
968   case ui::AX_ROLE_FOOTER:
969     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
970         IDS_AX_ROLE_FOOTER));
971   case ui::AX_ROLE_FORM:
972     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
973         IDS_AX_ROLE_FORM));
974   case ui::AX_ROLE_MAIN:
975     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
976         IDS_AX_ROLE_MAIN_CONTENT));
977   case ui::AX_ROLE_MARK:
978     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
979         IDS_AX_ROLE_MARK));
980   case ui::AX_ROLE_MATH:
981     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
982         IDS_AX_ROLE_MATH));
983   case ui::AX_ROLE_NAVIGATION:
984     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
985         IDS_AX_ROLE_NAVIGATIONAL_LINK));
986   case ui::AX_ROLE_REGION:
987     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
988         IDS_AX_ROLE_REGION));
989   case ui::AX_ROLE_SPIN_BUTTON:
990     // This control is similar to what VoiceOver calls a "stepper".
991     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
992         IDS_AX_ROLE_STEPPER));
993   case ui::AX_ROLE_STATUS:
994     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
995         IDS_AX_ROLE_STATUS));
996   case ui::AX_ROLE_SEARCH_BOX:
997     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
998         IDS_AX_ROLE_SEARCH_BOX));
999   case ui::AX_ROLE_SWITCH:
1000     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
1001         IDS_AX_ROLE_SWITCH));
1002   case ui::AX_ROLE_TOGGLE_BUTTON:
1003     return base::SysUTF16ToNSString(content_client->GetLocalizedString(
1004         IDS_AX_ROLE_TOGGLE_BUTTON));
1005   default:
1006     break;
1007   }
1009   return NSAccessibilityRoleDescription(role, nil);
1012 - (NSArray*)rowHeaders {
1013   if ([self internalRole] != ui::AX_ROLE_TABLE &&
1014       [self internalRole] != ui::AX_ROLE_GRID) {
1015     return nil;
1016   }
1018   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1019   const std::vector<int32>& uniqueCellIds =
1020       browserAccessibility_->GetIntListAttribute(
1021           ui::AX_ATTR_UNIQUE_CELL_IDS);
1022   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
1023     int id = uniqueCellIds[i];
1024     BrowserAccessibility* cell =
1025         browserAccessibility_->manager()->GetFromID(id);
1026     if (cell && cell->GetRole() == ui::AX_ROLE_ROW_HEADER)
1027       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
1028   }
1029   return ret;
1032 - (NSValue*)rowIndexRange {
1033   if (!browserAccessibility_->IsCellOrTableHeaderRole())
1034     return nil;
1036   int row = -1;
1037   int rowspan = -1;
1038   browserAccessibility_->GetIntAttribute(
1039       ui::AX_ATTR_TABLE_CELL_ROW_INDEX, &row);
1040   browserAccessibility_->GetIntAttribute(
1041       ui::AX_ATTR_TABLE_CELL_ROW_SPAN, &rowspan);
1042   if (row >= 0 && rowspan >= 1)
1043     return [NSValue valueWithRange:NSMakeRange(row, rowspan)];
1044   return nil;
1047 - (NSArray*)rows {
1048   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1050   if ([self internalRole] == ui::AX_ROLE_TABLE||
1051       [self internalRole] == ui::AX_ROLE_GRID) {
1052     for (BrowserAccessibilityCocoa* child in [self children]) {
1053       if ([[child role] isEqualToString:NSAccessibilityRowRole])
1054         [ret addObject:child];
1055     }
1056   } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
1057     const std::vector<int32>& indirectChildIds =
1058         browserAccessibility_->GetIntListAttribute(
1059             ui::AX_ATTR_INDIRECT_CHILD_IDS);
1060     for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
1061       int id = indirectChildIds[i];
1062       BrowserAccessibility* rowElement =
1063           browserAccessibility_->manager()->GetFromID(id);
1064       if (rowElement)
1065         [ret addObject:rowElement->ToBrowserAccessibilityCocoa()];
1066     }
1067   }
1069   return ret;
1072 - (NSArray*)selectedChildren {
1073   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1074   BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1075   BrowserAccessibility* focusedChild = manager->GetFocus(browserAccessibility_);
1077   // If it's not multiselectable, try to skip iterating over the
1078   // children.
1079   if (!GetState(browserAccessibility_, ui::AX_STATE_MULTISELECTABLE)) {
1080     // First try the focused child.
1081     if (focusedChild && focusedChild != browserAccessibility_) {
1082       [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
1083       return ret;
1084     }
1086     // Next try the active descendant.
1087     int activeDescendantId;
1088     if (browserAccessibility_->GetIntAttribute(
1089             ui::AX_ATTR_ACTIVEDESCENDANT_ID, &activeDescendantId)) {
1090       BrowserAccessibility* activeDescendant =
1091           manager->GetFromID(activeDescendantId);
1092       if (activeDescendant) {
1093         [ret addObject:activeDescendant->ToBrowserAccessibilityCocoa()];
1094         return ret;
1095       }
1096     }
1097   }
1099   // If it's multiselectable or if the previous attempts failed,
1100   // return any children with the "selected" state, which may
1101   // come from aria-selected.
1102   uint32 childCount = browserAccessibility_->PlatformChildCount();
1103   for (uint32 index = 0; index < childCount; ++index) {
1104     BrowserAccessibility* child =
1105       browserAccessibility_->PlatformGetChild(index);
1106     if (child->HasState(ui::AX_STATE_SELECTED))
1107       [ret addObject:child->ToBrowserAccessibilityCocoa()];
1108   }
1110   // And if nothing's selected but one has focus, use the focused one.
1111   if ([ret count] == 0 &&
1112       focusedChild &&
1113       focusedChild != browserAccessibility_) {
1114     [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
1115   }
1117   return ret;
1120 // Returns the size of this object.
1121 - (NSValue*)size {
1122   gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
1123   return  [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())];
1126 - (NSString*)sortDirection {
1127   int sortDirection;
1128   if (!browserAccessibility_->GetIntAttribute(
1129       ui::AX_ATTR_SORT_DIRECTION, &sortDirection))
1130     return @"";
1132   switch (sortDirection) {
1133   case ui::AX_SORT_DIRECTION_UNSORTED:
1134     return @"";
1135   case ui::AX_SORT_DIRECTION_ASCENDING:
1136     return @"AXSortDirectionAscending";
1137   case ui::AX_SORT_DIRECTION_DESCENDING:
1138     return @"AXSortDirectionDescending";
1139   case ui::AX_SORT_DIRECTION_OTHER:
1140     return @"AXSortDirectionUnknown";
1141   default:
1142     NOTREACHED();
1143   }
1145   return @"";
1148 // Returns a subrole based upon the role.
1149 - (NSString*) subrole {
1150   ui::AXRole browserAccessibilityRole = [self internalRole];
1151   if (browserAccessibilityRole == ui::AX_ROLE_TEXT_FIELD &&
1152       GetState(browserAccessibility_, ui::AX_STATE_PROTECTED)) {
1153     return @"AXSecureTextField";
1154   }
1156   if (browserAccessibilityRole == ui::AX_ROLE_DESCRIPTION_LIST)
1157     return @"AXDefinitionList";
1159   if (browserAccessibilityRole == ui::AX_ROLE_LIST)
1160     return @"AXContentList";
1162   return [AXPlatformNodeCocoa nativeSubroleFromAXRole:browserAccessibilityRole];
1165 // Returns all tabs in this subtree.
1166 - (NSArray*)tabs {
1167   NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
1169   if ([self internalRole] == ui::AX_ROLE_TAB)
1170     [tabSubtree addObject:self];
1172   for (uint i=0; i < [[self children] count]; ++i) {
1173     NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
1174     if ([tabChildren count] > 0)
1175       [tabSubtree addObjectsFromArray:tabChildren];
1176   }
1178   return tabSubtree;
1181 - (NSString*)title {
1182   return NSStringForStringAttribute(
1183       browserAccessibility_, ui::AX_ATTR_NAME);
1186 - (id)titleUIElement {
1187   int titleElementId;
1188   if (browserAccessibility_->GetIntAttribute(
1189           ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
1190     BrowserAccessibility* titleElement =
1191         browserAccessibility_->manager()->GetFromID(titleElementId);
1192     if (titleElement)
1193       return titleElement->ToBrowserAccessibilityCocoa();
1194   }
1195   std::vector<int32> labelledby_ids =
1196       browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
1197   if (labelledby_ids.size() == 1) {
1198     BrowserAccessibility* titleElement =
1199         browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
1200     if (titleElement)
1201       return titleElement->ToBrowserAccessibilityCocoa();
1202   }
1204   return nil;
1207 - (NSURL*)url {
1208   StringAttribute urlAttribute =
1209       [[self role] isEqualToString:@"AXWebArea"] ?
1210           ui::AX_ATTR_DOC_URL :
1211           ui::AX_ATTR_URL;
1213   std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute);
1214   if (urlStr.empty())
1215     return nil;
1217   return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))];
1220 - (id)value {
1221   // WebCore uses an attachmentView to get the below behavior.
1222   // We do not have any native views backing this object, so need
1223   // to approximate Cocoa ax behavior best as we can.
1224   NSString* role = [self role];
1225   if ([role isEqualToString:@"AXHeading"]) {
1226     int level = 0;
1227     if (browserAccessibility_->GetIntAttribute(
1228             ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) {
1229       return [NSNumber numberWithInt:level];
1230     }
1231   } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
1232     // AXValue does not make sense for pure buttons.
1233     return @"";
1234   } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) {
1235     int value = 0;
1236     bool isAriaPressedDefined;
1237     bool isMixed;
1238     value = browserAccessibility_->GetAriaTristate(
1239         "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0;
1241     if (isMixed)
1242       value = 2;
1244     return [NSNumber numberWithInt:value];
1246   } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
1247              [role isEqualToString:NSAccessibilityRadioButtonRole]) {
1248     int value = 0;
1249     value = GetState(
1250         browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0;
1251     value = GetState(
1252         browserAccessibility_, ui::AX_STATE_SELECTED) ?
1253             1 :
1254             value;
1256     if (browserAccessibility_->GetBoolAttribute(
1257         ui::AX_ATTR_BUTTON_MIXED)) {
1258       value = 2;
1259     }
1260     return [NSNumber numberWithInt:value];
1261   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1262              [role isEqualToString:NSAccessibilitySliderRole] ||
1263              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1264              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1265     float floatValue;
1266     if (browserAccessibility_->GetFloatAttribute(
1267             ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) {
1268       return [NSNumber numberWithFloat:floatValue];
1269     }
1270   } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
1271     int color = browserAccessibility_->GetIntAttribute(
1272         ui::AX_ATTR_COLOR_VALUE);
1273     int red = (color >> 16) & 0xFF;
1274     int green = (color >> 8) & 0xFF;
1275     int blue = color & 0xFF;
1276     // This string matches the one returned by a native Mac color well.
1277     return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
1278                 red / 255., green / 255., blue / 255.];
1279   }
1281   return NSStringForStringAttribute(
1282       browserAccessibility_, ui::AX_ATTR_VALUE);
1285 - (NSString*)valueDescription {
1286   return NSStringForStringAttribute(
1287       browserAccessibility_, ui::AX_ATTR_VALUE);
1290 - (NSValue*)visibleCharacterRange {
1291   std::string value = browserAccessibility_->GetStringAttribute(
1292       ui::AX_ATTR_VALUE);
1293   return [NSValue valueWithRange:NSMakeRange(0, value.size())];
1296 - (NSArray*)visibleCells {
1297   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1298   const std::vector<int32>& uniqueCellIds =
1299       browserAccessibility_->GetIntListAttribute(
1300           ui::AX_ATTR_UNIQUE_CELL_IDS);
1301   for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
1302     int id = uniqueCellIds[i];
1303     BrowserAccessibility* cell =
1304         browserAccessibility_->manager()->GetFromID(id);
1305     if (cell)
1306       [ret addObject:cell->ToBrowserAccessibilityCocoa()];
1307   }
1308   return ret;
1311 - (NSArray*)visibleChildren {
1312   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
1313   uint32 childCount = browserAccessibility_->PlatformChildCount();
1314   for (uint32 index = 0; index < childCount; ++index) {
1315     BrowserAccessibilityCocoa* child =
1316         browserAccessibility_->PlatformGetChild(index)->
1317             ToBrowserAccessibilityCocoa();
1318     [ret addObject:child];
1319   }
1320   return ret;
1323 - (NSArray*)visibleColumns {
1324   return [self columns];
1327 - (NSArray*)visibleRows {
1328   return [self rows];
1331 - (NSNumber*)visited {
1332   return [NSNumber numberWithBool:
1333       GetState(browserAccessibility_, ui::AX_STATE_VISITED)];
1336 - (id)window {
1337   if (!browserAccessibility_)
1338     return nil;
1340   BrowserAccessibilityManagerMac* manager =
1341       static_cast<BrowserAccessibilityManagerMac*>(
1342           browserAccessibility_->manager());
1343   if (!manager || !manager->parent_view())
1344     return nil;
1346   return [manager->parent_view() window];
1349 - (NSString*)methodNameForAttribute:(NSString*)attribute {
1350   return [attributeToMethodNameMap objectForKey:attribute];
1353 - (void)swapChildren:(base::scoped_nsobject<NSMutableArray>*)other {
1354   children_.swap(*other);
1357 // Returns the accessibility value for the given attribute.  If the value isn't
1358 // supported this will return nil.
1359 - (id)accessibilityAttributeValue:(NSString*)attribute {
1360   if (!browserAccessibility_)
1361     return nil;
1363   SEL selector =
1364       NSSelectorFromString([self methodNameForAttribute:attribute]);
1365   if (selector)
1366     return [self performSelector:selector];
1368   // TODO(dtseng): refactor remaining attributes.
1369   int selStart, selEnd;
1370   if (browserAccessibility_->GetIntAttribute(
1371           ui::AX_ATTR_TEXT_SEL_START, &selStart) &&
1372       browserAccessibility_->
1373           GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) {
1374     if (selStart > selEnd)
1375       std::swap(selStart, selEnd);
1376     int selLength = selEnd - selStart;
1377     if ([attribute isEqualToString:
1378         NSAccessibilityInsertionPointLineNumberAttribute]) {
1379       const std::vector<int32>& line_breaks =
1380           browserAccessibility_->GetIntListAttribute(
1381               ui::AX_ATTR_LINE_BREAKS);
1382       for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1383         if (line_breaks[i] > selStart)
1384           return [NSNumber numberWithInt:i];
1385       }
1386       return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1387     }
1388     if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
1389       base::string16 value = browserAccessibility_->GetString16Attribute(
1390           ui::AX_ATTR_VALUE);
1391       return base::SysUTF16ToNSString(value.substr(selStart, selLength));
1392     }
1393     if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1394       return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
1395     }
1396   }
1397   return nil;
1400 // Returns the accessibility value for the given attribute and parameter. If the
1401 // value isn't supported this will return nil.
1402 - (id)accessibilityAttributeValue:(NSString*)attribute
1403                      forParameter:(id)parameter {
1404   if (!browserAccessibility_)
1405     return nil;
1407   const std::vector<int32>& line_breaks =
1408       browserAccessibility_->GetIntListAttribute(
1409           ui::AX_ATTR_LINE_BREAKS);
1410   std::string value = browserAccessibility_->GetStringAttribute(
1411       ui::AX_ATTR_VALUE);
1412   int len = static_cast<int>(value.size());
1414   if ([attribute isEqualToString:
1415       NSAccessibilityStringForRangeParameterizedAttribute]) {
1416     NSRange range = [(NSValue*)parameter rangeValue];
1417     base::string16 value = browserAccessibility_->GetString16Attribute(
1418         ui::AX_ATTR_VALUE);
1419     return base::SysUTF16ToNSString(value.substr(range.location, range.length));
1420   }
1422   if ([attribute isEqualToString:
1423       NSAccessibilityLineForIndexParameterizedAttribute]) {
1424     int index = [(NSNumber*)parameter intValue];
1425     for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1426       if (line_breaks[i] > index)
1427         return [NSNumber numberWithInt:i];
1428     }
1429     return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1430   }
1432   if ([attribute isEqualToString:
1433       NSAccessibilityRangeForLineParameterizedAttribute]) {
1434     int line_index = [(NSNumber*)parameter intValue];
1435     int line_count = static_cast<int>(line_breaks.size()) + 1;
1436     if (line_index < 0 || line_index >= line_count)
1437       return nil;
1438     int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
1439     int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
1440     return [NSValue valueWithRange:
1441         NSMakeRange(start, end - start)];
1442   }
1444   if ([attribute isEqualToString:
1445       NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
1446     if ([self internalRole] != ui::AX_ROLE_TABLE &&
1447         [self internalRole] != ui::AX_ROLE_GRID) {
1448       return nil;
1449     }
1450     if (![parameter isKindOfClass:[NSArray self]])
1451       return nil;
1452     NSArray* array = parameter;
1453     int column = [[array objectAtIndex:0] intValue];
1454     int row = [[array objectAtIndex:1] intValue];
1455     int num_columns = browserAccessibility_->GetIntAttribute(
1456         ui::AX_ATTR_TABLE_COLUMN_COUNT);
1457     int num_rows = browserAccessibility_->GetIntAttribute(
1458         ui::AX_ATTR_TABLE_ROW_COUNT);
1459     if (column < 0 || column >= num_columns ||
1460         row < 0 || row >= num_rows) {
1461       return nil;
1462     }
1463     for (size_t i = 0;
1464          i < browserAccessibility_->PlatformChildCount();
1465          ++i) {
1466       BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i);
1467       if (child->GetRole() != ui::AX_ROLE_ROW)
1468         continue;
1469       int rowIndex;
1470       if (!child->GetIntAttribute(
1471               ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) {
1472         continue;
1473       }
1474       if (rowIndex < row)
1475         continue;
1476       if (rowIndex > row)
1477         break;
1478       for (size_t j = 0;
1479            j < child->PlatformChildCount();
1480            ++j) {
1481         BrowserAccessibility* cell = child->PlatformGetChild(j);
1482         if (!cell->IsCellOrTableHeaderRole())
1483           continue;
1484         int colIndex;
1485         if (!cell->GetIntAttribute(
1486                 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
1487                 &colIndex)) {
1488           continue;
1489         }
1490         if (colIndex == column)
1491           return cell->ToBrowserAccessibilityCocoa();
1492         if (colIndex > column)
1493           break;
1494       }
1495     }
1496     return nil;
1497   }
1499   if ([attribute isEqualToString:
1500       NSAccessibilityBoundsForRangeParameterizedAttribute]) {
1501     if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT)
1502       return nil;
1503     NSRange range = [(NSValue*)parameter rangeValue];
1504     gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange(
1505         range.location, range.length);
1506     NSPoint origin = NSMakePoint(rect.x(), rect.y());
1507     NSSize size = NSMakeSize(rect.width(), rect.height());
1508     NSPoint pointInScreen = [self pointInScreen:origin size:size];
1509     NSRect nsrect = NSMakeRect(
1510         pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
1511     return [NSValue valueWithRect:nsrect];
1512   }
1513   if ([attribute isEqualToString:@"AXUIElementCountForSearchPredicate"]) {
1514     OneShotAccessibilityTreeSearch search(browserAccessibility_->manager());
1515     if (InitializeAccessibilityTreeSearch(&search, parameter))
1516       return [NSNumber numberWithInt:search.CountMatches()];
1517     return nil;
1518   }
1520   if ([attribute isEqualToString:@"AXUIElementsForSearchPredicate"]) {
1521     OneShotAccessibilityTreeSearch search(browserAccessibility_->manager());
1522     if (InitializeAccessibilityTreeSearch(&search, parameter)) {
1523       size_t count = search.CountMatches();
1524       NSMutableArray* result = [NSMutableArray arrayWithCapacity:count];
1525       for (size_t i = 0; i < count; ++i) {
1526         BrowserAccessibility* match = search.GetMatchAtIndex(i);
1527         [result addObject:match->ToBrowserAccessibilityCocoa()];
1528       }
1529       return result;
1530     }
1531     return nil;
1532   }
1534   // TODO(dtseng): support the following attributes.
1535   if ([attribute isEqualTo:
1536           NSAccessibilityRangeForPositionParameterizedAttribute] ||
1537       [attribute isEqualTo:
1538           NSAccessibilityRangeForIndexParameterizedAttribute] ||
1539       [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
1540       [attribute isEqualTo:
1541           NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
1542     return nil;
1543   }
1544   return nil;
1547 // Returns an array of parameterized attributes names that this object will
1548 // respond to.
1549 - (NSArray*)accessibilityParameterizedAttributeNames {
1550   if (!browserAccessibility_)
1551     return nil;
1553   // General attributes.
1554   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1555       @"AXUIElementCountForSearchPredicate",
1556       @"AXUIElementsForSearchPredicate",
1557       nil];
1559   if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
1560       [[self role] isEqualToString:NSAccessibilityGridRole]) {
1561     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1562         NSAccessibilityCellForColumnAndRowParameterizedAttribute,
1563         nil]];
1564   }
1565   if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1566       [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
1567     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1568         NSAccessibilityLineForIndexParameterizedAttribute,
1569         NSAccessibilityRangeForLineParameterizedAttribute,
1570         NSAccessibilityStringForRangeParameterizedAttribute,
1571         NSAccessibilityRangeForPositionParameterizedAttribute,
1572         NSAccessibilityRangeForIndexParameterizedAttribute,
1573         NSAccessibilityBoundsForRangeParameterizedAttribute,
1574         NSAccessibilityRTFForRangeParameterizedAttribute,
1575         NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1576         NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1577         nil]];
1578   }
1579   if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1580     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1581         NSAccessibilityBoundsForRangeParameterizedAttribute,
1582         nil]];
1583   }
1584   return ret;
1587 // Returns an array of action names that this object will respond to.
1588 - (NSArray*)accessibilityActionNames {
1589   if (!browserAccessibility_)
1590     return nil;
1592   NSMutableArray* ret =
1593       [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1594   NSString* role = [self role];
1595   // TODO(dtseng): this should only get set when there's a default action.
1596   if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1597       ![role isEqualToString:NSAccessibilityTextFieldRole] &&
1598       ![role isEqualToString:NSAccessibilityTextAreaRole]) {
1599     [ret addObject:NSAccessibilityPressAction];
1600   }
1602   return ret;
1605 // Returns a sub-array of values for the given attribute value, starting at
1606 // index, with up to maxCount items.  If the given index is out of bounds,
1607 // or there are no values for the given attribute, it will return nil.
1608 // This method is used for querying subsets of values, without having to
1609 // return a large set of data, such as elements with a large number of
1610 // children.
1611 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1612                                         index:(NSUInteger)index
1613                                      maxCount:(NSUInteger)maxCount {
1614   if (!browserAccessibility_)
1615     return nil;
1617   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1618   if (!fullArray)
1619     return nil;
1620   NSUInteger arrayCount = [fullArray count];
1621   if (index >= arrayCount)
1622     return nil;
1623   NSRange subRange;
1624   if ((index + maxCount) > arrayCount) {
1625     subRange = NSMakeRange(index, arrayCount - index);
1626   } else {
1627     subRange = NSMakeRange(index, maxCount);
1628   }
1629   return [fullArray subarrayWithRange:subRange];
1632 // Returns the count of the specified accessibility array attribute.
1633 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1634   if (!browserAccessibility_)
1635     return 0;
1637   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1638   return [fullArray count];
1641 // Returns the list of accessibility attributes that this object supports.
1642 - (NSArray*)accessibilityAttributeNames {
1643   if (!browserAccessibility_)
1644     return nil;
1646   // General attributes.
1647   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1648       NSAccessibilityChildrenAttribute,
1649       NSAccessibilityDescriptionAttribute,
1650       NSAccessibilityEnabledAttribute,
1651       NSAccessibilityFocusedAttribute,
1652       NSAccessibilityHelpAttribute,
1653       NSAccessibilityLinkedUIElementsAttribute,
1654       NSAccessibilityParentAttribute,
1655       NSAccessibilityPositionAttribute,
1656       NSAccessibilityRoleAttribute,
1657       NSAccessibilityRoleDescriptionAttribute,
1658       NSAccessibilitySizeAttribute,
1659       NSAccessibilitySubroleAttribute,
1660       NSAccessibilityTitleAttribute,
1661       NSAccessibilityTopLevelUIElementAttribute,
1662       NSAccessibilityValueAttribute,
1663       NSAccessibilityWindowAttribute,
1664       @"AXAccessKey",
1665       @"AXInvalid",
1666       @"AXVisited",
1667       nil];
1669   // Specific role attributes.
1670   NSString* role = [self role];
1671   NSString* subrole = [self subrole];
1672   if ([role isEqualToString:NSAccessibilityTableRole] ||
1673       [role isEqualToString:NSAccessibilityGridRole]) {
1674     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1675         NSAccessibilityColumnsAttribute,
1676         NSAccessibilityVisibleColumnsAttribute,
1677         NSAccessibilityRowsAttribute,
1678         NSAccessibilityVisibleRowsAttribute,
1679         NSAccessibilityVisibleCellsAttribute,
1680         NSAccessibilityHeaderAttribute,
1681         NSAccessibilityColumnHeaderUIElementsAttribute,
1682         NSAccessibilityRowHeaderUIElementsAttribute,
1683         nil]];
1684   } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1685     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1686         NSAccessibilityIndexAttribute,
1687         NSAccessibilityHeaderAttribute,
1688         NSAccessibilityRowsAttribute,
1689         NSAccessibilityVisibleRowsAttribute,
1690         nil]];
1691   } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1692     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1693         NSAccessibilityColumnIndexRangeAttribute,
1694         NSAccessibilityRowIndexRangeAttribute,
1695         @"AXSortDirection",
1696         nil]];
1697   } else if ([role isEqualToString:@"AXWebArea"]) {
1698     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1699         @"AXLoaded",
1700         @"AXLoadingProgress",
1701         nil]];
1702   } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
1703              [role isEqualToString:NSAccessibilityTextAreaRole]) {
1704     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1705         NSAccessibilityInsertionPointLineNumberAttribute,
1706         NSAccessibilityNumberOfCharactersAttribute,
1707         NSAccessibilitySelectedTextAttribute,
1708         NSAccessibilitySelectedTextRangeAttribute,
1709         NSAccessibilityVisibleCharacterRangeAttribute,
1710         nil]];
1711   } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1712     [ret addObject:NSAccessibilityTabsAttribute];
1713   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1714              [role isEqualToString:NSAccessibilitySliderRole] ||
1715              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1716              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1717     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1718         NSAccessibilityMaxValueAttribute,
1719         NSAccessibilityMinValueAttribute,
1720         NSAccessibilityValueDescriptionAttribute,
1721         nil]];
1722   } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1723     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1724         NSAccessibilityDisclosingAttribute,
1725         NSAccessibilityDisclosedByRowAttribute,
1726         NSAccessibilityDisclosureLevelAttribute,
1727         NSAccessibilityDisclosedRowsAttribute,
1728         nil]];
1729   } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1730     if (browserAccessibility_->GetParent()) {
1731       base::string16 parentRole;
1732       browserAccessibility_->GetParent()->GetHtmlAttribute(
1733           "role", &parentRole);
1734       const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1735       if (parentRole == treegridRole) {
1736         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1737             NSAccessibilityDisclosingAttribute,
1738             NSAccessibilityDisclosedByRowAttribute,
1739             NSAccessibilityDisclosureLevelAttribute,
1740             NSAccessibilityDisclosedRowsAttribute,
1741             nil]];
1742       } else {
1743         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1744             NSAccessibilityIndexAttribute,
1745             nil]];
1746       }
1747     }
1748   } else if ([role isEqualToString:NSAccessibilityListRole]) {
1749     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1750         NSAccessibilitySelectedChildrenAttribute,
1751         NSAccessibilityVisibleChildrenAttribute,
1752         nil]];
1753   }
1755   // Add the url attribute only if it has a valid url.
1756   if ([self url] != nil) {
1757     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1758         NSAccessibilityURLAttribute,
1759         nil]];
1760   }
1762   // Position in set and Set size
1763   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_POS_IN_SET)) {
1764     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1765          @"AXARIAPosInSet",
1766          nil]];
1767   }
1768   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_SET_SIZE)) {
1769     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1770          @"AXARIASetSize",
1771          nil]];
1772   }
1774   // Live regions.
1775   if (browserAccessibility_->HasStringAttribute(
1776           ui::AX_ATTR_LIVE_STATUS)) {
1777     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1778         @"AXARIALive",
1779         nil]];
1780   }
1781   if (browserAccessibility_->HasStringAttribute(
1782           ui::AX_ATTR_LIVE_RELEVANT)) {
1783     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1784         @"AXARIARelevant",
1785         nil]];
1786   }
1787   if (browserAccessibility_->HasBoolAttribute(
1788           ui::AX_ATTR_LIVE_ATOMIC)) {
1789     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1790         @"AXARIAAtomic",
1791         nil]];
1792   }
1793   if (browserAccessibility_->HasBoolAttribute(
1794           ui::AX_ATTR_LIVE_BUSY)) {
1795     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1796         @"AXARIABusy",
1797         nil]];
1798   }
1800   std::string dropEffect;
1801   if (browserAccessibility_->GetHtmlAttribute("aria-dropeffect", &dropEffect)) {
1802     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1803         @"AXDropEffects",
1804         nil]];
1805   }
1807   std::string grabbed;
1808   if (browserAccessibility_->GetHtmlAttribute("aria-grabbed", &grabbed)) {
1809     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1810         @"AXGrabbed",
1811         nil]];
1812   }
1814   // Add expanded attribute only if it has expanded or collapsed state.
1815   if (GetState(browserAccessibility_, ui::AX_STATE_EXPANDED) ||
1816         GetState(browserAccessibility_, ui::AX_STATE_COLLAPSED)) {
1817     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1818         NSAccessibilityExpandedAttribute,
1819         nil]];
1820   }
1822   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)
1823       || GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL)) {
1824     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1825         NSAccessibilityOrientationAttribute, nil]];
1826   }
1828   if (browserAccessibility_->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER)) {
1829     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1830         NSAccessibilityPlaceholderValueAttribute, nil]];
1831   }
1833   if (GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)) {
1834     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1835         @"AXRequired", nil]];
1836   }
1838   // Title UI Element.
1839   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1840       (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1841        browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1842                             .size() == 1)) {
1843     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1844          NSAccessibilityTitleUIElementAttribute,
1845          nil]];
1846   }
1847   // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1848   // for elements which are referred to by labelledby or are labels
1850   return ret;
1853 // Returns the index of the child in this objects array of children.
1854 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1855   if (!browserAccessibility_)
1856     return 0;
1858   NSUInteger index = 0;
1859   for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1860     if ([child isEqual:childToCheck])
1861       return index;
1862     ++index;
1863   }
1864   return NSNotFound;
1867 // Returns whether or not the specified attribute can be set by the
1868 // accessibility API via |accessibilitySetValue:forAttribute:|.
1869 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1870   if (!browserAccessibility_)
1871     return NO;
1873   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1874     return GetState(browserAccessibility_,
1875         ui::AX_STATE_FOCUSABLE);
1876   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1877     return browserAccessibility_->GetBoolAttribute(
1878         ui::AX_ATTR_CAN_SET_VALUE);
1879   }
1880   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1881       ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1882        [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1883     return YES;
1885   return NO;
1888 // Returns whether or not this object should be ignored in the accessibility
1889 // tree.
1890 - (BOOL)accessibilityIsIgnored {
1891   if (!browserAccessibility_)
1892     return true;
1894   return [self isIgnored];
1897 // Performs the given accessibility action on the webkit accessibility object
1898 // that backs this object.
1899 - (void)accessibilityPerformAction:(NSString*)action {
1900   if (!browserAccessibility_)
1901     return;
1903   // TODO(dmazzoni): Support more actions.
1904   if ([action isEqualToString:NSAccessibilityPressAction]) {
1905     [self delegate]->AccessibilityDoDefaultAction(
1906         browserAccessibility_->GetId());
1907   } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1908     [self delegate]->AccessibilityShowContextMenu(
1909         browserAccessibility_->GetId());
1910   }
1913 // Returns the description of the given action.
1914 - (NSString*)accessibilityActionDescription:(NSString*)action {
1915   if (!browserAccessibility_)
1916     return nil;
1918   return NSAccessibilityActionDescription(action);
1921 // Sets an override value for a specific accessibility attribute.
1922 // This class does not support this.
1923 - (BOOL)accessibilitySetOverrideValue:(id)value
1924                          forAttribute:(NSString*)attribute {
1925   return NO;
1928 // Sets the value for an accessibility attribute via the accessibility API.
1929 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1930   if (!browserAccessibility_)
1931     return;
1933   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1934     BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1935     NSNumber* focusedNumber = value;
1936     BOOL focused = [focusedNumber intValue];
1937     if (focused)
1938       manager->SetFocus(browserAccessibility_, true);
1939   }
1940   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1941     NSRange range = [(NSValue*)value rangeValue];
1942     [self delegate]->AccessibilitySetTextSelection(
1943         browserAccessibility_->GetId(),
1944         range.location, range.location + range.length);
1945   }
1948 // Returns the deepest accessibility child that should not be ignored.
1949 // It is assumed that the hit test has been narrowed down to this object
1950 // or one of its children, so this will never return nil unless this
1951 // object is invalid.
1952 - (id)accessibilityHitTest:(NSPoint)point {
1953   if (!browserAccessibility_)
1954     return nil;
1956   BrowserAccessibilityCocoa* hit = self;
1957   for (BrowserAccessibilityCocoa* child in [self children]) {
1958     if (!child->browserAccessibility_)
1959       continue;
1960     NSPoint origin = [child origin];
1961     NSSize size = [[child size] sizeValue];
1962     NSRect rect;
1963     rect.origin = origin;
1964     rect.size = size;
1965     if (NSPointInRect(point, rect)) {
1966       hit = child;
1967       id childResult = [child accessibilityHitTest:point];
1968       if (![childResult accessibilityIsIgnored]) {
1969         hit = childResult;
1970         break;
1971       }
1972     }
1973   }
1974   return NSAccessibilityUnignoredAncestor(hit);
1977 - (BOOL)isEqual:(id)object {
1978   if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1979     return NO;
1980   return ([self hash] == [object hash]);
1983 - (NSUInteger)hash {
1984   // Potentially called during dealloc.
1985   if (!browserAccessibility_)
1986     return [super hash];
1987   return browserAccessibility_->GetId();
1990 - (BOOL)accessibilityShouldUseUniqueId {
1991   return YES;
1994 @end