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 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 "VisibleUnits.h"
70#import "WebCoreFrameView.h"
71#import "WebCoreObjCExtras.h"
72#import "WebCoreSystemInterface.h"
73#import "htmlediting.h"
74
75using namespace WebCore;
76using namespace HTMLNames;
77
78static NSArray *convertMathPairsToNSArray(const AccessibilityObject::AccessibilityMathMultiscriptPairs& pairs, NSString *subscriptKey, NSString *superscriptKey)
79{
80    NSMutableArray *array = [NSMutableArray arrayWithCapacity:pairs.size()];
81    for (const auto& pair : pairs) {
82        NSMutableDictionary *pairDictionary = [NSMutableDictionary dictionary];
83        if (pair.first && pair.first->wrapper() && !pair.first->accessibilityIsIgnored())
84            [pairDictionary setObject:pair.first->wrapper() forKey:subscriptKey];
85        if (pair.second && pair.second->wrapper() && !pair.second->accessibilityIsIgnored())
86            [pairDictionary setObject:pair.second->wrapper() forKey:superscriptKey];
87        [array addObject:pairDictionary];
88    }
89    return array;
90}
91
92@implementation WebAccessibilityObjectWrapperBase
93
94- (id)initWithAccessibilityObject:(AccessibilityObject*)axObject
95{
96    if (!(self = [super init]))
97        return nil;
98
99    m_object = axObject;
100    return self;
101}
102
103- (void)detach
104{
105    m_object = 0;
106}
107
108- (BOOL)updateObjectBackingStore
109{
110    // Calling updateBackingStore() can invalidate this element so self must be retained.
111    // If it does become invalidated, m_object will be nil.
112    [[self retain] autorelease];
113
114    if (!m_object)
115        return NO;
116
117    m_object->updateBackingStore();
118    if (!m_object)
119        return NO;
120
121    return YES;
122}
123
124- (id)attachmentView
125{
126    return nil;
127}
128
129- (AccessibilityObject*)accessibilityObject
130{
131    return m_object;
132}
133
134// FIXME: Different kinds of elements are putting the title tag to use in different
135// AX fields. This should be rectified, but in the initial patch I want to achieve
136// parity with existing behavior.
137- (BOOL)titleTagShouldBeUsedInDescriptionField
138{
139    return (m_object->isLink() && !m_object->isImageMapLink()) || m_object->isImage();
140}
141
142// On iOS, we don't have to return the value in the title. We can return the actual title, given the API.
143- (BOOL)fileUploadButtonReturnsValueInTitle
144{
145    return YES;
146}
147
148// This should be the "visible" text that's actually on the screen if possible.
149// If there's alternative text, that can override the title.
150- (NSString *)accessibilityTitle
151{
152    // Static text objects should not have a title. Its content is communicated in its AXValue.
153    if (m_object->roleValue() == StaticTextRole)
154        return [NSString string];
155
156    // A file upload button presents a challenge because it has button text and a value, but the
157    // API doesn't support this paradigm.
158    // The compromise is to return the button type in the role description and the value of the file path in the title
159    if (m_object->isFileUploadButton() && [self fileUploadButtonReturnsValueInTitle])
160        return m_object->stringValue();
161
162    Vector<AccessibilityText> textOrder;
163    m_object->accessibilityText(textOrder);
164
165    for (const auto& text : textOrder) {
166        // If we have alternative text, then we should not expose a title.
167        if (text.textSource == AlternativeText)
168            break;
169
170        // Once we encounter visible text, or the text from our children that should be used foremost.
171        if (text.textSource == VisibleText || text.textSource == ChildrenText)
172            return text.text;
173
174        // If there's an element that labels this object and it's not exposed, then we should use
175        // that text as our title.
176        if (text.textSource == LabelByElementText && !m_object->exposesTitleUIElement())
177            return text.text;
178    }
179
180    return [NSString string];
181}
182
183- (NSString *)accessibilityDescription
184{
185    // Static text objects should not have a description. Its content is communicated in its AXValue.
186    // One exception is the media control labels that have a value and a description. Those are set programatically.
187    if (m_object->roleValue() == StaticTextRole && !m_object->isMediaControlLabel())
188        return [NSString string];
189
190    Vector<AccessibilityText> textOrder;
191    m_object->accessibilityText(textOrder);
192
193    bool visibleTextAvailable = false;
194    for (const auto& text : textOrder) {
195        if (text.textSource == AlternativeText)
196            return text.text;
197
198        switch (text.textSource) {
199        case VisibleText:
200        case ChildrenText:
201        case LabelByElementText:
202            visibleTextAvailable = true;
203            break;
204        default:
205            break;
206        }
207
208        if (text.textSource == TitleTagText && !visibleTextAvailable)
209            return text.text;
210    }
211
212    return [NSString string];
213}
214
215- (NSString *)accessibilityHelpText
216{
217    Vector<AccessibilityText> textOrder;
218    m_object->accessibilityText(textOrder);
219
220    bool descriptiveTextAvailable = false;
221    for (const auto& text : textOrder) {
222        if (text.textSource == HelpText || text.textSource == SummaryText)
223            return text.text;
224
225        // If an element does NOT have other descriptive text the title tag should be used as its descriptive text.
226        // But, if those ARE available, then the title tag should be used for help text instead.
227        switch (text.textSource) {
228        case AlternativeText:
229        case VisibleText:
230        case ChildrenText:
231        case LabelByElementText:
232            descriptiveTextAvailable = true;
233            break;
234        default:
235            break;
236        }
237
238        if (text.textSource == TitleTagText && descriptiveTextAvailable)
239            return text.text;
240    }
241
242    return [NSString string];
243}
244
245struct PathConversionInfo {
246    WebAccessibilityObjectWrapperBase *wrapper;
247    CGMutablePathRef path;
248};
249
250static void ConvertPathToScreenSpaceFunction(void* info, const PathElement* element)
251{
252    PathConversionInfo* conversion = (PathConversionInfo*)info;
253    WebAccessibilityObjectWrapperBase *wrapper = conversion->wrapper;
254    CGMutablePathRef newPath = conversion->path;
255    switch (element->type) {
256    case PathElementMoveToPoint:
257    {
258        CGPoint newPoint = [wrapper convertPointToScreenSpace:element->points[0]];
259        CGPathMoveToPoint(newPath, nil, newPoint.x, newPoint.y);
260        break;
261    }
262    case PathElementAddLineToPoint:
263    {
264        CGPoint newPoint = [wrapper convertPointToScreenSpace:element->points[0]];
265        CGPathAddLineToPoint(newPath, nil, newPoint.x, newPoint.y);
266        break;
267    }
268    case PathElementAddQuadCurveToPoint:
269    {
270        CGPoint newPoint1 = [wrapper convertPointToScreenSpace:element->points[0]];
271        CGPoint newPoint2 = [wrapper convertPointToScreenSpace:element->points[1]];
272        CGPathAddQuadCurveToPoint(newPath, nil, newPoint1.x, newPoint1.y, newPoint2.x, newPoint2.y);
273        break;
274    }
275    case PathElementAddCurveToPoint:
276    {
277        CGPoint newPoint1 = [wrapper convertPointToScreenSpace:element->points[0]];
278        CGPoint newPoint2 = [wrapper convertPointToScreenSpace:element->points[1]];
279        CGPoint newPoint3 = [wrapper convertPointToScreenSpace:element->points[2]];
280        CGPathAddCurveToPoint(newPath, nil, newPoint1.x, newPoint1.y, newPoint2.x, newPoint2.y, newPoint3.x, newPoint3.y);
281        break;
282    }
283    case PathElementCloseSubpath:
284    {
285        CGPathCloseSubpath(newPath);
286        break;
287    }
288    }
289}
290
291- (CGPathRef)convertPathToScreenSpace:(Path &)path
292{
293    PathConversionInfo conversion = { self, CGPathCreateMutable() };
294    path.apply(&conversion, ConvertPathToScreenSpaceFunction);
295    return (CGPathRef)[(id)conversion.path autorelease];
296}
297
298- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point
299{
300    UNUSED_PARAM(point);
301    ASSERT_NOT_REACHED();
302    return CGPointZero;
303}
304
305- (NSString *)ariaLandmarkRoleDescription
306{
307    switch (m_object->roleValue()) {
308    case LandmarkApplicationRole:
309        return AXARIAContentGroupText(@"ARIALandmarkApplication");
310    case LandmarkBannerRole:
311        return AXARIAContentGroupText(@"ARIALandmarkBanner");
312    case LandmarkComplementaryRole:
313        return AXARIAContentGroupText(@"ARIALandmarkComplementary");
314    case LandmarkContentInfoRole:
315        return AXARIAContentGroupText(@"ARIALandmarkContentInfo");
316    case LandmarkMainRole:
317        return AXARIAContentGroupText(@"ARIALandmarkMain");
318    case LandmarkNavigationRole:
319        return AXARIAContentGroupText(@"ARIALandmarkNavigation");
320    case LandmarkSearchRole:
321        return AXARIAContentGroupText(@"ARIALandmarkSearch");
322    case ApplicationAlertRole:
323        return AXARIAContentGroupText(@"ARIAApplicationAlert");
324    case ApplicationAlertDialogRole:
325        return AXARIAContentGroupText(@"ARIAApplicationAlertDialog");
326    case ApplicationDialogRole:
327        return AXARIAContentGroupText(@"ARIAApplicationDialog");
328    case ApplicationLogRole:
329        return AXARIAContentGroupText(@"ARIAApplicationLog");
330    case ApplicationMarqueeRole:
331        return AXARIAContentGroupText(@"ARIAApplicationMarquee");
332    case ApplicationStatusRole:
333        return AXARIAContentGroupText(@"ARIAApplicationStatus");
334    case ApplicationTimerRole:
335        return AXARIAContentGroupText(@"ARIAApplicationTimer");
336    case DocumentRole:
337        return AXARIAContentGroupText(@"ARIADocument");
338    case DocumentArticleRole:
339        return AXARIAContentGroupText(@"ARIADocumentArticle");
340    case DocumentMathRole:
341        return AXARIAContentGroupText(@"ARIADocumentMath");
342    case DocumentNoteRole:
343        return AXARIAContentGroupText(@"ARIADocumentNote");
344    case DocumentRegionRole:
345        return AXARIAContentGroupText(@"ARIADocumentRegion");
346    case UserInterfaceTooltipRole:
347        return AXARIAContentGroupText(@"ARIAUserInterfaceTooltip");
348    case TabPanelRole:
349        return AXARIAContentGroupText(@"ARIATabPanel");
350    default:
351        return nil;
352    }
353}
354
355- (NSString *)accessibilityPlatformMathSubscriptKey
356{
357    ASSERT_NOT_REACHED();
358    return nil;
359}
360
361- (NSString *)accessibilityPlatformMathSuperscriptKey
362{
363    ASSERT_NOT_REACHED();
364    return nil;
365}
366
367- (NSArray *)accessibilityMathPostscriptPairs
368{
369    AccessibilityObject::AccessibilityMathMultiscriptPairs pairs;
370    m_object->mathPostscripts(pairs);
371    return convertMathPairsToNSArray(pairs, [self accessibilityPlatformMathSubscriptKey], [self accessibilityPlatformMathSuperscriptKey]);
372}
373
374- (NSArray *)accessibilityMathPrescriptPairs
375{
376    AccessibilityObject::AccessibilityMathMultiscriptPairs pairs;
377    m_object->mathPrescripts(pairs);
378    return convertMathPairsToNSArray(pairs, [self accessibilityPlatformMathSubscriptKey], [self accessibilityPlatformMathSuperscriptKey]);
379}
380
381// This is set by DRT when it wants to listen for notifications.
382static BOOL accessibilityShouldRepostNotifications;
383+ (void)accessibilitySetShouldRepostNotifications:(BOOL)repost
384{
385    accessibilityShouldRepostNotifications = repost;
386}
387
388- (void)accessibilityPostedNotification:(NSString *)notificationName
389{
390    if (accessibilityShouldRepostNotifications) {
391        NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", nil];
392        [[NSNotificationCenter defaultCenter] postNotificationName:@"AXDRTNotification" object:self userInfo:userInfo];
393    }
394}
395
396@end
397
398#endif // HAVE(ACCESSIBILITY)
399