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