1/*
2 * Copyright (C) 2008, 2009, 2010, 2011 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "config.h"
30#import "WebAccessibilityObjectWrapperBase.h"
31
32#if HAVE(ACCESSIBILITY)
33
34#import "AXObjectCache.h"
35#import "AccessibilityARIAGridRow.h"
36#import "AccessibilityList.h"
37#import "AccessibilityListBox.h"
38#import "AccessibilityRenderObject.h"
39#import "AccessibilityScrollView.h"
40#import "AccessibilitySpinButton.h"
41#import "AccessibilityTable.h"
42#import "AccessibilityTableCell.h"
43#import "AccessibilityTableColumn.h"
44#import "AccessibilityTableRow.h"
45#import "Chrome.h"
46#import "ChromeClient.h"
47#import "ColorMac.h"
48#import "ContextMenuController.h"
49#import "Font.h"
50#import "Frame.h"
51#import "FrameLoaderClient.h"
52#import "FrameSelection.h"
53#import "HTMLAnchorElement.h"
54#import "HTMLAreaElement.h"
55#import "HTMLFrameOwnerElement.h"
56#import "HTMLImageElement.h"
57#import "HTMLInputElement.h"
58#import "HTMLNames.h"
59#import "HTMLTextAreaElement.h"
60#import "LocalizedStrings.h"
61#import "Page.h"
62#import "RenderTextControl.h"
63#import "RenderView.h"
64#import "RenderWidget.h"
65#import "ScrollView.h"
66#import "SimpleFontData.h"
67#import "TextCheckerClient.h"
68#import "TextCheckingHelper.h"
69#import "TextIterator.h"
70#import "VisibleUnits.h"
71#import "WebCoreFrameView.h"
72#import "WebCoreObjCExtras.h"
73#import "WebCoreSystemInterface.h"
74#import "htmlediting.h"
75
76using namespace WebCore;
77using namespace HTMLNames;
78using namespace std;
79
80static NSArray *convertMathPairsToNSArray(const AccessibilityObject::AccessibilityMathMultiscriptPairs& pairs, NSString *subscriptKey, NSString *superscriptKey)
81{
82    unsigned length = pairs.size();
83    NSMutableArray *array = [NSMutableArray arrayWithCapacity:length];
84    for (unsigned i = 0; i < length; ++i) {
85        NSMutableDictionary *pairDictionary = [NSMutableDictionary dictionary];
86        pair<AccessibilityObject*, AccessibilityObject*> pair = pairs[i];
87        if (pair.first && pair.first->wrapper() && !pair.first->accessibilityIsIgnored())
88            [pairDictionary setObject:pair.first->wrapper() forKey:subscriptKey];
89        if (pair.second && pair.second->wrapper() && !pair.second->accessibilityIsIgnored())
90            [pairDictionary setObject:pair.second->wrapper() forKey:superscriptKey];
91        [array addObject:pairDictionary];
92    }
93    return array;
94}
95
96@implementation WebAccessibilityObjectWrapperBase
97
98- (id)initWithAccessibilityObject:(AccessibilityObject*)axObject
99{
100    if (!(self = [super init]))
101        return nil;
102
103    m_object = axObject;
104    return self;
105}
106
107- (void)detach
108{
109    m_object = 0;
110}
111
112- (BOOL)updateObjectBackingStore
113{
114    // Calling updateBackingStore() can invalidate this element so self must be retained.
115    // If it does become invalidated, m_object will be nil.
116    [[self retain] autorelease];
117
118    if (!m_object)
119        return NO;
120
121    m_object->updateBackingStore();
122    if (!m_object)
123        return NO;
124
125    return YES;
126}
127
128- (id)attachmentView
129{
130    return nil;
131}
132
133- (AccessibilityObject*)accessibilityObject
134{
135    return m_object;
136}
137
138// FIXME: Different kinds of elements are putting the title tag to use in different
139// AX fields. This should be rectified, but in the initial patch I want to achieve
140// parity with existing behavior.
141- (BOOL)titleTagShouldBeUsedInDescriptionField
142{
143    return (m_object->isLink() && !m_object->isImageMapLink()) || m_object->isImage();
144}
145
146// On iOS, we don't have to return the value in the title. We can return the actual title, given the API.
147- (BOOL)fileUploadButtonReturnsValueInTitle
148{
149    return YES;
150}
151
152// This should be the "visible" text that's actually on the screen if possible.
153// If there's alternative text, that can override the title.
154- (NSString *)accessibilityTitle
155{
156    // Static text objects should not have a title. Its content is communicated in its AXValue.
157    if (m_object->roleValue() == StaticTextRole)
158        return [NSString string];
159
160    // A file upload button presents a challenge because it has button text and a value, but the
161    // API doesn't support this paradigm.
162    // The compromise is to return the button type in the role description and the value of the file path in the title
163    if (m_object->isFileUploadButton() && [self fileUploadButtonReturnsValueInTitle])
164        return m_object->stringValue();
165
166    Vector<AccessibilityText> textOrder;
167    m_object->accessibilityText(textOrder);
168
169    unsigned length = textOrder.size();
170    for (unsigned k = 0; k < length; k++) {
171        const AccessibilityText& text = textOrder[k];
172
173        // If we have alternative text, then we should not expose a title.
174        if (text.textSource == AlternativeText)
175            break;
176
177        // Once we encounter visible text, or the text from our children that should be used foremost.
178        if (text.textSource == VisibleText || text.textSource == ChildrenText)
179            return text.text;
180
181        // If there's an element that labels this object and it's not exposed, then we should use
182        // that text as our title.
183        if (text.textSource == LabelByElementText && !m_object->exposesTitleUIElement())
184            return text.text;
185
186        // FIXME: The title tag is used in certain cases for the title. This usage should
187        // probably be in the description field since it's not "visible".
188        if (text.textSource == TitleTagText && ![self titleTagShouldBeUsedInDescriptionField])
189            return text.text;
190    }
191
192    return [NSString string];
193}
194
195- (NSString *)accessibilityDescription
196{
197    // Static text objects should not have a description. Its content is communicated in its AXValue.
198    // One exception is the media control labels that have a value and a description. Those are set programatically.
199    if (m_object->roleValue() == StaticTextRole && !m_object->isMediaControlLabel())
200        return [NSString string];
201
202    Vector<AccessibilityText> textOrder;
203    m_object->accessibilityText(textOrder);
204
205    unsigned length = textOrder.size();
206    for (unsigned k = 0; k < length; k++) {
207        const AccessibilityText& text = textOrder[k];
208
209        if (text.textSource == AlternativeText)
210            return text.text;
211
212        if (text.textSource == TitleTagText && [self titleTagShouldBeUsedInDescriptionField])
213            return text.text;
214    }
215
216    return [NSString string];
217}
218
219- (NSString *)accessibilityHelpText
220{
221    Vector<AccessibilityText> textOrder;
222    m_object->accessibilityText(textOrder);
223
224    unsigned length = textOrder.size();
225    bool descriptiveTextAvailable = false;
226    for (unsigned k = 0; k < length; k++) {
227        const AccessibilityText& text = textOrder[k];
228
229        if (text.textSource == HelpText || text.textSource == SummaryText)
230            return text.text;
231
232        // If an element does NOT have other descriptive text the title tag should be used as its descriptive text.
233        // But, if those ARE available, then the title tag should be used for help text instead.
234        switch (text.textSource) {
235        case AlternativeText:
236        case VisibleText:
237        case ChildrenText:
238        case LabelByElementText:
239            descriptiveTextAvailable = true;
240        default:
241            break;
242        }
243
244        if (text.textSource == TitleTagText && descriptiveTextAvailable)
245            return text.text;
246    }
247
248    return [NSString string];
249}
250
251struct PathConversionInfo {
252    WebAccessibilityObjectWrapperBase *wrapper;
253    CGMutablePathRef path;
254};
255
256static void ConvertPathToScreenSpaceFunction(void* info, const PathElement* element)
257{
258    PathConversionInfo* conversion = (PathConversionInfo*)info;
259    WebAccessibilityObjectWrapperBase *wrapper = conversion->wrapper;
260    CGMutablePathRef newPath = conversion->path;
261    switch (element->type) {
262    case PathElementMoveToPoint:
263    {
264        CGPoint newPoint = [wrapper convertPointToScreenSpace:element->points[0]];
265        CGPathMoveToPoint(newPath, nil, newPoint.x, newPoint.y);
266        break;
267    }
268    case PathElementAddLineToPoint:
269    {
270        CGPoint newPoint = [wrapper convertPointToScreenSpace:element->points[0]];
271        CGPathAddLineToPoint(newPath, nil, newPoint.x, newPoint.y);
272        break;
273    }
274    case PathElementAddQuadCurveToPoint:
275    {
276        CGPoint newPoint1 = [wrapper convertPointToScreenSpace:element->points[0]];
277        CGPoint newPoint2 = [wrapper convertPointToScreenSpace:element->points[1]];
278        CGPathAddQuadCurveToPoint(newPath, nil, newPoint1.x, newPoint1.y, newPoint2.x, newPoint2.y);
279        break;
280    }
281    case PathElementAddCurveToPoint:
282    {
283        CGPoint newPoint1 = [wrapper convertPointToScreenSpace:element->points[0]];
284        CGPoint newPoint2 = [wrapper convertPointToScreenSpace:element->points[1]];
285        CGPoint newPoint3 = [wrapper convertPointToScreenSpace:element->points[2]];
286        CGPathAddCurveToPoint(newPath, nil, newPoint1.x, newPoint1.y, newPoint2.x, newPoint2.y, newPoint3.x, newPoint3.y);
287        break;
288    }
289    case PathElementCloseSubpath:
290    {
291        CGPathCloseSubpath(newPath);
292        break;
293    }
294    }
295}
296
297- (CGPathRef)convertPathToScreenSpace:(Path &)path
298{
299    PathConversionInfo conversion = { self, CGPathCreateMutable() };
300    path.apply(&conversion, ConvertPathToScreenSpaceFunction);
301    return (CGPathRef)[(id)conversion.path autorelease];
302}
303
304- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point
305{
306    UNUSED_PARAM(point);
307    ASSERT_NOT_REACHED();
308    return CGPointZero;
309}
310
311- (NSString *)ariaLandmarkRoleDescription
312{
313    switch (m_object->roleValue()) {
314    case LandmarkApplicationRole:
315        return AXARIAContentGroupText(@"ARIALandmarkApplication");
316    case LandmarkBannerRole:
317        return AXARIAContentGroupText(@"ARIALandmarkBanner");
318    case LandmarkComplementaryRole:
319        return AXARIAContentGroupText(@"ARIALandmarkComplementary");
320    case LandmarkContentInfoRole:
321        return AXARIAContentGroupText(@"ARIALandmarkContentInfo");
322    case LandmarkMainRole:
323        return AXARIAContentGroupText(@"ARIALandmarkMain");
324    case LandmarkNavigationRole:
325        return AXARIAContentGroupText(@"ARIALandmarkNavigation");
326    case LandmarkSearchRole:
327        return AXARIAContentGroupText(@"ARIALandmarkSearch");
328    case ApplicationAlertRole:
329        return AXARIAContentGroupText(@"ARIAApplicationAlert");
330    case ApplicationAlertDialogRole:
331        return AXARIAContentGroupText(@"ARIAApplicationAlertDialog");
332    case ApplicationDialogRole:
333        return AXARIAContentGroupText(@"ARIAApplicationDialog");
334    case ApplicationLogRole:
335        return AXARIAContentGroupText(@"ARIAApplicationLog");
336    case ApplicationMarqueeRole:
337        return AXARIAContentGroupText(@"ARIAApplicationMarquee");
338    case ApplicationStatusRole:
339        return AXARIAContentGroupText(@"ARIAApplicationStatus");
340    case ApplicationTimerRole:
341        return AXARIAContentGroupText(@"ARIAApplicationTimer");
342    case DocumentRole:
343        return AXARIAContentGroupText(@"ARIADocument");
344    case DocumentArticleRole:
345        return AXARIAContentGroupText(@"ARIADocumentArticle");
346    case DocumentMathRole:
347        return AXARIAContentGroupText(@"ARIADocumentMath");
348    case DocumentNoteRole:
349        return AXARIAContentGroupText(@"ARIADocumentNote");
350    case DocumentRegionRole:
351        return AXARIAContentGroupText(@"ARIADocumentRegion");
352    case UserInterfaceTooltipRole:
353        return AXARIAContentGroupText(@"ARIAUserInterfaceTooltip");
354    case TabPanelRole:
355        return AXARIAContentGroupText(@"ARIATabPanel");
356    default:
357        return nil;
358    }
359}
360
361- (NSString *)accessibilityPlatformMathSubscriptKey
362{
363    ASSERT_NOT_REACHED();
364    return nil;
365}
366
367- (NSString *)accessibilityPlatformMathSuperscriptKey
368{
369    ASSERT_NOT_REACHED();
370    return nil;
371}
372
373- (NSArray *)accessibilityMathPostscriptPairs
374{
375    AccessibilityObject::AccessibilityMathMultiscriptPairs pairs;
376    m_object->mathPostscripts(pairs);
377    return convertMathPairsToNSArray(pairs, [self accessibilityPlatformMathSubscriptKey], [self accessibilityPlatformMathSuperscriptKey]);
378}
379
380- (NSArray *)accessibilityMathPrescriptPairs
381{
382    AccessibilityObject::AccessibilityMathMultiscriptPairs pairs;
383    m_object->mathPrescripts(pairs);
384    return convertMathPairsToNSArray(pairs, [self accessibilityPlatformMathSubscriptKey], [self accessibilityPlatformMathSuperscriptKey]);
385}
386
387// This is set by DRT when it wants to listen for notifications.
388static BOOL accessibilityShouldRepostNotifications;
389+ (void)accessibilitySetShouldRepostNotifications:(BOOL)repost
390{
391    accessibilityShouldRepostNotifications = repost;
392}
393
394- (void)accessibilityPostedNotification:(NSString *)notificationName
395{
396    if (accessibilityShouldRepostNotifications) {
397        NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", nil];
398        [[NSNotificationCenter defaultCenter] postNotificationName:@"AXDRTNotification" object:self userInfo:userInfo];
399    }
400}
401
402@end
403
404#endif // HAVE(ACCESSIBILITY)
405