f3ecb3b460de67940b3696bedd4d595c0bf5e2db
[WebKit-https.git] / Tools / WebKitTestRunner / InjectedBundle / mac / AccessibilityNotificationHandler.mm
1 /*
2  * Copyright (C) 2011 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #import "config.h"
32 #import "InjectedBundle.h"
33 #import "InjectedBundlePage.h"
34 #import "JSWrapper.h"
35 #import "AccessibilityNotificationHandler.h"
36 #import "AccessibilityUIElement.h"
37
38 #import <JavaScriptCore/JSRetainPtr.h>
39 #import <JavaScriptCore/JSStringRef.h>
40 #import <JavaScriptCore/JSStringRefCF.h>
41 #import <WebKit/WKBundleFrame.h>
42 #import <objc/runtime.h>
43 #import <wtf/RetainPtr.h>
44
45 @interface NSObject (WebAccessibilityObjectWrapperAdditions)
46 + (void)accessibilitySetShouldRepostNotifications:(BOOL)repost;
47 @end
48
49 @interface NSString (JSStringRefAdditions)
50 - (JSStringRef)createJSStringRef;
51 @end
52
53 @implementation NSString (JSStringRefAdditions)
54
55 - (JSStringRef)createJSStringRef
56 {
57     return JSStringCreateWithCFString((__bridge CFStringRef)self);
58 }
59
60 @end
61
62 @implementation AccessibilityNotificationHandler
63
64 - (id)init
65 {
66     if (!(self = [super init]))
67         return nil;
68
69     return self;
70 }
71
72 - (void)setPlatformElement:(id)platformElement
73 {
74     m_platformElement = platformElement;
75 }
76
77 - (void)dealloc
78 {
79     [self stopObserving];
80
81     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(WTR::InjectedBundle::singleton().page()->page());
82     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
83
84     JSValueUnprotect(context, m_notificationFunctionCallback);
85     m_notificationFunctionCallback = 0;
86
87     [super dealloc];
88 }
89
90 - (void)setCallback:(JSValueRef)callback
91 {
92     if (!callback)
93         return;
94
95     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(WTR::InjectedBundle::singleton().page()->page());
96     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
97
98     if (m_notificationFunctionCallback)
99         JSValueUnprotect(context, m_notificationFunctionCallback);
100
101     m_notificationFunctionCallback = callback;
102     JSValueProtect(context, m_notificationFunctionCallback);
103 }
104
105 static Class webAccessibilityObjectWrapperClass()
106 {
107     static Class cls = objc_getClass("WebAccessibilityObjectWrapper");
108     ASSERT(cls);
109     return cls;
110 }
111
112 - (void)startObserving
113 {
114     // Once we start requesting notifications, it's on for the duration of the program.
115     // This is to avoid any race conditions between tests turning this flag on and off. Instead
116     // AccessibilityNotificationHandler can ignore events it doesn't care about.
117     [webAccessibilityObjectWrapperClass() accessibilitySetShouldRepostNotifications:YES];
118     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_notificationReceived:) name:@"AXDRTNotification" object:nil];
119 }
120
121 - (void)stopObserving
122 {
123     [[NSNotificationCenter defaultCenter] removeObserver:self];
124 }
125
126 static JSValueRef makeValueRefForValue(JSContextRef context, id value)
127 {
128     if ([value isKindOfClass:[NSString class]])
129         return JSValueMakeString(context, adopt([value createJSStringRef]).get());
130     if ([value isKindOfClass:[NSNumber class]]) {
131         if (!strcmp([value objCType], @encode(BOOL)))
132             return JSValueMakeBoolean(context, [value boolValue]);
133         return JSValueMakeNumber(context, [value doubleValue]);
134     }
135     if ([value isKindOfClass:webAccessibilityObjectWrapperClass()])
136         return toJS(context, WTR::AccessibilityUIElement::create(static_cast<PlatformUIElement>(value)).ptr());
137     if ([value isKindOfClass:[NSDictionary class]])
138         return makeObjectRefForDictionary(context, value);
139     if ([value isKindOfClass:[NSArray class]])
140         return makeArrayRefForArray(context, value);
141     return nullptr;
142 }
143
144 static JSValueRef makeArrayRefForArray(JSContextRef context, NSArray *array)
145 {
146     NSUInteger count = array.count;
147     JSValueRef arguments[count];
148
149     for (NSUInteger i = 0; i < count; i++)
150         arguments[i] = makeValueRefForValue(context, [array objectAtIndex:i]);
151
152     return JSObjectMakeArray(context, count, arguments, nullptr);
153 }
154
155 static JSValueRef makeObjectRefForDictionary(JSContextRef context, NSDictionary *dictionary)
156 {
157     JSObjectRef object = JSObjectMake(context, nullptr, nullptr);
158
159     [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop)
160     {
161         if (JSValueRef propertyValue = makeValueRefForValue(context, obj))
162             JSObjectSetProperty(context, object, adopt([key createJSStringRef]).get(), propertyValue, kJSPropertyAttributeNone, nullptr);
163     }];
164
165     return object;
166 }
167
168 - (void)_notificationReceived:(NSNotification *)notification
169 {
170     NSString *notificationName = [[notification userInfo] objectForKey:@"notificationName"];
171     if (!notificationName)
172         return;
173     if (m_platformElement && m_platformElement != [notification object])
174         return;
175
176     NSDictionary *userInfo = [[notification userInfo] objectForKey:@"userInfo"];
177
178     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(WTR::InjectedBundle::singleton().page()->page());
179     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
180
181     JSValueRef notificationNameArgument = JSValueMakeString(context, adopt([notificationName createJSStringRef]).get());
182     JSValueRef userInfoArgument = makeObjectRefForDictionary(context, userInfo);
183     if (m_platformElement) {
184         // Listener for one element gets the notification name and userInfo.
185         JSValueRef arguments[2];
186         arguments[0] = notificationNameArgument;
187         arguments[1] = userInfoArgument;
188         JSObjectCallAsFunction(context, const_cast<JSObjectRef>(m_notificationFunctionCallback), 0, 2, arguments, 0);
189     } else {
190         // A global listener gets the element, notification name and userInfo.
191         JSValueRef arguments[3];
192         arguments[0] = toJS(context, WTR::AccessibilityUIElement::create([notification object]).ptr());
193         arguments[1] = notificationNameArgument;
194         arguments[2] = userInfoArgument;
195         JSObjectCallAsFunction(context, const_cast<JSObjectRef>(m_notificationFunctionCallback), 0, 3, arguments, 0);
196     }
197 }
198
199 @end
200