Clean up check for dependency_info.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_cocoa.mm
blobf59dde4d0bafc2598a73cf3ca981423246aa4495
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 (browserAccessibility_->IsEditableText()) {
1566     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1567         NSAccessibilityLineForIndexParameterizedAttribute,
1568         NSAccessibilityRangeForLineParameterizedAttribute,
1569         NSAccessibilityStringForRangeParameterizedAttribute,
1570         NSAccessibilityRangeForPositionParameterizedAttribute,
1571         NSAccessibilityRangeForIndexParameterizedAttribute,
1572         NSAccessibilityBoundsForRangeParameterizedAttribute,
1573         NSAccessibilityRTFForRangeParameterizedAttribute,
1574         NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1575         NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1576         nil]];
1577   }
1578   if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1579     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1580         NSAccessibilityBoundsForRangeParameterizedAttribute,
1581         nil]];
1582   }
1583   return ret;
1586 // Returns an array of action names that this object will respond to.
1587 - (NSArray*)accessibilityActionNames {
1588   if (!browserAccessibility_)
1589     return nil;
1591   NSMutableArray* ret =
1592       [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1593   NSString* role = [self role];
1594   // TODO(dtseng): this should only get set when there's a default action.
1595   if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1596       ![role isEqualToString:NSAccessibilityTextFieldRole] &&
1597       ![role isEqualToString:NSAccessibilityTextAreaRole]) {
1598     [ret addObject:NSAccessibilityPressAction];
1599   }
1601   return ret;
1604 // Returns a sub-array of values for the given attribute value, starting at
1605 // index, with up to maxCount items.  If the given index is out of bounds,
1606 // or there are no values for the given attribute, it will return nil.
1607 // This method is used for querying subsets of values, without having to
1608 // return a large set of data, such as elements with a large number of
1609 // children.
1610 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1611                                         index:(NSUInteger)index
1612                                      maxCount:(NSUInteger)maxCount {
1613   if (!browserAccessibility_)
1614     return nil;
1616   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1617   if (!fullArray)
1618     return nil;
1619   NSUInteger arrayCount = [fullArray count];
1620   if (index >= arrayCount)
1621     return nil;
1622   NSRange subRange;
1623   if ((index + maxCount) > arrayCount) {
1624     subRange = NSMakeRange(index, arrayCount - index);
1625   } else {
1626     subRange = NSMakeRange(index, maxCount);
1627   }
1628   return [fullArray subarrayWithRange:subRange];
1631 // Returns the count of the specified accessibility array attribute.
1632 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1633   if (!browserAccessibility_)
1634     return 0;
1636   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1637   return [fullArray count];
1640 // Returns the list of accessibility attributes that this object supports.
1641 - (NSArray*)accessibilityAttributeNames {
1642   if (!browserAccessibility_)
1643     return nil;
1645   // General attributes.
1646   NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1647       NSAccessibilityChildrenAttribute,
1648       NSAccessibilityDescriptionAttribute,
1649       NSAccessibilityEnabledAttribute,
1650       NSAccessibilityFocusedAttribute,
1651       NSAccessibilityHelpAttribute,
1652       NSAccessibilityLinkedUIElementsAttribute,
1653       NSAccessibilityParentAttribute,
1654       NSAccessibilityPositionAttribute,
1655       NSAccessibilityRoleAttribute,
1656       NSAccessibilityRoleDescriptionAttribute,
1657       NSAccessibilitySizeAttribute,
1658       NSAccessibilitySubroleAttribute,
1659       NSAccessibilityTitleAttribute,
1660       NSAccessibilityTopLevelUIElementAttribute,
1661       NSAccessibilityValueAttribute,
1662       NSAccessibilityWindowAttribute,
1663       @"AXAccessKey",
1664       @"AXInvalid",
1665       @"AXVisited",
1666       nil];
1668   // Specific role attributes.
1669   NSString* role = [self role];
1670   NSString* subrole = [self subrole];
1671   if ([role isEqualToString:NSAccessibilityTableRole] ||
1672       [role isEqualToString:NSAccessibilityGridRole]) {
1673     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1674         NSAccessibilityColumnsAttribute,
1675         NSAccessibilityVisibleColumnsAttribute,
1676         NSAccessibilityRowsAttribute,
1677         NSAccessibilityVisibleRowsAttribute,
1678         NSAccessibilityVisibleCellsAttribute,
1679         NSAccessibilityHeaderAttribute,
1680         NSAccessibilityColumnHeaderUIElementsAttribute,
1681         NSAccessibilityRowHeaderUIElementsAttribute,
1682         nil]];
1683   } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1684     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1685         NSAccessibilityIndexAttribute,
1686         NSAccessibilityHeaderAttribute,
1687         NSAccessibilityRowsAttribute,
1688         NSAccessibilityVisibleRowsAttribute,
1689         nil]];
1690   } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1691     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1692         NSAccessibilityColumnIndexRangeAttribute,
1693         NSAccessibilityRowIndexRangeAttribute,
1694         @"AXSortDirection",
1695         nil]];
1696   } else if ([role isEqualToString:@"AXWebArea"]) {
1697     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1698         @"AXLoaded",
1699         @"AXLoadingProgress",
1700         nil]];
1701   } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1702     [ret addObject:NSAccessibilityTabsAttribute];
1703   } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1704              [role isEqualToString:NSAccessibilitySliderRole] ||
1705              [role isEqualToString:NSAccessibilityIncrementorRole] ||
1706              [role isEqualToString:NSAccessibilityScrollBarRole]) {
1707     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1708         NSAccessibilityMaxValueAttribute,
1709         NSAccessibilityMinValueAttribute,
1710         NSAccessibilityValueDescriptionAttribute,
1711         nil]];
1712   } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1713     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1714         NSAccessibilityDisclosingAttribute,
1715         NSAccessibilityDisclosedByRowAttribute,
1716         NSAccessibilityDisclosureLevelAttribute,
1717         NSAccessibilityDisclosedRowsAttribute,
1718         nil]];
1719   } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1720     if (browserAccessibility_->GetParent()) {
1721       base::string16 parentRole;
1722       browserAccessibility_->GetParent()->GetHtmlAttribute(
1723           "role", &parentRole);
1724       const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1725       if (parentRole == treegridRole) {
1726         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1727             NSAccessibilityDisclosingAttribute,
1728             NSAccessibilityDisclosedByRowAttribute,
1729             NSAccessibilityDisclosureLevelAttribute,
1730             NSAccessibilityDisclosedRowsAttribute,
1731             nil]];
1732       } else {
1733         [ret addObjectsFromArray:[NSArray arrayWithObjects:
1734             NSAccessibilityIndexAttribute,
1735             nil]];
1736       }
1737     }
1738   } else if ([role isEqualToString:NSAccessibilityListRole]) {
1739     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1740         NSAccessibilitySelectedChildrenAttribute,
1741         NSAccessibilityVisibleChildrenAttribute,
1742         nil]];
1743   }
1745   // Caret navigation and text selection attributes.
1746   if (browserAccessibility_->IsEditableText()) {
1747     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1748         NSAccessibilityInsertionPointLineNumberAttribute,
1749         NSAccessibilityNumberOfCharactersAttribute,
1750         NSAccessibilitySelectedTextAttribute,
1751         NSAccessibilitySelectedTextRangeAttribute,
1752         NSAccessibilityVisibleCharacterRangeAttribute,
1753         nil]];
1754   }
1756   // Add the url attribute only if it has a valid url.
1757   if ([self url] != nil) {
1758     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1759         NSAccessibilityURLAttribute,
1760         nil]];
1761   }
1763   // Position in set and Set size
1764   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_POS_IN_SET)) {
1765     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1766          @"AXARIAPosInSet",
1767          nil]];
1768   }
1769   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_SET_SIZE)) {
1770     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1771          @"AXARIASetSize",
1772          nil]];
1773   }
1775   // Live regions.
1776   if (browserAccessibility_->HasStringAttribute(
1777           ui::AX_ATTR_LIVE_STATUS)) {
1778     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1779         @"AXARIALive",
1780         nil]];
1781   }
1782   if (browserAccessibility_->HasStringAttribute(
1783           ui::AX_ATTR_LIVE_RELEVANT)) {
1784     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1785         @"AXARIARelevant",
1786         nil]];
1787   }
1788   if (browserAccessibility_->HasBoolAttribute(
1789           ui::AX_ATTR_LIVE_ATOMIC)) {
1790     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1791         @"AXARIAAtomic",
1792         nil]];
1793   }
1794   if (browserAccessibility_->HasBoolAttribute(
1795           ui::AX_ATTR_LIVE_BUSY)) {
1796     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1797         @"AXARIABusy",
1798         nil]];
1799   }
1801   std::string dropEffect;
1802   if (browserAccessibility_->GetHtmlAttribute("aria-dropeffect", &dropEffect)) {
1803     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1804         @"AXDropEffects",
1805         nil]];
1806   }
1808   std::string grabbed;
1809   if (browserAccessibility_->GetHtmlAttribute("aria-grabbed", &grabbed)) {
1810     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1811         @"AXGrabbed",
1812         nil]];
1813   }
1815   // Add expanded attribute only if it has expanded or collapsed state.
1816   if (GetState(browserAccessibility_, ui::AX_STATE_EXPANDED) ||
1817         GetState(browserAccessibility_, ui::AX_STATE_COLLAPSED)) {
1818     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1819         NSAccessibilityExpandedAttribute,
1820         nil]];
1821   }
1823   if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)
1824       || GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL)) {
1825     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1826         NSAccessibilityOrientationAttribute, nil]];
1827   }
1829   if (browserAccessibility_->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER)) {
1830     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1831         NSAccessibilityPlaceholderValueAttribute, nil]];
1832   }
1834   if (GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)) {
1835     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1836         @"AXRequired", nil]];
1837   }
1839   // Title UI Element.
1840   if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1841       (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1842        browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1843                             .size() == 1)) {
1844     [ret addObjectsFromArray:[NSArray arrayWithObjects:
1845          NSAccessibilityTitleUIElementAttribute,
1846          nil]];
1847   }
1848   // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1849   // for elements which are referred to by labelledby or are labels
1851   return ret;
1854 // Returns the index of the child in this objects array of children.
1855 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1856   if (!browserAccessibility_)
1857     return 0;
1859   NSUInteger index = 0;
1860   for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1861     if ([child isEqual:childToCheck])
1862       return index;
1863     ++index;
1864   }
1865   return NSNotFound;
1868 // Returns whether or not the specified attribute can be set by the
1869 // accessibility API via |accessibilitySetValue:forAttribute:|.
1870 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1871   if (!browserAccessibility_)
1872     return NO;
1874   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1875     return GetState(browserAccessibility_,
1876         ui::AX_STATE_FOCUSABLE);
1877   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1878     return browserAccessibility_->GetBoolAttribute(
1879         ui::AX_ATTR_CAN_SET_VALUE);
1880   }
1881   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1882       ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1883        [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1884     return YES;
1886   return NO;
1889 // Returns whether or not this object should be ignored in the accessibility
1890 // tree.
1891 - (BOOL)accessibilityIsIgnored {
1892   if (!browserAccessibility_)
1893     return true;
1895   return [self isIgnored];
1898 // Performs the given accessibility action on the webkit accessibility object
1899 // that backs this object.
1900 - (void)accessibilityPerformAction:(NSString*)action {
1901   if (!browserAccessibility_)
1902     return;
1904   // TODO(dmazzoni): Support more actions.
1905   if ([action isEqualToString:NSAccessibilityPressAction]) {
1906     [self delegate]->AccessibilityDoDefaultAction(
1907         browserAccessibility_->GetId());
1908   } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1909     [self delegate]->AccessibilityShowContextMenu(
1910         browserAccessibility_->GetId());
1911   }
1914 // Returns the description of the given action.
1915 - (NSString*)accessibilityActionDescription:(NSString*)action {
1916   if (!browserAccessibility_)
1917     return nil;
1919   return NSAccessibilityActionDescription(action);
1922 // Sets an override value for a specific accessibility attribute.
1923 // This class does not support this.
1924 - (BOOL)accessibilitySetOverrideValue:(id)value
1925                          forAttribute:(NSString*)attribute {
1926   return NO;
1929 // Sets the value for an accessibility attribute via the accessibility API.
1930 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1931   if (!browserAccessibility_)
1932     return;
1934   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1935     BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1936     NSNumber* focusedNumber = value;
1937     BOOL focused = [focusedNumber intValue];
1938     if (focused)
1939       manager->SetFocus(browserAccessibility_, true);
1940   }
1941   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1942     NSRange range = [(NSValue*)value rangeValue];
1943     [self delegate]->AccessibilitySetTextSelection(
1944         browserAccessibility_->GetId(),
1945         range.location, range.location + range.length);
1946   }
1949 // Returns the deepest accessibility child that should not be ignored.
1950 // It is assumed that the hit test has been narrowed down to this object
1951 // or one of its children, so this will never return nil unless this
1952 // object is invalid.
1953 - (id)accessibilityHitTest:(NSPoint)point {
1954   if (!browserAccessibility_)
1955     return nil;
1957   BrowserAccessibilityCocoa* hit = self;
1958   for (BrowserAccessibilityCocoa* child in [self children]) {
1959     if (!child->browserAccessibility_)
1960       continue;
1961     NSPoint origin = [child origin];
1962     NSSize size = [[child size] sizeValue];
1963     NSRect rect;
1964     rect.origin = origin;
1965     rect.size = size;
1966     if (NSPointInRect(point, rect)) {
1967       hit = child;
1968       id childResult = [child accessibilityHitTest:point];
1969       if (![childResult accessibilityIsIgnored]) {
1970         hit = childResult;
1971         break;
1972       }
1973     }
1974   }
1975   return NSAccessibilityUnignoredAncestor(hit);
1978 - (BOOL)isEqual:(id)object {
1979   if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1980     return NO;
1981   return ([self hash] == [object hash]);
1984 - (NSUInteger)hash {
1985   // Potentially called during dealloc.
1986   if (!browserAccessibility_)
1987     return [super hash];
1988   return browserAccessibility_->GetId();
1991 - (BOOL)accessibilityShouldUseUniqueId {
1992   return YES;
1995 @end