1/* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "DumpRenderTree.h" 28#import "AccessibilityUIElement.h" 29 30#import <Foundation/Foundation.h> 31#import <JavaScriptCore/JSStringRef.h> 32#import <JavaScriptCore/JSStringRefCF.h> 33#import <WebKit/WebFrame.h> 34#import <WebKit/WebHTMLView.h> 35#import <WebKit/WebTypesInternal.h> 36#import <wtf/RetainPtr.h> 37#import <wtf/Vector.h> 38 39#ifdef BUILDING_ON_TIGER 40#define NSAccessibilityValueDescriptionAttribute @"AXValueDescription" 41#endif 42 43@interface NSObject (WebKitAccessibilityArrayCategory) 44- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount; 45@end 46 47AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element) 48 : m_element(element) 49{ 50 [m_element retain]; 51} 52 53AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other) 54 : m_element(other.m_element) 55{ 56 [m_element retain]; 57} 58 59AccessibilityUIElement::~AccessibilityUIElement() 60{ 61 [m_element release]; 62} 63 64@interface NSString (JSStringRefAdditions) 65+ (NSString *)stringWithJSStringRef:(JSStringRef)jsStringRef; 66- (JSStringRef)createJSStringRef; 67@end 68 69@implementation NSString (JSStringRefAdditions) 70 71+ (NSString *)stringWithJSStringRef:(JSStringRef)jsStringRef 72{ 73 if (!jsStringRef) 74 return NULL; 75 76 CFStringRef cfString = JSStringCopyCFString(kCFAllocatorDefault, jsStringRef); 77 return [(NSString *)cfString autorelease]; 78} 79 80- (JSStringRef)createJSStringRef 81{ 82 return JSStringCreateWithCFString((CFStringRef)self); 83} 84 85@end 86 87static NSString* descriptionOfValue(id valueObject, id focusedAccessibilityObject) 88{ 89 if (!valueObject) 90 return NULL; 91 92 if ([valueObject isKindOfClass:[NSArray class]]) 93 return [NSString stringWithFormat:@"<array of size %d>", [(NSArray*)valueObject count]]; 94 95 if ([valueObject isKindOfClass:[NSNumber class]]) 96 return [(NSNumber*)valueObject stringValue]; 97 98 if ([valueObject isKindOfClass:[NSValue class]]) { 99 NSString* type = [NSString stringWithCString:[valueObject objCType] encoding:NSASCIIStringEncoding]; 100 NSValue* value = (NSValue*)valueObject; 101 if ([type rangeOfString:@"NSRect"].length > 0) 102 return [NSString stringWithFormat:@"NSRect: %@", NSStringFromRect([value rectValue])]; 103 if ([type rangeOfString:@"NSPoint"].length > 0) 104 return [NSString stringWithFormat:@"NSPoint: %@", NSStringFromPoint([value pointValue])]; 105 if ([type rangeOfString:@"NSSize"].length > 0) 106 return [NSString stringWithFormat:@"NSSize: %@", NSStringFromSize([value sizeValue])]; 107 if ([type rangeOfString:@"NSRange"].length > 0) 108 return [NSString stringWithFormat:@"NSRange: %@", NSStringFromRange([value rangeValue])]; 109 } 110 111 // Strip absolute URL paths 112 NSString* description = [valueObject description]; 113 NSRange range = [description rangeOfString:@"LayoutTests"]; 114 if (range.length) 115 return [description substringFromIndex:range.location]; 116 117 // Strip pointer locations 118 if ([description rangeOfString:@"0x"].length) { 119 NSString* role = [focusedAccessibilityObject accessibilityAttributeValue:NSAccessibilityRoleAttribute]; 120 NSString* title = [focusedAccessibilityObject accessibilityAttributeValue:NSAccessibilityTitleAttribute]; 121 if ([title length]) 122 return [NSString stringWithFormat:@"<%@: '%@'>", role, title]; 123 return [NSString stringWithFormat:@"<%@>", role]; 124 } 125 126 return [valueObject description]; 127} 128 129static NSString* attributesOfElement(id accessibilityObject) 130{ 131 NSArray* supportedAttributes = [accessibilityObject accessibilityAttributeNames]; 132 133 NSMutableString* attributesString = [NSMutableString string]; 134 for (NSUInteger i = 0; i < [supportedAttributes count]; ++i) { 135 NSString* attribute = [supportedAttributes objectAtIndex:i]; 136 137 // Right now, position provides useless and screen-specific information, so we do not 138 // want to include it for the sake of universally passing tests. 139 if ([attribute isEqualToString:@"AXPosition"]) 140 continue; 141 142 // accessibilityAttributeValue: can throw an if an attribute is not returned. 143 // For DumpRenderTree's purpose, we should ignore those exceptions 144 @try { 145 id valueObject = [accessibilityObject accessibilityAttributeValue:attribute]; 146 NSString* value = descriptionOfValue(valueObject, accessibilityObject); 147 [attributesString appendFormat:@"%@: %@\n", attribute, value]; 148 } @catch (NSException* e) { } 149 } 150 151 return attributesString; 152} 153 154static JSStringRef concatenateAttributeAndValue(NSString* attribute, NSString* value) 155{ 156 Vector<UniChar> buffer([attribute length]); 157 [attribute getCharacters:buffer.data()]; 158 buffer.append(':'); 159 buffer.append(' '); 160 161 Vector<UniChar> valueBuffer([value length]); 162 [value getCharacters:valueBuffer.data()]; 163 buffer.append(valueBuffer); 164 165 return JSStringCreateWithCharacters(buffer.data(), buffer.size()); 166} 167 168static void convertNSArrayToVector(NSArray* array, Vector<AccessibilityUIElement>& elementVector) 169{ 170 NSUInteger count = [array count]; 171 for (NSUInteger i = 0; i < count; ++i) 172 elementVector.append(AccessibilityUIElement([array objectAtIndex:i])); 173} 174 175static JSStringRef descriptionOfElements(Vector<AccessibilityUIElement>& elementVector) 176{ 177 NSMutableString* allElementString = [NSMutableString string]; 178 size_t size = elementVector.size(); 179 for (size_t i = 0; i < size; ++i) { 180 NSString* attributes = attributesOfElement(elementVector[i].platformUIElement()); 181 [allElementString appendFormat:@"%@\n------------\n", attributes]; 182 } 183 184 return [allElementString createJSStringRef]; 185} 186 187void AccessibilityUIElement::getLinkedUIElements(Vector<AccessibilityUIElement>& elementVector) 188{ 189 NSArray* linkedElements = [m_element accessibilityAttributeValue:NSAccessibilityLinkedUIElementsAttribute]; 190 convertNSArrayToVector(linkedElements, elementVector); 191} 192 193void AccessibilityUIElement::getDocumentLinks(Vector<AccessibilityUIElement>& elementVector) 194{ 195 NSArray* linkElements = [m_element accessibilityAttributeValue:@"AXLinkUIElements"]; 196 convertNSArrayToVector(linkElements, elementVector); 197} 198 199void AccessibilityUIElement::getChildren(Vector<AccessibilityUIElement>& elementVector) 200{ 201 NSArray* children = [m_element accessibilityAttributeValue:NSAccessibilityChildrenAttribute]; 202 convertNSArrayToVector(children, elementVector); 203} 204 205void AccessibilityUIElement::getChildrenWithRange(Vector<AccessibilityUIElement>& elementVector, unsigned location, unsigned length) 206{ 207 NSArray* children = [m_element accessibilityArrayAttributeValues:NSAccessibilityChildrenAttribute index:location maxCount:length]; 208 convertNSArrayToVector(children, elementVector); 209} 210 211int AccessibilityUIElement::childrenCount() 212{ 213 Vector<AccessibilityUIElement> children; 214 getChildren(children); 215 216 return children.size(); 217} 218 219AccessibilityUIElement AccessibilityUIElement::elementAtPoint(int x, int y) 220{ 221 id element = [m_element accessibilityHitTest:NSMakePoint(x, y)]; 222 if (!element) 223 return nil; 224 225 return AccessibilityUIElement(element); 226} 227 228AccessibilityUIElement AccessibilityUIElement::getChildAtIndex(unsigned index) 229{ 230 Vector<AccessibilityUIElement> children; 231 getChildrenWithRange(children, index, 1); 232 233 if (children.size() == 1) 234 return children[0]; 235 return nil; 236} 237 238AccessibilityUIElement AccessibilityUIElement::titleUIElement() 239{ 240 id accessibilityObject = [m_element accessibilityAttributeValue:NSAccessibilityTitleUIElementAttribute]; 241 if (accessibilityObject) 242 return AccessibilityUIElement(accessibilityObject); 243 244 return nil; 245} 246 247AccessibilityUIElement AccessibilityUIElement::parentElement() 248{ 249 id accessibilityObject = [m_element accessibilityAttributeValue:NSAccessibilityParentAttribute]; 250 if (accessibilityObject) 251 return AccessibilityUIElement(accessibilityObject); 252 253 return nil; 254} 255 256JSStringRef AccessibilityUIElement::attributesOfLinkedUIElements() 257{ 258 Vector<AccessibilityUIElement> linkedElements; 259 getLinkedUIElements(linkedElements); 260 return descriptionOfElements(linkedElements); 261} 262 263JSStringRef AccessibilityUIElement::attributesOfDocumentLinks() 264{ 265 Vector<AccessibilityUIElement> linkElements; 266 getDocumentLinks(linkElements); 267 return descriptionOfElements(linkElements); 268} 269 270JSStringRef AccessibilityUIElement::attributesOfChildren() 271{ 272 Vector<AccessibilityUIElement> children; 273 getChildren(children); 274 return descriptionOfElements(children); 275} 276 277JSStringRef AccessibilityUIElement::allAttributes() 278{ 279 NSString* attributes = attributesOfElement(m_element); 280 return [attributes createJSStringRef]; 281} 282 283JSStringRef AccessibilityUIElement::attributeValue(JSStringRef attribute) 284{ 285 id value = [m_element accessibilityAttributeValue:[NSString stringWithJSStringRef:attribute]]; 286 if (![value isKindOfClass:[NSString class]]) 287 return NULL; 288 return [value createJSStringRef]; 289} 290 291bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute) 292{ 293 return [m_element accessibilityIsAttributeSettable:[NSString stringWithJSStringRef:attribute]]; 294} 295 296JSStringRef AccessibilityUIElement::parameterizedAttributeNames() 297{ 298 NSArray* supportedParameterizedAttributes = [m_element accessibilityParameterizedAttributeNames]; 299 300 NSMutableString* attributesString = [NSMutableString string]; 301 for (NSUInteger i = 0; i < [supportedParameterizedAttributes count]; ++i) { 302 [attributesString appendFormat:@"%@\n", [supportedParameterizedAttributes objectAtIndex:i]]; 303 } 304 305 return [attributesString createJSStringRef]; 306} 307 308JSStringRef AccessibilityUIElement::role() 309{ 310 NSString* role = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityRoleAttribute], m_element); 311 return concatenateAttributeAndValue(@"AXRole", role); 312} 313 314JSStringRef AccessibilityUIElement::title() 315{ 316 NSString* title = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityTitleAttribute], m_element); 317 return concatenateAttributeAndValue(@"AXTitle", title); 318} 319 320JSStringRef AccessibilityUIElement::description() 321{ 322 id description = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityDescriptionAttribute], m_element); 323 return concatenateAttributeAndValue(@"AXDescription", description); 324} 325 326JSStringRef AccessibilityUIElement::language() 327{ 328 id description = descriptionOfValue([m_element accessibilityAttributeValue:@"AXLanguage"], m_element); 329 return concatenateAttributeAndValue(@"AXLanguage", description); 330} 331 332double AccessibilityUIElement::x() 333{ 334 NSValue* positionValue = [m_element accessibilityAttributeValue:NSAccessibilityPositionAttribute]; 335 return static_cast<double>([positionValue pointValue].x); 336} 337 338double AccessibilityUIElement::y() 339{ 340 NSValue* positionValue = [m_element accessibilityAttributeValue:NSAccessibilityPositionAttribute]; 341 return static_cast<double>([positionValue pointValue].y); 342} 343 344double AccessibilityUIElement::width() 345{ 346 NSValue* sizeValue = [m_element accessibilityAttributeValue:NSAccessibilitySizeAttribute]; 347 return static_cast<double>([sizeValue sizeValue].width); 348} 349 350double AccessibilityUIElement::height() 351{ 352 NSValue* sizeValue = [m_element accessibilityAttributeValue:NSAccessibilitySizeAttribute]; 353 return static_cast<double>([sizeValue sizeValue].height); 354} 355 356double AccessibilityUIElement::clickPointX() 357{ 358 NSValue* positionValue = [m_element accessibilityAttributeValue:@"AXClickPoint"]; 359 return static_cast<double>([positionValue pointValue].x); 360} 361 362double AccessibilityUIElement::clickPointY() 363{ 364 NSValue* positionValue = [m_element accessibilityAttributeValue:@"AXClickPoint"]; 365 return static_cast<double>([positionValue pointValue].x); 366} 367 368double AccessibilityUIElement::intValue() 369{ 370 id value = [m_element accessibilityAttributeValue:NSAccessibilityValueAttribute]; 371 if ([value isKindOfClass:[NSNumber class]]) 372 return [(NSNumber*)value doubleValue]; 373 return 0.0f; 374} 375 376double AccessibilityUIElement::minValue() 377{ 378 id value = [m_element accessibilityAttributeValue:NSAccessibilityMinValueAttribute]; 379 if ([value isKindOfClass:[NSNumber class]]) 380 return [(NSNumber*)value doubleValue]; 381 return 0.0f; 382} 383 384double AccessibilityUIElement::maxValue() 385{ 386 id value = [m_element accessibilityAttributeValue:NSAccessibilityMaxValueAttribute]; 387 if ([value isKindOfClass:[NSNumber class]]) 388 return [(NSNumber*)value doubleValue]; 389 return 0.0; 390} 391 392JSStringRef AccessibilityUIElement::valueDescription() 393{ 394 NSString* valueDescription = [m_element accessibilityAttributeValue:NSAccessibilityValueDescriptionAttribute]; 395 if ([valueDescription isKindOfClass:[NSString class]]) 396 return [valueDescription createJSStringRef]; 397 return 0; 398} 399 400int AccessibilityUIElement::insertionPointLineNumber() 401{ 402 id value = [m_element accessibilityAttributeValue:NSAccessibilityInsertionPointLineNumberAttribute]; 403 if ([value isKindOfClass:[NSNumber class]]) 404 return [(NSNumber *)value intValue]; 405 return -1; 406} 407 408bool AccessibilityUIElement::isActionSupported(JSStringRef action) 409{ 410 NSArray* actions = [m_element accessibilityActionNames]; 411 return [actions containsObject:[NSString stringWithJSStringRef:action]]; 412} 413 414bool AccessibilityUIElement::isEnabled() 415{ 416 id value = [m_element accessibilityAttributeValue:NSAccessibilityEnabledAttribute]; 417 if ([value isKindOfClass:[NSNumber class]]) 418 return [value boolValue]; 419 return false; 420} 421 422bool AccessibilityUIElement::isRequired() const 423{ 424 id value = [m_element accessibilityAttributeValue:@"AXRequired"]; 425 if ([value isKindOfClass:[NSNumber class]]) 426 return [value boolValue]; 427 return false; 428} 429 430// parameterized attributes 431int AccessibilityUIElement::lineForIndex(int index) 432{ 433 id value = [m_element accessibilityAttributeValue:NSAccessibilityLineForIndexParameterizedAttribute forParameter:[NSNumber numberWithInt:index]]; 434 if ([value isKindOfClass:[NSNumber class]]) 435 return [(NSNumber *)value intValue]; 436 return -1; 437} 438 439JSStringRef AccessibilityUIElement::boundsForRange(unsigned location, unsigned length) 440{ 441 NSRange range = NSMakeRange(location, length); 442 id value = [m_element accessibilityAttributeValue:NSAccessibilityBoundsForRangeParameterizedAttribute forParameter:[NSValue valueWithRange:range]]; 443 NSRect rect = NSMakeRect(0,0,0,0); 444 if ([value isKindOfClass:[NSValue class]]) 445 rect = [value rectValue]; 446 447 // don't return position information because it is platform dependent 448 NSMutableString* boundsDescription = [NSMutableString stringWithFormat:@"{{%f, %f}, {%f, %f}}",-1.0f,-1.0f,rect.size.width,rect.size.height]; 449 return [boundsDescription createJSStringRef]; 450} 451 452JSStringRef AccessibilityUIElement::attributesOfColumnHeaders() 453{ 454 // not yet defined in AppKit... odd 455 NSArray* columnHeadersArray = [m_element accessibilityAttributeValue:@"AXColumnHeaderUIElements"]; 456 Vector<AccessibilityUIElement> columnHeadersVector; 457 convertNSArrayToVector(columnHeadersArray, columnHeadersVector); 458 return descriptionOfElements(columnHeadersVector); 459} 460 461JSStringRef AccessibilityUIElement::attributesOfRowHeaders() 462{ 463 NSArray* rowHeadersArray = [m_element accessibilityAttributeValue:@"AXRowHeaderUIElements"]; 464 Vector<AccessibilityUIElement> rowHeadersVector; 465 convertNSArrayToVector(rowHeadersArray, rowHeadersVector); 466 return descriptionOfElements(rowHeadersVector); 467} 468 469JSStringRef AccessibilityUIElement::attributesOfColumns() 470{ 471 NSArray* columnsArray = [m_element accessibilityAttributeValue:NSAccessibilityColumnsAttribute]; 472 Vector<AccessibilityUIElement> columnsVector; 473 convertNSArrayToVector(columnsArray, columnsVector); 474 return descriptionOfElements(columnsVector); 475} 476 477JSStringRef AccessibilityUIElement::attributesOfRows() 478{ 479 NSArray* rowsArray = [m_element accessibilityAttributeValue:NSAccessibilityRowsAttribute]; 480 Vector<AccessibilityUIElement> rowsVector; 481 convertNSArrayToVector(rowsArray, rowsVector); 482 return descriptionOfElements(rowsVector); 483} 484 485JSStringRef AccessibilityUIElement::attributesOfVisibleCells() 486{ 487 NSArray* cellsArray = [m_element accessibilityAttributeValue:@"AXVisibleCells"]; 488 Vector<AccessibilityUIElement> cellsVector; 489 convertNSArrayToVector(cellsArray, cellsVector); 490 return descriptionOfElements(cellsVector); 491} 492 493JSStringRef AccessibilityUIElement::attributesOfHeader() 494{ 495 id headerObject = [m_element accessibilityAttributeValue:NSAccessibilityHeaderAttribute]; 496 if (!headerObject) 497 return [@"" createJSStringRef]; 498 499 Vector<AccessibilityUIElement> headerVector; 500 headerVector.append(headerObject); 501 return descriptionOfElements(headerVector); 502} 503 504int AccessibilityUIElement::indexInTable() 505{ 506 NSNumber* indexNumber = [m_element accessibilityAttributeValue:NSAccessibilityIndexAttribute]; 507 if (!indexNumber) 508 return -1; 509 return [indexNumber intValue]; 510} 511 512JSStringRef AccessibilityUIElement::rowIndexRange() 513{ 514 NSValue* indexRange = [m_element accessibilityAttributeValue:@"AXRowIndexRange"]; 515 NSRange range = indexRange ? [indexRange rangeValue] : NSMakeRange(0,0); 516 NSMutableString* rangeDescription = [NSMutableString stringWithFormat:@"{%d, %d}",range.location, range.length]; 517 return [rangeDescription createJSStringRef]; 518} 519 520JSStringRef AccessibilityUIElement::columnIndexRange() 521{ 522 NSNumber* indexRange = [m_element accessibilityAttributeValue:@"AXColumnIndexRange"]; 523 NSRange range = indexRange ? [indexRange rangeValue] : NSMakeRange(0,0); 524 NSMutableString* rangeDescription = [NSMutableString stringWithFormat:@"{%d, %d}",range.location, range.length]; 525 return [rangeDescription createJSStringRef]; 526} 527 528AccessibilityUIElement AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row) 529{ 530 NSArray *colRowArray = [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:col], [NSNumber numberWithUnsignedInt:row], nil]; 531 return [m_element accessibilityAttributeValue:@"AXCellForColumnAndRow" forParameter:colRowArray]; 532} 533 534JSStringRef AccessibilityUIElement::selectedTextRange() 535{ 536 NSNumber *indexRange = [m_element accessibilityAttributeValue:NSAccessibilitySelectedTextRangeAttribute]; 537 NSRange range = indexRange ? [indexRange rangeValue] : NSMakeRange(0,0); 538 NSMutableString *rangeDescription = [NSMutableString stringWithFormat:@"{%d, %d}",range.location, range.length]; 539 return [rangeDescription createJSStringRef]; 540} 541 542void AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length) 543{ 544 NSRange textRange = NSMakeRange(location, length); 545 NSValue *textRangeValue = [NSValue valueWithRange:textRange]; 546 [m_element accessibilitySetValue:textRangeValue forAttribute:NSAccessibilitySelectedTextRangeAttribute]; 547} 548 549void AccessibilityUIElement::increment() 550{ 551 [m_element accessibilityPerformAction:NSAccessibilityIncrementAction]; 552} 553 554void AccessibilityUIElement::decrement() 555{ 556 [m_element accessibilityPerformAction:NSAccessibilityDecrementAction]; 557} 558