2 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #if PLATFORM(IOS_FAMILY)
31 #import "ClassMethodSwizzler.h"
32 #import "InstanceMethodSwizzler.h"
33 #import "PlatformUtilities.h"
34 #import "TestNavigationDelegate.h"
35 #import "TestWKWebView.h"
36 #import "TestWKWebViewController.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>
47 @interface ActionSheetObserver : NSObject<WKUIDelegatePrivate>
48 @property (nonatomic) BlockPtr<NSArray *(_WKActivatedElementInfo *, NSArray *)> presentationHandler;
49 @property (nonatomic) BlockPtr<NSDictionary *(void)> dataDetectionContextHandler;
52 @implementation ActionSheetObserver
54 - (NSArray *)_webView:(WKWebView *)webView actionsForElement:(_WKActivatedElementInfo *)element defaultActions:(NSArray<_WKElementAction *> *)defaultActions
56 return _presentationHandler ? _presentationHandler(element, defaultActions) : defaultActions;
59 - (NSDictionary *)_dataDetectionContextForWebView:(WKWebView *)WebView
61 return _dataDetectionContextHandler ? _dataDetectionContextHandler() : @{ };
66 namespace TestWebKitAPI {
68 class IPadUserInterfaceSwizzler {
70 IPadUserInterfaceSwizzler()
71 : m_swizzler([UIDevice class], @selector(userInterfaceIdiom), reinterpret_cast<IMP>(padUserInterfaceIdiom))
75 static UIUserInterfaceIdiom padUserInterfaceIdiom()
77 return UIUserInterfaceIdiomPad;
79 InstanceMethodSwizzler m_swizzler;
82 static RetainPtr<UIViewController> gOverrideViewControllerForFullscreenPresentation;
83 static void setOverrideViewControllerForFullscreenPresentation(UIViewController *viewController)
85 gOverrideViewControllerForFullscreenPresentation = viewController;
88 static UIViewController *overrideViewControllerForFullscreenPresentation()
90 return gOverrideViewControllerForFullscreenPresentation.get();
93 TEST(ActionSheetTests, DISABLED_DismissingActionSheetShouldNotDismissPresentingViewController)
95 IPadUserInterfaceSwizzler iPadUserInterface;
96 UIApplicationInitialize();
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];
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];
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));
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];
126 __block bool done = false;
127 [navigationDelegate setWebContentProcessDidTerminate:^(WKWebView *) {
128 dispatch_async(dispatch_get_main_queue(), ^{
133 __block bool didDismissWebViewController = false;
134 [webViewController setDismissalHandler:^{
135 didDismissWebViewController = true;
138 [webView _simulateLongPressActionAtLocation:CGPointMake(100, 100)];
139 TestWebKitAPI::Util::run(&done);
141 EXPECT_FALSE(didDismissWebViewController);
142 EXPECT_NULL([webViewController presentedViewController]);
143 EXPECT_NOT_NULL([webViewController presentingViewController]);
146 TEST(ActionSheetTests, ImageMapDoesNotDestroySelection)
148 IPadUserInterfaceSwizzler iPadUserInterface;
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])"];
156 EXPECT_WK_STREQ("Hello world", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
158 __block bool done = false;
159 [observer setPresentationHandler:^(_WKActivatedElementInfo *element, NSArray *actions) {
163 [webView _simulateLongPressActionAtLocation:CGPointMake(200, 200)];
164 TestWebKitAPI::Util::run(&done);
166 EXPECT_WK_STREQ("Hello world", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
169 TEST(ActionSheetTests, DataDetectorsLinkIsNotPresentedAsALink)
171 IPadUserInterfaceSwizzler iPadUserInterface;
173 auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)]);
174 auto observer = adoptNS([[ActionSheetObserver alloc] init]);
175 [webView setUIDelegate:observer.get()];
177 auto runTest = ^(NSString *phoneNumber) {
178 [webView synchronouslyLoadHTMLString:[NSString stringWithFormat:@"<a style='position: absolute; top: 0; left: 0;' href='tel:%@'>telephone number</a>", phoneNumber]];
180 __block bool done = false;
181 __block bool succeeded = true;
183 // We shouldn't present a normal action sheet, but instead a data detectors sheet.
184 [observer setDataDetectionContextHandler:^{
188 [observer setPresentationHandler:^(_WKActivatedElementInfo *, NSArray *) {
193 [webView _simulateLongPressActionAtLocation:CGPointMake(5, 5)];
194 TestWebKitAPI::Util::run(&done);
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"));
211 #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
213 static void presentActionSheetAndChooseAction(WKWebView *webView, ActionSheetObserver *observer, CGPoint location, _WKElementActionType actionType)
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)
225 return @[ copyAction.get() ];
227 [webView _simulateLongPressActionAtLocation:location];
228 TestWebKitAPI::Util::run(&done);
230 EXPECT_TRUE(!!copyAction);
231 EXPECT_TRUE(!!copyElement);
232 [copyAction runActionWithElementInfo:copyElement.get()];
235 TEST(ActionSheetTests, CopyImageElementWithHREFAndTitle)
237 UIApplicationInitialize();
238 [UIPasteboard generalPasteboard].items = @[ ];
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')"];
246 presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 50), _WKElementActionTypeCopy);
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);
263 TestWebKitAPI::Util::run(&done);
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);
273 TestWebKitAPI::Util::run(&doneLoading);
276 TEST(ActionSheetTests, CopyImageElementWithHREF)
278 UIApplicationInitialize();
279 [UIPasteboard generalPasteboard].items = @[ ];
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"];
286 presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 50), _WKElementActionTypeCopy);
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);
303 TestWebKitAPI::Util::run(&done);
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);
313 TestWebKitAPI::Util::run(&doneLoading);
316 TEST(ActionSheetTests, CopyImageElementWithoutHREF)
318 UIApplicationInitialize();
319 [UIPasteboard generalPasteboard].items = @[ ];
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"];
326 presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 100), _WKElementActionTypeCopy);
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);
340 TestWebKitAPI::Util::run(&done);
343 TEST(ActionSheetTests, CopyLinkWritesURLAndPlainText)
345 UIApplicationInitialize();
346 [UIPasteboard generalPasteboard].items = @[ ];
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"];
353 presentActionSheetAndChooseAction(webView.get(), observer.get(), CGPointMake(100, 100), _WKElementActionTypeCopy);
355 [webView synchronouslyLoadTestPageNamed:@"DataTransfer"];
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"]);
367 #endif // !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
369 } // namespace TestWebKitAPI
371 #endif // PLATFORM(IOS_FAMILY)