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.
7 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
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/public/common/content_client.h"
19 #import "ui/accessibility/platform/ax_platform_node_mac.h"
21 // See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5,
22 // 10.6, and 10.7. It allows accessibility clients to observe events posted on
24 extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
27 using content::BrowserAccessibility;
28 using content::BrowserAccessibilityDelegate;
29 using content::BrowserAccessibilityManager;
30 using content::BrowserAccessibilityManagerMac;
31 using content::ContentClient;
32 typedef ui::AXStringAttribute StringAttribute;
36 // Returns an autoreleased copy of the AXNodeData's attribute.
37 NSString* NSStringForStringAttribute(
38 BrowserAccessibility* browserAccessibility,
39 StringAttribute attribute) {
40 return base::SysUTF8ToNSString(
41 browserAccessibility->GetStringAttribute(attribute));
44 // GetState checks the bitmask used in AXNodeData to check
45 // if the given state was set on the accessibility object.
46 bool GetState(BrowserAccessibility* accessibility, ui::AXState state) {
47 return ((accessibility->GetState() >> state) & 1);
50 // A mapping from an accessibility attribute to its method name.
51 NSDictionary* attributeToMethodNameMap = nil;
55 @implementation BrowserAccessibilityCocoa
61 } attributeToMethodNameContainer[] = {
62 { NSAccessibilityChildrenAttribute, @"children" },
63 { NSAccessibilityColumnsAttribute, @"columns" },
64 { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" },
65 { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" },
66 { NSAccessibilityContentsAttribute, @"contents" },
67 { NSAccessibilityDescriptionAttribute, @"description" },
68 { NSAccessibilityDisclosingAttribute, @"disclosing" },
69 { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" },
70 { NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel" },
71 { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" },
72 { NSAccessibilityEnabledAttribute, @"enabled" },
73 { NSAccessibilityExpandedAttribute, @"expanded" },
74 { NSAccessibilityFocusedAttribute, @"focused" },
75 { NSAccessibilityHeaderAttribute, @"header" },
76 { NSAccessibilityHelpAttribute, @"help" },
77 { NSAccessibilityIndexAttribute, @"index" },
78 { NSAccessibilityLinkedUIElementsAttribute, @"linkedUIElements" },
79 { NSAccessibilityMaxValueAttribute, @"maxValue" },
80 { NSAccessibilityMinValueAttribute, @"minValue" },
81 { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" },
82 { NSAccessibilityOrientationAttribute, @"orientation" },
83 { NSAccessibilityParentAttribute, @"parent" },
84 { NSAccessibilityPositionAttribute, @"position" },
85 { NSAccessibilityRoleAttribute, @"role" },
86 { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" },
87 { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" },
88 { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" },
89 { NSAccessibilityRowsAttribute, @"rows" },
90 // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
91 { NSAccessibilitySelectedChildrenAttribute, @"selectedChildren" },
92 { NSAccessibilitySizeAttribute, @"size" },
93 { NSAccessibilitySubroleAttribute, @"subrole" },
94 { NSAccessibilityTabsAttribute, @"tabs" },
95 { NSAccessibilityTitleAttribute, @"title" },
96 { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" },
97 { NSAccessibilityTopLevelUIElementAttribute, @"window" },
98 { NSAccessibilityURLAttribute, @"url" },
99 { NSAccessibilityValueAttribute, @"value" },
100 { NSAccessibilityValueDescriptionAttribute, @"valueDescription" },
101 { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" },
102 { NSAccessibilityVisibleCellsAttribute, @"visibleCells" },
103 { NSAccessibilityVisibleChildrenAttribute, @"visibleChildren" },
104 { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" },
105 { NSAccessibilityVisibleRowsAttribute, @"visibleRows" },
106 { NSAccessibilityWindowAttribute, @"window" },
107 { @"AXAccessKey", @"accessKey" },
108 { @"AXARIAAtomic", @"ariaAtomic" },
109 { @"AXARIABusy", @"ariaBusy" },
110 { @"AXARIALive", @"ariaLive" },
111 { @"AXARIARelevant", @"ariaRelevant" },
112 { @"AXDropEffects", @"dropeffect" },
113 { @"AXGrabbed", @"grabbed" },
114 { @"AXInvalid", @"invalid" },
115 { @"AXLoaded", @"loaded" },
116 { @"AXLoadingProgress", @"loadingProgress" },
117 { @"AXPlaceholder", @"placeholder" },
118 { @"AXRequired", @"required" },
119 { @"AXSortDirection", @"sortDirection" },
120 { @"AXVisited", @"visited" },
123 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
124 const size_t numAttributes = sizeof(attributeToMethodNameContainer) /
125 sizeof(attributeToMethodNameContainer[0]);
126 for (size_t i = 0; i < numAttributes; ++i) {
127 [dict setObject:attributeToMethodNameContainer[i].methodName
128 forKey:attributeToMethodNameContainer[i].attribute];
130 attributeToMethodNameMap = dict;
134 - (id)initWithObject:(BrowserAccessibility*)accessibility {
135 if ((self = [super init]))
136 browserAccessibility_ = accessibility;
141 if (browserAccessibility_) {
142 NSAccessibilityUnregisterUniqueIdForUIElement(self);
143 browserAccessibility_ = NULL;
147 - (NSString*)accessKey {
148 return NSStringForStringAttribute(
149 browserAccessibility_, ui::AX_ATTR_ACCESS_KEY);
152 - (NSNumber*)ariaAtomic {
153 bool boolValue = browserAccessibility_->GetBoolAttribute(
154 ui::AX_ATTR_LIVE_ATOMIC);
155 return [NSNumber numberWithBool:boolValue];
158 - (NSNumber*)ariaBusy {
159 return [NSNumber numberWithBool:
160 GetState(browserAccessibility_, ui::AX_STATE_BUSY)];
163 - (NSString*)ariaLive {
164 return NSStringForStringAttribute(
165 browserAccessibility_, ui::AX_ATTR_LIVE_STATUS);
168 - (NSString*)ariaRelevant {
169 return NSStringForStringAttribute(
170 browserAccessibility_, ui::AX_ATTR_LIVE_RELEVANT);
173 // Returns an array of BrowserAccessibilityCocoa objects, representing the
174 // accessibility children of this object.
175 - (NSArray*)children {
177 uint32 childCount = browserAccessibility_->PlatformChildCount();
178 children_.reset([[NSMutableArray alloc] initWithCapacity:childCount]);
179 for (uint32 index = 0; index < childCount; ++index) {
180 BrowserAccessibilityCocoa* child =
181 browserAccessibility_->PlatformGetChild(index)->
182 ToBrowserAccessibilityCocoa();
183 if ([child isIgnored])
184 [children_ addObjectsFromArray:[child children]];
186 [children_ addObject:child];
189 // Also, add indirect children (if any).
190 const std::vector<int32>& indirectChildIds =
191 browserAccessibility_->GetIntListAttribute(
192 ui::AX_ATTR_INDIRECT_CHILD_IDS);
193 for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
194 int32 child_id = indirectChildIds[i];
195 BrowserAccessibility* child =
196 browserAccessibility_->manager()->GetFromID(child_id);
198 // This only became necessary as a result of crbug.com/93095. It should be
199 // a DCHECK in the future.
201 BrowserAccessibilityCocoa* child_cocoa =
202 child->ToBrowserAccessibilityCocoa();
203 [children_ addObject:child_cocoa];
210 - (void)childrenChanged {
211 if (![self isIgnored]) {
214 [browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa()
219 - (NSArray*)columnHeaders {
220 if ([self internalRole] != ui::AX_ROLE_TABLE &&
221 [self internalRole] != ui::AX_ROLE_GRID) {
225 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
226 const std::vector<int32>& uniqueCellIds =
227 browserAccessibility_->GetIntListAttribute(
228 ui::AX_ATTR_UNIQUE_CELL_IDS);
229 for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
230 int id = uniqueCellIds[i];
231 BrowserAccessibility* cell =
232 browserAccessibility_->manager()->GetFromID(id);
233 if (cell && cell->GetRole() == ui::AX_ROLE_COLUMN_HEADER)
234 [ret addObject:cell->ToBrowserAccessibilityCocoa()];
239 - (NSValue*)columnIndexRange {
240 if (!browserAccessibility_->IsCellOrTableHeaderRole())
245 browserAccessibility_->GetIntAttribute(
246 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, &column);
247 browserAccessibility_->GetIntAttribute(
248 ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN, &colspan);
249 if (column >= 0 && colspan >= 1)
250 return [NSValue valueWithRange:NSMakeRange(column, colspan)];
254 - (NSArray*)columns {
255 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
256 for (BrowserAccessibilityCocoa* child in [self children]) {
257 if ([[child role] isEqualToString:NSAccessibilityColumnRole])
258 [ret addObject:child];
263 - (NSString*)description {
264 std::string description;
265 if (browserAccessibility_->GetStringAttribute(
266 ui::AX_ATTR_DESCRIPTION, &description)) {
267 return base::SysUTF8ToNSString(description);
270 // If the role is anything other than an image, or if there's
271 // a title or title UI element, just return an empty string.
272 if (![[self role] isEqualToString:NSAccessibilityImageRole])
274 if (browserAccessibility_->HasStringAttribute(
278 if ([self titleUIElement])
281 // The remaining case is an image where there's no other title.
282 // Return the base part of the filename as the description.
284 if (browserAccessibility_->GetStringAttribute(
285 ui::AX_ATTR_URL, &url)) {
286 // Given a url like http://foo.com/bar/baz.png, just return the
287 // base name, e.g., "baz.png".
288 size_t leftIndex = url.rfind('/');
289 std::string basename =
290 leftIndex != std::string::npos ? url.substr(leftIndex) : url;
291 return base::SysUTF8ToNSString(basename);
297 - (NSNumber*)disclosing {
298 if ([self internalRole] == ui::AX_ROLE_TREE_ITEM) {
299 return [NSNumber numberWithBool:
300 GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
306 - (id)disclosedByRow {
307 // The row that contains this row.
308 // It should be the same as the first parent that is a treeitem.
312 - (NSNumber*)disclosureLevel {
313 ui::AXRole role = [self internalRole];
314 if (role == ui::AX_ROLE_ROW ||
315 role == ui::AX_ROLE_TREE_ITEM) {
316 int level = browserAccessibility_->GetIntAttribute(
317 ui::AX_ATTR_HIERARCHICAL_LEVEL);
318 // Mac disclosureLevel is 0-based, but web levels are 1-based.
321 return [NSNumber numberWithInt:level];
327 - (id)disclosedRows {
328 // The rows that are considered inside this row.
332 - (NSString*)dropeffect {
333 return NSStringForStringAttribute(
334 browserAccessibility_, ui::AX_ATTR_DROPEFFECT);
337 - (NSNumber*)enabled {
338 return [NSNumber numberWithBool:
339 GetState(browserAccessibility_, ui::AX_STATE_ENABLED)];
342 - (NSNumber*)expanded {
343 return [NSNumber numberWithBool:
344 GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)];
347 - (NSNumber*)focused {
348 BrowserAccessibilityManager* manager = browserAccessibility_->manager();
349 NSNumber* ret = [NSNumber numberWithBool:
350 manager->GetFocus(NULL) == browserAccessibility_];
354 - (NSNumber*)grabbed {
355 bool boolValue = browserAccessibility_->GetBoolAttribute(ui::AX_ATTR_GRABBED);
356 return [NSNumber numberWithBool:boolValue];
360 int headerElementId = -1;
361 if ([self internalRole] == ui::AX_ROLE_TABLE ||
362 [self internalRole] == ui::AX_ROLE_GRID) {
363 browserAccessibility_->GetIntAttribute(
364 ui::AX_ATTR_TABLE_HEADER_ID, &headerElementId);
365 } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
366 browserAccessibility_->GetIntAttribute(
367 ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId);
368 } else if ([self internalRole] == ui::AX_ROLE_ROW) {
369 browserAccessibility_->GetIntAttribute(
370 ui::AX_ATTR_TABLE_ROW_HEADER_ID, &headerElementId);
373 if (headerElementId > 0) {
374 BrowserAccessibility* headerObject =
375 browserAccessibility_->manager()->GetFromID(headerElementId);
377 return headerObject->ToBrowserAccessibilityCocoa();
383 return NSStringForStringAttribute(
384 browserAccessibility_, ui::AX_ATTR_HELP);
388 if ([self internalRole] == ui::AX_ROLE_COLUMN) {
389 int columnIndex = browserAccessibility_->GetIntAttribute(
390 ui::AX_ATTR_TABLE_COLUMN_INDEX);
391 return [NSNumber numberWithInt:columnIndex];
392 } else if ([self internalRole] == ui::AX_ROLE_ROW) {
393 int rowIndex = browserAccessibility_->GetIntAttribute(
394 ui::AX_ATTR_TABLE_ROW_INDEX);
395 return [NSNumber numberWithInt:rowIndex];
401 // Returns whether or not this node should be ignored in the
402 // accessibility tree.
404 return [[self role] isEqualToString:NSAccessibilityUnknownRole];
407 - (NSString*)invalid {
409 if (!browserAccessibility_->GetIntAttribute(
410 ui::AX_ATTR_INVALID_STATE, &invalidState))
413 switch (invalidState) {
414 case ui::AX_INVALID_STATE_FALSE:
416 case ui::AX_INVALID_STATE_TRUE:
418 case ui::AX_INVALID_STATE_SPELLING:
420 case ui::AX_INVALID_STATE_GRAMMAR:
422 case ui::AX_INVALID_STATE_OTHER:
424 std::string ariaInvalidValue;
425 if (browserAccessibility_->GetStringAttribute(
426 ui::AX_ATTR_ARIA_INVALID_VALUE,
428 return base::SysUTF8ToNSString(ariaInvalidValue);
429 // Return @"true" since we cannot be more specific about the value.
439 - (NSString*)placeholder {
440 return NSStringForStringAttribute(
441 browserAccessibility_, ui::AX_ATTR_PLACEHOLDER);
444 - (void)addLinkedUIElementsFromAttribute:(ui::AXIntListAttribute)attribute
445 addTo:(NSMutableArray*)outArray {
446 const std::vector<int32>& attributeValues =
447 browserAccessibility_->GetIntListAttribute(attribute);
448 for (size_t i = 0; i < attributeValues.size(); ++i) {
449 BrowserAccessibility* element =
450 browserAccessibility_->manager()->GetFromID(attributeValues[i]);
452 [outArray addObject:element->ToBrowserAccessibilityCocoa()];
456 - (NSArray*)linkedUIElements {
457 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
458 [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_OWNS_IDS addTo:ret];
459 [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_CONTROLS_IDS addTo:ret];
460 [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_FLOWTO_IDS addTo:ret];
461 if ([ret count] == 0)
466 - (NSNumber*)loaded {
467 return [NSNumber numberWithBool:YES];
470 - (NSNumber*)loadingProgress {
471 float floatValue = browserAccessibility_->GetFloatAttribute(
472 ui::AX_ATTR_DOC_LOADING_PROGRESS);
473 return [NSNumber numberWithFloat:floatValue];
476 - (NSNumber*)maxValue {
477 float floatValue = browserAccessibility_->GetFloatAttribute(
478 ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
479 return [NSNumber numberWithFloat:floatValue];
482 - (NSNumber*)minValue {
483 float floatValue = browserAccessibility_->GetFloatAttribute(
484 ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
485 return [NSNumber numberWithFloat:floatValue];
488 - (NSString*)orientation {
489 if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL))
490 return NSAccessibilityVerticalOrientationValue;
491 else if (GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL))
492 return NSAccessibilityHorizontalOrientationValue;
497 - (NSNumber*)numberOfCharacters {
498 std::string value = browserAccessibility_->GetStringAttribute(
500 return [NSNumber numberWithInt:value.size()];
503 // The origin of this accessibility object in the page's document.
504 // This is relative to webkit's top-left origin, not Cocoa's
505 // bottom-left origin.
507 gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
508 return NSMakePoint(bounds.x(), bounds.y());
512 // A nil parent means we're the root.
513 if (browserAccessibility_->GetParent()) {
514 return NSAccessibilityUnignoredAncestor(
515 browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa());
517 // Hook back up to RenderWidgetHostViewCocoa.
518 BrowserAccessibilityManagerMac* manager =
519 static_cast<BrowserAccessibilityManagerMac*>(
520 browserAccessibility_->manager());
521 return manager->parent_view();
525 - (NSValue*)position {
526 NSPoint origin = [self origin];
527 NSSize size = [[self size] sizeValue];
528 NSPoint pointInScreen = [self pointInScreen:origin size:size];
529 return [NSValue valueWithPoint:pointInScreen];
532 - (NSNumber*)required {
533 return [NSNumber numberWithBool:
534 GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)];
537 // Returns an enum indicating the role from browserAccessibility_.
538 - (ui::AXRole)internalRole {
539 return static_cast<ui::AXRole>(browserAccessibility_->GetRole());
542 - (content::BrowserAccessibilityDelegate*)delegate {
543 return browserAccessibility_->manager() ?
544 browserAccessibility_->manager()->delegate() :
548 - (NSPoint)pointInScreen:(NSPoint)origin
550 if (!browserAccessibility_)
553 // Get the delegate for the topmost BrowserAccessibilityManager, because
554 // that's the only one that can convert points to their origin in the screen.
555 BrowserAccessibilityDelegate* delegate =
556 browserAccessibility_->manager()->GetDelegateFromRootManager();
558 gfx::Rect bounds(origin.x, origin.y, size.width, size.height);
559 gfx::Point point = delegate->AccessibilityOriginInScreen(bounds);
560 return NSMakePoint(point.x(), point.y());
566 // Returns a string indicating the NSAccessibility role of this object.
568 ui::AXRole role = [self internalRole];
569 if (role == ui::AX_ROLE_CANVAS &&
570 browserAccessibility_->GetBoolAttribute(
571 ui::AX_ATTR_CANVAS_HAS_FALLBACK)) {
572 return NSAccessibilityGroupRole;
574 if (role == ui::AX_ROLE_BUTTON || role == ui::AX_ROLE_TOGGLE_BUTTON) {
575 bool isAriaPressedDefined;
577 browserAccessibility_->GetAriaTristate("aria-pressed",
578 &isAriaPressedDefined,
580 if (isAriaPressedDefined)
581 return NSAccessibilityCheckBoxRole;
583 return NSAccessibilityButtonRole;
586 // If this is a web area for a presentational iframe, give it a role of
587 // something other than WebArea so that the fact that it's a separate doc
588 // is not exposed to AT.
589 if (browserAccessibility_->IsWebAreaForPresentationalIframe())
590 return NSAccessibilityGroupRole;
592 return [AXPlatformNodeCocoa nativeRoleFromAXRole:role];
595 // Returns a string indicating the role description of this object.
596 - (NSString*)roleDescription {
597 NSString* role = [self role];
599 ContentClient* content_client = content::GetContentClient();
601 // The following descriptions are specific to webkit.
602 if ([role isEqualToString:@"AXWebArea"]) {
603 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
604 IDS_AX_ROLE_WEB_AREA));
607 if ([role isEqualToString:@"NSAccessibilityLinkRole"]) {
608 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
612 if ([role isEqualToString:@"AXHeading"]) {
613 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
614 IDS_AX_ROLE_HEADING));
617 if (([role isEqualToString:NSAccessibilityGroupRole] ||
618 [role isEqualToString:NSAccessibilityRadioButtonRole]) &&
619 !browserAccessibility_->IsWebAreaForPresentationalIframe()) {
621 if (browserAccessibility_->GetHtmlAttribute("role", &role)) {
622 ui::AXRole internalRole = [self internalRole];
623 if ((internalRole != ui::AX_ROLE_GROUP &&
624 internalRole != ui::AX_ROLE_LIST_ITEM) ||
625 internalRole == ui::AX_ROLE_TAB) {
626 // TODO(dtseng): This is not localized; see crbug/84814.
627 return base::SysUTF8ToNSString(role);
632 switch([self internalRole]) {
633 case ui::AX_ROLE_ARTICLE:
634 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
635 IDS_AX_ROLE_ARTICLE));
636 case ui::AX_ROLE_BANNER:
637 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
638 IDS_AX_ROLE_BANNER));
639 case ui::AX_ROLE_COMPLEMENTARY:
640 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
641 IDS_AX_ROLE_COMPLEMENTARY));
642 case ui::AX_ROLE_CONTENT_INFO:
643 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
644 IDS_AX_ROLE_ADDRESS));
645 case ui::AX_ROLE_DESCRIPTION_LIST:
646 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
647 IDS_AX_ROLE_DESCRIPTION_LIST));
648 case ui::AX_ROLE_DESCRIPTION_LIST_DETAIL:
649 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
650 IDS_AX_ROLE_DESCRIPTION_DETAIL));
651 case ui::AX_ROLE_DESCRIPTION_LIST_TERM:
652 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
653 IDS_AX_ROLE_DESCRIPTION_TERM));
654 case ui::AX_ROLE_FIGURE:
655 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
656 IDS_AX_ROLE_FIGURE));
657 case ui::AX_ROLE_FOOTER:
658 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
659 IDS_AX_ROLE_FOOTER));
660 case ui::AX_ROLE_FORM:
661 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
663 case ui::AX_ROLE_MAIN:
664 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
665 IDS_AX_ROLE_MAIN_CONTENT));
666 case ui::AX_ROLE_MATH:
667 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
669 case ui::AX_ROLE_NAVIGATION:
670 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
671 IDS_AX_ROLE_NAVIGATIONAL_LINK));
672 case ui::AX_ROLE_REGION:
673 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
674 IDS_AX_ROLE_REGION));
675 case ui::AX_ROLE_SPIN_BUTTON:
676 // This control is similar to what VoiceOver calls a "stepper".
677 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
678 IDS_AX_ROLE_STEPPER));
679 case ui::AX_ROLE_STATUS:
680 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
681 IDS_AX_ROLE_STATUS));
682 case ui::AX_ROLE_TOGGLE_BUTTON:
683 return base::SysUTF16ToNSString(content_client->GetLocalizedString(
684 IDS_AX_ROLE_TOGGLE_BUTTON));
689 return NSAccessibilityRoleDescription(role, nil);
692 - (NSArray*)rowHeaders {
693 if ([self internalRole] != ui::AX_ROLE_TABLE &&
694 [self internalRole] != ui::AX_ROLE_GRID) {
698 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
699 const std::vector<int32>& uniqueCellIds =
700 browserAccessibility_->GetIntListAttribute(
701 ui::AX_ATTR_UNIQUE_CELL_IDS);
702 for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
703 int id = uniqueCellIds[i];
704 BrowserAccessibility* cell =
705 browserAccessibility_->manager()->GetFromID(id);
706 if (cell && cell->GetRole() == ui::AX_ROLE_ROW_HEADER)
707 [ret addObject:cell->ToBrowserAccessibilityCocoa()];
712 - (NSValue*)rowIndexRange {
713 if (!browserAccessibility_->IsCellOrTableHeaderRole())
718 browserAccessibility_->GetIntAttribute(
719 ui::AX_ATTR_TABLE_CELL_ROW_INDEX, &row);
720 browserAccessibility_->GetIntAttribute(
721 ui::AX_ATTR_TABLE_CELL_ROW_SPAN, &rowspan);
722 if (row >= 0 && rowspan >= 1)
723 return [NSValue valueWithRange:NSMakeRange(row, rowspan)];
728 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
730 if ([self internalRole] == ui::AX_ROLE_TABLE||
731 [self internalRole] == ui::AX_ROLE_GRID) {
732 for (BrowserAccessibilityCocoa* child in [self children]) {
733 if ([[child role] isEqualToString:NSAccessibilityRowRole])
734 [ret addObject:child];
736 } else if ([self internalRole] == ui::AX_ROLE_COLUMN) {
737 const std::vector<int32>& indirectChildIds =
738 browserAccessibility_->GetIntListAttribute(
739 ui::AX_ATTR_INDIRECT_CHILD_IDS);
740 for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
741 int id = indirectChildIds[i];
742 BrowserAccessibility* rowElement =
743 browserAccessibility_->manager()->GetFromID(id);
745 [ret addObject:rowElement->ToBrowserAccessibilityCocoa()];
752 - (NSArray*)selectedChildren {
753 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
755 BrowserAccessibilityManager* manager = browserAccessibility_->manager();
756 BrowserAccessibility* focusedChild =
757 manager->GetFocus(browserAccessibility_);
758 if (focusedChild && focusedChild != browserAccessibility_) {
759 // First try the focused child.
760 [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()];
762 // Next try the active descendant.
763 int activeDescendantId;
764 if (browserAccessibility_->GetIntAttribute(
765 ui::AX_ATTR_ACTIVEDESCENDANT_ID, &activeDescendantId)) {
766 BrowserAccessibility* activeDescendant =
767 manager->GetFromID(activeDescendantId);
768 if (activeDescendant)
769 [ret addObject:activeDescendant->ToBrowserAccessibilityCocoa()];
771 // Otherwise return any children with the "selected" state, which
772 // may come from aria-selected.
773 uint32 childCount = browserAccessibility_->PlatformChildCount();
774 for (uint32 index = 0; index < childCount; ++index) {
775 BrowserAccessibility* child =
776 browserAccessibility_->PlatformGetChild(index);
777 if (child->HasState(ui::AX_STATE_SELECTED))
778 [ret addObject:child->ToBrowserAccessibilityCocoa()];
786 // Returns the size of this object.
788 gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
789 return [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())];
792 - (NSString*)sortDirection {
794 if (!browserAccessibility_->GetIntAttribute(
795 ui::AX_ATTR_SORT_DIRECTION, &sortDirection))
798 switch (sortDirection) {
799 case ui::AX_SORT_DIRECTION_UNSORTED:
801 case ui::AX_SORT_DIRECTION_ASCENDING:
802 return @"AXSortDirectionAscending";
803 case ui::AX_SORT_DIRECTION_DESCENDING:
804 return @"AXSortDirectionDescending";
805 case ui::AX_SORT_DIRECTION_OTHER:
806 return @"AXSortDirectionUnknown";
814 // Returns a subrole based upon the role.
815 - (NSString*) subrole {
816 ui::AXRole browserAccessibilityRole = [self internalRole];
817 if (browserAccessibilityRole == ui::AX_ROLE_TEXT_FIELD &&
818 GetState(browserAccessibility_, ui::AX_STATE_PROTECTED)) {
819 return @"AXSecureTextField";
822 if (browserAccessibilityRole == ui::AX_ROLE_DESCRIPTION_LIST)
823 return @"AXDefinitionList";
825 if (browserAccessibilityRole == ui::AX_ROLE_LIST)
826 return @"AXContentList";
828 return [AXPlatformNodeCocoa nativeSubroleFromAXRole:browserAccessibilityRole];
831 // Returns all tabs in this subtree.
833 NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
835 if ([self internalRole] == ui::AX_ROLE_TAB)
836 [tabSubtree addObject:self];
838 for (uint i=0; i < [[self children] count]; ++i) {
839 NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
840 if ([tabChildren count] > 0)
841 [tabSubtree addObjectsFromArray:tabChildren];
848 return NSStringForStringAttribute(
849 browserAccessibility_, ui::AX_ATTR_NAME);
852 - (id)titleUIElement {
854 if (browserAccessibility_->GetIntAttribute(
855 ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
856 BrowserAccessibility* titleElement =
857 browserAccessibility_->manager()->GetFromID(titleElementId);
859 return titleElement->ToBrowserAccessibilityCocoa();
861 std::vector<int32> labelledby_ids =
862 browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
863 if (labelledby_ids.size() == 1) {
864 BrowserAccessibility* titleElement =
865 browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
867 return titleElement->ToBrowserAccessibilityCocoa();
874 StringAttribute urlAttribute =
875 [[self role] isEqualToString:@"AXWebArea"] ?
876 ui::AX_ATTR_DOC_URL :
879 std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute);
883 return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))];
887 // WebCore uses an attachmentView to get the below behavior.
888 // We do not have any native views backing this object, so need
889 // to approximate Cocoa ax behavior best as we can.
890 NSString* role = [self role];
891 if ([role isEqualToString:@"AXHeading"]) {
893 if (browserAccessibility_->GetIntAttribute(
894 ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) {
895 return [NSNumber numberWithInt:level];
897 } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
898 // AXValue does not make sense for pure buttons.
900 } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) {
902 bool isAriaPressedDefined;
904 value = browserAccessibility_->GetAriaTristate(
905 "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0;
910 return [NSNumber numberWithInt:value];
912 } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
913 [role isEqualToString:NSAccessibilityRadioButtonRole]) {
916 browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0;
918 browserAccessibility_, ui::AX_STATE_SELECTED) ?
922 if (browserAccessibility_->GetBoolAttribute(
923 ui::AX_ATTR_BUTTON_MIXED)) {
926 return [NSNumber numberWithInt:value];
927 } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
928 [role isEqualToString:NSAccessibilitySliderRole] ||
929 [role isEqualToString:NSAccessibilityIncrementorRole] ||
930 [role isEqualToString:NSAccessibilityScrollBarRole]) {
932 if (browserAccessibility_->GetFloatAttribute(
933 ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) {
934 return [NSNumber numberWithFloat:floatValue];
936 } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
937 int r = browserAccessibility_->GetIntAttribute(
938 ui::AX_ATTR_COLOR_VALUE_RED);
939 int g = browserAccessibility_->GetIntAttribute(
940 ui::AX_ATTR_COLOR_VALUE_GREEN);
941 int b = browserAccessibility_->GetIntAttribute(
942 ui::AX_ATTR_COLOR_VALUE_BLUE);
943 // This string matches the one returned by a native Mac color well.
944 return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
945 r / 255., g / 255., b / 255.];
948 return NSStringForStringAttribute(
949 browserAccessibility_, ui::AX_ATTR_VALUE);
952 - (NSString*)valueDescription {
953 return NSStringForStringAttribute(
954 browserAccessibility_, ui::AX_ATTR_VALUE);
957 - (NSValue*)visibleCharacterRange {
958 std::string value = browserAccessibility_->GetStringAttribute(
960 return [NSValue valueWithRange:NSMakeRange(0, value.size())];
963 - (NSArray*)visibleCells {
964 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
965 const std::vector<int32>& uniqueCellIds =
966 browserAccessibility_->GetIntListAttribute(
967 ui::AX_ATTR_UNIQUE_CELL_IDS);
968 for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
969 int id = uniqueCellIds[i];
970 BrowserAccessibility* cell =
971 browserAccessibility_->manager()->GetFromID(id);
973 [ret addObject:cell->ToBrowserAccessibilityCocoa()];
978 - (NSArray*)visibleChildren {
979 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
980 uint32 childCount = browserAccessibility_->PlatformChildCount();
981 for (uint32 index = 0; index < childCount; ++index) {
982 BrowserAccessibilityCocoa* child =
983 browserAccessibility_->PlatformGetChild(index)->
984 ToBrowserAccessibilityCocoa();
985 [ret addObject:child];
990 - (NSArray*)visibleColumns {
991 return [self columns];
994 - (NSArray*)visibleRows {
998 - (NSNumber*)visited {
999 return [NSNumber numberWithBool:
1000 GetState(browserAccessibility_, ui::AX_STATE_VISITED)];
1004 if (!browserAccessibility_)
1007 BrowserAccessibilityManagerMac* manager =
1008 static_cast<BrowserAccessibilityManagerMac*>(
1009 browserAccessibility_->manager());
1010 return [manager->parent_view() window];
1013 - (NSString*)methodNameForAttribute:(NSString*)attribute {
1014 return [attributeToMethodNameMap objectForKey:attribute];
1017 - (void)swapChildren:(base::scoped_nsobject<NSMutableArray>*)other {
1018 children_.swap(*other);
1021 // Returns the accessibility value for the given attribute. If the value isn't
1022 // supported this will return nil.
1023 - (id)accessibilityAttributeValue:(NSString*)attribute {
1024 if (!browserAccessibility_)
1028 NSSelectorFromString([self methodNameForAttribute:attribute]);
1030 return [self performSelector:selector];
1032 // TODO(dtseng): refactor remaining attributes.
1033 int selStart, selEnd;
1034 if (browserAccessibility_->GetIntAttribute(
1035 ui::AX_ATTR_TEXT_SEL_START, &selStart) &&
1036 browserAccessibility_->
1037 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) {
1038 if (selStart > selEnd)
1039 std::swap(selStart, selEnd);
1040 int selLength = selEnd - selStart;
1041 if ([attribute isEqualToString:
1042 NSAccessibilityInsertionPointLineNumberAttribute]) {
1043 const std::vector<int32>& line_breaks =
1044 browserAccessibility_->GetIntListAttribute(
1045 ui::AX_ATTR_LINE_BREAKS);
1046 for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1047 if (line_breaks[i] > selStart)
1048 return [NSNumber numberWithInt:i];
1050 return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1052 if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
1053 std::string value = browserAccessibility_->GetStringAttribute(
1055 return base::SysUTF8ToNSString(value.substr(selStart, selLength));
1057 if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1058 return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
1064 // Returns the accessibility value for the given attribute and parameter. If the
1065 // value isn't supported this will return nil.
1066 - (id)accessibilityAttributeValue:(NSString*)attribute
1067 forParameter:(id)parameter {
1068 if (!browserAccessibility_)
1071 const std::vector<int32>& line_breaks =
1072 browserAccessibility_->GetIntListAttribute(
1073 ui::AX_ATTR_LINE_BREAKS);
1074 std::string value = browserAccessibility_->GetStringAttribute(
1076 int len = static_cast<int>(value.size());
1078 if ([attribute isEqualToString:
1079 NSAccessibilityStringForRangeParameterizedAttribute]) {
1080 NSRange range = [(NSValue*)parameter rangeValue];
1081 std::string value = browserAccessibility_->GetStringAttribute(
1083 return base::SysUTF8ToNSString(value.substr(range.location, range.length));
1086 if ([attribute isEqualToString:
1087 NSAccessibilityLineForIndexParameterizedAttribute]) {
1088 int index = [(NSNumber*)parameter intValue];
1089 for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
1090 if (line_breaks[i] > index)
1091 return [NSNumber numberWithInt:i];
1093 return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
1096 if ([attribute isEqualToString:
1097 NSAccessibilityRangeForLineParameterizedAttribute]) {
1098 int line_index = [(NSNumber*)parameter intValue];
1099 int line_count = static_cast<int>(line_breaks.size()) + 1;
1100 if (line_index < 0 || line_index >= line_count)
1102 int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
1103 int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
1104 return [NSValue valueWithRange:
1105 NSMakeRange(start, end - start)];
1108 if ([attribute isEqualToString:
1109 NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
1110 if ([self internalRole] != ui::AX_ROLE_TABLE &&
1111 [self internalRole] != ui::AX_ROLE_GRID) {
1114 if (![parameter isKindOfClass:[NSArray self]])
1116 NSArray* array = parameter;
1117 int column = [[array objectAtIndex:0] intValue];
1118 int row = [[array objectAtIndex:1] intValue];
1119 int num_columns = browserAccessibility_->GetIntAttribute(
1120 ui::AX_ATTR_TABLE_COLUMN_COUNT);
1121 int num_rows = browserAccessibility_->GetIntAttribute(
1122 ui::AX_ATTR_TABLE_ROW_COUNT);
1123 if (column < 0 || column >= num_columns ||
1124 row < 0 || row >= num_rows) {
1128 i < browserAccessibility_->PlatformChildCount();
1130 BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i);
1131 if (child->GetRole() != ui::AX_ROLE_ROW)
1134 if (!child->GetIntAttribute(
1135 ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) {
1143 j < child->PlatformChildCount();
1145 BrowserAccessibility* cell = child->PlatformGetChild(j);
1146 if (!browserAccessibility_->IsCellOrTableHeaderRole())
1149 if (!cell->GetIntAttribute(
1150 ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
1154 if (colIndex == column)
1155 return cell->ToBrowserAccessibilityCocoa();
1156 if (colIndex > column)
1163 if ([attribute isEqualToString:
1164 NSAccessibilityBoundsForRangeParameterizedAttribute]) {
1165 if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT)
1167 NSRange range = [(NSValue*)parameter rangeValue];
1168 gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange(
1169 range.location, range.length);
1170 NSPoint origin = NSMakePoint(rect.x(), rect.y());
1171 NSSize size = NSMakeSize(rect.width(), rect.height());
1172 NSPoint pointInScreen = [self pointInScreen:origin size:size];
1173 NSRect nsrect = NSMakeRect(
1174 pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
1175 return [NSValue valueWithRect:nsrect];
1178 // TODO(dtseng): support the following attributes.
1179 if ([attribute isEqualTo:
1180 NSAccessibilityRangeForPositionParameterizedAttribute] ||
1181 [attribute isEqualTo:
1182 NSAccessibilityRangeForIndexParameterizedAttribute] ||
1183 [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
1184 [attribute isEqualTo:
1185 NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
1191 // Returns an array of parameterized attributes names that this object will
1193 - (NSArray*)accessibilityParameterizedAttributeNames {
1194 if (!browserAccessibility_)
1197 if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
1198 [[self role] isEqualToString:NSAccessibilityGridRole]) {
1199 return [NSArray arrayWithObjects:
1200 NSAccessibilityCellForColumnAndRowParameterizedAttribute,
1203 if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1204 [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
1205 return [NSArray arrayWithObjects:
1206 NSAccessibilityLineForIndexParameterizedAttribute,
1207 NSAccessibilityRangeForLineParameterizedAttribute,
1208 NSAccessibilityStringForRangeParameterizedAttribute,
1209 NSAccessibilityRangeForPositionParameterizedAttribute,
1210 NSAccessibilityRangeForIndexParameterizedAttribute,
1211 NSAccessibilityBoundsForRangeParameterizedAttribute,
1212 NSAccessibilityRTFForRangeParameterizedAttribute,
1213 NSAccessibilityAttributedStringForRangeParameterizedAttribute,
1214 NSAccessibilityStyleRangeForIndexParameterizedAttribute,
1217 if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
1218 return [NSArray arrayWithObjects:
1219 NSAccessibilityBoundsForRangeParameterizedAttribute,
1225 // Returns an array of action names that this object will respond to.
1226 - (NSArray*)accessibilityActionNames {
1227 if (!browserAccessibility_)
1230 NSMutableArray* ret =
1231 [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
1232 NSString* role = [self role];
1233 // TODO(dtseng): this should only get set when there's a default action.
1234 if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
1235 ![role isEqualToString:NSAccessibilityTextAreaRole] &&
1236 ![role isEqualToString:NSAccessibilityTextFieldRole]) {
1237 [ret addObject:NSAccessibilityPressAction];
1243 // Returns a sub-array of values for the given attribute value, starting at
1244 // index, with up to maxCount items. If the given index is out of bounds,
1245 // or there are no values for the given attribute, it will return nil.
1246 // This method is used for querying subsets of values, without having to
1247 // return a large set of data, such as elements with a large number of
1249 - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
1250 index:(NSUInteger)index
1251 maxCount:(NSUInteger)maxCount {
1252 if (!browserAccessibility_)
1255 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1258 NSUInteger arrayCount = [fullArray count];
1259 if (index >= arrayCount)
1262 if ((index + maxCount) > arrayCount) {
1263 subRange = NSMakeRange(index, arrayCount - index);
1265 subRange = NSMakeRange(index, maxCount);
1267 return [fullArray subarrayWithRange:subRange];
1270 // Returns the count of the specified accessibility array attribute.
1271 - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
1272 if (!browserAccessibility_)
1275 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
1276 return [fullArray count];
1279 // Returns the list of accessibility attributes that this object supports.
1280 - (NSArray*)accessibilityAttributeNames {
1281 if (!browserAccessibility_)
1284 // General attributes.
1285 NSMutableArray* ret = [NSMutableArray arrayWithObjects:
1286 NSAccessibilityChildrenAttribute,
1287 NSAccessibilityDescriptionAttribute,
1288 NSAccessibilityEnabledAttribute,
1289 NSAccessibilityFocusedAttribute,
1290 NSAccessibilityHelpAttribute,
1291 NSAccessibilityLinkedUIElementsAttribute,
1292 NSAccessibilityParentAttribute,
1293 NSAccessibilityPositionAttribute,
1294 NSAccessibilityRoleAttribute,
1295 NSAccessibilityRoleDescriptionAttribute,
1296 NSAccessibilitySizeAttribute,
1297 NSAccessibilitySubroleAttribute,
1298 NSAccessibilityTitleAttribute,
1299 NSAccessibilityTopLevelUIElementAttribute,
1300 NSAccessibilityValueAttribute,
1301 NSAccessibilityWindowAttribute,
1307 // Specific role attributes.
1308 NSString* role = [self role];
1309 NSString* subrole = [self subrole];
1310 if ([role isEqualToString:NSAccessibilityTableRole] ||
1311 [role isEqualToString:NSAccessibilityGridRole]) {
1312 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1313 NSAccessibilityColumnsAttribute,
1314 NSAccessibilityVisibleColumnsAttribute,
1315 NSAccessibilityRowsAttribute,
1316 NSAccessibilityVisibleRowsAttribute,
1317 NSAccessibilityVisibleCellsAttribute,
1318 NSAccessibilityHeaderAttribute,
1319 NSAccessibilityColumnHeaderUIElementsAttribute,
1320 NSAccessibilityRowHeaderUIElementsAttribute,
1322 } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
1323 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1324 NSAccessibilityIndexAttribute,
1325 NSAccessibilityHeaderAttribute,
1326 NSAccessibilityRowsAttribute,
1327 NSAccessibilityVisibleRowsAttribute,
1329 } else if ([role isEqualToString:NSAccessibilityCellRole]) {
1330 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1331 NSAccessibilityColumnIndexRangeAttribute,
1332 NSAccessibilityRowIndexRangeAttribute,
1335 } else if ([role isEqualToString:@"AXWebArea"]) {
1336 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1338 @"AXLoadingProgress",
1340 } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
1341 [role isEqualToString:NSAccessibilityTextAreaRole]) {
1342 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1343 NSAccessibilityInsertionPointLineNumberAttribute,
1344 NSAccessibilityNumberOfCharactersAttribute,
1345 NSAccessibilitySelectedTextAttribute,
1346 NSAccessibilitySelectedTextRangeAttribute,
1347 NSAccessibilityVisibleCharacterRangeAttribute,
1349 } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
1350 [ret addObject:NSAccessibilityTabsAttribute];
1351 } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
1352 [role isEqualToString:NSAccessibilitySliderRole] ||
1353 [role isEqualToString:NSAccessibilityIncrementorRole] ||
1354 [role isEqualToString:NSAccessibilityScrollBarRole]) {
1355 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1356 NSAccessibilityMaxValueAttribute,
1357 NSAccessibilityMinValueAttribute,
1358 NSAccessibilityValueDescriptionAttribute,
1360 } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
1361 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1362 NSAccessibilityDisclosingAttribute,
1363 NSAccessibilityDisclosedByRowAttribute,
1364 NSAccessibilityDisclosureLevelAttribute,
1365 NSAccessibilityDisclosedRowsAttribute,
1367 } else if ([role isEqualToString:NSAccessibilityRowRole]) {
1368 if (browserAccessibility_->GetParent()) {
1369 base::string16 parentRole;
1370 browserAccessibility_->GetParent()->GetHtmlAttribute(
1371 "role", &parentRole);
1372 const base::string16 treegridRole(base::ASCIIToUTF16("treegrid"));
1373 if (parentRole == treegridRole) {
1374 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1375 NSAccessibilityDisclosingAttribute,
1376 NSAccessibilityDisclosedByRowAttribute,
1377 NSAccessibilityDisclosureLevelAttribute,
1378 NSAccessibilityDisclosedRowsAttribute,
1381 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1382 NSAccessibilityIndexAttribute,
1386 } else if ([role isEqualToString:NSAccessibilityListRole]) {
1387 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1388 NSAccessibilitySelectedChildrenAttribute,
1389 NSAccessibilityVisibleChildrenAttribute,
1393 // Add the url attribute only if it has a valid url.
1394 if ([self url] != nil) {
1395 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1396 NSAccessibilityURLAttribute,
1401 if (browserAccessibility_->HasStringAttribute(
1402 ui::AX_ATTR_LIVE_STATUS)) {
1403 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1407 if (browserAccessibility_->HasStringAttribute(
1408 ui::AX_ATTR_LIVE_RELEVANT)) {
1409 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1413 if (browserAccessibility_->HasBoolAttribute(
1414 ui::AX_ATTR_LIVE_ATOMIC)) {
1415 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1419 if (browserAccessibility_->HasBoolAttribute(
1420 ui::AX_ATTR_LIVE_BUSY)) {
1421 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1426 if (browserAccessibility_->HasStringAttribute(
1427 ui::AX_ATTR_DROPEFFECT)) {
1428 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1433 // Add aria-grabbed attribute only if it has true.
1434 if (browserAccessibility_->HasBoolAttribute(ui::AX_ATTR_GRABBED)) {
1435 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1440 // Add expanded attribute only if it has expanded or collapsed state.
1441 if (GetState(browserAccessibility_, ui::AX_STATE_EXPANDED) ||
1442 GetState(browserAccessibility_, ui::AX_STATE_COLLAPSED)) {
1443 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1444 NSAccessibilityExpandedAttribute,
1448 if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)
1449 || GetState(browserAccessibility_, ui::AX_STATE_HORIZONTAL)) {
1450 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1451 NSAccessibilityOrientationAttribute, nil]];
1454 if (browserAccessibility_->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER)) {
1455 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1456 @"AXPlaceholder", nil]];
1459 if (GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)) {
1460 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1461 @"AXRequired", nil]];
1464 // Title UI Element.
1465 if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) ||
1466 (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) &&
1467 browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS)
1469 [ret addObjectsFromArray:[NSArray arrayWithObjects:
1470 NSAccessibilityTitleUIElementAttribute,
1473 // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute
1474 // for elements which are referred to by labelledby or are labels
1479 // Returns the index of the child in this objects array of children.
1480 - (NSUInteger)accessibilityGetIndexOf:(id)child {
1481 if (!browserAccessibility_)
1484 NSUInteger index = 0;
1485 for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
1486 if ([child isEqual:childToCheck])
1493 // Returns whether or not the specified attribute can be set by the
1494 // accessibility API via |accessibilitySetValue:forAttribute:|.
1495 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
1496 if (!browserAccessibility_)
1499 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
1500 return GetState(browserAccessibility_,
1501 ui::AX_STATE_FOCUSABLE);
1502 if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
1503 return browserAccessibility_->GetBoolAttribute(
1504 ui::AX_ATTR_CAN_SET_VALUE);
1506 if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
1507 ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
1508 [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
1514 // Returns whether or not this object should be ignored in the accessibility
1516 - (BOOL)accessibilityIsIgnored {
1517 if (!browserAccessibility_)
1520 return [self isIgnored];
1523 // Performs the given accessibility action on the webkit accessibility object
1524 // that backs this object.
1525 - (void)accessibilityPerformAction:(NSString*)action {
1526 if (!browserAccessibility_)
1529 // TODO(dmazzoni): Support more actions.
1530 if ([action isEqualToString:NSAccessibilityPressAction]) {
1531 [self delegate]->AccessibilityDoDefaultAction(
1532 browserAccessibility_->GetId());
1533 } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
1534 NSPoint objOrigin = [self origin];
1535 NSSize size = [[self size] sizeValue];
1536 gfx::Point origin = [self delegate]->AccessibilityOriginInScreen(
1537 gfx::Rect(objOrigin.x, objOrigin.y, size.width, size.height));
1538 origin.Offset(size.width / 2, size.height / 2);
1539 [self delegate]->AccessibilityShowMenu(origin);
1543 // Returns the description of the given action.
1544 - (NSString*)accessibilityActionDescription:(NSString*)action {
1545 if (!browserAccessibility_)
1548 return NSAccessibilityActionDescription(action);
1551 // Sets an override value for a specific accessibility attribute.
1552 // This class does not support this.
1553 - (BOOL)accessibilitySetOverrideValue:(id)value
1554 forAttribute:(NSString*)attribute {
1558 // Sets the value for an accessibility attribute via the accessibility API.
1559 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
1560 if (!browserAccessibility_)
1563 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
1564 BrowserAccessibilityManager* manager = browserAccessibility_->manager();
1565 NSNumber* focusedNumber = value;
1566 BOOL focused = [focusedNumber intValue];
1568 manager->SetFocus(browserAccessibility_, true);
1570 if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
1571 NSRange range = [(NSValue*)value rangeValue];
1572 [self delegate]->AccessibilitySetTextSelection(
1573 browserAccessibility_->GetId(),
1574 range.location, range.location + range.length);
1578 // Returns the deepest accessibility child that should not be ignored.
1579 // It is assumed that the hit test has been narrowed down to this object
1580 // or one of its children, so this will never return nil unless this
1581 // object is invalid.
1582 - (id)accessibilityHitTest:(NSPoint)point {
1583 if (!browserAccessibility_)
1586 BrowserAccessibilityCocoa* hit = self;
1587 for (BrowserAccessibilityCocoa* child in [self children]) {
1588 if (!child->browserAccessibility_)
1590 NSPoint origin = [child origin];
1591 NSSize size = [[child size] sizeValue];
1593 rect.origin = origin;
1595 if (NSPointInRect(point, rect)) {
1597 id childResult = [child accessibilityHitTest:point];
1598 if (![childResult accessibilityIsIgnored]) {
1604 return NSAccessibilityUnignoredAncestor(hit);
1607 - (BOOL)isEqual:(id)object {
1608 if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
1610 return ([self hash] == [object hash]);
1613 - (NSUInteger)hash {
1614 // Potentially called during dealloc.
1615 if (!browserAccessibility_)
1616 return [super hash];
1617 return browserAccessibility_->GetId();
1620 - (BOOL)accessibilityShouldUseUniqueId {