43d4def5a3c16a3810bd906976efd46252b3f09a
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / WKActionMenuController.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 #import "WKActionMenuController.h"
28
29 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
30
31 #import "WKNSURLExtras.h"
32 #import "WKViewInternal.h"
33 #import "WebContext.h"
34 #import "WebKitSystemInterface.h"
35 #import "WebPageMessages.h"
36 #import "WebPageProxy.h"
37 #import "WebPageProxyMessages.h"
38 #import "WebProcessProxy.h"
39 #import <Foundation/Foundation.h>
40 #import <ImageIO/ImageIO.h>
41 #import <ImageKit/ImageKit.h>
42 #import <WebCore/DataDetectorsSPI.h>
43 #import <WebCore/LocalizedStrings.h>
44 #import <WebCore/LookupSPI.h>
45 #import <WebCore/NSMenuSPI.h>
46 #import <WebCore/NSSharingServiceSPI.h>
47 #import <WebCore/NSSharingServicePickerSPI.h>
48 #import <WebCore/NSViewSPI.h>
49 #import <WebCore/SoftLinking.h>
50 #import <WebCore/TextIndicator.h>
51 #import <WebCore/URL.h>
52
53 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, ImageKit)
54 SOFT_LINK_CLASS(ImageKit, IKSlideshow)
55
56 using namespace WebCore;
57 using namespace WebKit;
58
59 @interface WKActionMenuController () <NSSharingServiceDelegate, NSSharingServicePickerDelegate>
60 - (void)_updateActionMenuItems;
61 - (BOOL)_canAddMediaToPhotos;
62 - (void)_clearActionMenuState;
63 @end
64
65 @interface WKView (WKDeprecatedSPI)
66 - (NSArray *)_actionMenuItemsForHitTestResult:(WKHitTestResultRef)hitTestResult defaultActionMenuItems:(NSArray *)defaultMenuItems;
67 @end
68
69 @implementation WKActionMenuController
70
71 - (instancetype)initWithPage:(WebPageProxy&)page view:(WKView *)wkView
72 {
73     self = [super init];
74
75     if (!self)
76         return nil;
77
78     _page = &page;
79     _wkView = wkView;
80     _type = kWKActionMenuNone;
81
82     return self;
83 }
84
85 - (void)willDestroyView:(WKView *)view
86 {
87     _page = nullptr;
88     _wkView = nil;
89     _hitTestResult = ActionMenuHitTestResult();
90     _currentActionContext = nil;
91 }
92
93 - (void)wkView:(WKView *)wkView willHandleMouseDown:(NSEvent *)event
94 {
95     [self _clearActionMenuState];
96 }
97
98 - (void)prepareForMenu:(NSMenu *)menu withEvent:(NSEvent *)event
99 {
100     if (menu != _wkView.actionMenu)
101         return;
102
103     [_wkView _dismissContentRelativeChildWindows];
104
105     _page->performActionMenuHitTestAtLocation([_wkView convertPoint:event.locationInWindow fromView:nil]);
106
107     _state = ActionMenuState::Pending;
108     [self _updateActionMenuItems];
109 }
110
111 - (BOOL)isMenuForTextContent
112 {
113     return _type == kWKActionMenuReadOnlyText || _type == kWKActionMenuEditableText || _type == kWKActionMenuEditableTextWithSuggestions;
114 }
115
116 - (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event
117 {
118     if (menu != _wkView.actionMenu)
119         return;
120
121     if (!menu.numberOfItems)
122         return;
123
124     if (_type == kWKActionMenuDataDetectedItem) {
125         if (menu.numberOfItems == 1)
126             _page->clearSelection();
127         else
128             _page->selectLastActionMenuRange();
129         return;
130     }
131
132     if (_type == kWKActionMenuWhitespaceInEditableArea) {
133         _page->focusAndSelectLastActionMenuHitTestResult();
134         return;
135     }
136
137     if (![self isMenuForTextContent]) {
138         _page->clearSelection();
139         return;
140     }
141
142     // Action menus for text should highlight the text so that it is clear what the action menu actions
143     // will apply to. If the text is already selected, the menu will use the existing selection.
144     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
145     if (!hitTestResult->isSelected())
146         _page->selectLastActionMenuRange();
147 }
148
149 - (void)didCloseMenu:(NSMenu *)menu withEvent:(NSEvent *)event
150 {
151     if (menu != _wkView.actionMenu)
152         return;
153
154     [self _clearActionMenuState];
155 }
156
157 - (void)_clearActionMenuState
158 {
159     if (_currentActionContext && _hasActivatedActionContext) {
160         [getDDActionsManagerClass() didUseActions];
161         _hasActivatedActionContext = NO;
162     }
163
164     _state = ActionMenuState::None;
165     _hitTestResult = ActionMenuHitTestResult();
166     _type = kWKActionMenuNone;
167     _sharingServicePicker = nil;
168     _currentActionContext = nil;
169     _userData = nil;
170 }
171
172 - (void)didPerformActionMenuHitTest:(const ActionMenuHitTestResult&)hitTestResult userData:(API::Object*)userData
173 {
174     // FIXME: This needs to use the WebKit2 callback mechanism to avoid out-of-order replies.
175     _state = ActionMenuState::Ready;
176     _hitTestResult = hitTestResult;
177     _userData = userData;
178
179     [self _updateActionMenuItems];
180 }
181
182 #pragma mark Link actions
183
184 - (NSArray *)_defaultMenuItemsForLink
185 {
186     RetainPtr<NSMenuItem> openLinkItem = [self _createActionMenuItemForTag:kWKContextActionItemTagOpenLinkInDefaultBrowser];
187     RetainPtr<NSMenuItem> readingListItem = [self _createActionMenuItemForTag:kWKContextActionItemTagAddLinkToSafariReadingList];
188
189     return @[ openLinkItem.get(), [NSMenuItem separatorItem], [NSMenuItem separatorItem], readingListItem.get() ];
190 }
191
192 - (void)_openURLFromActionMenu:(id)sender
193 {
194     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
195     [[NSWorkspace sharedWorkspace] openURL:[NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()]];
196 }
197
198 - (void)_addToReadingListFromActionMenu:(id)sender
199 {
200     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
201     NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameAddToSafariReadingList];
202     [service performWithItems:@[ [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()] ]];
203 }
204
205 #pragma mark Video actions
206
207 - (NSArray *)_defaultMenuItemsForVideo
208 {
209     RetainPtr<NSMenuItem> copyVideoURLItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyVideoURL];
210
211     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
212     RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagSaveVideoToDownloads];
213     RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:kWKContextActionItemTagShareVideo];
214
215     String urlToShare = hitTestResult->absoluteMediaURL();
216     if (!hitTestResult->isDownloadableMedia()) {
217         [saveToDownloadsItem setEnabled:NO];
218         urlToShare = _page->mainFrame()->url();
219     }
220
221     if (!urlToShare.isEmpty()) {
222         _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ urlToShare ]]);
223         [_sharingServicePicker setDelegate:self];
224         [shareItem setSubmenu:[_sharingServicePicker menu]];
225     }
226
227     return @[ copyVideoURLItem.get(), [NSMenuItem separatorItem], saveToDownloadsItem.get(), shareItem.get() ];
228 }
229
230 - (void)_copyVideoURL:(id)sender
231 {
232     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
233     String urlToCopy = hitTestResult->absoluteMediaURL();
234     if (!hitTestResult->isDownloadableMedia())
235         urlToCopy = _page->mainFrame()->url();
236
237     [[NSPasteboard generalPasteboard] clearContents];
238     [[NSPasteboard generalPasteboard] writeObjects:@[ urlToCopy ]];
239 }
240
241 - (void)_saveVideoToDownloads:(id)sender
242 {
243     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
244     _page->process().context().download(_page, hitTestResult->absoluteMediaURL());
245 }
246
247 #pragma mark Image actions
248
249 - (NSImage *)_hitTestResultImage
250 {
251     RefPtr<SharedMemory> imageSharedMemory = _hitTestResult.imageSharedMemory;
252     if (!imageSharedMemory)
253         return nil;
254
255     RetainPtr<NSImage> nsImage = adoptNS([[NSImage alloc] initWithData:[NSData dataWithBytes:imageSharedMemory->data() length:imageSharedMemory->size()]]);
256     return nsImage.autorelease();
257 }
258
259 - (NSArray *)_defaultMenuItemsForImage
260 {
261     RetainPtr<NSMenuItem> copyImageItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyImage];
262     RetainPtr<NSMenuItem> addToPhotosItem;
263     if ([self _canAddMediaToPhotos])
264         addToPhotosItem = [self _createActionMenuItemForTag:kWKContextActionItemTagAddImageToPhotos];
265     else
266         addToPhotosItem = [NSMenuItem separatorItem];
267     RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagSaveImageToDownloads];
268     RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:kWKContextActionItemTagShareImage];
269
270     if (RetainPtr<NSImage> image = [self _hitTestResultImage]) {
271         _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ image.get() ]]);
272         [_sharingServicePicker setDelegate:self];
273         [shareItem setSubmenu:[_sharingServicePicker menu]];
274     }
275
276     return @[ copyImageItem.get(), addToPhotosItem.get(), saveToDownloadsItem.get(), shareItem.get() ];
277 }
278
279 - (void)_copyImage:(id)sender
280 {
281     RetainPtr<NSImage> image = [self _hitTestResultImage];
282     if (!image)
283         return;
284
285     [[NSPasteboard generalPasteboard] clearContents];
286     [[NSPasteboard generalPasteboard] writeObjects:@[ image.get() ]];
287 }
288
289 - (void)_saveImageToDownloads:(id)sender
290 {
291     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
292     _page->process().context().download(_page, hitTestResult->absoluteImageURL());
293 }
294
295 // FIXME: We should try to share this with WebPageProxyMac's similar PDF functions.
296 static NSString *temporaryPhotosDirectoryPath()
297 {
298     static NSString *temporaryPhotosDirectoryPath;
299
300     if (!temporaryPhotosDirectoryPath) {
301         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPhotos-XXXXXX"];
302         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
303
304         if (mkdtemp(templateRepresentation.mutableData()))
305             temporaryPhotosDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
306     }
307
308     return temporaryPhotosDirectoryPath;
309 }
310
311 static NSString *pathToPhotoOnDisk(NSString *suggestedFilename)
312 {
313     NSString *photoDirectoryPath = temporaryPhotosDirectoryPath();
314     if (!photoDirectoryPath) {
315         WTFLogAlways("Cannot create temporary photo download directory.");
316         return nil;
317     }
318
319     NSString *path = [photoDirectoryPath stringByAppendingPathComponent:suggestedFilename];
320
321     NSFileManager *fileManager = [NSFileManager defaultManager];
322     if ([fileManager fileExistsAtPath:path]) {
323         NSString *pathTemplatePrefix = [photoDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
324         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
325         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
326
327         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
328         if (fd < 0) {
329             WTFLogAlways("Cannot create photo file in the temporary directory (%@).", suggestedFilename);
330             return nil;
331         }
332
333         close(fd);
334         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
335     }
336
337     return path;
338 }
339
340 - (BOOL)_canAddMediaToPhotos
341 {
342     return [getIKSlideshowClass() canExportToApplication:@"com.apple.Photos"];
343 }
344
345 - (void)_addImageToPhotos:(id)sender
346 {
347     if (![self _canAddMediaToPhotos])
348         return;
349
350     RefPtr<SharedMemory> imageSharedMemory = _hitTestResult.imageSharedMemory;
351     if (!imageSharedMemory->size() || _hitTestResult.imageExtension.isEmpty())
352         return;
353
354     RetainPtr<NSData> imageData = adoptNS([[NSData alloc] initWithBytes:imageSharedMemory->data() length:imageSharedMemory->size()]);
355     RetainPtr<NSString> suggestedFilename = [[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:_hitTestResult.imageExtension];
356
357     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
358         NSString *filePath = pathToPhotoOnDisk(suggestedFilename.get());
359         if (!filePath)
360             return;
361
362         NSURL *fileURL = [NSURL fileURLWithPath:filePath];
363         [imageData writeToURL:fileURL atomically:NO];
364
365         dispatch_async(dispatch_get_main_queue(), ^{
366             // This API provides no way to report failure, but if 18420778 is fixed so that it does, we should handle this.
367             [getIKSlideshowClass() exportSlideshowItem:filePath toApplication:@"com.apple.Photos"];
368         });
369     });
370 }
371
372 #pragma mark Text actions
373
374 - (NSArray *)_defaultMenuItemsForDataDetectedText
375 {
376     DDActionContext *actionContext = _hitTestResult.actionContext.get();
377     if (!actionContext)
378         return @[ ];
379
380     actionContext.altMode = YES;
381     if ([[getDDActionsManagerClass() sharedManager] respondsToSelector:@selector(hasActionsForResult:actionContext:)]) {
382         if (![[getDDActionsManagerClass() sharedManager] hasActionsForResult:actionContext.mainResult actionContext:actionContext])
383             return @[ ];
384     }
385
386     // Ref our WebPageProxy for use in the blocks below.
387     RefPtr<WebPageProxy> page = _page;
388     PageOverlay::PageOverlayID overlayID = _hitTestResult.detectedDataOriginatingPageOverlay;
389     _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
390         page->send(Messages::WebPage::DataDetectorsDidPresentUI(overlayID));
391     } interactionChangedHandler:^() {
392         if (_hitTestResult.detectedDataTextIndicator)
393             _page->setTextIndicator(_hitTestResult.detectedDataTextIndicator->data(), false);
394         page->send(Messages::WebPage::DataDetectorsDidChangeUI(overlayID));
395     } interactionStoppedHandler:^() {
396         page->send(Messages::WebPage::DataDetectorsDidHideUI(overlayID));
397         page->clearTextIndicator();
398     }];
399
400     [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
401
402     NSArray *menuItems = [[getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()];
403     if (menuItems.count == 1 && _hitTestResult.detectedDataTextIndicator)
404         _hitTestResult.detectedDataTextIndicator->setPresentationTransition(TextIndicatorPresentationTransition::Bounce);
405     return menuItems;
406 }
407
408 - (NSArray *)_defaultMenuItemsForText
409 {
410     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
411     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
412     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
413     [pasteItem setEnabled:NO];
414
415     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
416 }
417
418 - (NSArray *)_defaultMenuItemsForEditableText
419 {
420     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
421     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
422     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
423
424     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
425 }
426
427 - (NSArray *)_defaultMenuItemsForEditableTextWithSuggestions
428 {
429     if (_hitTestResult.lookupText.isEmpty())
430         return @[ ];
431
432     Vector<TextCheckingResult> results;
433     _page->checkTextOfParagraph(_hitTestResult.lookupText, NSTextCheckingTypeSpelling, results);
434     if (results.isEmpty())
435         return @[ ];
436
437     Vector<String> guesses;
438     _page->getGuessesForWord(_hitTestResult.lookupText, String(), guesses);
439     if (guesses.isEmpty())
440         return @[ ];
441
442     RetainPtr<NSMenu> spellingSubMenu = adoptNS([[NSMenu alloc] init]);
443     for (const auto& guess : guesses) {
444         RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:guess action:@selector(_changeSelectionToSuggestion:) keyEquivalent:@""]);
445         [item setRepresentedObject:guess];
446         [item setTarget:self];
447         [spellingSubMenu addItem:item.get()];
448     }
449
450     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
451     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
452     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
453     RetainPtr<NSMenuItem> textSuggestionsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagTextSuggestions];
454
455     [textSuggestionsItem setSubmenu:spellingSubMenu.get()];
456
457     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get(), textSuggestionsItem.get() ];
458 }
459
460 - (void)_copySelection:(id)sender
461 {
462     _page->executeEditCommand("copy");
463 }
464
465 - (void)_paste:(id)sender
466 {
467     _page->executeEditCommand("paste");
468 }
469
470 - (void)_lookupText:(id)sender
471 {
472     _page->performDictionaryLookupOfCurrentSelection();
473 }
474
475 - (void)_changeSelectionToSuggestion:(id)sender
476 {
477     NSString *selectedCorrection = [sender representedObject];
478     if (!selectedCorrection)
479         return;
480
481     ASSERT([selectedCorrection isKindOfClass:[NSString class]]);
482
483     _page->changeSpellingToWord(selectedCorrection);
484 }
485
486 #pragma mark Whitespace actions
487
488 - (NSArray *)_defaultMenuItemsForWhitespaceInEditableArea
489 {
490     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
491     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
492     [copyTextItem setEnabled:NO];
493
494     return @[ copyTextItem.get(), [NSMenuItem separatorItem], pasteItem.get() ];
495 }
496
497 #pragma mark mailto: and tel: Link actions
498
499 - (NSArray *)_defaultMenuItemsForDataDetectableLink
500 {
501     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
502     RetainPtr<DDActionContext> actionContext = [allocDDActionContextInstance() init];
503
504     // FIXME: Should this show a yellow highlight?
505     _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
506     } interactionChangedHandler:^() {
507     } interactionStoppedHandler:^() {
508     }];
509
510     [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
511
512     return [[getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:hitTestResult->absoluteLinkURL() actionContext:_currentActionContext.get()];
513 }
514
515 #pragma mark NSMenuDelegate implementation
516
517 - (void)menuNeedsUpdate:(NSMenu *)menu
518 {
519     if (menu != _wkView.actionMenu)
520         return;
521
522     ASSERT(_state != ActionMenuState::None);
523
524     // FIXME: We need to be able to cancel this if the menu goes away.
525     // FIXME: Connection can be null if the process is closed; we should clean up better in that case.
526     if (_state == ActionMenuState::Pending) {
527         if (auto* connection = _page->process().connection()) {
528             bool receivedReply = connection->waitForAndDispatchImmediately<Messages::WebPageProxy::DidPerformActionMenuHitTest>(_page->pageID(), std::chrono::milliseconds(500));
529             if (!receivedReply)
530                 _state = ActionMenuState::TimedOut;
531         }
532     }
533
534     if (_state != ActionMenuState::Ready)
535         [self _updateActionMenuItems];
536
537     if (_currentActionContext) {
538         _hasActivatedActionContext = YES;
539         if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()]) {
540             [menu cancelTracking];
541             [menu removeAllItems];
542         }
543     }
544 }
545
546 #pragma mark NSSharingServicePickerDelegate implementation
547
548 - (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)mask proposedSharingServices:(NSArray *)proposedServices
549 {
550     RetainPtr<NSMutableArray> services = adoptNS([[NSMutableArray alloc] initWithCapacity:proposedServices.count]);
551
552     for (NSSharingService *service in proposedServices) {
553         if ([service.name isEqualToString:NSSharingServiceNameAddToIPhoto])
554             continue;
555         [services addObject:service];
556     }
557
558     return services.autorelease();
559 }
560
561 - (id <NSSharingServiceDelegate>)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker delegateForSharingService:(NSSharingService *)sharingService
562 {
563     return self;
564 }
565
566 #pragma mark NSSharingServiceDelegate implementation
567
568 - (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
569 {
570     return _wkView.window;
571 }
572
573 #pragma mark Menu Items
574
575 - (RetainPtr<NSMenuItem>)_createActionMenuItemForTag:(uint32_t)tag
576 {
577     SEL selector = nullptr;
578     NSString *title = nil;
579     NSImage *image = nil;
580     bool enabled = true;
581     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
582
583     switch (tag) {
584     case kWKContextActionItemTagOpenLinkInDefaultBrowser:
585         selector = @selector(_openURLFromActionMenu:);
586         title = WEB_UI_STRING_KEY("Open", "Open (action menu item)", "action menu item");
587         image = [NSImage imageNamed:@"NSActionMenuOpenInNewWindow"];
588         break;
589
590     case kWKContextActionItemTagPreviewLink:
591         ASSERT_NOT_REACHED();
592         break;
593
594     case kWKContextActionItemTagAddLinkToSafariReadingList:
595         selector = @selector(_addToReadingListFromActionMenu:);
596         title = WEB_UI_STRING_KEY("Add to Reading List", "Add to Reading List (action menu item)", "action menu item");
597         image = [NSImage imageNamed:@"NSActionMenuAddToReadingList"];
598         break;
599
600     case kWKContextActionItemTagCopyImage:
601         selector = @selector(_copyImage:);
602         title = WEB_UI_STRING_KEY("Copy", "Copy (image action menu item)", "image action menu item");
603         image = [NSImage imageNamed:@"NSActionMenuCopy"];
604         break;
605
606     case kWKContextActionItemTagAddImageToPhotos:
607         selector = @selector(_addImageToPhotos:);
608         title = WEB_UI_STRING_KEY("Add to Photos", "Add to Photos (action menu item)", "action menu item");
609         image = [NSImage imageNamed:@"NSActionMenuAddToPhotos"];
610         break;
611
612     case kWKContextActionItemTagSaveImageToDownloads:
613         selector = @selector(_saveImageToDownloads:);
614         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (image action menu item)", "image action menu item");
615         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
616         break;
617
618     case kWKContextActionItemTagShareImage:
619         title = WEB_UI_STRING_KEY("Share (image action menu item)", "Share (image action menu item)", "image action menu item");
620         image = [NSImage imageNamed:@"NSActionMenuShare"];
621         break;
622
623     case kWKContextActionItemTagCopyText:
624         selector = @selector(_copySelection:);
625         title = WEB_UI_STRING_KEY("Copy", "Copy (text action menu item)", "text action menu item");
626         image = [NSImage imageNamed:@"NSActionMenuCopy"];
627         enabled = hitTestResult->allowsCopy();
628         break;
629
630     case kWKContextActionItemTagLookupText:
631         selector = @selector(_lookupText:);
632         title = WEB_UI_STRING_KEY("Look Up", "Look Up (action menu item)", "action menu item");
633         image = [NSImage imageNamed:@"NSActionMenuLookup"];
634         enabled = getLULookupDefinitionModuleClass() && hitTestResult->allowsCopy();
635         break;
636
637     case kWKContextActionItemTagPaste:
638         selector = @selector(_paste:);
639         title = WEB_UI_STRING_KEY("Paste", "Paste (action menu item)", "action menu item");
640         image = [NSImage imageNamed:@"NSActionMenuPaste"];
641         break;
642
643     case kWKContextActionItemTagTextSuggestions:
644         title = WEB_UI_STRING_KEY("Suggestions", "Suggestions (action menu item)", "action menu item");
645         image = [NSImage imageNamed:@"NSActionMenuSpelling"];
646         break;
647
648     case kWKContextActionItemTagCopyVideoURL:
649         selector = @selector(_copyVideoURL:);
650         title = WEB_UI_STRING_KEY("Copy", "Copy (video action menu item)", "video action menu item");
651         image = [NSImage imageNamed:@"NSActionMenuCopy"];
652         break;
653
654     case kWKContextActionItemTagSaveVideoToDownloads:
655         selector = @selector(_saveVideoToDownloads:);
656         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (video action menu item)", "video action menu item");
657         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
658         break;
659
660     case kWKContextActionItemTagShareVideo:
661         title = WEB_UI_STRING_KEY("Share", "Share (video action menu item)", "video action menu item");
662         image = [NSImage imageNamed:@"NSActionMenuShare"];
663         break;
664
665     default:
666         ASSERT_NOT_REACHED();
667         return nil;
668     }
669
670     RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]);
671     [item setImage:image];
672     [item setTarget:self];
673     [item setTag:tag];
674     [item setEnabled:enabled];
675     return item;
676 }
677
678 - (PassRefPtr<WebHitTestResult>)_webHitTestResult
679 {
680     RefPtr<WebHitTestResult> hitTestResult;
681     if (_state == ActionMenuState::Ready)
682         hitTestResult = WebHitTestResult::create(_hitTestResult.hitTestResult);
683     else
684         hitTestResult = _page->lastMouseMoveHitTestResult();
685
686     return hitTestResult.release();
687 }
688
689 - (NSArray *)_defaultMenuItems
690 {
691     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
692     if (!hitTestResult) {
693         _type = kWKActionMenuNone;
694         return _state == ActionMenuState::Pending ? @[ [NSMenuItem separatorItem] ] : @[ ];
695     }
696
697     String absoluteLinkURL = hitTestResult->absoluteLinkURL();
698     if (!absoluteLinkURL.isEmpty()) {
699         if (WebCore::protocolIsInHTTPFamily(absoluteLinkURL)) {
700             _type = kWKActionMenuLink;
701             return [self _defaultMenuItemsForLink];
702         }
703
704         if (protocolIs(absoluteLinkURL, "mailto")) {
705             _type = kWKActionMenuMailtoLink;
706             return [self _defaultMenuItemsForDataDetectableLink];
707         }
708
709         if (protocolIs(absoluteLinkURL, "tel")) {
710             _type = kWKActionMenuTelLink;
711             return [self _defaultMenuItemsForDataDetectableLink];
712         }
713     }
714
715     if (!hitTestResult->absoluteMediaURL().isEmpty()) {
716         _type = kWKActionMenuVideo;
717         return [self _defaultMenuItemsForVideo];
718     }
719
720     if (!hitTestResult->absoluteImageURL().isEmpty() && _hitTestResult.imageSharedMemory && !_hitTestResult.imageExtension.isEmpty()) {
721         _type = kWKActionMenuImage;
722         return [self _defaultMenuItemsForImage];
723     }
724
725     if (hitTestResult->isTextNode() || hitTestResult->isOverTextInsideFormControlElement()) {
726         NSArray *dataDetectorMenuItems = [self _defaultMenuItemsForDataDetectedText];
727         if (_currentActionContext) {
728             // If this is a data detected item with no menu items, we should not fall back to regular text options.
729             if (!dataDetectorMenuItems.count) {
730                 _type = kWKActionMenuNone;
731                 return @[ ];
732             }
733             _type = kWKActionMenuDataDetectedItem;
734             return dataDetectorMenuItems;
735         }
736
737         if (hitTestResult->isContentEditable()) {
738             NSArray *editableTextWithSuggestions = [self _defaultMenuItemsForEditableTextWithSuggestions];
739             if (editableTextWithSuggestions.count) {
740                 _type = kWKActionMenuEditableTextWithSuggestions;
741                 return editableTextWithSuggestions;
742             }
743
744             _type = kWKActionMenuEditableText;
745             return [self _defaultMenuItemsForEditableText];
746         }
747
748         _type = kWKActionMenuReadOnlyText;
749         return [self _defaultMenuItemsForText];
750     }
751
752     if (hitTestResult->isContentEditable()) {
753         _type = kWKActionMenuWhitespaceInEditableArea;
754         return [self _defaultMenuItemsForWhitespaceInEditableArea];
755     }
756
757     if (hitTestResult->isSelected()) {
758         // A selection should present the read-only text menu. It might make more sense to present a new
759         // type of menu with just copy, but for the time being, we should stay consistent with text.
760         _type = kWKActionMenuReadOnlyText;
761         return [self _defaultMenuItemsForText];
762     }
763
764     _type = kWKActionMenuNone;
765     return _state == ActionMenuState::Pending ? @[ [NSMenuItem separatorItem] ] : @[ ];
766 }
767
768 - (void)_updateActionMenuItems
769 {
770     [_wkView.actionMenu removeAllItems];
771
772     NSArray *menuItems = [self _defaultMenuItems];
773     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
774
775     if ([_wkView respondsToSelector:@selector(_actionMenuItemsForHitTestResult:defaultActionMenuItems:)])
776         menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) defaultActionMenuItems:menuItems];
777     else
778         menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) withType:_type defaultActionMenuItems:menuItems userData:toAPI(_userData.get())];
779
780     for (NSMenuItem *item in menuItems)
781         [_wkView.actionMenu addItem:item];
782
783     if (!_wkView.actionMenu.numberOfItems)
784         [_wkView.actionMenu cancelTracking];
785 }
786
787 @end
788
789 #endif // PLATFORM(MAC)