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