summaryrefslogtreecommitdiffstats
path: root/content/browser/accessibility/browser_accessibility_cocoa.mm
diff options
context:
space:
mode:
Diffstat (limited to 'content/browser/accessibility/browser_accessibility_cocoa.mm')
-rw-r--r--content/browser/accessibility/browser_accessibility_cocoa.mm822
1 files changed, 822 insertions, 0 deletions
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
new file mode 100644
index 0000000..1920493
--- /dev/null
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -0,0 +1,822 @@
+// Copyright (c) 2011 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/string16.h"
+#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "grit/webkit_strings.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+namespace {
+
+// Returns an autoreleased copy of the WebAccessibility's attribute.
+NSString* NSStringForWebAccessibilityAttribute(
+ const std::map<int32, string16>& attributes,
+ WebAccessibility::Attribute attribute) {
+ std::map<int32, string16>::const_iterator iter =
+ attributes.find(attribute);
+ NSString* returnValue = @"";
+ if (iter != attributes.end()) {
+ returnValue = base::SysUTF16ToNSString(iter->second);
+ }
+ return returnValue;
+}
+
+struct MapEntry {
+ WebAccessibility::Role webKitValue;
+ NSString* nativeValue;
+};
+
+struct AttributeToMethodNameEntry {
+ NSString* attribute;
+ NSString* methodName;
+};
+
+static const MapEntry roles[] = {
+ { WebAccessibility::ROLE_NONE, NSAccessibilityUnknownRole },
+ { WebAccessibility::ROLE_ALERT, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_ALERT_DIALOG, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_ANNOTATION, NSAccessibilityUnknownRole },
+ { WebAccessibility::ROLE_APPLICATION, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_ARTICLE, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_BROWSER, NSAccessibilityBrowserRole },
+ { WebAccessibility::ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole },
+ { WebAccessibility::ROLE_BUTTON, NSAccessibilityButtonRole },
+ { WebAccessibility::ROLE_CELL, @"AXCell" },
+ { WebAccessibility::ROLE_CHECKBOX, NSAccessibilityCheckBoxRole },
+ { WebAccessibility::ROLE_COLOR_WELL, NSAccessibilityColorWellRole },
+ { WebAccessibility::ROLE_COLUMN, NSAccessibilityColumnRole },
+ { WebAccessibility::ROLE_COLUMN_HEADER, @"AXCell" },
+ { WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION,
+ NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_DEFINITION_LIST_TERM, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_DIALOG, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_DIRECTORY, NSAccessibilityListRole },
+ { WebAccessibility::ROLE_DISCLOSURE_TRIANGLE,
+ NSAccessibilityDisclosureTriangleRole },
+ { WebAccessibility::ROLE_DOCUMENT, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_DRAWER, NSAccessibilityDrawerRole },
+ { WebAccessibility::ROLE_EDITABLE_TEXT, NSAccessibilityTextFieldRole },
+ { WebAccessibility::ROLE_GRID, NSAccessibilityGridRole },
+ { WebAccessibility::ROLE_GROUP, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_GROW_AREA, NSAccessibilityGrowAreaRole },
+ { WebAccessibility::ROLE_HEADING, @"AXHeading" },
+ { WebAccessibility::ROLE_HELP_TAG, NSAccessibilityHelpTagRole },
+ { WebAccessibility::ROLE_IGNORED, NSAccessibilityUnknownRole },
+ { WebAccessibility::ROLE_IMAGE, NSAccessibilityImageRole },
+ { WebAccessibility::ROLE_IMAGE_MAP, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole },
+ { WebAccessibility::ROLE_INCREMENTOR, NSAccessibilityIncrementorRole },
+ { WebAccessibility::ROLE_LANDMARK_APPLICATION, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LANDMARK_BANNER, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LANDMARK_CONTENTINFO, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LANDMARK_MAIN, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LANDMARK_NAVIGATION, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LANDMARK_SEARCH, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LINK, NSAccessibilityLinkRole },
+ { WebAccessibility::ROLE_LIST, NSAccessibilityListRole },
+ { WebAccessibility::ROLE_LIST_ITEM, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LIST_MARKER, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LISTBOX, NSAccessibilityListRole },
+ { WebAccessibility::ROLE_LISTBOX_OPTION, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_LOG, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_MARQUEE, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_MATH, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_MATTE, NSAccessibilityMatteRole },
+ { WebAccessibility::ROLE_MENU, NSAccessibilityMenuRole },
+ { WebAccessibility::ROLE_MENU_ITEM, NSAccessibilityMenuItemRole },
+ { WebAccessibility::ROLE_MENU_BUTTON, NSAccessibilityButtonRole },
+ { WebAccessibility::ROLE_MENU_LIST_OPTION, NSAccessibilityMenuItemRole },
+ { WebAccessibility::ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole },
+ { WebAccessibility::ROLE_NOTE, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_OUTLINE, NSAccessibilityOutlineRole },
+ { WebAccessibility::ROLE_POPUP_BUTTON, NSAccessibilityPopUpButtonRole },
+ { WebAccessibility::ROLE_PROGRESS_INDICATOR,
+ NSAccessibilityProgressIndicatorRole },
+ { WebAccessibility::ROLE_RADIO_BUTTON, NSAccessibilityRadioButtonRole },
+ { WebAccessibility::ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole },
+ { WebAccessibility::ROLE_REGION, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_ROW, NSAccessibilityRowRole },
+ { WebAccessibility::ROLE_ROW_HEADER, @"AXCell" },
+ { WebAccessibility::ROLE_RULER, NSAccessibilityRulerRole },
+ { WebAccessibility::ROLE_RULER_MARKER, NSAccessibilityRulerMarkerRole },
+ { WebAccessibility::ROLE_SCROLLAREA, NSAccessibilityScrollAreaRole },
+ { WebAccessibility::ROLE_SCROLLBAR, NSAccessibilityScrollBarRole },
+ { WebAccessibility::ROLE_SHEET, NSAccessibilitySheetRole },
+ { WebAccessibility::ROLE_SLIDER, NSAccessibilitySliderRole },
+ { WebAccessibility::ROLE_SLIDER_THUMB, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_SPLITTER, NSAccessibilitySplitterRole },
+ { WebAccessibility::ROLE_SPLIT_GROUP, NSAccessibilitySplitGroupRole },
+ { WebAccessibility::ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole },
+ { WebAccessibility::ROLE_STATUS, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_SYSTEM_WIDE, NSAccessibilityUnknownRole },
+ { WebAccessibility::ROLE_TAB, NSAccessibilityRadioButtonRole },
+ { WebAccessibility::ROLE_TAB_LIST, NSAccessibilityTabGroupRole },
+ { WebAccessibility::ROLE_TAB_PANEL, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_TABLE, NSAccessibilityTableRole },
+ { WebAccessibility::ROLE_TABLE_HEADER_CONTAINER, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_TAB_GROUP, NSAccessibilityTabGroupRole },
+ { WebAccessibility::ROLE_TEXTAREA, NSAccessibilityTextAreaRole },
+ { WebAccessibility::ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole },
+ { WebAccessibility::ROLE_TIMER, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_TOOLBAR, NSAccessibilityToolbarRole },
+ { WebAccessibility::ROLE_TOOLTIP, NSAccessibilityGroupRole },
+ { WebAccessibility::ROLE_TREE, NSAccessibilityOutlineRole },
+ { WebAccessibility::ROLE_TREE_GRID, NSAccessibilityTableRole },
+ { WebAccessibility::ROLE_TREE_ITEM, NSAccessibilityRowRole },
+ { WebAccessibility::ROLE_VALUE_INDICATOR, NSAccessibilityValueIndicatorRole },
+ { WebAccessibility::ROLE_WEBCORE_LINK, NSAccessibilityLinkRole },
+ { WebAccessibility::ROLE_WEB_AREA, @"AXWebArea" },
+ { WebAccessibility::ROLE_WINDOW, NSAccessibilityUnknownRole },
+};
+
+static const MapEntry subroles[] = {
+ { WebAccessibility::ROLE_ALERT, @"AXApplicationAlert" },
+ { WebAccessibility::ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog" },
+ { WebAccessibility::ROLE_ARTICLE, @"AXDocumentArticle" },
+ { WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION, @"AXDefinition" },
+ { WebAccessibility::ROLE_DEFINITION_LIST_TERM, @"AXTerm" },
+ { WebAccessibility::ROLE_DIALOG, @"AXApplicationDialog" },
+ { WebAccessibility::ROLE_DOCUMENT, @"AXDocument" },
+ { WebAccessibility::ROLE_LANDMARK_APPLICATION, @"AXLandmarkApplication" },
+ { WebAccessibility::ROLE_LANDMARK_BANNER, @"AXLandmarkBanner" },
+ { WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY, @"AXLandmarkComplementary" },
+ { WebAccessibility::ROLE_LANDMARK_CONTENTINFO, @"AXLandmarkContentInfo" },
+ { WebAccessibility::ROLE_LANDMARK_MAIN, @"AXLandmarkMain" },
+ { WebAccessibility::ROLE_LANDMARK_NAVIGATION, @"AXLandmarkNavigation" },
+ { WebAccessibility::ROLE_LANDMARK_SEARCH, @"AXLandmarkSearch" },
+ { WebAccessibility::ROLE_LOG, @"AXApplicationLog" },
+ { WebAccessibility::ROLE_MARQUEE, @"AXApplicationMarquee" },
+ { WebAccessibility::ROLE_MATH, @"AXDocumentMath" },
+ { WebAccessibility::ROLE_NOTE, @"AXDocumentNote" },
+ { WebAccessibility::ROLE_REGION, @"AXDocumentRegion" },
+ { WebAccessibility::ROLE_STATUS, @"AXApplicationStatus" },
+ { WebAccessibility::ROLE_TAB_PANEL, @"AXTabPanel" },
+ { WebAccessibility::ROLE_TIMER, @"AXApplicationTimer" },
+ { WebAccessibility::ROLE_TOOLTIP, @"AXUserInterfaceTooltip" },
+ { WebAccessibility::ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole },
+};
+
+static const AttributeToMethodNameEntry attributeToMethodNameContainer[] = {
+ { NSAccessibilityChildrenAttribute, @"children" },
+ { NSAccessibilityColumnsAttribute, @"columns" },
+ { NSAccessibilityDescriptionAttribute, @"description" },
+ { NSAccessibilityEnabledAttribute, @"enabled" },
+ { NSAccessibilityFocusedAttribute, @"focused" },
+ { NSAccessibilityHelpAttribute, @"help" },
+ { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" },
+ { NSAccessibilityParentAttribute, @"parent" },
+ { NSAccessibilityPositionAttribute, @"position" },
+ { NSAccessibilityRoleAttribute, @"role" },
+ { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" },
+ { NSAccessibilityRowsAttribute, @"rows" },
+ { NSAccessibilitySizeAttribute, @"size" },
+ { NSAccessibilitySubroleAttribute, @"subrole" },
+ { NSAccessibilityTabsAttribute, @"tabs" },
+ { NSAccessibilityTitleAttribute, @"title" },
+ { NSAccessibilityTopLevelUIElementAttribute, @"window" },
+ { NSAccessibilityURLAttribute, @"url" },
+ { NSAccessibilityValueAttribute, @"value" },
+ { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" },
+ { NSAccessibilityWindowAttribute, @"window" },
+ { @"AXVisited", @"visited" },
+};
+
+// GetState checks the bitmask used in webaccessibility.h to check
+// if the given state was set on the accessibility object.
+bool GetState(BrowserAccessibility* accessibility, int state) {
+ return ((accessibility->state() >> state) & 1);
+}
+
+// A mapping of webkit roles to native roles.
+std::map<WebAccessibility::Role, NSString*> webAccessibilityToNativeRole;
+// A mapping of webkit roles to native subroles.
+std::map<WebAccessibility::Role, NSString*> webAccessibilityToNativeSubrole;
+// A mapping from an accessibility attribute to its method name.
+NSDictionary* attributeToMethodNameMap = nil;
+
+} // namespace
+
+@implementation BrowserAccessibilityCocoa
+
++ (void)initialize {
+ const size_t numRoles = sizeof(roles) / sizeof(roles[0]);
+ for (size_t i = 0; i < numRoles; ++i) {
+ webAccessibilityToNativeRole[roles[i].webKitValue] = roles[i].nativeValue;
+ }
+
+ const size_t numSubroles = sizeof(subroles) / sizeof(subroles[0]);
+ for (size_t i = 0; i < numSubroles; ++i) {
+ webAccessibilityToNativeSubrole[subroles[i].webKitValue] =
+ subroles[i].nativeValue;
+ }
+
+ 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
+ delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate {
+ if ((self = [super init])) {
+ browserAccessibility_ = accessibility;
+ delegate_ = delegate;
+ }
+ return self;
+}
+
+// Deletes our associated BrowserAccessibilityMac.
+- (void)dealloc {
+ if (browserAccessibility_) {
+ delete browserAccessibility_;
+ browserAccessibility_ = NULL;
+ }
+
+ [super dealloc];
+}
+
+// Returns an array of BrowserAccessibilityCocoa objects, representing the
+// accessibility children of this object.
+- (NSArray*)children {
+ if (!children_.get()) {
+ children_.reset([[NSMutableArray alloc]
+ initWithCapacity:browserAccessibility_->child_count()] );
+ for (uint32 index = 0;
+ index < browserAccessibility_->child_count();
+ ++index) {
+ BrowserAccessibilityCocoa* child =
+ browserAccessibility_->GetChild(index)->toBrowserAccessibilityCocoa();
+ if ([child isIgnored])
+ [children_ addObjectsFromArray:[child children]];
+ else
+ [children_ addObject:child];
+ }
+
+ // Also, add indirect children (if any).
+ for (uint32 i = 0;
+ i < browserAccessibility_->indirect_child_ids().size();
+ ++i) {
+ int32 child_id = browserAccessibility_->indirect_child_ids()[i];
+ BrowserAccessibilityCocoa* child =
+ browserAccessibility_->manager()->GetFromRendererID(child_id)->
+ toBrowserAccessibilityCocoa();
+ [children_ addObject:child];
+ }
+ }
+ return children_;
+}
+
+- (void)childrenChanged {
+ if (![self isIgnored]) {
+ children_.reset();
+ } else {
+ [browserAccessibility_->parent()->toBrowserAccessibilityCocoa()
+ childrenChanged];
+ }
+}
+
+- (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 {
+ return NSStringForWebAccessibilityAttribute(
+ browserAccessibility_->attributes(),
+ WebAccessibility::ATTR_DESCRIPTION);
+}
+
+- (NSNumber*)enabled {
+ return [NSNumber numberWithBool:
+ !GetState(browserAccessibility_, WebAccessibility::STATE_UNAVAILABLE)];
+}
+
+- (NSNumber*)focused {
+ NSNumber* ret = [NSNumber numberWithBool:
+ GetState(browserAccessibility_, WebAccessibility::STATE_FOCUSED)];
+ return ret;
+}
+
+- (NSString*)help {
+ return NSStringForWebAccessibilityAttribute(
+ browserAccessibility_->attributes(),
+ WebAccessibility::ATTR_HELP);
+}
+
+// Returns whether or not this node should be ignored in the
+// accessibility tree.
+- (BOOL)isIgnored {
+ return [[self role] isEqualToString:NSAccessibilityUnknownRole];
+}
+
+- (NSNumber*)loaded {
+ return [NSNumber numberWithBool:YES];
+}
+
+// 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 {
+ return NSMakePoint(browserAccessibility_->location().x(),
+ browserAccessibility_->location().y());
+}
+
+- (NSNumber*)numberOfCharacters {
+ return [NSNumber numberWithInt:browserAccessibility_->value().length()];
+}
+
+- (id)parent {
+ // A nil parent means we're the root.
+ if (browserAccessibility_->parent()) {
+ return NSAccessibilityUnignoredAncestor(
+ browserAccessibility_->parent()->toBrowserAccessibilityCocoa());
+ } else {
+ // Hook back up to RenderWidgetHostViewCocoa.
+ return browserAccessibility_->manager()->GetParentView();
+ }
+}
+
+- (NSValue*)position {
+ return [NSValue valueWithPoint:[delegate_ accessibilityPointInScreen:self]];
+}
+
+// Returns a string indicating the role of this object.
+- (NSString*)role {
+ WebAccessibility::Role browserAccessibilityRole =
+ static_cast<WebAccessibility::Role>( browserAccessibility_->role());
+
+ // Roles that we only determine at runtime.
+ if (browserAccessibilityRole == WebAccessibility::ROLE_TEXT_FIELD &&
+ GetState(browserAccessibility_, WebAccessibility::STATE_PROTECTED)) {
+ return @"AXSecureTextField";
+ }
+
+ std::map<WebAccessibility::Role, NSString*>::iterator it =
+ webAccessibilityToNativeRole.find(browserAccessibilityRole);
+
+ if (it != webAccessibilityToNativeRole.end())
+ return it->second;
+ else
+ return NSAccessibilityUnknownRole;
+}
+
+// Returns a string indicating the role description of this object.
+- (NSString*)roleDescription {
+ NSString* role = [self role];
+ // The following descriptions are specific to webkit.
+ if ([role isEqualToString:@"AXWebArea"])
+ return l10n_util::GetNSString(IDS_AX_ROLE_WEB_AREA);
+
+ if ([role isEqualToString:@"NSAccessibilityLinkRole"])
+ return l10n_util::GetNSString(IDS_AX_ROLE_LINK);
+
+ if ([role isEqualToString:@"AXHeading"])
+ return l10n_util::GetNSString(IDS_AX_ROLE_HEADING);
+
+ if ([role isEqualToString:NSAccessibilityGroupRole] ||
+ [role isEqualToString:NSAccessibilityRadioButtonRole]) {
+ const std::vector<std::pair<string16, string16> >& htmlAttributes =
+ browserAccessibility_->html_attributes();
+ WebAccessibility::Role browserAccessibilityRole =
+ static_cast<WebAccessibility::Role>(browserAccessibility_->role());
+
+ if ((browserAccessibilityRole != WebAccessibility::ROLE_GROUP &&
+ browserAccessibilityRole != WebAccessibility::ROLE_LIST_ITEM) ||
+ browserAccessibilityRole == WebAccessibility::ROLE_TAB) {
+ for (size_t i = 0; i < htmlAttributes.size(); ++i) {
+ const std::pair<string16, string16>& htmlAttribute = htmlAttributes[i];
+ if (htmlAttribute.first == ASCIIToUTF16("role")) {
+ // TODO(dtseng): This is not localized; see crbug/84814.
+ return base::SysUTF16ToNSString(htmlAttribute.second);
+ }
+ }
+ }
+ }
+
+ return NSAccessibilityRoleDescription(role, nil);
+}
+
+- (NSArray*)rows {
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ for (BrowserAccessibilityCocoa* child in [self children]) {
+ if ([[child role] isEqualToString:NSAccessibilityRowRole])
+ [ret addObject:child];
+ }
+
+ return ret;
+}
+
+// Returns the size of this object.
+- (NSValue*)size {
+ return [NSValue valueWithSize:NSMakeSize(
+ browserAccessibility_->location().width(),
+ browserAccessibility_->location().height())];
+}
+
+// Returns a subrole based upon the role.
+- (NSString*) subrole {
+ // TODO: support password field -> NSAccessibilitySecureTextFieldSubrole
+ // TODO: support attachments
+ // TODO: support lists -> NSAccessibilityContentListSubrole ||
+ // NSAccessibilityDefinitionListSubrole
+
+ WebAccessibility::Role browserAccessibilityRole =
+ static_cast<WebAccessibility::Role>( browserAccessibility_->role());
+
+ std::map<WebAccessibility::Role, NSString*>::iterator it =
+ webAccessibilityToNativeSubrole.find(browserAccessibilityRole);
+
+ if (it != webAccessibilityToNativeSubrole.end())
+ return it->second;
+ else
+ return nil;
+}
+
+// Returns all tabs in this subtree.
+- (NSArray*)tabs {
+ NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
+
+ if (browserAccessibility_->role() == WebAccessibility::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 base::SysUTF16ToNSString(browserAccessibility_->name());
+}
+
+- (NSString*)url {
+ WebAccessibility::Attribute urlAttribute =
+ [[self role] isEqualToString:@"AXWebArea"] ?
+ WebAccessibility::ATTR_DOC_URL :
+ WebAccessibility::ATTR_URL;
+ return NSStringForWebAccessibilityAttribute(
+ browserAccessibility_->attributes(),
+ urlAttribute);
+}
+
+- (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"]) {
+ NSString* headingLevel =
+ NSStringForWebAccessibilityAttribute(
+ browserAccessibility_->attributes(),
+ WebAccessibility::ATTR_HTML_TAG);
+ if ([headingLevel length] >= 2) {
+ return [NSNumber numberWithInt:
+ [[headingLevel substringFromIndex:1] intValue]];
+ }
+ } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
+ // AXValue does not make sense for pure buttons.
+ return @"";
+ } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
+ [role isEqualToString:NSAccessibilityRadioButtonRole]) {
+ return [NSNumber numberWithInt:GetState(
+ browserAccessibility_, WebAccessibility::STATE_CHECKED) ? 1 : 0];
+ }
+ return base::SysUTF16ToNSString(browserAccessibility_->value());
+}
+
+- (NSValue*)visibleCharacterRange {
+ return [NSValue valueWithRange:
+ NSMakeRange(0, browserAccessibility_->value().length())];
+}
+
+- (NSNumber*)visited {
+ return [NSNumber numberWithBool:
+ GetState(browserAccessibility_, WebAccessibility::STATE_TRAVERSED)];
+}
+
+- (id)window {
+ return [delegate_ window];
+}
+
+// Returns the accessibility value for the given attribute. If the value isn't
+// supported this will return nil.
+- (id)accessibilityAttributeValue:(NSString*)attribute {
+ SEL selector =
+ NSSelectorFromString([attributeToMethodNameMap objectForKey:attribute]);
+ if (selector)
+ return [self performSelector:selector];
+
+ // TODO(dtseng): refactor remaining attributes.
+ int selStart, selEnd;
+ if (browserAccessibility_->GetAttributeAsInt(
+ WebAccessibility::ATTR_TEXT_SEL_START, &selStart) &&
+ browserAccessibility_->
+ GetAttributeAsInt(WebAccessibility::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_->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]) {
+ return base::SysUTF16ToNSString(browserAccessibility_->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 {
+ const std::vector<int32>& line_breaks = browserAccessibility_->line_breaks();
+ int len = static_cast<int>(browserAccessibility_->value().size());
+
+ if ([attribute isEqualToString:
+ NSAccessibilityStringForRangeParameterizedAttribute]) {
+ NSRange range = [(NSValue*)parameter rangeValue];
+ return base::SysUTF16ToNSString(
+ browserAccessibility_->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)];
+ }
+
+ // TODO(dtseng): support the following attributes.
+ if ([attribute isEqualTo:
+ NSAccessibilityRangeForPositionParameterizedAttribute] ||
+ [attribute isEqualTo:
+ NSAccessibilityRangeForIndexParameterizedAttribute] ||
+ [attribute isEqualTo:
+ NSAccessibilityBoundsForRangeParameterizedAttribute] ||
+ [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 ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
+ [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
+ return [NSArray arrayWithObjects:
+ NSAccessibilityLineForIndexParameterizedAttribute,
+ NSAccessibilityRangeForLineParameterizedAttribute,
+ NSAccessibilityStringForRangeParameterizedAttribute,
+ NSAccessibilityRangeForPositionParameterizedAttribute,
+ NSAccessibilityRangeForIndexParameterizedAttribute,
+ NSAccessibilityBoundsForRangeParameterizedAttribute,
+ NSAccessibilityRTFForRangeParameterizedAttribute,
+ NSAccessibilityAttributedStringForRangeParameterizedAttribute,
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute,
+ nil];
+ }
+ return nil;
+}
+
+// Returns an array of action names that this object will respond to.
+- (NSArray*)accessibilityActionNames {
+ 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 {
+ 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 {
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ return [fullArray count];
+}
+
+// Returns the list of accessibility attributes that this object supports.
+- (NSArray*)accessibilityAttributeNames {
+ // General attributes.
+ NSMutableArray* ret = [NSMutableArray arrayWithObjects:
+ NSAccessibilityChildrenAttribute,
+ NSAccessibilityDescriptionAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityParentAttribute,
+ NSAccessibilityPositionAttribute,
+ NSAccessibilityRoleAttribute,
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilitySizeAttribute,
+ NSAccessibilitySubroleAttribute,
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityTopLevelUIElementAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilityWindowAttribute,
+ NSAccessibilityURLAttribute,
+ @"AXVisited",
+ nil];
+
+ // Specific role attributes.
+ NSString* role = [self role];
+ if ([role isEqualToString:NSAccessibilityTableRole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityColumnsAttribute,
+ NSAccessibilityRowsAttribute,
+ nil]];
+ } else if ([role isEqualToString:@"AXWebArea"]) {
+ [ret addObject:@"AXLoaded"];
+ } 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];
+ }
+
+ return ret;
+}
+
+// Returns the index of the child in this objects array of children.
+- (NSUInteger)accessibilityGetIndexOf:(id)child {
+ 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 ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
+ return GetState(browserAccessibility_, WebAccessibility::STATE_FOCUSABLE);
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return !GetState(browserAccessibility_, WebAccessibility::STATE_READONLY);
+ return NO;
+}
+
+// Returns whether or not this object should be ignored in the accessibilty
+// tree.
+- (BOOL)accessibilityIsIgnored {
+ return [self isIgnored];
+}
+
+// Performs the given accessibilty action on the webkit accessibility object
+// that backs this object.
+- (void)accessibilityPerformAction:(NSString*)action {
+ // TODO(feldstein): Support more actions.
+ if ([action isEqualToString:NSAccessibilityPressAction])
+ [delegate_ doDefaultAction:browserAccessibility_->renderer_id()];
+ else if ([action isEqualToString:NSAccessibilityShowMenuAction])
+ [delegate_ performShowMenuAction:self];
+}
+
+// Returns the description of the given action.
+- (NSString*)accessibilityActionDescription:(NSString*)action {
+ 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 ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
+ NSNumber* focusedNumber = value;
+ BOOL focused = [focusedNumber intValue];
+ [delegate_ setAccessibilityFocus:focused
+ accessibilityId:browserAccessibility_->renderer_id()];
+ }
+}
+
+// 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.
+- (id)accessibilityHitTest:(NSPoint)point {
+ BrowserAccessibilityCocoa* hit = self;
+ for (BrowserAccessibilityCocoa* child in [self children]) {
+ 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_->renderer_id();
+}
+
+@end
+