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