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