Add WKUIDelegatePrivate equivalent of WKPageUIClient's drawHeader, drawFooter, header...
[WebKit-https.git] / Tools / TestWebKitAPI / cocoa / TestWKWebView.mm
1 /*
2  * Copyright (C) 2016 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 "TestWKWebView.h"
28
29 #if WK_API_ENABLED
30
31 #import "TestNavigationDelegate.h"
32 #import "Utilities.h"
33
34 #import <WebKit/WKWebViewConfigurationPrivate.h>
35 #import <WebKit/WebKitPrivate.h>
36 #import <WebKit/_WKActivatedElementInfo.h>
37 #import <WebKit/_WKProcessPoolConfiguration.h>
38 #import <objc/runtime.h>
39 #import <wtf/RetainPtr.h>
40
41 #if PLATFORM(MAC)
42 #import <AppKit/AppKit.h>
43 #import <Carbon/Carbon.h>
44 #import <wtf/mac/AppKitCompatibilityDeclarations.h>
45 #endif
46
47 #if PLATFORM(IOS)
48 #import <wtf/SoftLinking.h>
49 SOFT_LINK_FRAMEWORK(UIKit)
50 SOFT_LINK_CLASS(UIKit, UIWindow)
51 #endif
52
53 @implementation TestMessageHandler {
54     NSMutableDictionary<NSString *, dispatch_block_t> *_messageHandlers;
55 }
56
57 - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler
58 {
59     if (!_messageHandlers)
60         _messageHandlers = [NSMutableDictionary dictionary];
61
62     _messageHandlers[message] = [handler copy];
63 }
64
65 - (void)removeMessage:(NSString *)message
66 {
67     [_messageHandlers removeObjectForKey:message];
68 }
69
70 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
71 {
72     dispatch_block_t handler = _messageHandlers[message.body];
73     if (handler)
74         handler();
75 }
76
77 @end
78
79 #if PLATFORM(MAC)
80 @interface TestWKWebViewHostWindow : NSWindow
81 #else
82 @interface TestWKWebViewHostWindow : UIWindow
83 #endif // PLATFORM(MAC)
84 @end
85
86 @implementation TestWKWebViewHostWindow {
87     BOOL _forceKeyWindow;
88 }
89
90 #if PLATFORM(MAC)
91 static int gEventNumber = 1;
92
93 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
94 NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
95 {
96     return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
97 }
98 #endif
99
100 - (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount
101 {
102     NSEventType mouseEventType = NSEventTypeLeftMouseDown;
103
104     NSEventMask modifierFlags = 0;
105 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
106     if (simulatePressure)
107         modifierFlags |= NSEventMaskPressure;
108 #else
109     simulatePressure = NO;
110 #endif
111
112     NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
113     if (!simulatePressure) {
114         [self sendEvent:event];
115         return;
116     }
117
118 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
119     IMP simulatedAssociatedEventsMaskImpl = (IMP)__simulated_forceClickAssociatedEventsMask;
120     Method associatedEventsMaskMethod = class_getInstanceMethod([NSEvent class], @selector(associatedEventsMask));
121     IMP originalAssociatedEventsMaskImpl = method_setImplementation(associatedEventsMaskMethod, simulatedAssociatedEventsMaskImpl);
122     @try {
123         [self sendEvent:event];
124     } @finally {
125         // In the case where event sending raises an exception, we still want to restore the original implementation
126         // to prevent subsequent event sending tests from being affected.
127         method_setImplementation(associatedEventsMaskMethod, originalAssociatedEventsMaskImpl);
128     }
129 #endif
130 }
131
132 - (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount
133 {
134     [self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
135 }
136 #endif // PLATFORM(MAC)
137
138 - (BOOL)isKeyWindow
139 {
140     return _forceKeyWindow || [super isKeyWindow];
141 }
142
143 - (void)makeKeyWindow
144 {
145     if (_forceKeyWindow)
146         return;
147
148     _forceKeyWindow = YES;
149 #if PLATFORM(MAC)
150     [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self];
151 #else
152     [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self];
153 #endif
154 }
155
156 - (void)resignKeyWindow
157 {
158     _forceKeyWindow = NO;
159     [super resignKeyWindow];
160 }
161
162 @end
163
164 @implementation TestWKWebView {
165     RetainPtr<TestWKWebViewHostWindow> _hostWindow;
166     RetainPtr<TestMessageHandler> _testHandler;
167 }
168
169 - (instancetype)initWithFrame:(CGRect)frame
170 {
171     WKWebViewConfiguration *defaultConfiguration = [[[WKWebViewConfiguration alloc] init] autorelease];
172     return [self initWithFrame:frame configuration:defaultConfiguration];
173 }
174
175 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
176 {
177     if (self = [super initWithFrame:frame configuration:configuration])
178         [self _setUpTestWindow:frame];
179
180     return self;
181 }
182
183 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration processPoolConfiguration:(_WKProcessPoolConfiguration *)processPoolConfiguration
184 {
185     [configuration setProcessPool:[[[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration] autorelease]];
186     return [self initWithFrame:frame configuration:configuration];
187 }
188
189 - (void)_setUpTestWindow:(NSRect)frame
190 {
191 #if PLATFORM(MAC)
192     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]);
193     [_hostWindow setFrameOrigin:NSMakePoint(0, 0)];
194     [_hostWindow setIsVisible:YES];
195     [_hostWindow contentView].wantsLayer = YES;
196     [[_hostWindow contentView] addSubview:self];
197     [_hostWindow makeKeyAndOrderFront:self];
198 #else
199     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithFrame:frame]);
200     [_hostWindow setHidden:NO];
201     [_hostWindow addSubview:self];
202 #endif
203 }
204
205 - (void)clearMessageHandlers:(NSArray *)messageNames
206 {
207     for (NSString *messageName in messageNames)
208         [_testHandler removeMessage:messageName];
209 }
210
211 - (void)performAfterReceivingMessage:(NSString *)message action:(dispatch_block_t)action
212 {
213     if (!_testHandler) {
214         _testHandler = adoptNS([[TestMessageHandler alloc] init]);
215         [[[self configuration] userContentController] addScriptMessageHandler:_testHandler.get() name:@"testHandler"];
216     }
217
218     [_testHandler addMessage:message withHandler:action];
219 }
220
221 - (void)loadTestPageNamed:(NSString *)pageName
222 {
223     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:pageName withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
224     [self loadRequest:request];
225 }
226
227 - (void)synchronouslyLoadHTMLString:(NSString *)html
228 {
229     NSURL *testResourceURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"];
230     [self loadHTMLString:html baseURL:testResourceURL];
231     [self _test_waitForDidFinishNavigation];
232 }
233
234 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName
235 {
236     [self loadTestPageNamed:pageName];
237     [self _test_waitForDidFinishNavigation];
238 }
239
240 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script
241 {
242     __block bool isWaitingForJavaScript = false;
243     __block NSString *evalResult = nil;
244     [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:^(id result, NSError *error)
245     {
246         evalResult = [[NSString alloc] initWithFormat:@"%@", result];
247         isWaitingForJavaScript = true;
248         EXPECT_TRUE(!error);
249         if (error)
250             NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
251     }];
252
253     TestWebKitAPI::Util::run(&isWaitingForJavaScript);
254     return [evalResult autorelease];
255 }
256
257 - (void)waitForMessage:(NSString *)message
258 {
259     __block bool isDoneWaiting = false;
260     [self performAfterReceivingMessage:message action:^()
261     {
262         isDoneWaiting = true;
263     }];
264     TestWebKitAPI::Util::run(&isDoneWaiting);
265 }
266
267 - (void)performAfterLoading:(dispatch_block_t)actions {
268     TestMessageHandler *handler = [[TestMessageHandler alloc] init];
269     [handler addMessage:@"loaded" withHandler:actions];
270
271     NSString *onloadScript = @"window.onload = () => window.webkit.messageHandlers.onloadHandler.postMessage('loaded')";
272     WKUserScript *script = [[WKUserScript alloc] initWithSource:onloadScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
273
274     WKUserContentController* contentController = [[self configuration] userContentController];
275     [contentController addUserScript:script];
276     [contentController addScriptMessageHandler:handler name:@"onloadHandler"];
277 }
278
279 - (void)waitForNextPresentationUpdate
280 {
281     __block bool done = false;
282     [self _doAfterNextPresentationUpdate:^() {
283         done = true;
284     }];
285
286     TestWebKitAPI::Util::run(&done);
287 }
288
289 @end
290
291 #if PLATFORM(IOS)
292
293 @implementation TestWKWebView (IOSOnly)
294
295 - (RetainPtr<NSArray>)selectionRectsAfterPresentationUpdate
296 {
297     RetainPtr<TestWKWebView> retainedSelf = self;
298
299     __block bool isDone = false;
300     __block RetainPtr<NSArray> selectionRects;
301     [self _doAfterNextPresentationUpdate:^() {
302         selectionRects = adoptNS([[retainedSelf _uiTextSelectionRects] retain]);
303         isDone = true;
304     }];
305
306     TestWebKitAPI::Util::run(&isDone);
307     return selectionRects;
308 }
309
310 - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position
311 {
312     __block RetainPtr<_WKActivatedElementInfo> info;
313     __block bool finished = false;
314     [self _requestActivatedElementAtPosition:position completionBlock:^(_WKActivatedElementInfo *elementInfo) {
315         info = elementInfo;
316         finished = true;
317     }];
318
319     TestWebKitAPI::Util::run(&finished);
320     return info.autorelease();
321 }
322
323 @end
324
325 #endif
326
327 #if PLATFORM(MAC)
328 @implementation TestWKWebView (MacOnly)
329 - (void)mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure
330 {
331     [_hostWindow _mouseDownAtPoint:point simulatePressure:simulatePressure clickCount:1];
332 }
333
334 - (void)mouseUpAtPoint:(NSPoint)point
335 {
336     [_hostWindow _mouseUpAtPoint:point clickCount:1];
337 }
338
339 - (void)mouseMoveToPoint:(NSPoint)point withFlags:(NSEventModifierFlags)flags
340 {
341     [self mouseMoved:[NSEvent mouseEventWithType:NSEventTypeMouseMoved location:point modifierFlags:flags timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:0 pressure:0]];
342 }
343
344 - (void)sendClicksAtPoint:(NSPoint)point numberOfClicks:(NSUInteger)numberOfClicks
345 {
346     for (NSUInteger clickCount = 1; clickCount <= numberOfClicks; ++clickCount) {
347         [_hostWindow _mouseDownAtPoint:point simulatePressure:NO clickCount:clickCount];
348         [_hostWindow _mouseUpAtPoint:point clickCount:clickCount];
349     }
350 }
351
352 - (NSWindow *)hostWindow
353 {
354     return _hostWindow.get();
355 }
356
357 - (void)typeCharacter:(char)character {
358     NSString *characterAsString = [NSString stringWithFormat:@"%c" , character];
359     NSEventType keyDownEventType = NSEventTypeKeyDown;
360     NSEventType keyUpEventType = NSEventTypeKeyUp;
361     [self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
362     [self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
363 }
364 @end
365 #endif
366
367 #endif // WK_API_ENABLED