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