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#import "WebFrame.h"
30#import "WebPage.h"
31#import "WKArray.h"
32#import "WKNumber.h"
33#import "WKRetainPtr.h"
34#import "WKSharedAPICast.h"
35#import "WKString.h"
36#import "WKStringCF.h"
37#import <WebCore/AXObjectCache.h>
38#import <WebCore/Frame.h>
39#import <WebCore/FrameView.h>
40#import <WebCore/Page.h>
41#import <WebCore/ScrollView.h>
42#import <WebCore/Scrollbar.h>
43#import <WebKitSystemInterface.h>
44#import <wtf/ObjcRuntimeExtras.h>
45
46using namespace WebCore;
47using namespace WebKit;
48
49@implementation WKAccessibilityWebPageObject
50
51- (id)accessibilityRootObjectWrapper
52{
53    if (!WebCore::AXObjectCache::accessibilityEnabled())
54        WebCore::AXObjectCache::enableAccessibility();
55
56    NSObject* mainFramePluginAccessibilityObjectWrapper = m_page->accessibilityObjectForMainFramePlugin();
57    if (mainFramePluginAccessibilityObjectWrapper)
58        return mainFramePluginAccessibilityObjectWrapper;
59
60    WebCore::Page* page = m_page->corePage();
61    if (!page)
62        return nil;
63
64    WebCore::Frame* core = page->mainFrame();
65    if (!core || !core->document())
66        return nil;
67
68    AccessibilityObject* root = core->document()->axObjectCache()->rootObject();
69    if (!root)
70        return nil;
71
72    return root->wrapper();
73}
74
75- (void)setWebPage:(WebPage*)page
76{
77    m_page = page;
78}
79
80- (void)setRemoteParent:(id)parent
81{
82    if (parent != m_parent) {
83        [m_parent release];
84        m_parent = [parent retain];
85    }
86}
87
88- (void)dealloc
89{
90    WKUnregisterUniqueIdForElement(self);
91    [m_accessibilityChildren release];
92    [m_attributeNames release];
93    [m_parent release];
94    [super dealloc];
95}
96
97- (BOOL)accessibilityIsIgnored
98{
99    return NO;
100}
101
102- (NSArray *)accessibilityAttributeNames
103{
104    if (!m_attributeNames)
105        m_attributeNames = [[NSArray alloc] initWithObjects:
106                           NSAccessibilityRoleAttribute, NSAccessibilityRoleDescriptionAttribute, NSAccessibilityFocusedAttribute,
107                           NSAccessibilityParentAttribute, NSAccessibilityWindowAttribute, NSAccessibilityTopLevelUIElementAttribute,
108                           NSAccessibilityPositionAttribute, NSAccessibilitySizeAttribute, NSAccessibilityChildrenAttribute, nil];
109
110    return m_attributeNames;
111}
112
113- (NSArray *)accessibilityParameterizedAttributeNames
114{
115    WKRetainPtr<WKArrayRef> result = adoptWK(m_page->pageOverlayCopyAccessibilityAttributesNames(true));
116    if (!result)
117        return nil;
118
119    NSMutableArray *names = [NSMutableArray array];
120    size_t count = WKArrayGetSize(result.get());
121    for (size_t k = 0; k < count; k++) {
122        WKTypeRef item = WKArrayGetItemAtIndex(result.get(), k);
123        if (toImpl(item)->type() == WKStringGetTypeID()) {
124            RetainPtr<CFStringRef> name = adoptCF(WKStringCopyCFString(kCFAllocatorDefault, (WKStringRef)item));
125            [names addObject:(NSString *)name.get()];
126        }
127    }
128
129    return names;
130}
131
132- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute
133{
134    return NO;
135}
136
137- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
138{
139    return;
140}
141
142- (NSArray *)accessibilityActionNames
143{
144    return [NSArray array];
145}
146
147- (NSArray *)accessibilityChildren
148{
149    id wrapper = [self accessibilityRootObjectWrapper];
150    if (!wrapper)
151        return [NSArray array];
152
153    return [NSArray arrayWithObject:wrapper];
154}
155
156- (id)accessibilityAttributeValue:(NSString *)attribute
157{
158    if (!WebCore::AXObjectCache::accessibilityEnabled())
159        WebCore::AXObjectCache::enableAccessibility();
160
161    if ([attribute isEqualToString:NSAccessibilityParentAttribute])
162        return m_parent;
163    if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
164        return [m_parent accessibilityAttributeValue:NSAccessibilityWindowAttribute];
165    if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
166        return [m_parent accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
167    if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
168        return NSAccessibilityGroupRole;
169    if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
170        return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
171    if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
172        return [NSNumber numberWithBool:NO];
173
174    if (!m_page)
175        return nil;
176
177    if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) {
178        const WebCore::FloatPoint& point = m_page->accessibilityPosition();
179        return [NSValue valueWithPoint:NSMakePoint(point.x(), point.y())];
180    }
181    if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) {
182        const IntSize& s = m_page->size();
183        return [NSValue valueWithSize:NSMakeSize(s.width(), s.height())];
184    }
185    if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
186        return [self accessibilityChildren];
187
188    return nil;
189}
190
191- (NSPoint)_convertScreenPointToWindow:(NSPoint)point
192{
193    return m_page->screenToWindow(IntPoint(point.x, point.y));
194}
195
196- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter
197{
198    WKRetainPtr<WKTypeRef> pageOverlayParameter = 0;
199
200    if ([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue*)parameter objCType], @encode(NSPoint)) == 0) {
201        NSPoint point = [self _convertScreenPointToWindow:[(NSValue *)parameter pointValue]];
202        pageOverlayParameter = WKPointCreate(WKPointMake(point.x, point.y));
203    }
204
205    WKRetainPtr<WKStringRef> attributeRef = adoptWK(WKStringCreateWithCFString((CFStringRef)attribute));
206    WKRetainPtr<WKTypeRef> result = adoptWK(m_page->pageOverlayCopyAccessibilityAttributeValue(attributeRef.get(), pageOverlayParameter.get()));
207    if (!result)
208        return nil;
209
210    if (toImpl(result.get())->type() == WKStringGetTypeID())
211        return HardAutorelease(WKStringCopyCFString(kCFAllocatorDefault, (WKStringRef)result.get()));
212    else if (toImpl(result.get())->type() == WKBooleanGetTypeID())
213        return [NSNumber numberWithBool:WKBooleanGetValue(static_cast<WKBooleanRef>(result.get()))];
214
215    return nil;
216}
217
218- (BOOL)accessibilityShouldUseUniqueId
219{
220    return YES;
221}
222
223- (id)accessibilityHitTest:(NSPoint)point
224{
225    // Hit-test point comes in as bottom-screen coordinates. Needs to be normalized to the frame of the web page.
226    NSPoint remotePosition = [[self accessibilityAttributeValue:NSAccessibilityPositionAttribute] pointValue];
227    NSSize remoteSize = [[self accessibilityAttributeValue:NSAccessibilitySizeAttribute] sizeValue];
228
229    // Get the y position of the WKView (we have to screen-flip and go from bottom left to top left).
230    CGFloat screenHeight = [(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame].size.height;
231    remotePosition.y = (screenHeight - remotePosition.y) - remoteSize.height;
232
233    point.y = screenHeight - point.y;
234
235    // Re-center point into the web page's frame.
236    point.y -= remotePosition.y;
237    point.x -= remotePosition.x;
238
239    WebCore::FrameView* frameView = m_page ? m_page->mainFrameView() : 0;
240    if (frameView) {
241        point.y += frameView->scrollPosition().y();
242        point.x += frameView->scrollPosition().x();
243    }
244
245    return [[self accessibilityRootObjectWrapper] accessibilityHitTest:point];
246}
247
248- (id)accessibilityFocusedUIElement
249{
250    return [[self accessibilityRootObjectWrapper] accessibilityFocusedUIElement];
251}
252
253
254@end
255