• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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