1/*
2 * Copyright (C) 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 * 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WKAccessibilityWebPageObject.h"
28
29#if PLATFORM(MAC)
30
31#import "WebFrame.h"
32#import "WebPage.h"
33#import "WKArray.h"
34#import "WKNumber.h"
35#import "WKRetainPtr.h"
36#import "WKSharedAPICast.h"
37#import "WKString.h"
38#import "WKStringCF.h"
39#import <WebCore/AXObjectCache.h>
40#import <WebCore/FrameView.h>
41#import <WebCore/MainFrame.h>
42#import <WebCore/Page.h>
43#import <WebCore/ScrollView.h>
44#import <WebCore/Scrollbar.h>
45#import <WebKitSystemInterface.h>
46#import <wtf/ObjcRuntimeExtras.h>
47
48using namespace WebCore;
49using namespace WebKit;
50
51@implementation WKAccessibilityWebPageObject
52
53- (id)accessibilityRootObjectWrapper
54{
55    if (!WebCore::AXObjectCache::accessibilityEnabled())
56        WebCore::AXObjectCache::enableAccessibility();
57
58    NSObject* mainFramePluginAccessibilityObjectWrapper = m_page->accessibilityObjectForMainFramePlugin();
59    if (mainFramePluginAccessibilityObjectWrapper)
60        return mainFramePluginAccessibilityObjectWrapper;
61
62    WebCore::Page* page = m_page->corePage();
63    if (!page)
64        return nil;
65
66    WebCore::Frame& core = page->mainFrame();
67    if (!core.document())
68        return nil;
69
70    AccessibilityObject* root = core.document()->axObjectCache()->rootObject();
71    if (!root)
72        return nil;
73
74    return root->wrapper();
75}
76
77- (void)setWebPage:(WebPage*)page
78{
79    m_page = page;
80}
81
82- (void)setRemoteParent:(id)parent
83{
84    if (parent != m_parent) {
85        [m_parent release];
86        m_parent = [parent retain];
87    }
88}
89
90- (void)dealloc
91{
92    WKUnregisterUniqueIdForElement(self);
93    [m_accessibilityChildren release];
94    [m_attributeNames release];
95    [m_parent release];
96    [super dealloc];
97}
98
99- (BOOL)accessibilityIsIgnored
100{
101    return NO;
102}
103
104- (NSArray *)accessibilityAttributeNames
105{
106    if (!m_attributeNames)
107        m_attributeNames = [[NSArray alloc] initWithObjects:
108                           NSAccessibilityRoleAttribute, NSAccessibilityRoleDescriptionAttribute, NSAccessibilityFocusedAttribute,
109                           NSAccessibilityParentAttribute, NSAccessibilityWindowAttribute, NSAccessibilityTopLevelUIElementAttribute,
110                           NSAccessibilityPositionAttribute, NSAccessibilitySizeAttribute, NSAccessibilityChildrenAttribute, nil];
111
112    return m_attributeNames;
113}
114
115- (NSArray *)accessibilityParameterizedAttributeNames
116{
117    WKRetainPtr<WKArrayRef> result = adoptWK(m_page->pageOverlayCopyAccessibilityAttributesNames(true));
118    if (!result)
119        return nil;
120
121    NSMutableArray *names = [NSMutableArray array];
122    size_t count = WKArrayGetSize(result.get());
123    for (size_t k = 0; k < count; k++) {
124        WKTypeRef item = WKArrayGetItemAtIndex(result.get(), k);
125        if (toImpl(item)->type() == API::String::APIType) {
126            RetainPtr<CFStringRef> name = adoptCF(WKStringCopyCFString(kCFAllocatorDefault, (WKStringRef)item));
127            [names addObject:(NSString *)name.get()];
128        }
129    }
130
131    return names;
132}
133
134- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute
135{
136    return NO;
137}
138
139- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
140{
141    return;
142}
143
144- (NSArray *)accessibilityActionNames
145{
146    return [NSArray array];
147}
148
149- (NSArray *)accessibilityChildren
150{
151    id wrapper = [self accessibilityRootObjectWrapper];
152    if (!wrapper)
153        return [NSArray array];
154
155    return [NSArray arrayWithObject:wrapper];
156}
157
158- (id)accessibilityAttributeValue:(NSString *)attribute
159{
160    if (!WebCore::AXObjectCache::accessibilityEnabled())
161        WebCore::AXObjectCache::enableAccessibility();
162
163    if ([attribute isEqualToString:NSAccessibilityParentAttribute])
164        return m_parent;
165    if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
166        return [m_parent accessibilityAttributeValue:NSAccessibilityWindowAttribute];
167    if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
168        return [m_parent accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
169    if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
170        return NSAccessibilityGroupRole;
171    if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
172        return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
173    if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
174        return [NSNumber numberWithBool:NO];
175
176    if (!m_page)
177        return nil;
178
179    if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) {
180        const WebCore::FloatPoint& point = m_page->accessibilityPosition();
181        return [NSValue valueWithPoint:NSMakePoint(point.x(), point.y())];
182    }
183    if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) {
184        const IntSize& s = m_page->size();
185        return [NSValue valueWithSize:NSMakeSize(s.width(), s.height())];
186    }
187    if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
188        return [self accessibilityChildren];
189
190    return nil;
191}
192
193- (NSPoint)_convertScreenPointToRootView:(NSPoint)point
194{
195    return m_page->screenToRootView(IntPoint(point.x, point.y));
196}
197
198- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter
199{
200    WKRetainPtr<WKTypeRef> pageOverlayParameter = 0;
201
202    if ([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue*)parameter objCType], @encode(NSPoint)) == 0) {
203        NSPoint point = [self _convertScreenPointToRootView:[(NSValue *)parameter pointValue]];
204        pageOverlayParameter = WKPointCreate(WKPointMake(point.x, point.y));
205    }
206
207    WKRetainPtr<WKStringRef> attributeRef = adoptWK(WKStringCreateWithCFString((CFStringRef)attribute));
208    WKRetainPtr<WKTypeRef> result = adoptWK(m_page->pageOverlayCopyAccessibilityAttributeValue(attributeRef.get(), pageOverlayParameter.get()));
209    if (!result)
210        return nil;
211
212    if (toImpl(result.get())->type() == API::String::APIType)
213        return CFBridgingRelease(WKStringCopyCFString(kCFAllocatorDefault, (WKStringRef)result.get()));
214    else if (toImpl(result.get())->type() == API::Boolean::APIType)
215        return [NSNumber numberWithBool:WKBooleanGetValue(static_cast<WKBooleanRef>(result.get()))];
216
217    return nil;
218}
219
220- (BOOL)accessibilityShouldUseUniqueId
221{
222    return YES;
223}
224
225#pragma clang diagnostic push
226#pragma clang diagnostic ignored "-Wdeprecated-declarations"
227- (id)accessibilityHitTest:(NSPoint)point
228{
229    // Hit-test point comes in as bottom-screen coordinates. Needs to be normalized to the frame of the web page.
230    NSPoint remotePosition = [[self accessibilityAttributeValue:NSAccessibilityPositionAttribute] pointValue];
231    NSSize remoteSize = [[self accessibilityAttributeValue:NSAccessibilitySizeAttribute] sizeValue];
232
233    // Get the y position of the WKView (we have to screen-flip and go from bottom left to top left).
234    CGFloat screenHeight = [(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame].size.height;
235    remotePosition.y = (screenHeight - remotePosition.y) - remoteSize.height;
236
237    point.y = screenHeight - point.y;
238
239    // Re-center point into the web page's frame.
240    point.y -= remotePosition.y;
241    point.x -= remotePosition.x;
242
243    WebCore::FrameView* frameView = m_page ? m_page->mainFrameView() : 0;
244    if (frameView) {
245        point.y += frameView->scrollPosition().y();
246        point.x += frameView->scrollPosition().x();
247    }
248
249    return [[self accessibilityRootObjectWrapper] accessibilityHitTest:point];
250}
251#pragma clang diagnostic pop
252
253- (id)accessibilityFocusedUIElement
254{
255    return [[self accessibilityRootObjectWrapper] accessibilityFocusedUIElement];
256}
257
258@end
259
260#endif // PLATFORM(MAC)
261