// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <execinfo.h> #import "content/browser/accessibility/browser_accessibility_cocoa.h" #include <map> #include "base/basictypes.h" #include "base/strings/string16.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/accessibility/browser_accessibility_manager.h" #include "content/browser/accessibility/browser_accessibility_manager_mac.h" #include "content/public/common/content_client.h" #include "grit/webkit_strings.h" // See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5, // 10.6, and 10.7. It allows accessibility clients to observe events posted on // this object. extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element); using ui::AXNodeData; using content::BrowserAccessibility; using content::BrowserAccessibilityManager; using content::BrowserAccessibilityManagerMac; using content::ContentClient; typedef ui::AXStringAttribute StringAttribute; namespace { // Returns an autoreleased copy of the AXNodeData's attribute. NSString* NSStringForStringAttribute( BrowserAccessibility* browserAccessibility, StringAttribute attribute) { return base::SysUTF8ToNSString( browserAccessibility->GetStringAttribute(attribute)); } struct MapEntry { ui::AXRole webKitValue; NSString* nativeValue; }; typedef std::map<ui::AXRole, NSString*> RoleMap; // GetState checks the bitmask used in AXNodeData to check // if the given state was set on the accessibility object. bool GetState(BrowserAccessibility* accessibility, ui::AXState state) { return ((accessibility->GetState() >> state) & 1); } RoleMap BuildRoleMap() { const MapEntry roles[] = { { ui::AX_ROLE_ALERT, NSAccessibilityGroupRole }, { ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole }, { ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole }, { ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole }, { ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole }, { ui::AX_ROLE_BANNER, NSAccessibilityGroupRole }, { ui::AX_ROLE_BROWSER, NSAccessibilityBrowserRole }, { ui::AX_ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole }, { ui::AX_ROLE_BUTTON, NSAccessibilityButtonRole }, { ui::AX_ROLE_CANVAS, NSAccessibilityImageRole }, { ui::AX_ROLE_CELL, @"AXCell" }, { ui::AX_ROLE_CHECK_BOX, NSAccessibilityCheckBoxRole }, { ui::AX_ROLE_COLOR_WELL, NSAccessibilityColorWellRole }, { ui::AX_ROLE_COLUMN, NSAccessibilityColumnRole }, { ui::AX_ROLE_COLUMN_HEADER, @"AXCell" }, { ui::AX_ROLE_COMBO_BOX, NSAccessibilityComboBoxRole }, { ui::AX_ROLE_COMPLEMENTARY, NSAccessibilityGroupRole }, { ui::AX_ROLE_CONTENT_INFO, NSAccessibilityGroupRole }, { ui::AX_ROLE_DEFINITION, NSAccessibilityGroupRole }, { ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, NSAccessibilityGroupRole }, { ui::AX_ROLE_DESCRIPTION_LIST_TERM, NSAccessibilityGroupRole }, { ui::AX_ROLE_DIALOG, NSAccessibilityGroupRole }, { ui::AX_ROLE_DIRECTORY, NSAccessibilityListRole }, { ui::AX_ROLE_DISCLOSURE_TRIANGLE, NSAccessibilityDisclosureTriangleRole }, { ui::AX_ROLE_DIV, NSAccessibilityGroupRole }, { ui::AX_ROLE_DOCUMENT, NSAccessibilityGroupRole }, { ui::AX_ROLE_DRAWER, NSAccessibilityDrawerRole }, { ui::AX_ROLE_EDITABLE_TEXT, NSAccessibilityTextFieldRole }, { ui::AX_ROLE_FOOTER, NSAccessibilityGroupRole }, { ui::AX_ROLE_FORM, NSAccessibilityGroupRole }, { ui::AX_ROLE_GRID, NSAccessibilityGridRole }, { ui::AX_ROLE_GROUP, NSAccessibilityGroupRole }, { ui::AX_ROLE_GROW_AREA, NSAccessibilityGrowAreaRole }, { ui::AX_ROLE_HEADING, @"AXHeading" }, { ui::AX_ROLE_HELP_TAG, NSAccessibilityHelpTagRole }, { ui::AX_ROLE_HORIZONTAL_RULE, NSAccessibilityGroupRole }, { ui::AX_ROLE_IFRAME, NSAccessibilityGroupRole }, { ui::AX_ROLE_IGNORED, NSAccessibilityUnknownRole }, { ui::AX_ROLE_IMAGE, NSAccessibilityImageRole }, { ui::AX_ROLE_IMAGE_MAP, NSAccessibilityGroupRole }, { ui::AX_ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole }, { ui::AX_ROLE_INCREMENTOR, NSAccessibilityIncrementorRole }, { ui::AX_ROLE_LABEL_TEXT, NSAccessibilityGroupRole }, { ui::AX_ROLE_LINK, NSAccessibilityLinkRole }, { ui::AX_ROLE_LIST, NSAccessibilityListRole }, { ui::AX_ROLE_LIST_BOX, NSAccessibilityListRole }, { ui::AX_ROLE_LIST_BOX_OPTION, NSAccessibilityStaticTextRole }, { ui::AX_ROLE_LIST_ITEM, NSAccessibilityGroupRole }, { ui::AX_ROLE_LIST_MARKER, @"AXListMarker" }, { ui::AX_ROLE_LOG, NSAccessibilityGroupRole }, { ui::AX_ROLE_MAIN, NSAccessibilityGroupRole }, { ui::AX_ROLE_MARQUEE, NSAccessibilityGroupRole }, { ui::AX_ROLE_MATH, NSAccessibilityGroupRole }, { ui::AX_ROLE_MATTE, NSAccessibilityMatteRole }, { ui::AX_ROLE_MENU, NSAccessibilityMenuRole }, { ui::AX_ROLE_MENU_BAR, NSAccessibilityMenuBarRole }, { ui::AX_ROLE_MENU_BUTTON, NSAccessibilityButtonRole }, { ui::AX_ROLE_MENU_ITEM, NSAccessibilityMenuItemRole }, { ui::AX_ROLE_MENU_LIST_OPTION, NSAccessibilityMenuItemRole }, { ui::AX_ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole }, { ui::AX_ROLE_NAVIGATION, NSAccessibilityGroupRole }, { ui::AX_ROLE_NOTE, NSAccessibilityGroupRole }, { ui::AX_ROLE_OUTLINE, NSAccessibilityOutlineRole }, { ui::AX_ROLE_PARAGRAPH, NSAccessibilityGroupRole }, { ui::AX_ROLE_POP_UP_BUTTON, NSAccessibilityPopUpButtonRole }, { ui::AX_ROLE_PRESENTATIONAL, NSAccessibilityGroupRole }, { ui::AX_ROLE_PROGRESS_INDICATOR, NSAccessibilityProgressIndicatorRole }, { ui::AX_ROLE_RADIO_BUTTON, NSAccessibilityRadioButtonRole }, { ui::AX_ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole }, { ui::AX_ROLE_REGION, NSAccessibilityGroupRole }, { ui::AX_ROLE_ROOT_WEB_AREA, @"AXWebArea" }, { ui::AX_ROLE_ROW, NSAccessibilityRowRole }, { ui::AX_ROLE_ROW_HEADER, @"AXCell" }, { ui::AX_ROLE_RULER, NSAccessibilityRulerRole }, { ui::AX_ROLE_RULER_MARKER, NSAccessibilityRulerMarkerRole }, { ui::AX_ROLE_SCROLL_BAR, NSAccessibilityScrollBarRole }, { ui::AX_ROLE_SEARCH, NSAccessibilityGroupRole }, { ui::AX_ROLE_SHEET, NSAccessibilitySheetRole }, { ui::AX_ROLE_SLIDER, NSAccessibilitySliderRole }, { ui::AX_ROLE_SLIDER_THUMB, NSAccessibilityValueIndicatorRole }, { ui::AX_ROLE_SPIN_BUTTON, NSAccessibilitySliderRole }, { ui::AX_ROLE_SPLITTER, NSAccessibilitySplitterRole }, { ui::AX_ROLE_SPLIT_GROUP, NSAccessibilitySplitGroupRole }, { ui::AX_ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole }, { ui::AX_ROLE_STATUS, NSAccessibilityGroupRole }, { ui::AX_ROLE_SVG_ROOT, NSAccessibilityGroupRole }, { ui::AX_ROLE_SYSTEM_WIDE, NSAccessibilityUnknownRole }, { ui::AX_ROLE_TAB, NSAccessibilityRadioButtonRole }, { ui::AX_ROLE_TABLE, NSAccessibilityTableRole }, { ui::AX_ROLE_TABLE_HEADER_CONTAINER, NSAccessibilityGroupRole }, { ui::AX_ROLE_TAB_LIST, NSAccessibilityTabGroupRole }, { ui::AX_ROLE_TAB_PANEL, NSAccessibilityGroupRole }, { ui::AX_ROLE_TEXT_AREA, NSAccessibilityTextAreaRole }, { ui::AX_ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole }, { ui::AX_ROLE_TIMER, NSAccessibilityGroupRole }, { ui::AX_ROLE_TOGGLE_BUTTON, NSAccessibilityCheckBoxRole }, { ui::AX_ROLE_TOOLBAR, NSAccessibilityToolbarRole }, { ui::AX_ROLE_TOOLTIP, NSAccessibilityGroupRole }, { ui::AX_ROLE_TREE, NSAccessibilityOutlineRole }, { ui::AX_ROLE_TREE_GRID, NSAccessibilityTableRole }, { ui::AX_ROLE_TREE_ITEM, NSAccessibilityRowRole }, { ui::AX_ROLE_VALUE_INDICATOR, NSAccessibilityValueIndicatorRole }, { ui::AX_ROLE_WEB_AREA, @"AXWebArea" }, { ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole }, // TODO(dtseng): we don't correctly support the attributes for these roles. // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, }; RoleMap role_map; for (size_t i = 0; i < arraysize(roles); ++i) role_map[roles[i].webKitValue] = roles[i].nativeValue; return role_map; } // A mapping of webkit roles to native roles. NSString* NativeRoleFromAXRole( const ui::AXRole& role) { CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_role, (BuildRoleMap())); RoleMap::iterator it = web_accessibility_to_native_role.find(role); if (it != web_accessibility_to_native_role.end()) return it->second; else return NSAccessibilityUnknownRole; } RoleMap BuildSubroleMap() { const MapEntry subroles[] = { { ui::AX_ROLE_ALERT, @"AXApplicationAlert" }, { ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog" }, { ui::AX_ROLE_ARTICLE, @"AXDocumentArticle" }, { ui::AX_ROLE_DEFINITION, @"AXDefinition" }, { ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDescription" }, { ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm" }, { ui::AX_ROLE_DIALOG, @"AXApplicationDialog" }, { ui::AX_ROLE_DOCUMENT, @"AXDocument" }, { ui::AX_ROLE_FOOTER, @"AXLandmarkContentInfo" }, { ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication" }, { ui::AX_ROLE_BANNER, @"AXLandmarkBanner" }, { ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary" }, { ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo" }, { ui::AX_ROLE_MAIN, @"AXLandmarkMain" }, { ui::AX_ROLE_NAVIGATION, @"AXLandmarkNavigation" }, { ui::AX_ROLE_SEARCH, @"AXLandmarkSearch" }, { ui::AX_ROLE_LOG, @"AXApplicationLog" }, { ui::AX_ROLE_MARQUEE, @"AXApplicationMarquee" }, { ui::AX_ROLE_MATH, @"AXDocumentMath" }, { ui::AX_ROLE_NOTE, @"AXDocumentNote" }, { ui::AX_ROLE_REGION, @"AXDocumentRegion" }, { ui::AX_ROLE_STATUS, @"AXApplicationStatus" }, { ui::AX_ROLE_TAB_PANEL, @"AXTabPanel" }, { ui::AX_ROLE_TIMER, @"AXApplicationTimer" }, { ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton" }, { ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip" }, { ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole }, }; RoleMap subrole_map; for (size_t i = 0; i < arraysize(subroles); ++i) subrole_map[subroles[i].webKitValue] = subroles[i].nativeValue; return subrole_map; } // A mapping of webkit roles to native subroles. NSString* NativeSubroleFromAXRole( const ui::AXRole& role) { CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_subrole, (BuildSubroleMap())); RoleMap::iterator it = web_accessibility_to_native_subrole.find(role); if (it != web_accessibility_to_native_subrole.end()) return it->second; else return nil; } // A mapping from an accessibility attribute to its method name. NSDictionary* attributeToMethodNameMap = nil; } // namespace @implementation BrowserAccessibilityCocoa + (void)initialize { const struct { NSString* attribute; NSString* methodName; } attributeToMethodNameContainer[] = { { NSAccessibilityChildrenAttribute, @"children" }, { NSAccessibilityColumnsAttribute, @"columns" }, { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" }, { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" }, { NSAccessibilityContentsAttribute, @"contents" }, { NSAccessibilityDescriptionAttribute, @"description" }, { NSAccessibilityDisclosingAttribute, @"disclosing" }, { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" }, { NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel" }, { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" }, { NSAccessibilityEnabledAttribute, @"enabled" }, { NSAccessibilityFocusedAttribute, @"focused" }, { NSAccessibilityHeaderAttribute, @"header" }, { NSAccessibilityHelpAttribute, @"help" }, { NSAccessibilityIndexAttribute, @"index" }, { NSAccessibilityLinkedUIElementsAttribute, @"linkedUIElements" }, { NSAccessibilityMaxValueAttribute, @"maxValue" }, { NSAccessibilityMinValueAttribute, @"minValue" }, { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" }, { NSAccessibilityOrientationAttribute, @"orientation" }, { NSAccessibilityParentAttribute, @"parent" }, { NSAccessibilityPositionAttribute, @"position" }, { NSAccessibilityRoleAttribute, @"role" }, { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" }, { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" }, { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" }, { NSAccessibilityRowsAttribute, @"rows" }, // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute { NSAccessibilitySizeAttribute, @"size" }, { NSAccessibilitySubroleAttribute, @"subrole" }, { NSAccessibilityTabsAttribute, @"tabs" }, { NSAccessibilityTitleAttribute, @"title" }, { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" }, { NSAccessibilityTopLevelUIElementAttribute, @"window" }, { NSAccessibilityURLAttribute, @"url" }, { NSAccessibilityValueAttribute, @"value" }, { NSAccessibilityValueDescriptionAttribute, @"valueDescription" }, { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" }, { NSAccessibilityVisibleCellsAttribute, @"visibleCells" }, { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" }, { NSAccessibilityVisibleRowsAttribute, @"visibleRows" }, { NSAccessibilityWindowAttribute, @"window" }, { @"AXAccessKey", @"accessKey" }, { @"AXARIAAtomic", @"ariaAtomic" }, { @"AXARIABusy", @"ariaBusy" }, { @"AXARIALive", @"ariaLive" }, { @"AXARIARelevant", @"ariaRelevant" }, { @"AXInvalid", @"invalid" }, { @"AXLoaded", @"loaded" }, { @"AXLoadingProgress", @"loadingProgress" }, { @"AXRequired", @"required" }, { @"AXVisited", @"visited" }, }; NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; const size_t numAttributes = sizeof(attributeToMethodNameContainer) / sizeof(attributeToMethodNameContainer[0]); for (size_t i = 0; i < numAttributes; ++i) { [dict setObject:attributeToMethodNameContainer[i].methodName forKey:attributeToMethodNameContainer[i].attribute]; } attributeToMethodNameMap = dict; dict = nil; } - (id)initWithObject:(BrowserAccessibility*)accessibility { if ((self = [super init])) browserAccessibility_ = accessibility; return self; } - (void)detach { if (browserAccessibility_) { NSAccessibilityUnregisterUniqueIdForUIElement(self); browserAccessibility_ = NULL; } } - (NSString*)accessKey { return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_ACCESS_KEY); } - (NSNumber*)ariaAtomic { bool boolValue = browserAccessibility_->GetBoolAttribute( ui::AX_ATTR_LIVE_ATOMIC); return [NSNumber numberWithBool:boolValue]; } - (NSNumber*)ariaBusy { bool boolValue = browserAccessibility_->GetBoolAttribute( ui::AX_ATTR_LIVE_BUSY); return [NSNumber numberWithBool:boolValue]; } - (NSString*)ariaLive { return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_LIVE_STATUS); } - (NSString*)ariaRelevant { return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_LIVE_RELEVANT); } // Returns an array of BrowserAccessibilityCocoa objects, representing the // accessibility children of this object. - (NSArray*)children { if (!children_) { uint32 childCount = browserAccessibility_->PlatformChildCount(); children_.reset([[NSMutableArray alloc] initWithCapacity:childCount]); for (uint32 index = 0; index < childCount; ++index) { BrowserAccessibilityCocoa* child = browserAccessibility_->PlatformGetChild(index)-> ToBrowserAccessibilityCocoa(); if ([child isIgnored]) [children_ addObjectsFromArray:[child children]]; else [children_ addObject:child]; } // Also, add indirect children (if any). const std::vector<int32>& indirectChildIds = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_INDIRECT_CHILD_IDS); for (uint32 i = 0; i < indirectChildIds.size(); ++i) { int32 child_id = indirectChildIds[i]; BrowserAccessibility* child = browserAccessibility_->manager()->GetFromID(child_id); // This only became necessary as a result of crbug.com/93095. It should be // a DCHECK in the future. if (child) { BrowserAccessibilityCocoa* child_cocoa = child->ToBrowserAccessibilityCocoa(); [children_ addObject:child_cocoa]; } } } return children_; } - (void)childrenChanged { if (![self isIgnored]) { children_.reset(); } else { [browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa() childrenChanged]; } } - (NSArray*)columnHeaders { if ([self internalRole] != ui::AX_ROLE_TABLE && [self internalRole] != ui::AX_ROLE_GRID) { return nil; } NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; const std::vector<int32>& uniqueCellIds = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_UNIQUE_CELL_IDS); for (size_t i = 0; i < uniqueCellIds.size(); ++i) { int id = uniqueCellIds[i]; BrowserAccessibility* cell = browserAccessibility_->manager()->GetFromID(id); if (cell && cell->GetRole() == ui::AX_ROLE_COLUMN_HEADER) [ret addObject:cell->ToBrowserAccessibilityCocoa()]; } return ret; } - (NSValue*)columnIndexRange { if ([self internalRole] != ui::AX_ROLE_CELL) return nil; int column = -1; int colspan = -1; browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, &column); browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN, &colspan); if (column >= 0 && colspan >= 1) return [NSValue valueWithRange:NSMakeRange(column, colspan)]; return nil; } - (NSArray*)columns { NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; for (BrowserAccessibilityCocoa* child in [self children]) { if ([[child role] isEqualToString:NSAccessibilityColumnRole]) [ret addObject:child]; } return ret; } - (NSString*)description { std::string description; if (browserAccessibility_->GetStringAttribute( ui::AX_ATTR_DESCRIPTION, &description)) { return base::SysUTF8ToNSString(description); } // If the role is anything other than an image, or if there's // a title or title UI element, just return an empty string. if (![[self role] isEqualToString:NSAccessibilityImageRole]) return @""; if (browserAccessibility_->HasStringAttribute( ui::AX_ATTR_NAME)) { return @""; } if ([self titleUIElement]) return @""; // The remaining case is an image where there's no other title. // Return the base part of the filename as the description. std::string url; if (browserAccessibility_->GetStringAttribute( ui::AX_ATTR_URL, &url)) { // Given a url like http://foo.com/bar/baz.png, just return the // base name, e.g., "baz.png". size_t leftIndex = url.rfind('/'); std::string basename = leftIndex != std::string::npos ? url.substr(leftIndex) : url; return base::SysUTF8ToNSString(basename); } return @""; } - (NSNumber*)disclosing { if ([self internalRole] == ui::AX_ROLE_TREE_ITEM) { return [NSNumber numberWithBool: GetState(browserAccessibility_, ui::AX_STATE_EXPANDED)]; } else { return nil; } } - (id)disclosedByRow { // The row that contains this row. // It should be the same as the first parent that is a treeitem. return nil; } - (NSNumber*)disclosureLevel { ui::AXRole role = [self internalRole]; if (role == ui::AX_ROLE_ROW || role == ui::AX_ROLE_TREE_ITEM) { int level = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_HIERARCHICAL_LEVEL); // Mac disclosureLevel is 0-based, but web levels are 1-based. if (level > 0) level--; return [NSNumber numberWithInt:level]; } else { return nil; } } - (id)disclosedRows { // The rows that are considered inside this row. return nil; } - (NSNumber*)enabled { return [NSNumber numberWithBool: GetState(browserAccessibility_, ui::AX_STATE_ENABLED)]; } - (NSNumber*)focused { BrowserAccessibilityManager* manager = browserAccessibility_->manager(); NSNumber* ret = [NSNumber numberWithBool: manager->GetFocus(NULL) == browserAccessibility_]; return ret; } - (id)header { int headerElementId = -1; if ([self internalRole] == ui::AX_ROLE_TABLE || [self internalRole] == ui::AX_ROLE_GRID) { browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_HEADER_ID, &headerElementId); } else if ([self internalRole] == ui::AX_ROLE_COLUMN) { browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId); } else if ([self internalRole] == ui::AX_ROLE_ROW) { browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_ROW_HEADER_ID, &headerElementId); } if (headerElementId > 0) { BrowserAccessibility* headerObject = browserAccessibility_->manager()->GetFromID(headerElementId); if (headerObject) return headerObject->ToBrowserAccessibilityCocoa(); } return nil; } - (NSString*)help { return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_HELP); } - (NSNumber*)index { if ([self internalRole] == ui::AX_ROLE_COLUMN) { int columnIndex = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_COLUMN_INDEX); return [NSNumber numberWithInt:columnIndex]; } else if ([self internalRole] == ui::AX_ROLE_ROW) { int rowIndex = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_ROW_INDEX); return [NSNumber numberWithInt:rowIndex]; } return nil; } // Returns whether or not this node should be ignored in the // accessibility tree. - (BOOL)isIgnored { return [[self role] isEqualToString:NSAccessibilityUnknownRole]; } - (NSString*)invalid { base::string16 invalidUTF; if (!browserAccessibility_->GetHtmlAttribute("aria-invalid", &invalidUTF)) return NULL; NSString* invalid = base::SysUTF16ToNSString(invalidUTF); if ([invalid isEqualToString:@"false"] || [invalid isEqualToString:@""]) { return @"false"; } return invalid; } - (void)addLinkedUIElementsFromAttribute:(ui::AXIntListAttribute)attribute addTo:(NSMutableArray*)outArray { const std::vector<int32>& attributeValues = browserAccessibility_->GetIntListAttribute(attribute); for (size_t i = 0; i < attributeValues.size(); ++i) { BrowserAccessibility* element = browserAccessibility_->manager()->GetFromID(attributeValues[i]); if (element) [outArray addObject:element->ToBrowserAccessibilityCocoa()]; } } - (NSArray*)linkedUIElements { NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_OWNS_IDS addTo:ret]; [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_CONTROLS_IDS addTo:ret]; [self addLinkedUIElementsFromAttribute:ui::AX_ATTR_FLOWTO_IDS addTo:ret]; if ([ret count] == 0) return nil; return ret; } - (NSNumber*)loaded { return [NSNumber numberWithBool:YES]; } - (NSNumber*)loadingProgress { float floatValue = browserAccessibility_->GetFloatAttribute( ui::AX_ATTR_DOC_LOADING_PROGRESS); return [NSNumber numberWithFloat:floatValue]; } - (NSNumber*)maxValue { float floatValue = browserAccessibility_->GetFloatAttribute( ui::AX_ATTR_MAX_VALUE_FOR_RANGE); return [NSNumber numberWithFloat:floatValue]; } - (NSNumber*)minValue { float floatValue = browserAccessibility_->GetFloatAttribute( ui::AX_ATTR_MIN_VALUE_FOR_RANGE); return [NSNumber numberWithFloat:floatValue]; } - (NSString*)orientation { // We present a spin button as a vertical slider, with a role description // of "spin button". if ([self internalRole] == ui::AX_ROLE_SPIN_BUTTON) return NSAccessibilityVerticalOrientationValue; if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)) return NSAccessibilityVerticalOrientationValue; else return NSAccessibilityHorizontalOrientationValue; } - (NSNumber*)numberOfCharacters { return [NSNumber numberWithInt:browserAccessibility_->value().length()]; } // The origin of this accessibility object in the page's document. // This is relative to webkit's top-left origin, not Cocoa's // bottom-left origin. - (NSPoint)origin { gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect(); return NSMakePoint(bounds.x(), bounds.y()); } - (id)parent { // A nil parent means we're the root. if (browserAccessibility_->GetParent()) { return NSAccessibilityUnignoredAncestor( browserAccessibility_->GetParent()->ToBrowserAccessibilityCocoa()); } else { // Hook back up to RenderWidgetHostViewCocoa. BrowserAccessibilityManagerMac* manager = static_cast<BrowserAccessibilityManagerMac*>( browserAccessibility_->manager()); return manager->parent_view(); } } - (NSValue*)position { NSPoint origin = [self origin]; NSSize size = [[self size] sizeValue]; NSPoint pointInScreen = [self pointInScreen:origin size:size]; return [NSValue valueWithPoint:pointInScreen]; } - (NSNumber*)required { return [NSNumber numberWithBool: GetState(browserAccessibility_, ui::AX_STATE_REQUIRED)]; } // Returns an enum indicating the role from browserAccessibility_. - (ui::AXRole)internalRole { return static_cast<ui::AXRole>(browserAccessibility_->GetRole()); } - (content::BrowserAccessibilityDelegate*)delegate { return browserAccessibility_->manager() ? browserAccessibility_->manager()->delegate() : nil; } - (NSPoint)pointInScreen:(NSPoint)origin size:(NSSize)size { if (!browserAccessibility_) return NSZeroPoint; gfx::Rect bounds(origin.x, origin.y, size.width, size.height); gfx::Point point = [self delegate]->AccessibilityOriginInScreen(bounds); return NSMakePoint(point.x(), point.y()); } // Returns a string indicating the NSAccessibility role of this object. - (NSString*)role { ui::AXRole role = [self internalRole]; if (role == ui::AX_ROLE_CANVAS && browserAccessibility_->GetBoolAttribute( ui::AX_ATTR_CANVAS_HAS_FALLBACK)) { return NSAccessibilityGroupRole; } if (role == ui::AX_ROLE_BUTTON || role == ui::AX_ROLE_TOGGLE_BUTTON) { bool isAriaPressedDefined; bool isMixed; browserAccessibility_->GetAriaTristate("aria-pressed", &isAriaPressedDefined, &isMixed); if (isAriaPressedDefined) return NSAccessibilityCheckBoxRole; else return NSAccessibilityButtonRole; } return NativeRoleFromAXRole(role); } // Returns a string indicating the role description of this object. - (NSString*)roleDescription { NSString* role = [self role]; ContentClient* content_client = content::GetContentClient(); // The following descriptions are specific to webkit. if ([role isEqualToString:@"AXWebArea"]) { return base::SysUTF16ToNSString(content_client->GetLocalizedString( IDS_AX_ROLE_WEB_AREA)); } if ([role isEqualToString:@"NSAccessibilityLinkRole"]) { return base::SysUTF16ToNSString(content_client->GetLocalizedString( IDS_AX_ROLE_LINK)); } if ([role isEqualToString:@"AXHeading"]) { return base::SysUTF16ToNSString(content_client->GetLocalizedString( IDS_AX_ROLE_HEADING)); } if ([role isEqualToString:NSAccessibilityGroupRole] || [role isEqualToString:NSAccessibilityRadioButtonRole]) { std::string role; if (browserAccessibility_->GetHtmlAttribute("role", &role)) { ui::AXRole internalRole = [self internalRole]; if ((internalRole != ui::AX_ROLE_GROUP && internalRole != ui::AX_ROLE_LIST_ITEM) || internalRole == ui::AX_ROLE_TAB) { // TODO(dtseng): This is not localized; see crbug/84814. return base::SysUTF8ToNSString(role); } } } switch([self internalRole]) { case ui::AX_ROLE_FOOTER: return base::SysUTF16ToNSString(content_client->GetLocalizedString( IDS_AX_ROLE_FOOTER)); case ui::AX_ROLE_SPIN_BUTTON: // This control is similar to what VoiceOver calls a "stepper". return base::SysUTF16ToNSString(content_client->GetLocalizedString( IDS_AX_ROLE_STEPPER)); case ui::AX_ROLE_TOGGLE_BUTTON: return base::SysUTF16ToNSString(content_client->GetLocalizedString( IDS_AX_ROLE_TOGGLE_BUTTON)); default: break; } return NSAccessibilityRoleDescription(role, nil); } - (NSArray*)rowHeaders { if ([self internalRole] != ui::AX_ROLE_TABLE && [self internalRole] != ui::AX_ROLE_GRID) { return nil; } NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; const std::vector<int32>& uniqueCellIds = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_UNIQUE_CELL_IDS); for (size_t i = 0; i < uniqueCellIds.size(); ++i) { int id = uniqueCellIds[i]; BrowserAccessibility* cell = browserAccessibility_->manager()->GetFromID(id); if (cell && cell->GetRole() == ui::AX_ROLE_ROW_HEADER) [ret addObject:cell->ToBrowserAccessibilityCocoa()]; } return ret; } - (NSValue*)rowIndexRange { if ([self internalRole] != ui::AX_ROLE_CELL) return nil; int row = -1; int rowspan = -1; browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_CELL_ROW_INDEX, &row); browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_CELL_ROW_SPAN, &rowspan); if (row >= 0 && rowspan >= 1) return [NSValue valueWithRange:NSMakeRange(row, rowspan)]; return nil; } - (NSArray*)rows { NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; if ([self internalRole] == ui::AX_ROLE_TABLE|| [self internalRole] == ui::AX_ROLE_GRID) { for (BrowserAccessibilityCocoa* child in [self children]) { if ([[child role] isEqualToString:NSAccessibilityRowRole]) [ret addObject:child]; } } else if ([self internalRole] == ui::AX_ROLE_COLUMN) { const std::vector<int32>& indirectChildIds = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_INDIRECT_CHILD_IDS); for (uint32 i = 0; i < indirectChildIds.size(); ++i) { int id = indirectChildIds[i]; BrowserAccessibility* rowElement = browserAccessibility_->manager()->GetFromID(id); if (rowElement) [ret addObject:rowElement->ToBrowserAccessibilityCocoa()]; } } return ret; } // Returns the size of this object. - (NSValue*)size { gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect(); return [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())]; } // Returns a subrole based upon the role. - (NSString*) subrole { ui::AXRole browserAccessibilityRole = [self internalRole]; if (browserAccessibilityRole == ui::AX_ROLE_TEXT_FIELD && GetState(browserAccessibility_, ui::AX_STATE_PROTECTED)) { return @"AXSecureTextField"; } NSString* htmlTag = NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_HTML_TAG); if (browserAccessibilityRole == ui::AX_ROLE_LIST) { if ([htmlTag isEqualToString:@"dl"]) { return @"AXDescriptionList"; } else { return @"AXContentList"; } } return NativeSubroleFromAXRole(browserAccessibilityRole); } // Returns all tabs in this subtree. - (NSArray*)tabs { NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease]; if ([self internalRole] == ui::AX_ROLE_TAB) [tabSubtree addObject:self]; for (uint i=0; i < [[self children] count]; ++i) { NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs]; if ([tabChildren count] > 0) [tabSubtree addObjectsFromArray:tabChildren]; } return tabSubtree; } - (NSString*)title { return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_NAME); } - (id)titleUIElement { int titleElementId; if (browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TITLE_UI_ELEMENT, &titleElementId)) { BrowserAccessibility* titleElement = browserAccessibility_->manager()->GetFromID(titleElementId); if (titleElement) return titleElement->ToBrowserAccessibilityCocoa(); } std::vector<int32> labelledby_ids = browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS); if (labelledby_ids.size() == 1) { BrowserAccessibility* titleElement = browserAccessibility_->manager()->GetFromID(labelledby_ids[0]); if (titleElement) return titleElement->ToBrowserAccessibilityCocoa(); } return nil; } - (NSURL*)url { StringAttribute urlAttribute = [[self role] isEqualToString:@"AXWebArea"] ? ui::AX_ATTR_DOC_URL : ui::AX_ATTR_URL; std::string urlStr = browserAccessibility_->GetStringAttribute(urlAttribute); if (urlStr.empty()) return nil; return [NSURL URLWithString:(base::SysUTF8ToNSString(urlStr))]; } - (id)value { // WebCore uses an attachmentView to get the below behavior. // We do not have any native views backing this object, so need // to approximate Cocoa ax behavior best as we can. NSString* role = [self role]; if ([role isEqualToString:@"AXHeading"]) { int level = 0; if (browserAccessibility_->GetIntAttribute( ui::AX_ATTR_HIERARCHICAL_LEVEL, &level)) { return [NSNumber numberWithInt:level]; } } else if ([role isEqualToString:NSAccessibilityButtonRole]) { // AXValue does not make sense for pure buttons. return @""; } else if ([self internalRole] == ui::AX_ROLE_TOGGLE_BUTTON) { int value = 0; bool isAriaPressedDefined; bool isMixed; value = browserAccessibility_->GetAriaTristate( "aria-pressed", &isAriaPressedDefined, &isMixed) ? 1 : 0; if (isMixed) value = 2; return [NSNumber numberWithInt:value]; } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] || [role isEqualToString:NSAccessibilityRadioButtonRole]) { int value = 0; value = GetState( browserAccessibility_, ui::AX_STATE_CHECKED) ? 1 : 0; value = GetState( browserAccessibility_, ui::AX_STATE_SELECTED) ? 1 : value; if (browserAccessibility_->GetBoolAttribute( ui::AX_ATTR_BUTTON_MIXED)) { value = 2; } return [NSNumber numberWithInt:value]; } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] || [role isEqualToString:NSAccessibilitySliderRole] || [role isEqualToString:NSAccessibilityScrollBarRole]) { float floatValue; if (browserAccessibility_->GetFloatAttribute( ui::AX_ATTR_VALUE_FOR_RANGE, &floatValue)) { return [NSNumber numberWithFloat:floatValue]; } } else if ([role isEqualToString:NSAccessibilityColorWellRole]) { int r = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_COLOR_VALUE_RED); int g = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_COLOR_VALUE_GREEN); int b = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_COLOR_VALUE_BLUE); // This string matches the one returned by a native Mac color well. return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1", r / 255., g / 255., b / 255.]; } return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_VALUE); } - (NSString*)valueDescription { return NSStringForStringAttribute( browserAccessibility_, ui::AX_ATTR_VALUE); } - (NSValue*)visibleCharacterRange { return [NSValue valueWithRange: NSMakeRange(0, browserAccessibility_->value().length())]; } - (NSArray*)visibleCells { NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; const std::vector<int32>& uniqueCellIds = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_UNIQUE_CELL_IDS); for (size_t i = 0; i < uniqueCellIds.size(); ++i) { int id = uniqueCellIds[i]; BrowserAccessibility* cell = browserAccessibility_->manager()->GetFromID(id); if (cell) [ret addObject:cell->ToBrowserAccessibilityCocoa()]; } return ret; } - (NSArray*)visibleColumns { return [self columns]; } - (NSArray*)visibleRows { return [self rows]; } - (NSNumber*)visited { return [NSNumber numberWithBool: GetState(browserAccessibility_, ui::AX_STATE_VISITED)]; } - (id)window { if (!browserAccessibility_) return nil; BrowserAccessibilityManagerMac* manager = static_cast<BrowserAccessibilityManagerMac*>( browserAccessibility_->manager()); return [manager->parent_view() window]; } - (NSString*)methodNameForAttribute:(NSString*)attribute { return [attributeToMethodNameMap objectForKey:attribute]; } - (void)swapChildren:(base::scoped_nsobject<NSMutableArray>*)other { children_.swap(*other); } // Returns the accessibility value for the given attribute. If the value isn't // supported this will return nil. - (id)accessibilityAttributeValue:(NSString*)attribute { if (!browserAccessibility_) return nil; SEL selector = NSSelectorFromString([self methodNameForAttribute:attribute]); if (selector) return [self performSelector:selector]; // TODO(dtseng): refactor remaining attributes. int selStart, selEnd; if (browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TEXT_SEL_START, &selStart) && browserAccessibility_-> GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &selEnd)) { if (selStart > selEnd) std::swap(selStart, selEnd); int selLength = selEnd - selStart; if ([attribute isEqualToString: NSAccessibilityInsertionPointLineNumberAttribute]) { const std::vector<int32>& line_breaks = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_LINE_BREAKS); for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { if (line_breaks[i] > selStart) return [NSNumber numberWithInt:i]; } return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; } if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { std::string value = browserAccessibility_->GetStringAttribute( ui::AX_ATTR_VALUE); return base::SysUTF8ToNSString(value.substr(selStart, selLength)); } if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { return [NSValue valueWithRange:NSMakeRange(selStart, selLength)]; } } return nil; } // Returns the accessibility value for the given attribute and parameter. If the // value isn't supported this will return nil. - (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter { if (!browserAccessibility_) return nil; const std::vector<int32>& line_breaks = browserAccessibility_->GetIntListAttribute( ui::AX_ATTR_LINE_BREAKS); int len = static_cast<int>(browserAccessibility_->value().size()); if ([attribute isEqualToString: NSAccessibilityStringForRangeParameterizedAttribute]) { NSRange range = [(NSValue*)parameter rangeValue]; std::string value = browserAccessibility_->GetStringAttribute( ui::AX_ATTR_VALUE); return base::SysUTF8ToNSString(value.substr(range.location, range.length)); } if ([attribute isEqualToString: NSAccessibilityLineForIndexParameterizedAttribute]) { int index = [(NSNumber*)parameter intValue]; for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { if (line_breaks[i] > index) return [NSNumber numberWithInt:i]; } return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; } if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) { int line_index = [(NSNumber*)parameter intValue]; int line_count = static_cast<int>(line_breaks.size()) + 1; if (line_index < 0 || line_index >= line_count) return nil; int start = line_index > 0 ? line_breaks[line_index - 1] : 0; int end = line_index < line_count - 1 ? line_breaks[line_index] : len; return [NSValue valueWithRange: NSMakeRange(start, end - start)]; } if ([attribute isEqualToString: NSAccessibilityCellForColumnAndRowParameterizedAttribute]) { if ([self internalRole] != ui::AX_ROLE_TABLE && [self internalRole] != ui::AX_ROLE_GRID) { return nil; } if (![parameter isKindOfClass:[NSArray self]]) return nil; NSArray* array = parameter; int column = [[array objectAtIndex:0] intValue]; int row = [[array objectAtIndex:1] intValue]; int num_columns = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_COLUMN_COUNT); int num_rows = browserAccessibility_->GetIntAttribute( ui::AX_ATTR_TABLE_ROW_COUNT); if (column < 0 || column >= num_columns || row < 0 || row >= num_rows) { return nil; } for (size_t i = 0; i < browserAccessibility_->PlatformChildCount(); ++i) { BrowserAccessibility* child = browserAccessibility_->PlatformGetChild(i); if (child->GetRole() != ui::AX_ROLE_ROW) continue; int rowIndex; if (!child->GetIntAttribute( ui::AX_ATTR_TABLE_ROW_INDEX, &rowIndex)) { continue; } if (rowIndex < row) continue; if (rowIndex > row) break; for (size_t j = 0; j < child->PlatformChildCount(); ++j) { BrowserAccessibility* cell = child->PlatformGetChild(j); if (cell->GetRole() != ui::AX_ROLE_CELL) continue; int colIndex; if (!cell->GetIntAttribute( ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX, &colIndex)) { continue; } if (colIndex == column) return cell->ToBrowserAccessibilityCocoa(); if (colIndex > column) break; } } return nil; } if ([attribute isEqualToString: NSAccessibilityBoundsForRangeParameterizedAttribute]) { if ([self internalRole] != ui::AX_ROLE_STATIC_TEXT) return nil; NSRange range = [(NSValue*)parameter rangeValue]; gfx::Rect rect = browserAccessibility_->GetGlobalBoundsForRange( range.location, range.length); NSPoint origin = NSMakePoint(rect.x(), rect.y()); NSSize size = NSMakeSize(rect.width(), rect.height()); NSPoint pointInScreen = [self pointInScreen:origin size:size]; NSRect nsrect = NSMakeRect( pointInScreen.x, pointInScreen.y, rect.width(), rect.height()); return [NSValue valueWithRect:nsrect]; } // TODO(dtseng): support the following attributes. if ([attribute isEqualTo: NSAccessibilityRangeForPositionParameterizedAttribute] || [attribute isEqualTo: NSAccessibilityRangeForIndexParameterizedAttribute] || [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] || [attribute isEqualTo: NSAccessibilityStyleRangeForIndexParameterizedAttribute]) { return nil; } return nil; } // Returns an array of parameterized attributes names that this object will // respond to. - (NSArray*)accessibilityParameterizedAttributeNames { if (!browserAccessibility_) return nil; if ([[self role] isEqualToString:NSAccessibilityTableRole] || [[self role] isEqualToString:NSAccessibilityGridRole]) { return [NSArray arrayWithObjects: NSAccessibilityCellForColumnAndRowParameterizedAttribute, nil]; } if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] || [[self role] isEqualToString:NSAccessibilityTextAreaRole]) { return [NSArray arrayWithObjects: NSAccessibilityLineForIndexParameterizedAttribute, NSAccessibilityRangeForLineParameterizedAttribute, NSAccessibilityStringForRangeParameterizedAttribute, NSAccessibilityRangeForPositionParameterizedAttribute, NSAccessibilityRangeForIndexParameterizedAttribute, NSAccessibilityBoundsForRangeParameterizedAttribute, NSAccessibilityRTFForRangeParameterizedAttribute, NSAccessibilityAttributedStringForRangeParameterizedAttribute, NSAccessibilityStyleRangeForIndexParameterizedAttribute, nil]; } if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) { return [NSArray arrayWithObjects: NSAccessibilityBoundsForRangeParameterizedAttribute, nil]; } return nil; } // Returns an array of action names that this object will respond to. - (NSArray*)accessibilityActionNames { if (!browserAccessibility_) return nil; NSMutableArray* ret = [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction]; NSString* role = [self role]; // TODO(dtseng): this should only get set when there's a default action. if (![role isEqualToString:NSAccessibilityStaticTextRole] && ![role isEqualToString:NSAccessibilityTextAreaRole] && ![role isEqualToString:NSAccessibilityTextFieldRole]) { [ret addObject:NSAccessibilityPressAction]; } return ret; } // Returns a sub-array of values for the given attribute value, starting at // index, with up to maxCount items. If the given index is out of bounds, // or there are no values for the given attribute, it will return nil. // This method is used for querying subsets of values, without having to // return a large set of data, such as elements with a large number of // children. - (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount { if (!browserAccessibility_) return nil; NSArray* fullArray = [self accessibilityAttributeValue:attribute]; if (!fullArray) return nil; NSUInteger arrayCount = [fullArray count]; if (index >= arrayCount) return nil; NSRange subRange; if ((index + maxCount) > arrayCount) { subRange = NSMakeRange(index, arrayCount - index); } else { subRange = NSMakeRange(index, maxCount); } return [fullArray subarrayWithRange:subRange]; } // Returns the count of the specified accessibility array attribute. - (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute { if (!browserAccessibility_) return nil; NSArray* fullArray = [self accessibilityAttributeValue:attribute]; return [fullArray count]; } // Returns the list of accessibility attributes that this object supports. - (NSArray*)accessibilityAttributeNames { if (!browserAccessibility_) return nil; // General attributes. NSMutableArray* ret = [NSMutableArray arrayWithObjects: NSAccessibilityChildrenAttribute, NSAccessibilityDescriptionAttribute, NSAccessibilityEnabledAttribute, NSAccessibilityFocusedAttribute, NSAccessibilityHelpAttribute, NSAccessibilityLinkedUIElementsAttribute, NSAccessibilityParentAttribute, NSAccessibilityPositionAttribute, NSAccessibilityRoleAttribute, NSAccessibilityRoleDescriptionAttribute, NSAccessibilitySizeAttribute, NSAccessibilitySubroleAttribute, NSAccessibilityTitleAttribute, NSAccessibilityTopLevelUIElementAttribute, NSAccessibilityValueAttribute, NSAccessibilityWindowAttribute, @"AXAccessKey", @"AXInvalid", @"AXRequired", @"AXVisited", nil]; // Specific role attributes. NSString* role = [self role]; NSString* subrole = [self subrole]; if ([role isEqualToString:NSAccessibilityTableRole] || [role isEqualToString:NSAccessibilityGridRole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityColumnsAttribute, NSAccessibilityVisibleColumnsAttribute, NSAccessibilityRowsAttribute, NSAccessibilityVisibleRowsAttribute, NSAccessibilityVisibleCellsAttribute, NSAccessibilityHeaderAttribute, NSAccessibilityColumnHeaderUIElementsAttribute, NSAccessibilityRowHeaderUIElementsAttribute, nil]]; } else if ([role isEqualToString:NSAccessibilityColumnRole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityIndexAttribute, NSAccessibilityHeaderAttribute, NSAccessibilityRowsAttribute, NSAccessibilityVisibleRowsAttribute, nil]]; } else if ([role isEqualToString:NSAccessibilityCellRole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityColumnIndexRangeAttribute, NSAccessibilityRowIndexRangeAttribute, nil]]; } else if ([role isEqualToString:@"AXWebArea"]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: @"AXLoaded", @"AXLoadingProgress", nil]]; } else if ([role isEqualToString:NSAccessibilityTextFieldRole] || [role isEqualToString:NSAccessibilityTextAreaRole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityInsertionPointLineNumberAttribute, NSAccessibilityNumberOfCharactersAttribute, NSAccessibilitySelectedTextAttribute, NSAccessibilitySelectedTextRangeAttribute, NSAccessibilityVisibleCharacterRangeAttribute, nil]]; } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) { [ret addObject:NSAccessibilityTabsAttribute]; } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] || [role isEqualToString:NSAccessibilitySliderRole] || [role isEqualToString:NSAccessibilityScrollBarRole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityMaxValueAttribute, NSAccessibilityMinValueAttribute, NSAccessibilityOrientationAttribute, NSAccessibilityValueDescriptionAttribute, nil]]; } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityDisclosingAttribute, NSAccessibilityDisclosedByRowAttribute, NSAccessibilityDisclosureLevelAttribute, NSAccessibilityDisclosedRowsAttribute, nil]]; } else if ([role isEqualToString:NSAccessibilityRowRole]) { if (browserAccessibility_->GetParent()) { base::string16 parentRole; browserAccessibility_->GetParent()->GetHtmlAttribute( "role", &parentRole); const base::string16 treegridRole(base::ASCIIToUTF16("treegrid")); if (parentRole == treegridRole) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityDisclosingAttribute, NSAccessibilityDisclosedByRowAttribute, NSAccessibilityDisclosureLevelAttribute, NSAccessibilityDisclosedRowsAttribute, nil]]; } else { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityIndexAttribute, nil]]; } } } // Add the url attribute only if it has a valid url. if ([self url] != nil) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityURLAttribute, nil]]; } // Live regions. if (browserAccessibility_->HasStringAttribute( ui::AX_ATTR_LIVE_STATUS)) { [ret addObjectsFromArray:[NSArray arrayWithObjects: @"AXARIALive", @"AXARIARelevant", nil]]; } if (browserAccessibility_->HasStringAttribute( ui::AX_ATTR_CONTAINER_LIVE_STATUS)) { [ret addObjectsFromArray:[NSArray arrayWithObjects: @"AXARIAAtomic", @"AXARIABusy", nil]]; } // Title UI Element. if (browserAccessibility_->HasIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT) || (browserAccessibility_->HasIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) && browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS) .size() == 1)) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityTitleUIElementAttribute, nil]]; } // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute // for elements which are referred to by labelledby or are labels return ret; } // Returns the index of the child in this objects array of children. - (NSUInteger)accessibilityGetIndexOf:(id)child { if (!browserAccessibility_) return nil; NSUInteger index = 0; for (BrowserAccessibilityCocoa* childToCheck in [self children]) { if ([child isEqual:childToCheck]) return index; ++index; } return NSNotFound; } // Returns whether or not the specified attribute can be set by the // accessibility API via |accessibilitySetValue:forAttribute:|. - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { if (!browserAccessibility_) return nil; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) return GetState(browserAccessibility_, ui::AX_STATE_FOCUSABLE); if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { return browserAccessibility_->GetBoolAttribute( ui::AX_ATTR_CAN_SET_VALUE); } if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] && ([[self role] isEqualToString:NSAccessibilityTextFieldRole] || [[self role] isEqualToString:NSAccessibilityTextAreaRole])) return YES; return NO; } // Returns whether or not this object should be ignored in the accessibilty // tree. - (BOOL)accessibilityIsIgnored { if (!browserAccessibility_) return true; return [self isIgnored]; } // Performs the given accessibilty action on the webkit accessibility object // that backs this object. - (void)accessibilityPerformAction:(NSString*)action { if (!browserAccessibility_) return; // TODO(dmazzoni): Support more actions. if ([action isEqualToString:NSAccessibilityPressAction]) { [self delegate]->AccessibilityDoDefaultAction( browserAccessibility_->GetId()); } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) { [self delegate]->AccessibilityShowMenu(browserAccessibility_->GetId()); } } // Returns the description of the given action. - (NSString*)accessibilityActionDescription:(NSString*)action { if (!browserAccessibility_) return nil; return NSAccessibilityActionDescription(action); } // Sets an override value for a specific accessibility attribute. // This class does not support this. - (BOOL)accessibilitySetOverrideValue:(id)value forAttribute:(NSString*)attribute { return NO; } // Sets the value for an accessibility attribute via the accessibility API. - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute { if (!browserAccessibility_) return; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { NSNumber* focusedNumber = value; BOOL focused = [focusedNumber intValue]; if (focused) [self delegate]->AccessibilitySetFocus(browserAccessibility_->GetId()); } if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { NSRange range = [(NSValue*)value rangeValue]; [self delegate]->AccessibilitySetTextSelection( browserAccessibility_->GetId(), range.location, range.location + range.length); } } // Returns the deepest accessibility child that should not be ignored. // It is assumed that the hit test has been narrowed down to this object // or one of its children, so this will never return nil unless this // object is invalid. - (id)accessibilityHitTest:(NSPoint)point { if (!browserAccessibility_) return nil; BrowserAccessibilityCocoa* hit = self; for (BrowserAccessibilityCocoa* child in [self children]) { if (!child->browserAccessibility_) continue; NSPoint origin = [child origin]; NSSize size = [[child size] sizeValue]; NSRect rect; rect.origin = origin; rect.size = size; if (NSPointInRect(point, rect)) { hit = child; id childResult = [child accessibilityHitTest:point]; if (![childResult accessibilityIsIgnored]) { hit = childResult; break; } } } return NSAccessibilityUnignoredAncestor(hit); } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[BrowserAccessibilityCocoa class]]) return NO; return ([self hash] == [object hash]); } - (NSUInteger)hash { // Potentially called during dealloc. if (!browserAccessibility_) return [super hash]; return browserAccessibility_->GetId(); } - (BOOL)accessibilityShouldUseUniqueId { return YES; } @end