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