/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "DumpRenderTree.h" #import "AccessibilityUIElement.h" #import #import #import #import #import #import #import #import #import #ifdef BUILDING_ON_TIGER #define NSAccessibilityValueDescriptionAttribute @"AXValueDescription" #endif #ifndef NSAccessibilityOwnsAttribute #define NSAccessibilityOwnsAttribute @"AXOwns" #endif #ifndef NSAccessibilityGrabbedAttribute #define NSAccessibilityGrabbedAttribute @"AXGrabbed" #endif #ifndef NSAccessibilityDropEffectsAttribute #define NSAccessibilityDropEffectsAttribute @"AXDropEffects" #endif // If an unsupported attribute is passed in, it will raise an accessibility exception. These are usually caught by the Accessibility Runtime to inform // the AX client app of the error. However, DRT is the AX client app, so it must catch these exceptions. #define BEGIN_AX_OBJC_EXCEPTIONS @try { #define END_AX_OBJC_EXCEPTIONS } @catch(NSException *e) { if (![[e name] isEqualToString:NSAccessibilityException]) @throw; } typedef void (*AXPostedNotificationCallback)(id element, NSString* notification, void* context); @interface NSObject (WebKitAccessibilityAdditions) - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount; - (void)accessibilitySetShouldRepostNotifications:(BOOL)repost; - (NSUInteger)accessibilityIndexOfChild:(id)child; - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute; @end @interface NSString (JSStringRefAdditions) + (NSString *)stringWithJSStringRef:(JSStringRef)jsStringRef; - (JSStringRef)createJSStringRef; @end @implementation NSString (JSStringRefAdditions) + (NSString *)stringWithJSStringRef:(JSStringRef)jsStringRef { if (!jsStringRef) return NULL; CFStringRef cfString = JSStringCopyCFString(kCFAllocatorDefault, jsStringRef); return [(NSString *)cfString autorelease]; } - (JSStringRef)createJSStringRef { return JSStringCreateWithCFString((CFStringRef)self); } @end @interface AccessibilityNotificationHandler : NSObject { id m_platformElement; JSObjectRef m_notificationFunctionCallback; } @end @implementation AccessibilityNotificationHandler - (id)initWithPlatformElement:(id)platformElement { self = [super init]; m_platformElement = platformElement; // Once an object starts requesting notifications, it's on for the duration of the program. // This is to avoid any race conditions between tests turning this flag on and off. Instead // AccessibilityNotificationHandler can just listen when they want to. [m_platformElement accessibilitySetShouldRepostNotifications:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_notificationReceived:) name:@"AXDRTNotification" object:nil]; return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; JSValueUnprotect([mainFrame globalContext], m_notificationFunctionCallback); m_notificationFunctionCallback = 0; [super dealloc]; } - (void)_notificationReceived:(NSNotification *)notification { NSString *notificationName = [[notification userInfo] objectForKey:@"notificationName"]; if (!notificationName) return; JSRetainPtr jsNotification(Adopt, [notificationName createJSStringRef]); JSValueRef argument = JSValueMakeString([mainFrame globalContext], jsNotification.get()); JSObjectCallAsFunction([mainFrame globalContext], m_notificationFunctionCallback, 0, 1, &argument, 0); } - (void)setCallback:(JSObjectRef)callback { if (!callback) return; // Release the old callback. if (m_notificationFunctionCallback) JSValueUnprotect([mainFrame globalContext], m_notificationFunctionCallback); m_notificationFunctionCallback = callback; JSValueProtect([mainFrame globalContext], m_notificationFunctionCallback); } @end AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element) : m_element(element) , m_notificationHandler(0) { // FIXME: ap@webkit.org says ObjC objects need to be CFRetained/CFRelease to be GC-compliant on the mac. [m_element retain]; } AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other) : m_element(other.m_element) , m_notificationHandler(0) { [m_element retain]; } AccessibilityUIElement::~AccessibilityUIElement() { // The notification handler should be nil because removeNotificationListener() should have been called in the test. ASSERT(!m_notificationHandler); [m_element release]; } static NSString* descriptionOfValue(id valueObject, id focusedAccessibilityObject) { if (!valueObject) return NULL; if ([valueObject isKindOfClass:[NSArray class]]) return [NSString stringWithFormat:@"", [(NSArray*)valueObject count]]; if ([valueObject isKindOfClass:[NSNumber class]]) return [(NSNumber*)valueObject stringValue]; if ([valueObject isKindOfClass:[NSValue class]]) { NSString* type = [NSString stringWithCString:[valueObject objCType] encoding:NSASCIIStringEncoding]; NSValue* value = (NSValue*)valueObject; if ([type rangeOfString:@"NSRect"].length > 0) return [NSString stringWithFormat:@"NSRect: %@", NSStringFromRect([value rectValue])]; if ([type rangeOfString:@"NSPoint"].length > 0) return [NSString stringWithFormat:@"NSPoint: %@", NSStringFromPoint([value pointValue])]; if ([type rangeOfString:@"NSSize"].length > 0) return [NSString stringWithFormat:@"NSSize: %@", NSStringFromSize([value sizeValue])]; if ([type rangeOfString:@"NSRange"].length > 0) return [NSString stringWithFormat:@"NSRange: %@", NSStringFromRange([value rangeValue])]; } // Strip absolute URL paths NSString* description = [valueObject description]; NSRange range = [description rangeOfString:@"LayoutTests"]; if (range.length) return [description substringFromIndex:range.location]; // Strip pointer locations if ([description rangeOfString:@"0x"].length) { NSString* role = [focusedAccessibilityObject accessibilityAttributeValue:NSAccessibilityRoleAttribute]; NSString* title = [focusedAccessibilityObject accessibilityAttributeValue:NSAccessibilityTitleAttribute]; if ([title length]) return [NSString stringWithFormat:@"<%@: '%@'>", role, title]; return [NSString stringWithFormat:@"<%@>", role]; } return [valueObject description]; } static NSString* attributesOfElement(id accessibilityObject) { NSArray* supportedAttributes = [accessibilityObject accessibilityAttributeNames]; NSMutableString* attributesString = [NSMutableString string]; for (NSUInteger i = 0; i < [supportedAttributes count]; ++i) { NSString* attribute = [supportedAttributes objectAtIndex:i]; // Right now, position provides useless and screen-specific information, so we do not // want to include it for the sake of universally passing tests. if ([attribute isEqualToString:@"AXPosition"]) continue; // accessibilityAttributeValue: can throw an if an attribute is not returned. // For DumpRenderTree's purpose, we should ignore those exceptions BEGIN_AX_OBJC_EXCEPTIONS id valueObject = [accessibilityObject accessibilityAttributeValue:attribute]; NSString* value = descriptionOfValue(valueObject, accessibilityObject); [attributesString appendFormat:@"%@: %@\n", attribute, value]; END_AX_OBJC_EXCEPTIONS } return attributesString; } static JSStringRef concatenateAttributeAndValue(NSString* attribute, NSString* value) { Vector buffer([attribute length]); [attribute getCharacters:buffer.data()]; buffer.append(':'); buffer.append(' '); Vector valueBuffer([value length]); [value getCharacters:valueBuffer.data()]; buffer.append(valueBuffer); return JSStringCreateWithCharacters(buffer.data(), buffer.size()); } static void convertNSArrayToVector(NSArray* array, Vector& elementVector) { NSUInteger count = [array count]; for (NSUInteger i = 0; i < count; ++i) elementVector.append(AccessibilityUIElement([array objectAtIndex:i])); } static JSStringRef descriptionOfElements(Vector& elementVector) { NSMutableString* allElementString = [NSMutableString string]; size_t size = elementVector.size(); for (size_t i = 0; i < size; ++i) { NSString* attributes = attributesOfElement(elementVector[i].platformUIElement()); [allElementString appendFormat:@"%@\n------------\n", attributes]; } return [allElementString createJSStringRef]; } void AccessibilityUIElement::getLinkedUIElements(Vector& elementVector) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* linkedElements = [m_element accessibilityAttributeValue:NSAccessibilityLinkedUIElementsAttribute]; convertNSArrayToVector(linkedElements, elementVector); END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::getDocumentLinks(Vector& elementVector) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* linkElements = [m_element accessibilityAttributeValue:@"AXLinkUIElements"]; convertNSArrayToVector(linkElements, elementVector); END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::getChildren(Vector& elementVector) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* children = [m_element accessibilityAttributeValue:NSAccessibilityChildrenAttribute]; convertNSArrayToVector(children, elementVector); END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::getChildrenWithRange(Vector& elementVector, unsigned location, unsigned length) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* children = [m_element accessibilityArrayAttributeValues:NSAccessibilityChildrenAttribute index:location maxCount:length]; convertNSArrayToVector(children, elementVector); END_AX_OBJC_EXCEPTIONS } int AccessibilityUIElement::childrenCount() { Vector children; getChildren(children); return children.size(); } AccessibilityUIElement AccessibilityUIElement::elementAtPoint(int x, int y) { id element = [m_element accessibilityHitTest:NSMakePoint(x, y)]; if (!element) return nil; return AccessibilityUIElement(element); } unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element) { return [m_element accessibilityIndexOfChild:element->platformUIElement()]; } AccessibilityUIElement AccessibilityUIElement::getChildAtIndex(unsigned index) { Vector children; getChildrenWithRange(children, index, 1); if (children.size() == 1) return children[0]; return 0; } AccessibilityUIElement AccessibilityUIElement::linkedUIElementAtIndex(unsigned index) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* objects = [m_element accessibilityAttributeValue:NSAccessibilityLinkedUIElementsAttribute]; if (index < [objects count]) return [objects objectAtIndex:index]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* objects = [m_element accessibilityAttributeValue:NSAccessibilityOwnsAttribute]; if (index < [objects count]) return [objects objectAtIndex:index]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* objects = [m_element accessibilityAttributeValue:NSAccessibilityLinkedUIElementsAttribute]; if (index < [objects count]) return [objects objectAtIndex:index]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::disclosedRowAtIndex(unsigned index) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* rows = [m_element accessibilityAttributeValue:NSAccessibilityDisclosedRowsAttribute]; if (index < [rows count]) return [rows objectAtIndex:index]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::selectedChildAtIndex(unsigned index) const { BEGIN_AX_OBJC_EXCEPTIONS NSArray* array = [m_element accessibilityAttributeValue:NSAccessibilitySelectedChildrenAttribute]; if (index < [array count]) return [array objectAtIndex:index]; END_AX_OBJC_EXCEPTIONS return 0; } unsigned AccessibilityUIElement::selectedChildrenCount() const { BEGIN_AX_OBJC_EXCEPTIONS return [m_element accessibilityArrayAttributeCount:NSAccessibilitySelectedChildrenAttribute]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::selectedRowAtIndex(unsigned index) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* rows = [m_element accessibilityAttributeValue:NSAccessibilitySelectedRowsAttribute]; if (index < [rows count]) return [rows objectAtIndex:index]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::titleUIElement() { BEGIN_AX_OBJC_EXCEPTIONS id accessibilityObject = [m_element accessibilityAttributeValue:NSAccessibilityTitleUIElementAttribute]; if (accessibilityObject) return AccessibilityUIElement(accessibilityObject); END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::parentElement() { BEGIN_AX_OBJC_EXCEPTIONS id accessibilityObject = [m_element accessibilityAttributeValue:NSAccessibilityParentAttribute]; if (accessibilityObject) return AccessibilityUIElement(accessibilityObject); END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::disclosedByRow() { BEGIN_AX_OBJC_EXCEPTIONS id accessibilityObject = [m_element accessibilityAttributeValue:NSAccessibilityDisclosedByRowAttribute]; if (accessibilityObject) return AccessibilityUIElement(accessibilityObject); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributesOfLinkedUIElements() { Vector linkedElements; getLinkedUIElements(linkedElements); return descriptionOfElements(linkedElements); } JSStringRef AccessibilityUIElement::attributesOfDocumentLinks() { Vector linkElements; getDocumentLinks(linkElements); return descriptionOfElements(linkElements); } JSStringRef AccessibilityUIElement::attributesOfChildren() { Vector children; getChildren(children); return descriptionOfElements(children); } JSStringRef AccessibilityUIElement::allAttributes() { NSString* attributes = attributesOfElement(m_element); return [attributes createJSStringRef]; } JSStringRef AccessibilityUIElement::stringAttributeValue(JSStringRef attribute) { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:[NSString stringWithJSStringRef:attribute]]; if ([value isKindOfClass:[NSString class]]) return [value createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute) { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:[NSString stringWithJSStringRef:attribute]]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute) { BEGIN_AX_OBJC_EXCEPTIONS return [m_element accessibilityIsAttributeSettable:[NSString stringWithJSStringRef:attribute]]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute) { BEGIN_AX_OBJC_EXCEPTIONS return [[m_element accessibilityAttributeNames] containsObject:[NSString stringWithJSStringRef:attribute]]; END_AX_OBJC_EXCEPTIONS return false; } JSStringRef AccessibilityUIElement::parameterizedAttributeNames() { NSArray* supportedParameterizedAttributes = [m_element accessibilityParameterizedAttributeNames]; NSMutableString* attributesString = [NSMutableString string]; for (NSUInteger i = 0; i < [supportedParameterizedAttributes count]; ++i) { [attributesString appendFormat:@"%@\n", [supportedParameterizedAttributes objectAtIndex:i]]; } return [attributesString createJSStringRef]; } JSStringRef AccessibilityUIElement::role() { BEGIN_AX_OBJC_EXCEPTIONS NSString *role = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityRoleAttribute], m_element); return concatenateAttributeAndValue(@"AXRole", role); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::subrole() { BEGIN_AX_OBJC_EXCEPTIONS NSString* role = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilitySubroleAttribute], m_element); return concatenateAttributeAndValue(@"AXSubrole", role); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::roleDescription() { BEGIN_AX_OBJC_EXCEPTIONS NSString* role = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute], m_element); return concatenateAttributeAndValue(@"AXRoleDescription", role); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::title() { BEGIN_AX_OBJC_EXCEPTIONS NSString* title = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityTitleAttribute], m_element); return concatenateAttributeAndValue(@"AXTitle", title); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::description() { BEGIN_AX_OBJC_EXCEPTIONS id description = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityDescriptionAttribute], m_element); return concatenateAttributeAndValue(@"AXDescription", description); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::orientation() const { BEGIN_AX_OBJC_EXCEPTIONS id description = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityOrientationAttribute], m_element); return concatenateAttributeAndValue(@"AXOrientation", description); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::stringValue() { BEGIN_AX_OBJC_EXCEPTIONS id description = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityValueAttribute], m_element); return concatenateAttributeAndValue(@"AXValue", description); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::language() { BEGIN_AX_OBJC_EXCEPTIONS id description = descriptionOfValue([m_element accessibilityAttributeValue:@"AXLanguage"], m_element); return concatenateAttributeAndValue(@"AXLanguage", description); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::helpText() const { BEGIN_AX_OBJC_EXCEPTIONS id description = descriptionOfValue([m_element accessibilityAttributeValue:NSAccessibilityHelpAttribute], m_element); return concatenateAttributeAndValue(@"AXHelp", description); END_AX_OBJC_EXCEPTIONS return 0; } double AccessibilityUIElement::x() { BEGIN_AX_OBJC_EXCEPTIONS NSValue* positionValue = [m_element accessibilityAttributeValue:NSAccessibilityPositionAttribute]; return static_cast([positionValue pointValue].x); END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::y() { BEGIN_AX_OBJC_EXCEPTIONS NSValue* positionValue = [m_element accessibilityAttributeValue:NSAccessibilityPositionAttribute]; return static_cast([positionValue pointValue].y); END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::width() { BEGIN_AX_OBJC_EXCEPTIONS NSValue* sizeValue = [m_element accessibilityAttributeValue:NSAccessibilitySizeAttribute]; return static_cast([sizeValue sizeValue].width); END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::height() { BEGIN_AX_OBJC_EXCEPTIONS NSValue* sizeValue = [m_element accessibilityAttributeValue:NSAccessibilitySizeAttribute]; return static_cast([sizeValue sizeValue].height); END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::clickPointX() { BEGIN_AX_OBJC_EXCEPTIONS NSValue* positionValue = [m_element accessibilityAttributeValue:@"AXClickPoint"]; return static_cast([positionValue pointValue].x); END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::clickPointY() { BEGIN_AX_OBJC_EXCEPTIONS NSValue* positionValue = [m_element accessibilityAttributeValue:@"AXClickPoint"]; return static_cast([positionValue pointValue].y); END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::intValue() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityValueAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [(NSNumber*)value doubleValue]; END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::minValue() { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityMinValueAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [(NSNumber*)value doubleValue]; END_AX_OBJC_EXCEPTIONS return 0.0f; } double AccessibilityUIElement::maxValue() { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityMaxValueAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [(NSNumber*)value doubleValue]; END_AX_OBJC_EXCEPTIONS return 0.0; } JSStringRef AccessibilityUIElement::valueDescription() { BEGIN_AX_OBJC_EXCEPTIONS NSString* valueDescription = [m_element accessibilityAttributeValue:NSAccessibilityValueDescriptionAttribute]; if ([valueDescription isKindOfClass:[NSString class]]) return [valueDescription createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } int AccessibilityUIElement::insertionPointLineNumber() { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityInsertionPointLineNumberAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [(NSNumber *)value intValue]; END_AX_OBJC_EXCEPTIONS return -1; } bool AccessibilityUIElement::isActionSupported(JSStringRef action) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* actions = [m_element accessibilityActionNames]; return [actions containsObject:[NSString stringWithJSStringRef:action]]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isEnabled() { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityEnabledAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isRequired() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:@"AXRequired"]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isSelected() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilitySelectedAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isExpanded() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityExpandedAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } bool AccessibilityUIElement::isChecked() const { // On the Mac, intValue()==1 if a a checkable control is checked. return intValue() == 1; } int AccessibilityUIElement::hierarchicalLevel() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityDisclosureLevelAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [value intValue]; END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::speak() { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:@"AXDRTSpeechAttribute"]; if ([value isKindOfClass:[NSString class]]) return [value createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } bool AccessibilityUIElement::ariaIsGrabbed() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityGrabbedAttribute]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } JSStringRef AccessibilityUIElement::ariaDropEffects() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityDropEffectsAttribute]; if (![value isKindOfClass:[NSArray class]]) return 0; NSMutableString* dropEffects = [NSMutableString string]; NSInteger length = [value count]; for (NSInteger k = 0; k < length; ++k) { [dropEffects appendString:[value objectAtIndex:k]]; if (k < length - 1) [dropEffects appendString:@","]; } return [dropEffects createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } // parameterized attributes int AccessibilityUIElement::lineForIndex(int index) { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityLineForIndexParameterizedAttribute forParameter:[NSNumber numberWithInt:index]]; if ([value isKindOfClass:[NSNumber class]]) return [(NSNumber *)value intValue]; END_AX_OBJC_EXCEPTIONS return -1; } JSStringRef AccessibilityUIElement::rangeForLine(int line) { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityRangeForLineParameterizedAttribute forParameter:[NSNumber numberWithInt:line]]; if ([value isKindOfClass:[NSValue class]]) { return [NSStringFromRange([value rangeValue]) createJSStringRef]; } END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::boundsForRange(unsigned location, unsigned length) { NSRange range = NSMakeRange(location, length); BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:NSAccessibilityBoundsForRangeParameterizedAttribute forParameter:[NSValue valueWithRange:range]]; NSRect rect = NSMakeRect(0,0,0,0); if ([value isKindOfClass:[NSValue class]]) rect = [value rectValue]; // don't return position information because it is platform dependent NSMutableString* boundsDescription = [NSMutableString stringWithFormat:@"{{%f, %f}, {%f, %f}}",-1.0f,-1.0f,rect.size.width,rect.size.height]; return [boundsDescription createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::stringForRange(unsigned location, unsigned length) { NSRange range = NSMakeRange(location, length); BEGIN_AX_OBJC_EXCEPTIONS id string = [m_element accessibilityAttributeValue:NSAccessibilityStringForRangeParameterizedAttribute forParameter:[NSValue valueWithRange:range]]; if (![string isKindOfClass:[NSString class]]) return 0; return [string createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributedStringForRange(unsigned location, unsigned length) { NSRange range = NSMakeRange(location, length); BEGIN_AX_OBJC_EXCEPTIONS NSAttributedString* string = [m_element accessibilityAttributeValue:NSAccessibilityAttributedStringForRangeParameterizedAttribute forParameter:[NSValue valueWithRange:range]]; if (![string isKindOfClass:[NSAttributedString class]]) return 0; NSString* stringWithAttrs = [string description]; return [stringWithAttrs createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length) { NSRange range = NSMakeRange(location, length); BEGIN_AX_OBJC_EXCEPTIONS NSAttributedString* string = [m_element accessibilityAttributeValue:NSAccessibilityAttributedStringForRangeParameterizedAttribute forParameter:[NSValue valueWithRange:range]]; if (![string isKindOfClass:[NSAttributedString class]]) return false; NSDictionary* attrs = [string attributesAtIndex:0 effectiveRange:nil]; if([[attrs objectForKey:NSAccessibilityMisspelledTextAttribute] boolValue]) return true; END_AX_OBJC_EXCEPTIONS return false; } JSStringRef AccessibilityUIElement::attributesOfColumnHeaders() { // not yet defined in AppKit... odd BEGIN_AX_OBJC_EXCEPTIONS NSArray* columnHeadersArray = [m_element accessibilityAttributeValue:@"AXColumnHeaderUIElements"]; Vector columnHeadersVector; convertNSArrayToVector(columnHeadersArray, columnHeadersVector); return descriptionOfElements(columnHeadersVector); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributesOfRowHeaders() { BEGIN_AX_OBJC_EXCEPTIONS NSArray* rowHeadersArray = [m_element accessibilityAttributeValue:@"AXRowHeaderUIElements"]; Vector rowHeadersVector; convertNSArrayToVector(rowHeadersArray, rowHeadersVector); return descriptionOfElements(rowHeadersVector); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributesOfColumns() { BEGIN_AX_OBJC_EXCEPTIONS NSArray* columnsArray = [m_element accessibilityAttributeValue:NSAccessibilityColumnsAttribute]; Vector columnsVector; convertNSArrayToVector(columnsArray, columnsVector); return descriptionOfElements(columnsVector); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributesOfRows() { BEGIN_AX_OBJC_EXCEPTIONS NSArray* rowsArray = [m_element accessibilityAttributeValue:NSAccessibilityRowsAttribute]; Vector rowsVector; convertNSArrayToVector(rowsArray, rowsVector); return descriptionOfElements(rowsVector); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributesOfVisibleCells() { BEGIN_AX_OBJC_EXCEPTIONS NSArray* cellsArray = [m_element accessibilityAttributeValue:@"AXVisibleCells"]; Vector cellsVector; convertNSArrayToVector(cellsArray, cellsVector); return descriptionOfElements(cellsVector); END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::attributesOfHeader() { BEGIN_AX_OBJC_EXCEPTIONS id headerObject = [m_element accessibilityAttributeValue:NSAccessibilityHeaderAttribute]; if (!headerObject) return [@"" createJSStringRef]; Vector headerVector; headerVector.append(headerObject); return descriptionOfElements(headerVector); END_AX_OBJC_EXCEPTIONS return 0; } int AccessibilityUIElement::rowCount() { BEGIN_AX_OBJC_EXCEPTIONS return [m_element accessibilityArrayAttributeCount:NSAccessibilityRowsAttribute]; END_AX_OBJC_EXCEPTIONS return 0; } int AccessibilityUIElement::columnCount() { BEGIN_AX_OBJC_EXCEPTIONS return [m_element accessibilityArrayAttributeCount:NSAccessibilityColumnsAttribute]; END_AX_OBJC_EXCEPTIONS return 0; } int AccessibilityUIElement::indexInTable() { BEGIN_AX_OBJC_EXCEPTIONS NSNumber* indexNumber = [m_element accessibilityAttributeValue:NSAccessibilityIndexAttribute]; if (indexNumber) return [indexNumber intValue]; END_AX_OBJC_EXCEPTIONS return -1; } JSStringRef AccessibilityUIElement::rowIndexRange() { NSRange range = NSMakeRange(0,0); BEGIN_AX_OBJC_EXCEPTIONS NSValue* indexRange = [m_element accessibilityAttributeValue:@"AXRowIndexRange"]; if (indexRange) range = [indexRange rangeValue]; NSMutableString* rangeDescription = [NSMutableString stringWithFormat:@"{%d, %d}",range.location, range.length]; return [rangeDescription createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::columnIndexRange() { NSRange range = NSMakeRange(0,0); BEGIN_AX_OBJC_EXCEPTIONS NSNumber* indexRange = [m_element accessibilityAttributeValue:@"AXColumnIndexRange"]; if (indexRange) range = [indexRange rangeValue]; NSMutableString* rangeDescription = [NSMutableString stringWithFormat:@"{%d, %d}",range.location, range.length]; return [rangeDescription createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row) { NSArray *colRowArray = [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:col], [NSNumber numberWithUnsignedInt:row], nil]; BEGIN_AX_OBJC_EXCEPTIONS return [m_element accessibilityAttributeValue:@"AXCellForColumnAndRow" forParameter:colRowArray]; END_AX_OBJC_EXCEPTIONS return 0; } JSStringRef AccessibilityUIElement::selectedTextRange() { NSRange range = NSMakeRange(NSNotFound, 0); BEGIN_AX_OBJC_EXCEPTIONS NSValue *indexRange = [m_element accessibilityAttributeValue:NSAccessibilitySelectedTextRangeAttribute]; if (indexRange) range = [indexRange rangeValue]; NSMutableString *rangeDescription = [NSMutableString stringWithFormat:@"{%d, %d}",range.location, range.length]; return [rangeDescription createJSStringRef]; END_AX_OBJC_EXCEPTIONS return 0; } void AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length) { NSRange textRange = NSMakeRange(location, length); NSValue *textRangeValue = [NSValue valueWithRange:textRange]; BEGIN_AX_OBJC_EXCEPTIONS [m_element accessibilitySetValue:textRangeValue forAttribute:NSAccessibilitySelectedTextRangeAttribute]; END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::increment() { BEGIN_AX_OBJC_EXCEPTIONS [m_element accessibilityPerformAction:NSAccessibilityIncrementAction]; END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::decrement() { BEGIN_AX_OBJC_EXCEPTIONS [m_element accessibilityPerformAction:NSAccessibilityDecrementAction]; END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::showMenu() { BEGIN_AX_OBJC_EXCEPTIONS [m_element accessibilityPerformAction:NSAccessibilityShowMenuAction]; END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::press() { BEGIN_AX_OBJC_EXCEPTIONS [m_element accessibilityPerformAction:NSAccessibilityPressAction]; END_AX_OBJC_EXCEPTIONS } void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) const { BEGIN_AX_OBJC_EXCEPTIONS NSArray* array = [NSArray arrayWithObject:element->platformUIElement()]; [m_element accessibilitySetValue:array forAttribute:NSAccessibilitySelectedChildrenAttribute]; END_AX_OBJC_EXCEPTIONS } JSStringRef AccessibilityUIElement::accessibilityValue() const { // FIXME: implement return JSStringCreateWithCharacters(0, 0); } JSStringRef AccessibilityUIElement::documentEncoding() { return JSStringCreateWithCharacters(0, 0); } JSStringRef AccessibilityUIElement::documentURI() { return JSStringCreateWithCharacters(0, 0); } JSStringRef AccessibilityUIElement::url() { BEGIN_AX_OBJC_EXCEPTIONS NSURL *url = [m_element accessibilityAttributeValue:NSAccessibilityURLAttribute]; return [[url absoluteString] createJSStringRef]; END_AX_OBJC_EXCEPTIONS return nil; } bool AccessibilityUIElement::addNotificationListener(JSObjectRef functionCallback) { if (!functionCallback) return false; // Mac programmers should not be adding more than one notification listener per element. // Other platforms may be different. if (m_notificationHandler) return false; m_notificationHandler = [[AccessibilityNotificationHandler alloc] initWithPlatformElement:platformUIElement()]; [m_notificationHandler setCallback:functionCallback]; return true; } void AccessibilityUIElement::removeNotificationListener() { // Mac programmers should not be trying to remove a listener that's already removed. ASSERT(m_notificationHandler); [m_notificationHandler release]; m_notificationHandler = nil; } bool AccessibilityUIElement::isSelectable() const { // FIXME: implement return false; } bool AccessibilityUIElement::isMultiSelectable() const { // FIXME: implement return false; } bool AccessibilityUIElement::isVisible() const { // FIXME: implement return false; } bool AccessibilityUIElement::isOffScreen() const { // FIXME: implement return false; } bool AccessibilityUIElement::isCollapsed() const { // FIXME: implement return false; } bool AccessibilityUIElement::isIgnored() const { BOOL result = NO; BEGIN_AX_OBJC_EXCEPTIONS result = [m_element accessibilityIsIgnored]; END_AX_OBJC_EXCEPTIONS return result; } bool AccessibilityUIElement::hasPopup() const { BEGIN_AX_OBJC_EXCEPTIONS id value = [m_element accessibilityAttributeValue:@"AXHasPopup"]; if ([value isKindOfClass:[NSNumber class]]) return [value boolValue]; END_AX_OBJC_EXCEPTIONS return false; } void AccessibilityUIElement::takeFocus() { // FIXME: implement } void AccessibilityUIElement::takeSelection() { // FIXME: implement } void AccessibilityUIElement::addSelection() { // FIXME: implement } void AccessibilityUIElement::removeSelection() { // FIXME: implement } #if SUPPORTS_AX_TEXTMARKERS // Text markers AccessibilityTextMarkerRange AccessibilityUIElement::textMarkerRangeForElement(AccessibilityUIElement* element) { BEGIN_AX_OBJC_EXCEPTIONS id textMarkerRange = [m_element accessibilityAttributeValue:@"AXTextMarkerRangeForUIElement" forParameter:element->platformUIElement()]; return AccessibilityTextMarkerRange(textMarkerRange); END_AX_OBJC_EXCEPTIONS return 0; } int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* range) { BEGIN_AX_OBJC_EXCEPTIONS NSNumber* lengthValue = [m_element accessibilityAttributeValue:@"AXLengthForTextMarkerRange" forParameter:(id)range->platformTextMarkerRange()]; return [lengthValue intValue]; END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityTextMarkerRange AccessibilityUIElement::textMarkerRangeForMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker) { BEGIN_AX_OBJC_EXCEPTIONS NSArray* textMarkers = [NSArray arrayWithObjects:(id)startMarker->platformTextMarker(), (id)endMarker->platformTextMarker(), nil]; id textMarkerRange = [m_element accessibilityAttributeValue:@"AXTextMarkerRangeForUnorderedTextMarkers" forParameter:textMarkers]; return AccessibilityTextMarkerRange(textMarkerRange); END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityTextMarker AccessibilityUIElement::startTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range) { BEGIN_AX_OBJC_EXCEPTIONS id textMarker = [m_element accessibilityAttributeValue:@"AXStartTextMarkerForTextMarkerRange" forParameter:(id)range->platformTextMarkerRange()]; return AccessibilityTextMarker(textMarker); END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityTextMarker AccessibilityUIElement::endTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range) { BEGIN_AX_OBJC_EXCEPTIONS id textMarker = [m_element accessibilityAttributeValue:@"AXEndTextMarkerForTextMarkerRange" forParameter:(id)range->platformTextMarkerRange()]; return AccessibilityTextMarker(textMarker); END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityTextMarker AccessibilityUIElement::textMarkerForPoint(int x, int y) { BEGIN_AX_OBJC_EXCEPTIONS id textMarker = [m_element accessibilityAttributeValue:@"AXTextMarkerForPosition" forParameter:[NSValue valueWithPoint:NSMakePoint(x, y)]]; return AccessibilityTextMarker(textMarker); END_AX_OBJC_EXCEPTIONS return 0; } AccessibilityUIElement AccessibilityUIElement::accessibilityElementForTextMarker(AccessibilityTextMarker* marker) { BEGIN_AX_OBJC_EXCEPTIONS id uiElement = [m_element accessibilityAttributeValue:@"AXUIElementForTextMarker" forParameter:(id)marker->platformTextMarker()]; return AccessibilityUIElement(uiElement); END_AX_OBJC_EXCEPTIONS return 0; } #endif // SUPPORTS_AX_TEXTMARKERS