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