9876c86883d69bdb86dc2f951bbed26831301db6
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / ContextMenus.mm
1 /*
2  * Copyright (C) 2019 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
28 #if PLATFORM(IOS) && USE(UICONTEXTMENU)
29
30 #import "TestContextMenuDriver.h"
31 #import "TestWKWebView.h"
32 #import "TestWKWebViewController.h"
33 #import "Utilities.h"
34 #import <WebKit/WKWebViewConfigurationPrivate.h>
35 #import <WebKit/WebKit.h>
36
37 static bool contextMenuRequested;
38 static bool contextMenuSPIRequested;
39 static bool willPresentCalled;
40 static bool willCommitCalled;
41 static bool previewingViewControllerCalled;
42 static bool previewActionItemsCalled;
43 static bool didEndCalled;
44 static bool alternateURLRequested;
45 static RetainPtr<NSURL> linkURL;
46
47 static RetainPtr<TestContextMenuDriver> contextMenuWebViewDriver(Class delegateClass, NSString *customHTMLString = nil)
48 {
49     static auto window = adoptNS([[UIWindow alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
50     static auto driver = adoptNS([TestContextMenuDriver new]);
51     static auto uiDelegate = adoptNS((NSObject<WKUIDelegate> *)[delegateClass new]);
52     static auto configuration = adoptNS([WKWebViewConfiguration new]);
53     [configuration _setClickInteractionDriverForTesting:driver.get()];
54     static auto webViewController = adoptNS([[TestWKWebViewController alloc] initWithFrame:CGRectMake(0, 0, 200, 200) configuration:configuration.get()]);
55     TestWKWebView *webView = [webViewController webView];
56     [window addSubview:webView];
57     [webView setUIDelegate:uiDelegate.get()];
58     if (!customHTMLString) {
59         linkURL = [NSURL URLWithString:@"http://127.0.0.1/"];
60         [webView synchronouslyLoadHTMLString:[NSString stringWithFormat:@"<a href='%@'>This is a link</a>", linkURL.get()]];
61     } else
62         [webView synchronouslyLoadHTMLString:customHTMLString];
63     return driver;
64 }
65
66 @interface TestContextMenuUIDelegate : NSObject <WKUIDelegate>
67 @end
68
69 @implementation TestContextMenuUIDelegate
70
71 - (void)webView:(WKWebView *)webView contextMenuConfigurationForElement:(WKContextMenuElementInfo *)elementInfo completionHandler:(void(^)(UIContextMenuConfiguration * _Nullable))completionHandler
72 {
73     EXPECT_TRUE([elementInfo.linkURL.absoluteString isEqualToString:[linkURL absoluteString]]);
74     contextMenuRequested = true;
75     UIContextMenuContentPreviewProvider previewProvider = ^UIViewController * ()
76     {
77         return [[[UIViewController alloc] init] autorelease];
78     };
79     UIContextMenuActionProvider actionProvider = ^UIMenu *(NSArray<UIMenuElement *> *suggestedActions)
80     {
81         return [UIMenu menuWithTitle:@"" children:suggestedActions];
82     };
83     completionHandler([UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:previewProvider actionProvider:actionProvider]);
84 }
85
86 - (void)webView:(WKWebView *)webView contextMenuWillPresentForElement:(WKContextMenuElementInfo *)elementInfo
87 {
88     willPresentCalled = true;
89 }
90
91 - (void)webView:(WKWebView *)webView contextMenuForElement:(WKContextMenuElementInfo *)elementInfo willCommitWithAnimator:(id<UIContextMenuInteractionCommitAnimating>)animator
92 {
93     willCommitCalled = true;
94 }
95
96 - (void)webView:(WKWebView *)webView contextMenuDidEndForElement:(WKContextMenuElementInfo *)elementInfo
97 {
98     didEndCalled = true;
99 }
100
101 @end
102
103 TEST(ContextMenu, Click)
104 {
105     auto driver = contextMenuWebViewDriver([TestContextMenuUIDelegate class]);
106     [driver begin:^(BOOL result) {
107         EXPECT_TRUE(result);
108         [driver clickDown];
109         [driver clickUp];
110     }];
111     TestWebKitAPI::Util::run(&willPresentCalled);
112     EXPECT_TRUE(contextMenuRequested);
113     EXPECT_TRUE(willPresentCalled);
114     EXPECT_FALSE(willCommitCalled);
115     EXPECT_FALSE(didEndCalled);
116 }
117
118 TEST(ContextMenu, End)
119 {
120     auto driver = contextMenuWebViewDriver([TestContextMenuUIDelegate class]);
121     [driver begin:^(BOOL result) {
122         EXPECT_TRUE(result);
123         [driver end];
124     }];
125     TestWebKitAPI::Util::run(&didEndCalled);
126     EXPECT_TRUE(contextMenuRequested);
127     EXPECT_FALSE(willPresentCalled);
128     EXPECT_FALSE(willCommitCalled);
129     EXPECT_TRUE(didEndCalled);
130 }
131
132 @interface TestContextMenuAPIBeforeSPIUIDelegate : NSObject <WKUIDelegate>
133 @end
134
135 @implementation TestContextMenuAPIBeforeSPIUIDelegate
136
137 - (void)webView:(WKWebView *)webView contextMenuConfigurationForElement:(WKContextMenuElementInfo *)elementInfo completionHandler:(void(^)(UIContextMenuConfiguration * _Nullable))completionHandler
138 {
139     contextMenuRequested = true;
140     UIContextMenuContentPreviewProvider previewProvider = ^UIViewController * ()
141     {
142         return [[[UIViewController alloc] init] autorelease];
143     };
144     UIContextMenuActionProvider actionProvider = ^UIMenu *(NSArray<UIMenuElement *> *suggestedActions)
145     {
146         return [UIMenu menuWithTitle:@"" children:suggestedActions];
147     };
148     completionHandler([UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:previewProvider actionProvider:actionProvider]);
149 }
150
151 - (void)_webView:(WKWebView *)webView contextMenuConfigurationForElement:(WKContextMenuElementInfo *)elementInfo completionHandler:(void(^)(UIContextMenuConfiguration * _Nullable))completionHandler
152 {
153     contextMenuSPIRequested = true;
154     completionHandler(nil);
155 }
156
157 - (void)webView:(WKWebView *)webView contextMenuWillPresentForElement:(WKContextMenuElementInfo *)elementInfo
158 {
159     willPresentCalled = true;
160 }
161
162 @end
163
164 TEST(ContextMenu, APIBeforeSPI)
165 {
166     auto driver = contextMenuWebViewDriver([TestContextMenuAPIBeforeSPIUIDelegate class]);
167     [driver begin:^(BOOL result) {
168         EXPECT_TRUE(result);
169         [driver clickDown];
170         [driver clickUp];
171     }];
172     TestWebKitAPI::Util::run(&willPresentCalled);
173     EXPECT_TRUE(contextMenuRequested);
174     EXPECT_FALSE(contextMenuSPIRequested);
175 }
176
177 @interface TestContextMenuImageUIDelegate : NSObject <WKUIDelegate>
178 @end
179
180 @implementation TestContextMenuImageUIDelegate
181
182 - (void)_webView:(WKWebView *)webView contextMenuConfigurationForElement:(WKContextMenuElementInfo *)elementInfo completionHandler:(void(^)(UIContextMenuConfiguration * _Nullable))completionHandler
183 {
184     contextMenuRequested = true;
185     completionHandler(nil);
186 }
187
188 - (NSURL *)_webView:(WKWebView *)webView alternateURLFromImage:(UIImage *)image userInfo:(NSDictionary **)userInfo
189 {
190     alternateURLRequested = true;
191     return linkURL.get();
192 }
193
194 - (void)webView:(WKWebView *)webView contextMenuWillPresentForElement:(WKContextMenuElementInfo *)elementInfo
195 {
196     willPresentCalled = true;
197     EXPECT_TRUE([elementInfo.linkURL.absoluteString isEqualToString:[linkURL absoluteString]]);
198 }
199
200 - (void)webView:(WKWebView *)webView contextMenuForElement:(WKContextMenuElementInfo *)elementInfo willCommitWithAnimator:(id<UIContextMenuInteractionCommitAnimating>)animator
201 {
202     willCommitCalled = true;
203 }
204
205 - (void)webView:(WKWebView *)webView contextMenuDidEndForElement:(WKContextMenuElementInfo *)elementInfo
206 {
207     didEndCalled = true;
208 }
209
210 @end
211
212 TEST(ContextMenu, Image)
213 {
214     linkURL = [NSURL URLWithString:@"http://127.0.0.1/image"];
215     auto driver = contextMenuWebViewDriver([TestContextMenuImageUIDelegate class], @"<img style='width:400px;height:400px' src='data:image/png;base64,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'>");
216     [driver begin:^(BOOL result) {
217         EXPECT_TRUE(result);
218         [driver clickDown];
219         [driver clickUp];
220     }];
221     TestWebKitAPI::Util::run(&willPresentCalled);
222     EXPECT_TRUE(contextMenuRequested);
223     EXPECT_TRUE(alternateURLRequested);
224     EXPECT_TRUE(willPresentCalled);
225     EXPECT_FALSE(willCommitCalled);
226     EXPECT_FALSE(didEndCalled);
227 }
228
229 #pragma clang diagnostic push
230 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
231 #pragma clang diagnostic ignored "-Wdeprecated-implementations"
232
233 @interface LegacyPreviewViewController : UIViewController
234 @end
235
236 @implementation LegacyPreviewViewController
237
238 - (NSArray<UIPreviewAction *> *)previewActionItems
239 {
240     previewActionItemsCalled = true;
241     return @[
242         [UIPreviewAction actionWithTitle:@"Action" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction *, UIViewController *) { }]
243     ];
244 }
245
246 @end
247
248 @interface LegacyContextMenuUIDelegate : NSObject <WKUIDelegate>
249 @end
250
251 @implementation LegacyContextMenuUIDelegate
252
253 - (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo
254 {
255     EXPECT_TRUE([elementInfo.linkURL.absoluteString isEqualToString:[linkURL absoluteString]]);
256     contextMenuRequested = true;
257     return YES;
258 }
259
260 - (UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions
261 {
262     EXPECT_TRUE(previewActions.count);
263     previewingViewControllerCalled = true;
264     return [LegacyPreviewViewController new];
265 }
266
267 /* Even though this is non-legacy API, it should not be enough to trigger the non-legacy flow. */
268 - (void)webView:(WKWebView *)webView contextMenuWillPresentForElement:(WKContextMenuElementInfo *)elementInfo
269 {
270     willPresentCalled = true;
271 }
272
273 /* Even though this is non-legacy API, it should not be enough to trigger the non-legacy flow. */
274 - (void)_webView:(WKWebView *)webView contextMenuDidEndForElement:(WKContextMenuElementInfo *)elementInfo
275 {
276 }
277
278 @end
279
280 TEST(ContextMenu, Legacy)
281 {
282     auto driver = contextMenuWebViewDriver([LegacyContextMenuUIDelegate class]);
283     [driver begin:^(BOOL result) {
284         EXPECT_TRUE(result);
285         [driver clickDown];
286         [driver clickUp];
287     }];
288     TestWebKitAPI::Util::run(&previewActionItemsCalled);
289     EXPECT_TRUE(contextMenuRequested);
290     EXPECT_TRUE(previewingViewControllerCalled);
291     EXPECT_TRUE(willPresentCalled);
292 }
293
294 #pragma clang diagnostic pop
295
296 @interface TestContextMenuSuggestedActionsUIDelegate : NSObject <WKUIDelegate>
297 @end
298
299 @implementation TestContextMenuSuggestedActionsUIDelegate
300
301 - (void)webView:(WKWebView *)webView contextMenuConfigurationForElement:(WKContextMenuElementInfo *)elementInfo completionHandler:(void(^)(UIContextMenuConfiguration * _Nullable))completionHandler
302 {
303     EXPECT_TRUE([elementInfo.linkURL.absoluteString isEqualToString:[linkURL absoluteString]]);
304     contextMenuRequested = true;
305     UIContextMenuContentPreviewProvider previewProvider = ^UIViewController * ()
306     {
307         return [[[UIViewController alloc] init] autorelease];
308     };
309     UIContextMenuActionProvider actionProvider = ^UIMenu *(NSArray<UIMenuElement *> *suggestedActions)
310     {
311         NSArray<NSString *> *expectedIdentifiers = @[
312             @"WKElementActionTypeOpen",
313             @"WKElementActionTypeAddToReadingList",
314             @"WKElementActionTypeCopy",
315             @"WKElementActionTypeShare"
316         ];
317         EXPECT_TRUE(expectedIdentifiers.count == suggestedActions.count);
318
319         [suggestedActions enumerateObjectsUsingBlock:^(UIMenuElement *menuElement, NSUInteger index, BOOL *) {
320             EXPECT_TRUE([menuElement isKindOfClass:[UIAction class]]);
321             EXPECT_TRUE([[(UIAction *)menuElement identifier] isEqualToString:expectedIdentifiers[index]]);
322         }];
323         return [UIMenu menuWithTitle:@"" children:suggestedActions];
324     };
325     completionHandler([UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:previewProvider actionProvider:actionProvider]);
326 }
327
328 - (void)webView:(WKWebView *)webView contextMenuWillPresentForElement:(WKContextMenuElementInfo *)elementInfo
329 {
330     willPresentCalled = true;
331 }
332
333 @end
334
335 TEST(ContextMenu, SuggestedActions)
336 {
337     auto driver = contextMenuWebViewDriver([TestContextMenuSuggestedActionsUIDelegate class]);
338     [driver begin:^(BOOL result) {
339         EXPECT_TRUE(result);
340         [driver clickDown];
341         [driver clickUp];
342     }];
343     TestWebKitAPI::Util::run(&willPresentCalled);
344     EXPECT_TRUE(contextMenuRequested);
345     EXPECT_TRUE(willPresentCalled);
346 }
347
348 #endif // PLATFORM(IOS) && USE(UICONTEXTMENU)