Adjust naming of action menu SPI
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKit2ObjC / ActionMenus.mm
1 /*
2  * Copyright (C) 2014 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(MAC)
29
30 #import "Test.h"
31
32 #import "PlatformUtilities.h"
33 #import "PlatformWebView.h"
34 #import "TestBrowsingContextLoadDelegate.h"
35 #import <JavaScriptCore/JSContextRef.h>
36 #import <JavaScriptCore/JSRetainPtr.h>
37 #import <WebKit/WebKit2.h>
38 #import <WebKit/WKActionMenuItemTypes.h>
39 #import <WebKit/WKActionMenuTypes.h>
40 #import <WebKit/WKPreferencesPrivate.h>
41 #import <WebKit/WKSerializedScriptValue.h>
42 #import <WebKit/WKViewPrivate.h>
43 #import <wtf/RetainPtr.h>
44
45 static bool didFinishLoad = false;
46 static bool didFinishDownload = false;
47
48 @interface WKView (Details)
49
50 - (void)prepareForMenu:(NSMenu *)menu withEvent:(NSEvent *)event;
51 - (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event;
52 - (void)didCloseMenu:(NSMenu *)menu withEvent:(NSEvent *)event;
53
54 - (NSMenu *)_actionMenu;
55 - (void)copy:(id)sender;
56
57 @end
58
59 struct ActionMenuResult {
60     WKRetainPtr<WKHitTestResultRef> hitTestResult;
61     _WKActionMenuType type;
62     RetainPtr<NSArray> defaultMenuItems;
63     WKRetainPtr<WKTypeRef> userData;
64 };
65
66 @interface ActionMenusTestWKView : WKView {
67     ActionMenuResult _actionMenuResult;
68     RetainPtr<NSArray> _overrideItems;
69     BOOL _shouldHaveUserData;
70 }
71
72 @property (nonatomic, assign, setter=_setActionMenuResult:) ActionMenuResult _actionMenuResult;
73
74 @end
75
76 @implementation ActionMenusTestWKView
77
78 @synthesize _actionMenuResult=_actionMenuResult;
79
80 - (NSArray *)_actionMenuItemsForHitTestResult:(WKHitTestResultRef)hitTestResult withType:(_WKActionMenuType)type defaultActionMenuItems:(NSArray *)defaultMenuItems userData:(WKTypeRef)userData
81 {
82     if (type != kWKActionMenuNone)
83         EXPECT_GT(defaultMenuItems.count, (NSUInteger)0);
84
85     // Clients should be able to pass userData from the Web to UI process, between
86     // the WKBundlePageContextMenuClient's prepareForActionMenu, and here.
87     // http://trac.webkit.org/changeset/175444
88     if (_shouldHaveUserData) {
89         EXPECT_NOT_NULL(userData);
90         EXPECT_EQ(WKDictionaryGetTypeID(), WKGetTypeID(userData));
91         WKRetainPtr<WKStringRef> hasLinkKey = adoptWK(WKStringCreateWithUTF8CString("hasLinkURL"));
92         WKTypeRef hasLinkValue = WKDictionaryGetItemForKey((WKDictionaryRef)userData, hasLinkKey.get());
93         EXPECT_NOT_NULL(hasLinkValue);
94         EXPECT_EQ(WKBooleanGetTypeID(), WKGetTypeID(hasLinkValue));
95         WKRetainPtr<WKURLRef> absoluteLinkURL = adoptWK(WKHitTestResultCopyAbsoluteLinkURL(hitTestResult));
96         EXPECT_EQ(!!absoluteLinkURL, WKBooleanGetValue((WKBooleanRef)hasLinkValue));
97     } else
98         EXPECT_NULL(userData);
99
100     _actionMenuResult.hitTestResult = hitTestResult;
101     _actionMenuResult.type = type;
102     _actionMenuResult.defaultMenuItems = defaultMenuItems;
103     _actionMenuResult.userData = userData;
104     return _overrideItems ? _overrideItems.get() : defaultMenuItems;
105 }
106
107 - (void)runMenuSequenceAtPoint:(NSPoint)point preDidCloseMenuHandler:(void(^)(void))preDidCloseMenuHandler
108 {
109     __block bool didFinishSequence = false;
110
111     NSMenu *actionMenu = self._actionMenu;
112     RetainPtr<NSEvent> event = [NSEvent mouseEventWithType:NSLeftMouseDown location:point modifierFlags:0 timestamp:0 windowNumber:self.window.windowNumber context:0 eventNumber:0 clickCount:0 pressure:0];
113
114     dispatch_async(dispatch_get_main_queue(), ^{
115         [self prepareForMenu:actionMenu withEvent:event.get()];
116     });
117
118     dispatch_async(dispatch_get_main_queue(), ^{
119         _shouldHaveUserData = YES;
120         [[actionMenu delegate] menuNeedsUpdate:actionMenu];
121         _shouldHaveUserData = NO;
122     });
123
124     dispatch_async(dispatch_get_main_queue(), ^{
125         [self willOpenMenu:actionMenu withEvent:event.get()];
126     });
127
128     void (^copiedPreDidCloseMenuHandler)() = Block_copy(preDidCloseMenuHandler);
129     dispatch_async(dispatch_get_main_queue(), ^{
130         copiedPreDidCloseMenuHandler();
131         Block_release(copiedPreDidCloseMenuHandler);
132         [self didCloseMenu:actionMenu withEvent:event.get()];
133         [self mouseDown:event.get()];
134         didFinishSequence = true;
135     });
136
137     TestWebKitAPI::Util::run(&didFinishSequence);
138 }
139
140 - (void)_setOverrideActionMenuItems:(NSArray *)overrideItems
141 {
142     _overrideItems = overrideItems;
143 }
144
145 @end
146
147 namespace TestWebKitAPI {
148
149 struct ActiveDownloadContext {
150     WKRetainPtr<WKStringRef> path;
151     bool shouldCheckForImage = 0;
152 };
153
154 static void didFinishLoadForFrameCallback(WKPageRef page, WKFrameRef frame, WKTypeRef userData, const void* clientInfo)
155 {
156     didFinishLoad = true;
157 }
158
159 static void didFinishDownloadCallback(WKContextRef context, WKDownloadRef download, const void *clientInfo)
160 {
161     WKStringRef wkActiveDownloadPath = ((ActiveDownloadContext*)clientInfo)->path.get();
162     size_t length = WKStringGetLength(wkActiveDownloadPath) + 1;
163     char *activeDownloadPath = (char *)calloc(1, length);
164     WKStringGetUTF8CString(wkActiveDownloadPath, activeDownloadPath, length);
165
166     if (((ActiveDownloadContext*)clientInfo)->shouldCheckForImage) {
167         RetainPtr<NSImage> image = adoptNS([[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:activeDownloadPath]]);
168
169         EXPECT_EQ(215, [image size].width);
170         EXPECT_EQ(174, [image size].height);
171     }
172
173     didFinishDownload = true;
174 }
175
176 static void didCreateDownloadDestinationCallback(WKContextRef context, WKDownloadRef download, WKStringRef path, const void *clientInfo)
177 {
178     ((ActiveDownloadContext*)clientInfo)->path = path;
179 }
180
181 static NSString *watchPasteboardForString()
182 {
183     [[NSPasteboard generalPasteboard] clearContents];
184
185     while (true) {
186         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
187         NSString *pasteboardString = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString];
188         if (pasteboardString)
189             return pasteboardString;
190     }
191 }
192
193 static NSImage *watchPasteboardForImage()
194 {
195     [[NSPasteboard generalPasteboard] clearContents];
196
197     while (true) {
198         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
199         NSArray *pasteboardItems = [[NSPasteboard generalPasteboard] readObjectsForClasses:@[ [NSImage class] ] options:nil];
200         if (pasteboardItems && pasteboardItems.count)
201             return pasteboardItems.lastObject;
202     }
203 }
204
205 struct JavaScriptStringCallbackContext {
206     JavaScriptStringCallbackContext()
207         : didFinish(false)
208     {
209     }
210
211     bool didFinish;
212     JSRetainPtr<JSStringRef> actualString;
213 };
214
215 struct JavaScriptBoolCallbackContext {
216     JavaScriptBoolCallbackContext()
217         : didFinish(false)
218     {
219     }
220
221     bool didFinish;
222     bool value;
223 };
224
225 static void javaScriptStringCallback(WKSerializedScriptValueRef resultSerializedScriptValue, WKErrorRef error, void* ctx)
226 {
227     EXPECT_NOT_NULL(resultSerializedScriptValue);
228
229     JavaScriptStringCallbackContext* context = static_cast<JavaScriptStringCallbackContext*>(ctx);
230
231     JSGlobalContextRef scriptContext = JSGlobalContextCreate(0);
232     EXPECT_NOT_NULL(scriptContext);
233
234     JSValueRef scriptValue = WKSerializedScriptValueDeserialize(resultSerializedScriptValue, scriptContext, 0);
235     EXPECT_NOT_NULL(scriptValue);
236
237     context->actualString.adopt(JSValueToStringCopy(scriptContext, scriptValue, 0));
238     EXPECT_NOT_NULL(context->actualString.get());
239
240     context->didFinish = true;
241     
242     JSGlobalContextRelease(scriptContext);
243     
244     EXPECT_NULL(error);
245 }
246
247 static void javaScriptBoolCallback(WKSerializedScriptValueRef resultSerializedScriptValue, WKErrorRef error, void* ctx)
248 {
249     EXPECT_NOT_NULL(resultSerializedScriptValue);
250
251     JavaScriptBoolCallbackContext* context = static_cast<JavaScriptBoolCallbackContext*>(ctx);
252
253     JSGlobalContextRef scriptContext = JSGlobalContextCreate(0);
254     EXPECT_NOT_NULL(scriptContext);
255
256     JSValueRef scriptValue = WKSerializedScriptValueDeserialize(resultSerializedScriptValue, scriptContext, 0);
257     EXPECT_NOT_NULL(scriptValue);
258
259     EXPECT_TRUE(JSValueIsBoolean(scriptContext, scriptValue));
260
261     context->value = JSValueToBoolean(scriptContext, scriptValue);
262     context->didFinish = true;
263     
264     JSGlobalContextRelease(scriptContext);
265     
266     EXPECT_NULL(error);
267 }
268
269 static std::unique_ptr<char[]> callJavaScriptReturningString(WKPageRef page, const char* js)
270 {
271     JavaScriptStringCallbackContext context;
272     WKPageRunJavaScriptInMainFrame(page, Util::toWK(js).get(), &context, javaScriptStringCallback);
273     Util::run(&context.didFinish);
274
275     size_t bufferSize = JSStringGetMaximumUTF8CStringSize(context.actualString.get());
276     auto buffer = std::make_unique<char[]>(bufferSize);
277     JSStringGetUTF8CString(context.actualString.get(), buffer.get(), bufferSize);
278     return buffer;
279 }
280
281 static bool callJavaScriptReturningBool(WKPageRef page, const char* js)
282 {
283     JavaScriptBoolCallbackContext context;
284     WKPageRunJavaScriptInMainFrame(page, Util::toWK(js).get(), &context, javaScriptBoolCallback);
285     Util::run(&context.didFinish);
286     return context.value;
287 }
288
289 static void watchEditableAreaForString(WKPageRef page, const char *areaName, const char *watchString)
290 {
291     while (true) {
292         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
293         auto buffer = callJavaScriptReturningString(page, [[NSString stringWithFormat:@"editableAreaString('%s')", areaName] UTF8String]);
294
295         if (!strcmp(buffer.get(), watchString))
296             return;
297     }
298 }
299
300 static void waitForVideoReady(WKPageRef page)
301 {
302     while (true) {
303         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
304         if (callJavaScriptReturningBool(page, "isVideoReady()"))
305             return;
306     }
307 }
308
309 static NSString *retrieveSelection(WKPageRef page)
310 {
311     auto buffer = callJavaScriptReturningString(page, "stringifySelection()");
312     return [NSString stringWithUTF8String:buffer.get()];
313 }
314
315 static NSString *retrieveSelectionInElement(WKPageRef page, const char *areaName)
316 {
317     auto buffer = callJavaScriptReturningString(page, [[NSString stringWithFormat:@"stringifySelectionInElement('%s')", areaName] UTF8String]);
318     return [NSString stringWithUTF8String:buffer.get()];
319 }
320
321 static void performMenuItemAtIndexOfTypeAsync(NSMenu *menu, NSInteger index, int type)
322 {
323     EXPECT_LT(index, menu.numberOfItems);
324     if (index >= menu.numberOfItems)
325         return;
326     NSMenuItem *menuItem = [menu itemAtIndex:index];
327     EXPECT_NOT_NULL(menuItem);
328     EXPECT_EQ(type, [menuItem tag]);
329     EXPECT_TRUE([menuItem isEnabled]);
330     [menuItem.target performSelector:menuItem.action withObject:menuItem afterDelay:0];
331 }
332
333 static void ensureMenuItemAtIndexOfTypeIsDisabled(NSMenu *menu, NSInteger index, int type)
334 {
335     EXPECT_LT(index, menu.numberOfItems);
336     if (index >= menu.numberOfItems)
337         return;
338     NSMenuItem *menuItem = [menu itemAtIndex:index];
339     EXPECT_NOT_NULL(menuItem);
340     EXPECT_EQ(type, [menuItem tag]);
341     EXPECT_FALSE([menuItem isEnabled]);
342 }
343
344 enum class TargetType {
345     Word,
346     Phrase,
347     Address,
348     Date,
349     PhoneNumber,
350     ContentEditableWords,
351     ContentEditablePhrase,
352     TextInputWords,
353     TextInputPhrase,
354     TextAreaWords,
355     TextAreaPhrase,
356     Image,
357     ImageAsLink,
358     HTTPLink,
359     FTPLink,
360     MailtoLink,
361     JavaScriptLink,
362     PageOverlay,
363     PageWhitespace,
364     Video,
365     MSEVideo,
366 };
367
368 static NSPoint windowPointForTarget(TargetType target)
369 {
370     NSPoint contentPoint;
371     switch (target) {
372     case TargetType::Word:
373         contentPoint = NSMakePoint(0, 0);
374         break;
375     case TargetType::Phrase:
376         contentPoint = NSMakePoint(200, 0);
377         break;
378     case TargetType::Address:
379         contentPoint = NSMakePoint(0, 50);
380         break;
381     case TargetType::Date:
382         contentPoint = NSMakePoint(200, 50);
383         break;
384     case TargetType::PhoneNumber:
385         contentPoint = NSMakePoint(400, 50);
386         break;
387     case TargetType::ContentEditableWords:
388         contentPoint = NSMakePoint(0, 150);
389         break;
390     case TargetType::ContentEditablePhrase:
391         contentPoint = NSMakePoint(0, 200);
392         break;
393     case TargetType::TextInputWords:
394         contentPoint = NSMakePoint(200, 150);
395         break;
396     case TargetType::TextInputPhrase:
397         contentPoint = NSMakePoint(200, 200);
398         break;
399     case TargetType::TextAreaWords:
400         contentPoint = NSMakePoint(400, 150);
401         break;
402     case TargetType::TextAreaPhrase:
403         contentPoint = NSMakePoint(400, 200);
404         break;
405     case TargetType::Image:
406         contentPoint = NSMakePoint(0, 250);
407         break;
408     case TargetType::ImageAsLink:
409         contentPoint = NSMakePoint(200, 250);
410         break;
411     case TargetType::HTTPLink:
412         contentPoint = NSMakePoint(0, 300);
413         break;
414     case TargetType::FTPLink:
415         contentPoint = NSMakePoint(100, 300);
416         break;
417     case TargetType::MailtoLink:
418         contentPoint = NSMakePoint(200, 300);
419         break;
420     case TargetType::JavaScriptLink:
421         contentPoint = NSMakePoint(300, 300);
422         break;
423     case TargetType::PageOverlay:
424         contentPoint = NSMakePoint(750, 100);
425         break;
426     case TargetType::PageWhitespace:
427         contentPoint = NSMakePoint(650, 0);
428         break;
429     case TargetType::Video:
430         contentPoint = NSMakePoint(0, 350);
431         break;
432     case TargetType::MSEVideo:
433         contentPoint = NSMakePoint(200, 350);
434         break;
435     }
436
437     return NSMakePoint(contentPoint.x + 8, 600 - contentPoint.y - 8);
438 }
439
440 // FIXME: Ideally, each of these would be able to run as its own subtest in a suite, sharing a WKView (for performance reasons),
441 // but we cannot because run-api-tests explicitly runs each test in a separate process. So, we use a single test for many tests instead.
442 // FIXME: Temporarily disabled.
443 TEST(WebKit2, DISABLED_ActionMenusTest)
444 {
445     WKRetainPtr<WKContextRef> context = adoptWK(Util::createContextForInjectedBundleTest("ActionMenusTest"));
446
447     WKRetainPtr<WKPageGroupRef> pageGroup = adoptWK(WKPageGroupCreateWithIdentifier(Util::toWK("ActionMenusTestGroup").get()));
448     WKPreferencesRef preferences = WKPageGroupGetPreferences(pageGroup.get());
449     WKPreferencesSetMediaSourceEnabled(preferences, true);
450     WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
451
452     PlatformWebView platformWebView(context.get(), pageGroup.get(), [ActionMenusTestWKView class]);
453     RetainPtr<ActionMenusTestWKView> wkView = (ActionMenusTestWKView *)platformWebView.platformView();
454
455     if (![wkView respondsToSelector:@selector(setActionMenu:)])
456         return;
457
458     WKPageLoaderClientV0 loaderClient;
459     memset(&loaderClient, 0, sizeof(loaderClient));
460     loaderClient.base.version = 0;
461     loaderClient.didFinishLoadForFrame = didFinishLoadForFrameCallback;
462     WKPageSetPageLoaderClient([wkView pageRef], &loaderClient.base);
463
464     ActiveDownloadContext activeDownloadContext;
465
466     WKContextDownloadClientV0 downloadClient;
467     memset(&downloadClient, 0, sizeof(downloadClient));
468     downloadClient.base.version = 0;
469     downloadClient.base.clientInfo = &activeDownloadContext;
470     downloadClient.didFinish = didFinishDownloadCallback;
471     downloadClient.didCreateDestination = didCreateDownloadDestinationCallback;
472     WKContextSetDownloadClient(context.get(), &downloadClient.base);
473
474     WKRetainPtr<WKURLRef> url(AdoptWK, Util::createURLForResource("action-menu-targets", "html"));
475     WKPageLoadURL([wkView pageRef], url.get());
476
477     Util::run(&didFinishLoad);
478
479     // Read-only text.
480     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Word) preDidCloseMenuHandler:^() {
481         EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
482         EXPECT_WK_STREQ(@"word", retrieveSelection([wkView pageRef]));
483         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
484         EXPECT_WK_STREQ(@"word", watchPasteboardForString());
485     }];
486
487     // Read-only text, on a phrase.
488     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Phrase) preDidCloseMenuHandler:^() {
489         EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
490         EXPECT_WK_STREQ(@"New York", retrieveSelection([wkView pageRef]));
491         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
492         EXPECT_WK_STREQ(@"New York", watchPasteboardForString());
493     }];
494
495     // Read-only text, on an address.
496     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Address) preDidCloseMenuHandler:^() {
497         EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
498
499         // Addresses don't get selected, because they immediately show a TextIndicator.
500         EXPECT_WK_STREQ(@"<no selection>", retrieveSelection([wkView pageRef]));
501     }];
502
503     // Read-only text, on a date.
504     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Date) preDidCloseMenuHandler:^() {
505         EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
506
507         // Dates don't get selected, because they immediately show a TextIndicator.
508         EXPECT_WK_STREQ(@"<no selection>", retrieveSelection([wkView pageRef]));
509     }];
510
511     // Read-only text, on a phone number.
512     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::PhoneNumber) preDidCloseMenuHandler:^() {
513         EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
514         EXPECT_WK_STREQ(@"(408) 996-1010", retrieveSelection([wkView pageRef]));
515     }];
516
517     // Copy from a contentEditable div.
518     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditableWords) preDidCloseMenuHandler:^() {
519         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
520         EXPECT_WK_STREQ(@"editable", retrieveSelection([wkView pageRef]));
521         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
522         EXPECT_WK_STREQ(@"editable", watchPasteboardForString());
523     }];
524
525     // Copy a phrase from a contentEditable div.
526     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditablePhrase) preDidCloseMenuHandler:^() {
527         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
528         EXPECT_WK_STREQ(@"New York", retrieveSelection([wkView pageRef]));
529         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
530         EXPECT_WK_STREQ(@"New York", watchPasteboardForString());
531     }];
532
533     // Paste on top of the text in the contentEditable div.
534     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditableWords) preDidCloseMenuHandler:^() {
535         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
536         [[NSPasteboard generalPasteboard] clearContents];
537         [[NSPasteboard generalPasteboard] setString:@"pasted string" forType:NSPasteboardTypeString];
538         EXPECT_WK_STREQ(@"editable", retrieveSelection([wkView pageRef]));
539         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagPaste);
540
541         // Now check and see if our paste succeeded. It should only replace one 'editable'.
542         watchEditableAreaForString([wkView pageRef], "editable1", "pasted string editable editable editable");
543     }];
544
545     // Paste on top of a phrase in the contentEditable div.
546     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditablePhrase) preDidCloseMenuHandler:^() {
547         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
548         [[NSPasteboard generalPasteboard] clearContents];
549         [[NSPasteboard generalPasteboard] setString:@"pasted over phrase" forType:NSPasteboardTypeString];
550         EXPECT_WK_STREQ(@"New York", retrieveSelection([wkView pageRef]));
551         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagPaste);
552
553         // Now check and see if our paste succeeded, and replaced the whole phrase.
554         watchEditableAreaForString([wkView pageRef], "editable2", "pasted over phrase some words");
555     }];
556
557     // Copy from an <input>.
558     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputWords) preDidCloseMenuHandler:^() {
559         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
560         EXPECT_WK_STREQ(@"editable", retrieveSelectionInElement([wkView pageRef], "input1"));
561         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
562         EXPECT_WK_STREQ(@"editable", watchPasteboardForString());
563     }];
564
565     // Copy a phrase from an <input>.
566     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputPhrase) preDidCloseMenuHandler:^() {
567         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
568         EXPECT_WK_STREQ(@"New York", retrieveSelectionInElement([wkView pageRef], "input2"));
569         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
570         EXPECT_WK_STREQ(@"New York", watchPasteboardForString());
571     }];
572
573     // Paste on top of the editable text in an <input>.
574     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputWords) preDidCloseMenuHandler:^() {
575         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
576         [[NSPasteboard generalPasteboard] clearContents];
577         [[NSPasteboard generalPasteboard] setString:@"pasted string" forType:NSPasteboardTypeString];
578         EXPECT_WK_STREQ(@"editable", retrieveSelectionInElement([wkView pageRef], "input1"));
579         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagPaste);
580
581         // Now check and see if our paste succeeded. It should only replace one 'editable'.
582         watchEditableAreaForString([wkView pageRef], "input1", "pasted string editable editable editable");
583     }];
584
585     // Paste on top of the editable text, on a phrase in an <input>.
586     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputPhrase) preDidCloseMenuHandler:^() {
587         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
588         [[NSPasteboard generalPasteboard] clearContents];
589         [[NSPasteboard generalPasteboard] setString:@"pasted over phrase" forType:NSPasteboardTypeString];
590         EXPECT_WK_STREQ(@"New York", retrieveSelectionInElement([wkView pageRef], "input2"));
591         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagPaste);
592
593         // Now check and see if our paste succeeded, and replaced the whole phrase.
594         watchEditableAreaForString([wkView pageRef], "input2", "pasted over phrase some words");
595     }];
596
597     // Copy from a <textarea>.
598     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaWords) preDidCloseMenuHandler:^() {
599         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
600         EXPECT_WK_STREQ(@"editable", retrieveSelectionInElement([wkView pageRef], "textarea1"));
601         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
602         EXPECT_WK_STREQ(@"editable", watchPasteboardForString());
603     }];
604
605     // Copy a phrase from a <textarea>.
606     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaPhrase) preDidCloseMenuHandler:^() {
607         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
608         EXPECT_WK_STREQ(@"New York", retrieveSelectionInElement([wkView pageRef], "textarea2"));
609         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyText);
610         EXPECT_WK_STREQ(@"New York", watchPasteboardForString());
611     }];
612
613     // Paste on top of the editable text in a <textarea>.
614     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaWords) preDidCloseMenuHandler:^() {
615         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
616         [[NSPasteboard generalPasteboard] clearContents];
617         [[NSPasteboard generalPasteboard] setString:@"pasted" forType:NSPasteboardTypeString];
618         EXPECT_WK_STREQ(@"editable", retrieveSelectionInElement([wkView pageRef], "textarea1"));
619         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagPaste);
620
621         // Now check and see if our paste succeeded. It should only replace one 'editable'.
622         watchEditableAreaForString([wkView pageRef], "textarea1", "pasted editable editable editable");
623     }];
624
625     // Paste on top of the editable text, on a phrase in a <textarea>.
626     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaPhrase) preDidCloseMenuHandler:^() {
627         EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
628         [[NSPasteboard generalPasteboard] clearContents];
629         [[NSPasteboard generalPasteboard] setString:@"pasted over phrase" forType:NSPasteboardTypeString];
630         EXPECT_WK_STREQ(@"New York", retrieveSelectionInElement([wkView pageRef], "textarea2"));
631         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagPaste);
632
633         // Now check and see if our paste succeeded, and replaced the whole phrase.
634         watchEditableAreaForString([wkView pageRef], "textarea2", "pasted over phrase some words");
635     }];
636
637     // Copy an image.
638     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Image) preDidCloseMenuHandler:^() {
639         EXPECT_EQ(kWKActionMenuImage, [wkView _actionMenuResult].type);
640
641         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyImage);
642         NSImage *image = watchPasteboardForImage();
643
644         EXPECT_EQ(215, image.size.width);
645         EXPECT_EQ(174, image.size.height);
646     }];
647
648     // Download an image.
649     activeDownloadContext.shouldCheckForImage = true;
650     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Image) preDidCloseMenuHandler:^() {
651         EXPECT_EQ(kWKActionMenuImage, [wkView _actionMenuResult].type);
652
653         didFinishDownload = false;
654         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 2, kWKContextActionItemTagSaveImageToDownloads);
655         Util::run(&didFinishDownload);
656     }];
657     activeDownloadContext.shouldCheckForImage = false;
658
659     // Images that are also links should be treated as links.
660     // http://trac.webkit.org/changeset/175701
661     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ImageAsLink) preDidCloseMenuHandler:^() {
662         EXPECT_EQ(kWKActionMenuLink, [wkView _actionMenuResult].type);
663     }];
664
665     waitForVideoReady([wkView pageRef]);
666
667     // Copy a video URL.
668     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Video) preDidCloseMenuHandler:^() {
669         EXPECT_EQ(kWKActionMenuVideo, [wkView _actionMenuResult].type);
670
671         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyVideoURL);
672         NSString *videoURL = watchPasteboardForString();
673         EXPECT_WK_STREQ(@"test.mp4", [videoURL lastPathComponent]);
674     }];
675
676     // Copying a video URL for a non-downloadable video should result in copying the page URL instead.
677     // http://trac.webkit.org/changeset/176131
678     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::MSEVideo) preDidCloseMenuHandler:^() {
679         EXPECT_EQ(kWKActionMenuVideo, [wkView _actionMenuResult].type);
680
681         performMenuItemAtIndexOfTypeAsync([wkView _actionMenu], 0, kWKContextActionItemTagCopyVideoURL);
682         NSString *videoURL = watchPasteboardForString();
683         EXPECT_WK_STREQ(@"action-menu-targets.html", [videoURL lastPathComponent]);
684
685         // Also, the download menu item should be disabled for non-downloadable video.
686         ensureMenuItemAtIndexOfTypeIsDisabled([wkView _actionMenu], 2, kWKContextActionItemTagSaveVideoToDownloads);
687     }];
688
689     // HTTP link.
690     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::HTTPLink) preDidCloseMenuHandler:^() {
691         EXPECT_EQ(kWKActionMenuLink, [wkView _actionMenuResult].type);
692
693         // Invoking an action menu should dismiss any existing selection.
694         // http://trac.webkit.org/changeset/175753
695         EXPECT_WK_STREQ(@"<no selection>", retrieveSelection([wkView pageRef]));
696     }];
697
698     // Mailto link.
699     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::MailtoLink) preDidCloseMenuHandler:^() {
700         EXPECT_EQ(kWKActionMenuMailtoLink, [wkView _actionMenuResult].type);
701
702         // Data detected links don't get a selection nor special indication, for consistency with HTTP links.
703         EXPECT_WK_STREQ(@"<no selection>", retrieveSelection([wkView pageRef]));
704     }];
705
706     // Non-HTTP(S), non-mailto links should fall back to the text menu.
707     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::FTPLink) preDidCloseMenuHandler:^() {
708         EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
709         EXPECT_WK_STREQ(@"ftp", retrieveSelection([wkView pageRef]));
710     }];
711
712     // JavaScript links should not be executed, and should fall back to the text menu.
713     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::JavaScriptLink) preDidCloseMenuHandler:^() {
714         EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
715         EXPECT_WK_STREQ(@"javascript", retrieveSelection([wkView pageRef]));
716         EXPECT_FALSE(callJavaScriptReturningBool([wkView pageRef], "wasFailCalled()"));
717     }];
718
719     // Clients should be able to customize the menu by overriding WKView's _actionMenuItemsForHitTestResult.
720     // http://trac.webkit.org/changeset/174908
721     RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:@"Some Action" action:@selector(copy:) keyEquivalent:@""]);
722     [wkView _setOverrideActionMenuItems:@[ item.get() ]];
723     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Image) preDidCloseMenuHandler:^() {
724         EXPECT_EQ(1, [wkView _actionMenu].numberOfItems);
725         EXPECT_WK_STREQ(@"Some Action", [[wkView _actionMenu] itemAtIndex:0].title);
726     }];
727     [wkView _setOverrideActionMenuItems:nil];
728
729     // Clients should be able to customize the DataDetectors actions by implementing
730     // WKBundlePageOverlayClient's prepareForActionMenu callback.
731     // http://trac.webkit.org/changeset/176086
732     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::PageOverlay) preDidCloseMenuHandler:^() {
733         EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
734     }];
735
736     // No menu should be built for whitespace (except in editable areas).
737     [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::PageWhitespace) preDidCloseMenuHandler:^() {
738         EXPECT_EQ(kWKActionMenuNone, [wkView _actionMenuResult].type);
739         EXPECT_EQ(0, [wkView _actionMenu].numberOfItems);
740     }];
741 }
742
743 } // namespace TestWebKitAPI
744
745 #endif