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