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