bf7da796acb6dd216d06a97ef796382817cc8c92
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / ios / ActionSheetTests.mm
1 /*
2  * Copyright (C) 2017 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 "Test.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "ClassMethodSwizzler.h"
32 #import "InstanceMethodSwizzler.h"
33 #import "PlatformUtilities.h"
34 #import "TestNavigationDelegate.h"
35 #import "TestWKWebView.h"
36 #import "TestWKWebViewController.h"
37 #import "UIKitSPI.h"
38 #import <MobileCoreServices/MobileCoreServices.h>
39 #import <WebKit/WKUIDelegatePrivate.h>
40 #import <WebKit/WKWebViewPrivate.h>
41 #import <WebKit/_WKActivatedElementInfo.h>
42 #import <WebKit/_WKElementAction.h>
43 #import <wtf/BlockPtr.h>
44 #import <wtf/RetainPtr.h>
45 #import <wtf/SoftLinking.h>
46
47 @interface ActionSheetObserver : NSObject<WKUIDelegatePrivate>
48 @property (nonatomic) BlockPtr<NSArray *(_WKActivatedElementInfo *, NSArray *)> presentationHandler;
49 @property (nonatomic) BlockPtr<NSDictionary *(void)> dataDetectionContextHandler;
50 @end
51
52 @implementation ActionSheetObserver
53
54 - (NSArray *)_webView:(WKWebView *)webView actionsForElement:(_WKActivatedElementInfo *)element defaultActions:(NSArray<_WKElementAction *> *)defaultActions
55 {
56     return _presentationHandler ? _presentationHandler(element, defaultActions) : defaultActions;
57 }
58
59 - (NSDictionary *)_dataDetectionContextForWebView:(WKWebView *)WebView
60 {
61     return _dataDetectionContextHandler ? _dataDetectionContextHandler() : @{ };
62 }
63
64 @end
65
66 namespace TestWebKitAPI {
67
68 class IPadUserInterfaceSwizzler {
69 public:
70     IPadUserInterfaceSwizzler()
71         : m_swizzler([UIDevice class], @selector(userInterfaceIdiom), reinterpret_cast<IMP>(padUserInterfaceIdiom))
72     {
73     }
74 private:
75     static UIUserInterfaceIdiom padUserInterfaceIdiom()
76     {
77         return UIUserInterfaceIdiomPad;
78     }
79     InstanceMethodSwizzler m_swizzler;
80 };
81
82 static RetainPtr<UIViewController> gOverrideViewControllerForFullscreenPresentation;
83 static void setOverrideViewControllerForFullscreenPresentation(UIViewController *viewController)
84 {
85     gOverrideViewControllerForFullscreenPresentation = viewController;
86 }
87
88 static UIViewController *overrideViewControllerForFullscreenPresentation()
89 {
90     return gOverrideViewControllerForFullscreenPresentation.get();
91 }
92
93 TEST(ActionSheetTests, DISABLED_DismissingActionSheetShouldNotDismissPresentingViewController)
94 {
95     IPadUserInterfaceSwizzler iPadUserInterface;
96     UIApplicationInitialize();
97
98     auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
99     auto window = adoptNS([[TestWKWebViewControllerWindow alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)]);
100     auto rootViewController = adoptNS([[UIViewController alloc] init]);
101     auto navigationController = adoptNS([[UINavigationController alloc] initWithRootViewController:rootViewController.get()]);
102     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
103     [window setRootViewController:navigationController.get()];
104     [window makeKeyAndVisible];
105
106     auto webViewController = adoptNS([[TestWKWebViewController alloc] initWithFrame:CGRectMake(0, 0, 1024, 768) configuration:nil]);
107     TestWKWebView *webView = [webViewController webView];
108     webView.UIDelegate = observer.get();
109     [webView synchronouslyLoadTestPageNamed:@"link-and-input"];
110     [webView setNavigationDelegate:navigationDelegate.get()];
111     [rootViewController presentViewController:webViewController.get() animated:NO completion:nil];
112
113     // Since TestWebKitAPI is not a UI application, +[UIViewController _viewControllerForFullScreenPresentationFromView:]
114     // returns nil. To ensure that we actually present the action sheet from the web view controller, we mock this for the
115     // time being until https://webkit.org/b/175204 is fixed.
116     setOverrideViewControllerForFullscreenPresentation(webViewController.get());
117     ClassMethodSwizzler swizzler([UIViewController class], @selector(_viewControllerForFullScreenPresentationFromView:), reinterpret_cast<IMP>(overrideViewControllerForFullscreenPresentation));
118
119     [observer setPresentationHandler:^(_WKActivatedElementInfo *, NSArray *actions) {
120         // Killing the web content process should dismiss the action sheet.
121         // This should not tell the web view controller to dismiss in the process.
122         [webView _killWebContentProcess];
123         return actions;
124     }];
125
126     __block bool done = false;
127     [navigationDelegate setWebContentProcessDidTerminate:^(WKWebView *) {
128         dispatch_async(dispatch_get_main_queue(), ^{
129             done = true;
130         });
131     }];
132
133     __block bool didDismissWebViewController = false;
134     [webViewController setDismissalHandler:^{
135         didDismissWebViewController = true;
136     }];
137
138     [webView _simulateLongPressActionAtLocation:CGPointMake(100, 100)];
139     TestWebKitAPI::Util::run(&done);
140
141     EXPECT_FALSE(didDismissWebViewController);
142     EXPECT_NULL([webViewController presentedViewController]);
143     EXPECT_NOT_NULL([webViewController presentingViewController]);
144 }
145
146 TEST(ActionSheetTests, ImageMapDoesNotDestroySelection)
147 {
148     IPadUserInterfaceSwizzler iPadUserInterface;
149
150     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)]);
151     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
152     [webView setUIDelegate:observer.get()];
153     [webView synchronouslyLoadTestPageNamed:@"image-map"];
154     [webView stringByEvaluatingJavaScript:@"selectTextNode(h1.childNodes[0])"];
155
156     EXPECT_WK_STREQ("Hello world", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
157
158     __block bool done = false;
159     [observer setPresentationHandler:^(_WKActivatedElementInfo *element, NSArray *actions) {
160         done = true;
161         return actions;
162     }];
163     [webView _simulateLongPressActionAtLocation:CGPointMake(200, 200)];
164     TestWebKitAPI::Util::run(&done);
165
166     EXPECT_WK_STREQ("Hello world", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
167 }
168
169 TEST(ActionSheetTests, DataDetectorsLinkIsNotPresentedAsALink)
170 {
171     IPadUserInterfaceSwizzler iPadUserInterface;
172
173     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)]);
174     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
175     [webView setUIDelegate:observer.get()];
176
177     auto runTest = ^(NSString *phoneNumber) {
178         [webView synchronouslyLoadHTMLString:[NSString stringWithFormat:@"<a style='position: absolute; top: 0; left: 0;' href='tel:%@'>telephone number</a>", phoneNumber]];
179
180         __block bool done = false;
181         __block bool succeeded = true;
182
183         // We shouldn't present a normal action sheet, but instead a data detectors sheet.
184         [observer setDataDetectionContextHandler:^{
185             done = true;
186             return @{ };
187         }];
188         [observer setPresentationHandler:^(_WKActivatedElementInfo *, NSArray *) {
189             done = true;
190             succeeded = false;
191             return @[ ];
192         }];
193         [webView _simulateLongPressActionAtLocation:CGPointMake(5, 5)];
194         TestWebKitAPI::Util::run(&done);
195
196         return succeeded;
197     };
198
199     EXPECT_TRUE(runTest(@"4089961010"));
200     EXPECT_TRUE(runTest(@"408 996 1010"));
201     EXPECT_TRUE(runTest(@"1-866-MY-APPLE"));
202     EXPECT_TRUE(runTest(@"(123) 456 - 7890"));
203     EXPECT_TRUE(runTest(@"01 23 45 67 00"));
204     EXPECT_TRUE(runTest(@"+33 (0)1 23 34 45 56"));
205     EXPECT_TRUE(runTest(@"(0)1 12 23 34 56"));
206     EXPECT_TRUE(runTest(@"010-1-800-MY-APPLE"));
207     EXPECT_TRUE(runTest(@"+1-408-1234567"));
208     EXPECT_TRUE(runTest(@"08080808080"));
209 }
210
211 #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
212
213 static void presentActionSheetAndChooseAction(WKWebView *webView, ActionSheetObserver *observer, CGPoint location, _WKElementActionType actionType)
214 {
215     __block RetainPtr<_WKElementAction> copyAction;
216     __block RetainPtr<_WKActivatedElementInfo> copyElement;
217     __block bool done = false;
218     [observer setPresentationHandler:^(_WKActivatedElementInfo *element, NSArray *actions) {
219         copyElement = element;
220         for (_WKElementAction *action in actions) {
221             if (action.type == actionType)
222                 copyAction = action;
223         }
224         done = true;
225         return @[ copyAction.get() ];
226     }];
227     [webView _simulateLongPressActionAtLocation:location];
228     TestWebKitAPI::Util::run(&done);
229
230     EXPECT_TRUE(!!copyAction);
231     EXPECT_TRUE(!!copyElement);
232     [copyAction runActionWithElementInfo:copyElement.get()];
233 }
234
235 TEST(ActionSheetTests, CopyImageElementWithHREFAndTitle)
236 {
237     UIApplicationInitialize();
238     [UIPasteboard generalPasteboard].items = @[ ];
239
240     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
241     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
242     [webView setUIDelegate:observer.get()];
243     [webView synchronouslyLoadTestPageNamed:@"image-in-link-and-input"];
244     [webView stringByEvaluatingJavaScript:@"document.querySelector('a').setAttribute('title', 'hello world')"];
245
246     presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 50), _WKElementActionTypeCopy);
247
248     __block bool done = false;
249     __block RetainPtr<NSItemProvider> itemProvider;
250     [webView _doAfterNextPresentationUpdate:^() {
251         NSArray <NSString *> *pasteboardTypes = [[UIPasteboard generalPasteboard] pasteboardTypes];
252         EXPECT_EQ(2UL, pasteboardTypes.count);
253         EXPECT_WK_STREQ((__bridge NSString *)kUTTypePNG, pasteboardTypes.firstObject);
254         EXPECT_WK_STREQ((__bridge NSString *)kUTTypeURL, pasteboardTypes.lastObject);
255         NSArray <NSItemProvider *> *itemProviders = [[UIPasteboard generalPasteboard] itemProviders];
256         EXPECT_EQ(1UL, itemProviders.count);
257         itemProvider = itemProviders.firstObject;
258         EXPECT_EQ(2UL, [itemProvider registeredTypeIdentifiers].count);
259         EXPECT_WK_STREQ((__bridge NSString *)kUTTypePNG, [itemProvider registeredTypeIdentifiers].firstObject);
260         EXPECT_WK_STREQ((__bridge NSString *)kUTTypeURL, [itemProvider registeredTypeIdentifiers].lastObject);
261         done = true;
262     }];
263     TestWebKitAPI::Util::run(&done);
264
265     __block bool doneLoading = false;
266     [itemProvider loadObjectOfClass:[NSURL class] completionHandler:^(id <NSItemProviderReading> result, NSError *) {
267         EXPECT_TRUE([result isKindOfClass:[NSURL class]]);
268         NSURL *url = (NSURL *)result;
269         EXPECT_WK_STREQ("https://www.apple.com/", url.absoluteString);
270         EXPECT_WK_STREQ("hello world", url._title);
271         doneLoading = true;
272     }];
273     TestWebKitAPI::Util::run(&doneLoading);
274 }
275
276 TEST(ActionSheetTests, CopyImageElementWithHREF)
277 {
278     UIApplicationInitialize();
279     [UIPasteboard generalPasteboard].items = @[ ];
280
281     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
282     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
283     [webView setUIDelegate:observer.get()];
284     [webView synchronouslyLoadTestPageNamed:@"image-in-link-and-input"];
285
286     presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 50), _WKElementActionTypeCopy);
287
288     __block bool done = false;
289     __block RetainPtr<NSItemProvider> itemProvider;
290     [webView _doAfterNextPresentationUpdate:^() {
291         NSArray <NSString *> *pasteboardTypes = [[UIPasteboard generalPasteboard] pasteboardTypes];
292         EXPECT_EQ(2UL, pasteboardTypes.count);
293         EXPECT_WK_STREQ((__bridge NSString *)kUTTypePNG, pasteboardTypes.firstObject);
294         EXPECT_WK_STREQ((__bridge NSString *)kUTTypeURL, pasteboardTypes.lastObject);
295         NSArray <NSItemProvider *> *itemProviders = [[UIPasteboard generalPasteboard] itemProviders];
296         EXPECT_EQ(1UL, itemProviders.count);
297         itemProvider = itemProviders.firstObject;
298         EXPECT_EQ(2UL, [itemProvider registeredTypeIdentifiers].count);
299         EXPECT_WK_STREQ((__bridge NSString *)kUTTypePNG, [itemProvider registeredTypeIdentifiers].firstObject);
300         EXPECT_WK_STREQ((__bridge NSString *)kUTTypeURL, [itemProvider registeredTypeIdentifiers].lastObject);
301         done = true;
302     }];
303     TestWebKitAPI::Util::run(&done);
304
305     __block bool doneLoading = false;
306     [itemProvider loadObjectOfClass:[NSURL class] completionHandler:^(id <NSItemProviderReading> result, NSError *) {
307         EXPECT_TRUE([result isKindOfClass:[NSURL class]]);
308         NSURL *url = (NSURL *)result;
309         EXPECT_WK_STREQ("https://www.apple.com/", url.absoluteString);
310         EXPECT_WK_STREQ("https://www.apple.com/", url._title);
311         doneLoading = true;
312     }];
313     TestWebKitAPI::Util::run(&doneLoading);
314 }
315
316 TEST(ActionSheetTests, CopyImageElementWithoutHREF)
317 {
318     UIApplicationInitialize();
319     [UIPasteboard generalPasteboard].items = @[ ];
320
321     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
322     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
323     [webView setUIDelegate:observer.get()];
324     [webView synchronouslyLoadTestPageNamed:@"image-and-contenteditable"];
325
326     presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 100), _WKElementActionTypeCopy);
327
328     __block bool done = false;
329     [webView _doAfterNextPresentationUpdate:^() {
330         NSArray <NSString *> *pasteboardTypes = [[UIPasteboard generalPasteboard] pasteboardTypes];
331         EXPECT_EQ(1UL, pasteboardTypes.count);
332         EXPECT_WK_STREQ((__bridge NSString *)kUTTypePNG, pasteboardTypes.firstObject);
333         NSArray <NSItemProvider *> *itemProviders = [[UIPasteboard generalPasteboard] itemProviders];
334         EXPECT_EQ(1UL, itemProviders.count);
335         NSItemProvider *itemProvider = itemProviders.firstObject;
336         EXPECT_EQ(1UL, itemProvider.registeredTypeIdentifiers.count);
337         EXPECT_WK_STREQ((__bridge NSString *)kUTTypePNG, itemProvider.registeredTypeIdentifiers.firstObject);
338         done = true;
339     }];
340     TestWebKitAPI::Util::run(&done);
341 }
342
343 TEST(ActionSheetTests, CopyLinkWritesURLAndPlainText)
344 {
345     UIApplicationInitialize();
346     [UIPasteboard generalPasteboard].items = @[ ];
347
348     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
349     auto observer = adoptNS([[ActionSheetObserver alloc] init]);
350     [webView setUIDelegate:observer.get()];
351     [webView synchronouslyLoadTestPageNamed:@"link-and-input"];
352
353     presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 100), _WKElementActionTypeCopy);
354
355     [webView synchronouslyLoadTestPageNamed:@"DataTransfer"];
356     [webView paste:nil];
357
358     EXPECT_WK_STREQ("text/uri-list, text/plain", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
359     EXPECT_WK_STREQ("(STRING, text/uri-list), (STRING, text/plain)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
360     EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
361     EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
362     EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
363     EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
364     EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"rawHTMLData.textContent"]);
365 }
366
367 #endif // !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
368
369 } // namespace TestWebKitAPI
370
371 #endif // PLATFORM(IOS_FAMILY)