Clean up some obsolete macOS version guards
[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_FAMILY)
48 #import "UIKitSPI.h"
49 #import <wtf/SoftLinking.h>
50 SOFT_LINK_FRAMEWORK(UIKit)
51 SOFT_LINK_CLASS(UIKit, UIWindow)
52
53 @implementation WKWebView (WKWebViewTestingQuicks)
54
55 // TestWebKitAPI is currently not a UIApplication so we are unable to track if it is in
56 // the background or not (https://bugs.webkit.org/show_bug.cgi?id=175204). This can
57 // cause our processes to get suspended on iOS. We work around this by having
58 // WKWebView._isBackground always return NO in the context of API tests.
59 - (BOOL)_isBackground
60 {
61     return NO;
62 }
63 @end
64 #endif
65
66 @implementation TestMessageHandler {
67     NSMutableDictionary<NSString *, dispatch_block_t> *_messageHandlers;
68 }
69
70 - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler
71 {
72     if (!_messageHandlers)
73         _messageHandlers = [NSMutableDictionary dictionary];
74
75     _messageHandlers[message] = [handler copy];
76 }
77
78 - (void)removeMessage:(NSString *)message
79 {
80     [_messageHandlers removeObjectForKey:message];
81 }
82
83 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
84 {
85     dispatch_block_t handler = _messageHandlers[message.body];
86     if (handler)
87         handler();
88 }
89
90 @end
91
92 #if PLATFORM(MAC)
93 @interface TestWKWebViewHostWindow : NSWindow
94 #else
95 @interface TestWKWebViewHostWindow : UIWindow
96 #endif // PLATFORM(MAC)
97 @end
98
99 @implementation TestWKWebViewHostWindow {
100     BOOL _forceKeyWindow;
101 }
102
103 #if PLATFORM(MAC)
104 static int gEventNumber = 1;
105
106 NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
107 {
108     return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
109 }
110
111 - (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount
112 {
113     NSEventType mouseEventType = NSEventTypeLeftMouseDown;
114
115     NSEventMask modifierFlags = 0;
116     if (simulatePressure)
117         modifierFlags |= NSEventMaskPressure;
118
119     NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
120     if (!simulatePressure) {
121         [self sendEvent:event];
122         return;
123     }
124
125     IMP simulatedAssociatedEventsMaskImpl = (IMP)__simulated_forceClickAssociatedEventsMask;
126     Method associatedEventsMaskMethod = class_getInstanceMethod([NSEvent class], @selector(associatedEventsMask));
127     IMP originalAssociatedEventsMaskImpl = method_setImplementation(associatedEventsMaskMethod, simulatedAssociatedEventsMaskImpl);
128     @try {
129         [self sendEvent:event];
130     } @finally {
131         // In the case where event sending raises an exception, we still want to restore the original implementation
132         // to prevent subsequent event sending tests from being affected.
133         method_setImplementation(associatedEventsMaskMethod, originalAssociatedEventsMaskImpl);
134     }
135 }
136
137 - (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount
138 {
139     [self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
140 }
141 #endif // PLATFORM(MAC)
142
143 - (BOOL)isKeyWindow
144 {
145     return _forceKeyWindow || [super isKeyWindow];
146 }
147
148 - (void)makeKeyWindow
149 {
150     if (_forceKeyWindow)
151         return;
152
153     _forceKeyWindow = YES;
154 #if PLATFORM(MAC)
155     [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self];
156 #else
157     [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self];
158 #endif
159 }
160
161 - (void)resignKeyWindow
162 {
163     _forceKeyWindow = NO;
164     [super resignKeyWindow];
165 }
166
167 @end
168
169 @implementation TestWKWebView {
170     RetainPtr<TestWKWebViewHostWindow> _hostWindow;
171     RetainPtr<TestMessageHandler> _testHandler;
172 }
173
174 - (instancetype)initWithFrame:(CGRect)frame
175 {
176     WKWebViewConfiguration *defaultConfiguration = [[[WKWebViewConfiguration alloc] init] autorelease];
177     return [self initWithFrame:frame configuration:defaultConfiguration];
178 }
179
180 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
181 {
182     return [self initWithFrame:frame configuration:configuration addToWindow:YES];
183 }
184
185 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration addToWindow:(BOOL)addToWindow
186 {
187     self = [super initWithFrame:frame configuration:configuration];
188     if (!self)
189         return nil;
190
191     if (addToWindow)
192         [self _setUpTestWindow:frame];
193
194     return self;
195 }
196
197 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration processPoolConfiguration:(_WKProcessPoolConfiguration *)processPoolConfiguration
198 {
199     [configuration setProcessPool:[[[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration] autorelease]];
200     return [self initWithFrame:frame configuration:configuration];
201 }
202
203 - (void)_setUpTestWindow:(NSRect)frame
204 {
205 #if PLATFORM(MAC)
206     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]);
207     [_hostWindow setFrameOrigin:NSMakePoint(0, 0)];
208     [_hostWindow setIsVisible:YES];
209     [_hostWindow contentView].wantsLayer = YES;
210     [[_hostWindow contentView] addSubview:self];
211     [_hostWindow makeKeyAndOrderFront:self];
212 #else
213     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithFrame:frame]);
214     [_hostWindow setHidden:NO];
215     [_hostWindow addSubview:self];
216 #endif
217 }
218
219 - (void)clearMessageHandlers:(NSArray *)messageNames
220 {
221     for (NSString *messageName in messageNames)
222         [_testHandler removeMessage:messageName];
223 }
224
225 - (void)performAfterReceivingMessage:(NSString *)message action:(dispatch_block_t)action
226 {
227     if (!_testHandler) {
228         _testHandler = adoptNS([[TestMessageHandler alloc] init]);
229         [[[self configuration] userContentController] addScriptMessageHandler:_testHandler.get() name:@"testHandler"];
230     }
231
232     [_testHandler addMessage:message withHandler:action];
233 }
234
235 - (void)loadTestPageNamed:(NSString *)pageName
236 {
237     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:pageName withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
238     [self loadRequest:request];
239 }
240
241 - (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url
242 {
243     [self loadHTMLString:html baseURL:url];
244     [self _test_waitForDidFinishNavigation];
245 }
246
247 - (void)synchronouslyLoadHTMLString:(NSString *)html
248 {
249     [self synchronouslyLoadHTMLString:html baseURL:[[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"]];
250 }
251
252 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName
253 {
254     [self loadTestPageNamed:pageName];
255     [self _test_waitForDidFinishNavigation];
256 }
257
258 - (id)objectByEvaluatingJavaScript:(NSString *)script
259 {
260     bool isWaitingForJavaScript = false;
261     RetainPtr<id> evalResult;
262     [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:[&] (id result, NSError *error) {
263         evalResult = result;
264         isWaitingForJavaScript = true;
265         EXPECT_TRUE(!error);
266         if (error)
267             NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
268     }];
269     TestWebKitAPI::Util::run(&isWaitingForJavaScript);
270     return evalResult.autorelease();
271 }
272
273 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script
274 {
275     return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
276 }
277
278 - (void)waitForMessage:(NSString *)message
279 {
280     __block bool isDoneWaiting = false;
281     [self performAfterReceivingMessage:message action:^()
282     {
283         isDoneWaiting = true;
284     }];
285     TestWebKitAPI::Util::run(&isDoneWaiting);
286 }
287
288 - (void)performAfterLoading:(dispatch_block_t)actions {
289     TestMessageHandler *handler = [[TestMessageHandler alloc] init];
290     [handler addMessage:@"loaded" withHandler:actions];
291
292     NSString *onloadScript = @"window.onload = () => window.webkit.messageHandlers.onloadHandler.postMessage('loaded')";
293     WKUserScript *script = [[WKUserScript alloc] initWithSource:onloadScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
294
295     WKUserContentController* contentController = [[self configuration] userContentController];
296     [contentController addUserScript:script];
297     [contentController addScriptMessageHandler:handler name:@"onloadHandler"];
298 }
299
300 - (void)waitForNextPresentationUpdate
301 {
302     __block bool done = false;
303     [self _doAfterNextPresentationUpdate:^() {
304         done = true;
305     }];
306
307     TestWebKitAPI::Util::run(&done);
308 }
309
310 - (NSString *)stylePropertyAtSelectionStart:(NSString *)propertyName
311 {
312     NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).startContainer.parentElement)['%@']", propertyName];
313     return [self stringByEvaluatingJavaScript:script];
314 }
315
316 - (NSString *)stylePropertyAtSelectionEnd:(NSString *)propertyName
317 {
318     NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).endContainer.parentElement)['%@']", propertyName];
319     return [self stringByEvaluatingJavaScript:script];
320 }
321
322 - (void)collapseToStart
323 {
324     [self evaluateJavaScript:@"getSelection().collapseToStart()" completionHandler:nil];
325 }
326
327 - (void)collapseToEnd
328 {
329     [self evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
330 }
331
332 @end
333
334 #if PLATFORM(IOS_FAMILY)
335
336 @implementation TestWKWebView (IOSOnly)
337
338 - (UIView <UITextInput, UITextInputMultiDocument> *)textInputContentView
339 {
340     return (UIView <UITextInput, UITextInputMultiDocument> *)[self valueForKey:@"_currentContentView"];
341 }
342
343 - (RetainPtr<NSArray>)selectionRectsAfterPresentationUpdate
344 {
345     RetainPtr<TestWKWebView> retainedSelf = self;
346
347     __block bool isDone = false;
348     __block RetainPtr<NSArray> selectionRects;
349     [self _doAfterNextPresentationUpdate:^() {
350         selectionRects = adoptNS([[retainedSelf _uiTextSelectionRects] retain]);
351         isDone = true;
352     }];
353
354     TestWebKitAPI::Util::run(&isDone);
355     return selectionRects;
356 }
357
358 - (CGRect)caretViewRectInContentCoordinates
359 {
360     UIView *selectionView = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView"];
361     CGRect caretFrame = [[selectionView valueForKeyPath:@"caretView.frame"] CGRectValue];
362     return [selectionView convertRect:caretFrame toView:self.textInputContentView];
363 }
364
365 - (NSArray<NSValue *> *)selectionViewRectsInContentCoordinates
366 {
367     NSMutableArray *selectionRects = [NSMutableArray array];
368     NSArray<UITextSelectionRect *> *rects = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView.rects"];
369     for (UITextSelectionRect *rect in rects)
370         [selectionRects addObject:[NSValue valueWithCGRect:rect.rect]];
371     return selectionRects;
372 }
373
374 - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position
375 {
376     __block RetainPtr<_WKActivatedElementInfo> info;
377     __block bool finished = false;
378     [self _requestActivatedElementAtPosition:position completionBlock:^(_WKActivatedElementInfo *elementInfo) {
379         info = elementInfo;
380         finished = true;
381     }];
382
383     TestWebKitAPI::Util::run(&finished);
384     return info.autorelease();
385 }
386
387 @end
388
389 #endif
390
391 #if PLATFORM(MAC)
392 @implementation TestWKWebView (MacOnly)
393 - (void)mouseDownAtPoint:(NSPoint)pointInWindow simulatePressure:(BOOL)simulatePressure
394 {
395     [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:simulatePressure clickCount:1];
396 }
397
398 - (void)mouseUpAtPoint:(NSPoint)pointInWindow
399 {
400     [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:1];
401 }
402
403 - (void)mouseMoveToPoint:(NSPoint)pointInWindow withFlags:(NSEventModifierFlags)flags
404 {
405     [self mouseMoved:[self _mouseEventWithType:NSEventTypeMouseMoved atLocation:pointInWindow flags:flags timestamp:GetCurrentEventTime() clickCount:0]];
406 }
407
408 - (void)sendClicksAtPoint:(NSPoint)pointInWindow numberOfClicks:(NSUInteger)numberOfClicks
409 {
410     for (NSUInteger clickCount = 1; clickCount <= numberOfClicks; ++clickCount) {
411         [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:NO clickCount:clickCount];
412         [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:clickCount];
413     }
414 }
415
416 - (void)mouseEnterAtPoint:(NSPoint)pointInWindow
417 {
418     [self mouseEntered:[self _mouseEventWithType:NSEventTypeMouseEntered atLocation:pointInWindow]];
419 }
420
421 - (void)mouseDragToPoint:(NSPoint)pointInWindow
422 {
423     [self mouseDragged:[self _mouseEventWithType:NSEventTypeLeftMouseDragged atLocation:pointInWindow]];
424 }
425
426 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)pointInWindow
427 {
428     return [self _mouseEventWithType:type atLocation:pointInWindow flags:0 timestamp:GetCurrentEventTime() clickCount:0];
429 }
430
431 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)locationInWindow flags:(NSEventModifierFlags)flags timestamp:(NSTimeInterval)timestamp clickCount:(NSUInteger)clickCount
432 {
433     switch (type) {
434     case NSEventTypeMouseEntered:
435     case NSEventTypeMouseExited:
436         return [NSEvent enterExitEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber trackingNumber:1 userData:nil];
437     default:
438         return [NSEvent mouseEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0];
439     }
440 }
441
442 - (NSWindow *)hostWindow
443 {
444     return _hostWindow.get();
445 }
446
447 - (void)typeCharacter:(char)character
448 {
449     NSString *characterAsString = [NSString stringWithFormat:@"%c" , character];
450     NSEventType keyDownEventType = NSEventTypeKeyDown;
451     NSEventType keyUpEventType = NSEventTypeKeyUp;
452     [self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
453     [self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
454 }
455
456 @end
457 #endif
458
459 #endif // WK_API_ENABLED