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