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