94d00c66f18363b92f5a961397fa20f5c5570820
[WebKit-https.git] / Source / WebKit / mac / WebView / WebActionMenuController.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 "WebActionMenuController.h"
27
28 #import "DOMElementInternal.h"
29 #import "DOMNodeInternal.h"
30 #import "DOMRangeInternal.h"
31 #import "WebDocumentInternal.h"
32 #import "WebElementDictionary.h"
33 #import "WebFrameInternal.h"
34 #import "WebHTMLView.h"
35 #import "WebHTMLViewInternal.h"
36 #import "WebSystemInterface.h"
37 #import "WebUIDelegatePrivate.h"
38 #import "WebViewInternal.h"
39 #import <ImageIO/ImageIO.h>
40 #import <ImageKit/ImageKit.h>
41 #import <WebCore/DataDetection.h>
42 #import <WebCore/DataDetectorsSPI.h>
43 #import <WebCore/DictionaryLookup.h>
44 #import <WebCore/Editor.h>
45 #import <WebCore/Element.h>
46 #import <WebCore/EventHandler.h>
47 #import <WebCore/Frame.h>
48 #import <WebCore/FrameView.h>
49 #import <WebCore/HTMLConverter.h>
50 #import <WebCore/NSSharingServicePickerSPI.h>
51 #import <WebCore/NSSharingServiceSPI.h>
52 #import <WebCore/NSViewSPI.h>
53 #import <WebCore/Page.h>
54 #import <WebCore/Range.h>
55 #import <WebCore/RenderElement.h>
56 #import <WebCore/RenderObject.h>
57 #import <WebCore/SoftLinking.h>
58 #import <WebCore/TextCheckerClient.h>
59 #import <WebKitSystemInterface.h>
60 #import <objc/objc-class.h>
61 #import <objc/objc.h>
62
63 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, QuickLookUI)
64 SOFT_LINK_CLASS(QuickLookUI, QLPreviewBubble)
65
66 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, ImageKit)
67 SOFT_LINK_CLASS(ImageKit, IKSlideshow)
68
69 @class QLPreviewBubble;
70 @interface NSObject (WKQLPreviewBubbleDetails)
71 @property (copy) NSArray * controls;
72 @property NSSize maximumSize;
73 @property NSRectEdge preferredEdge;
74 @property (retain) IBOutlet NSWindow* parentWindow;
75 - (void)showPreviewItem:(id)previewItem itemFrame:(NSRect)frame;
76 - (void)setAutomaticallyCloseWithMask:(NSEventMask)autocloseMask filterMask:(NSEventMask)filterMask block:(void (^)(void))block;
77 @end
78
79 using namespace WebCore;
80
81 struct DictionaryPopupInfo {
82     NSPoint origin;
83     RetainPtr<NSDictionary> options;
84     RetainPtr<NSAttributedString> attributedString;
85 };
86
87 @implementation WebActionMenuController
88
89 - (id)initWithWebView:(WebView *)webView
90 {
91     if (!(self = [super init]))
92         return nil;
93
94     _webView = webView;
95     _type = WebActionMenuNone;
96
97     return self;
98 }
99
100 - (void)webViewClosed
101 {
102     _webView = nil;
103 }
104
105 - (WebElementDictionary *)performHitTestAtPoint:(NSPoint)windowPoint
106 {
107     WebHTMLView *documentView = [[[_webView _selectedOrMainFrame] frameView] documentView];
108     NSPoint point = [documentView convertPoint:windowPoint fromView:nil];
109
110     Frame* coreFrame = core([documentView _frame]);
111     if (!coreFrame)
112         return nil;
113     HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly | HitTestRequest::Active;
114     _hitTestResult = coreFrame->eventHandler().hitTestResultAtPoint(IntPoint(point), hitType);
115
116     // We hit test including shadow content to get the desired result for editable text regions.
117     // But for media, we want to re-set to the shadow root.
118     if (Node* node = _hitTestResult.innerNode()) {
119         if (Element* shadowHost = node->shadowHost()) {
120             if (shadowHost->isMediaElement())
121                 _hitTestResult.setToNonShadowAncestor();
122         }
123     }
124
125     return [[[WebElementDictionary alloc] initWithHitTestResult:_hitTestResult] autorelease];
126 }
127
128 - (void)prepareForMenu:(NSMenu *)menu withEvent:(NSEvent *)event
129 {
130     if (!_webView)
131         return;
132
133     NSMenu *actionMenu = _webView.actionMenu;
134     if (menu != actionMenu)
135         return;
136
137     [actionMenu removeAllItems];
138
139     WebElementDictionary *hitTestResult = [self performHitTestAtPoint:event.locationInWindow];
140     NSArray *menuItems = [self _defaultMenuItems];
141
142     // Allow clients to customize the menu items.
143     if ([[_webView UIDelegate] respondsToSelector:@selector(_webView:actionMenuItemsForHitTestResult:withType:defaultActionMenuItems:)])
144         menuItems = [[_webView UIDelegate] _webView:_webView actionMenuItemsForHitTestResult:hitTestResult withType:_type defaultActionMenuItems:menuItems];
145
146     for (NSMenuItem *item in menuItems)
147         [actionMenu addItem:item];
148 }
149
150 - (BOOL)isMenuForTextContent
151 {
152     return _type == WebActionMenuReadOnlyText || _type == WebActionMenuEditableText || _type == WebActionMenuEditableTextWithSuggestions || _type == WebActionMenuWhitespaceInEditableArea;
153 }
154
155 - (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event
156 {
157     if (menu != _webView.actionMenu)
158         return;
159
160     if (_type == WebActionMenuDataDetectedItem) {
161         if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()]) {
162             [menu cancelTracking];
163             return;
164         }
165
166         if (menu.numberOfItems == 1)
167             [[_webView _selectedOrMainFrame] _clearSelection];
168         else
169             [self _selectDataDetectedText];
170         return;
171     }
172
173     if (![self isMenuForTextContent]) {
174         [[_webView _selectedOrMainFrame] _clearSelection];
175         return;
176     }
177
178     // Action menus for text should highlight the text so that it is clear what the action menu actions
179     // will apply to. If the text is already selected, the menu will use the existing selection.
180     if (!_hitTestResult.isSelected())
181         [self _selectLookupText];
182 }
183
184 - (void)didCloseMenu:(NSMenu *)menu withEvent:(NSEvent *)event
185 {
186     if (menu != _webView.actionMenu)
187         return;
188
189     if (_type == WebActionMenuDataDetectedItem && _currentActionContext)
190         [getDDActionsManagerClass() didUseActions];
191
192     _type = WebActionMenuNone;
193     _sharingServicePicker = nil;
194 }
195
196 #pragma mark Link actions
197
198 - (void)_openURLFromActionMenu:(id)sender
199 {
200     if (!_webView)
201         return;
202
203     [[NSWorkspace sharedWorkspace] openURL:_hitTestResult.absoluteLinkURL()];
204 }
205
206 - (void)_addToReadingListFromActionMenu:(id)sender
207 {
208     if (!_webView)
209         return;
210
211     NSURL *url = _hitTestResult.absoluteLinkURL();
212     NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameAddToSafariReadingList];
213     [service performWithItems:@[ url ]];
214 }
215
216 static IntRect elementBoundingBoxInWindowCoordinatesFromNode(Node* node)
217 {
218     if (!node)
219         return IntRect();
220
221     Frame* frame = node->document().frame();
222     if (!frame)
223         return IntRect();
224
225     FrameView* view = frame->view();
226     if (!view)
227         return IntRect();
228
229     return view->contentsToWindow(node->pixelSnappedBoundingBox());
230 }
231
232 - (void)_quickLookURLFromActionMenu:(id)sender
233 {
234     if (!_webView)
235         return;
236
237     NSURL *url = _hitTestResult.absoluteLinkURL();
238     if (!url)
239         return;
240
241     Node* node = _hitTestResult.innerNode();
242     if (!node)
243         return;
244
245     NSRect itemFrame = elementBoundingBoxInWindowCoordinatesFromNode(node);
246     NSSize maximumPreviewSize = NSMakeSize(_webView.bounds.size.width * 0.75, _webView.bounds.size.height * 0.75);
247
248     RetainPtr<QLPreviewBubble> bubble = adoptNS([[getQLPreviewBubbleClass() alloc] init]);
249     [bubble setParentWindow:_webView.window];
250     [bubble setMaximumSize:maximumPreviewSize];
251     [bubble setPreferredEdge:NSMaxYEdge];
252     [bubble setControls:@[ ]];
253     NSEventMask filterMask = NSAnyEventMask & ~(NSAppKitDefinedMask | NSSystemDefinedMask | NSApplicationDefinedMask | NSMouseEnteredMask | NSMouseExitedMask);
254     NSEventMask autocloseMask = NSLeftMouseDownMask | NSRightMouseDownMask | NSKeyDownMask;
255     [bubble setAutomaticallyCloseWithMask:autocloseMask filterMask:filterMask block:[bubble] {
256         [bubble close];
257     }];
258     [bubble showPreviewItem:url itemFrame:itemFrame];
259 }
260
261 - (NSArray *)_defaultMenuItemsForLink
262 {
263     RetainPtr<NSMenuItem> openLinkItem = [self _createActionMenuItemForTag:WebActionMenuItemTagOpenLinkInDefaultBrowser];
264     RetainPtr<NSMenuItem> previewLinkItem = [self _createActionMenuItemForTag:WebActionMenuItemTagPreviewLink];
265     RetainPtr<NSMenuItem> readingListItem = [self _createActionMenuItemForTag:WebActionMenuItemTagAddLinkToSafariReadingList];
266
267     return @[ openLinkItem.get(), previewLinkItem.get(), [NSMenuItem separatorItem], readingListItem.get() ];
268 }
269
270 #pragma mark Mailto Link actions
271
272 - (NSArray *)_defaultMenuItemsForMailtoLink
273 {
274     Node* node = _hitTestResult.innerNode();
275     if (!node)
276         return @[ ];
277
278     RetainPtr<DDActionContext> actionContext = [[getDDActionContextClass() alloc] init];
279     [actionContext setForActionMenuContent:YES];
280
281     // FIXME: Should this show a yellow highlight?
282     [actionContext setHighlightFrame:elementBoundingBoxInWindowCoordinatesFromNode(node)];
283     return [[getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:_hitTestResult.absoluteLinkURL() actionContext:actionContext.get()];
284 }
285
286 #pragma mark Image actions
287
288 - (NSArray *)_defaultMenuItemsForImage
289 {
290     RetainPtr<NSMenuItem> copyImageItem = [self _createActionMenuItemForTag:WebActionMenuItemTagCopyImage];
291
292     RetainPtr<NSMenuItem> addToPhotosItem;
293     if ([self _canAddMediaToPhotos])
294         addToPhotosItem = [self _createActionMenuItemForTag:WebActionMenuItemTagAddImageToPhotos];
295     else
296         addToPhotosItem = [NSMenuItem separatorItem];
297
298     RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:WebActionMenuItemTagSaveImageToDownloads];
299     if (!_webView.downloadDelegate)
300         [saveToDownloadsItem setEnabled:NO];
301
302     RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:WebActionMenuItemTagShareImage];
303     if (Image* image = _hitTestResult.image()) {
304         RetainPtr<CGImageRef> cgImage = image->getCGImageRef();
305         RetainPtr<NSImage> nsImage = adoptNS([[NSImage alloc] initWithCGImage:cgImage.get() size:NSZeroSize]);
306         _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ nsImage.get() ]]);
307         [_sharingServicePicker setDelegate:self];
308         [shareItem setSubmenu:[_sharingServicePicker menu]];
309     }
310
311     return @[ copyImageItem.get(), addToPhotosItem.get(), saveToDownloadsItem.get(), shareItem.get() ];
312 }
313
314 - (void)_copyImage:(id)sender
315 {
316     Frame* frame = core([_webView _selectedOrMainFrame]);
317     if (!frame)
318         return;
319     frame->editor().copyImage(_hitTestResult);
320 }
321
322 static NSString *temporaryPhotosDirectoryPath()
323 {
324     static NSString *temporaryPhotosDirectoryPath;
325
326     if (!temporaryPhotosDirectoryPath) {
327         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPhotos-XXXXXX"];
328         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
329
330         if (mkdtemp(templateRepresentation.mutableData()))
331             temporaryPhotosDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
332     }
333
334     return temporaryPhotosDirectoryPath;
335 }
336
337 static NSString *pathToPhotoOnDisk(NSString *suggestedFilename)
338 {
339     NSString *photoDirectoryPath = temporaryPhotosDirectoryPath();
340     if (!photoDirectoryPath) {
341         WTFLogAlways("Cannot create temporary photo download directory.");
342         return nil;
343     }
344
345     NSString *path = [photoDirectoryPath stringByAppendingPathComponent:suggestedFilename];
346
347     NSFileManager *fileManager = [NSFileManager defaultManager];
348     if ([fileManager fileExistsAtPath:path]) {
349         NSString *pathTemplatePrefix = [photoDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
350         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
351         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
352
353         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
354         if (fd < 0) {
355             WTFLogAlways("Cannot create photo file in the temporary directory (%@).", suggestedFilename);
356             return nil;
357         }
358
359         close(fd);
360         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
361     }
362
363     return path;
364 }
365
366 - (BOOL)_canAddMediaToPhotos
367 {
368     return [getIKSlideshowClass() canExportToApplication:@"com.apple.Photos"];
369 }
370
371 - (void)_addImageToPhotos:(id)sender
372 {
373     if (![self _canAddMediaToPhotos])
374         return;
375
376     Image* image = _hitTestResult.image();
377     if (!image)
378         return;
379
380     RetainPtr<CGImageRef> cgImage = image->getCGImageRef();
381
382     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
383         NSString * const suggestedFilename = @"image.jpg";
384
385         NSString *filePath = pathToPhotoOnDisk(suggestedFilename);
386         if (!filePath)
387             return;
388
389         NSURL *fileURL = [NSURL fileURLWithPath:filePath];
390         auto dest = adoptCF(CGImageDestinationCreateWithURL((CFURLRef)fileURL, kUTTypeJPEG, 1, nullptr));
391         CGImageDestinationAddImage(dest.get(), cgImage.get(), nullptr);
392         CGImageDestinationFinalize(dest.get());
393
394         dispatch_async(dispatch_get_main_queue(), ^{
395             // This API provides no way to report failure, but if 18420778 is fixed so that it does, we should handle this.
396             [getIKSlideshowClass() exportSlideshowItem:filePath toApplication:@"com.apple.Photos"];
397         });
398     });
399 }
400
401 - (void)_saveImageToDownloads:(id)sender
402 {
403     [_webView _downloadURL:_hitTestResult.absoluteImageURL()];
404 }
405
406 #pragma mark Video actions
407
408 - (NSArray*)_defaultMenuItemsForVideo
409 {
410     RetainPtr<NSMenuItem> copyVideoURLItem = [self _createActionMenuItemForTag:WebActionMenuItemTagCopyVideoURL];
411
412     RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:WebActionMenuItemTagSaveVideoToDownloads];
413     if (!_hitTestResult.isDownloadableMedia() || !_webView.downloadDelegate)
414         [saveToDownloadsItem setEnabled:NO];
415
416     RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:WebActionMenuItemTagShareVideo];
417     NSString *urlToShare = _hitTestResult.absoluteMediaURL();
418     if (!_hitTestResult.isDownloadableMedia()) {
419         [saveToDownloadsItem setEnabled:NO];
420         urlToShare = [_webView mainFrameURL];
421     }
422     _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ urlToShare ]]);
423     [_sharingServicePicker setDelegate:self];
424     [shareItem setSubmenu:[_sharingServicePicker menu]];
425
426     return @[ copyVideoURLItem.get(), [NSMenuItem separatorItem], saveToDownloadsItem.get(), shareItem.get() ];
427 }
428
429 - (void)_copyVideoURL:(id)sender
430 {
431     NSString *urlToCopy = _hitTestResult.absoluteMediaURL();
432     if (!_hitTestResult.isDownloadableMedia())
433         urlToCopy = [_webView mainFrameURL];
434
435     [[NSPasteboard generalPasteboard] clearContents];
436     [[NSPasteboard generalPasteboard] writeObjects:@[ urlToCopy ]];
437 }
438
439 - (void)_saveVideoToDownloads:(id)sender
440 {
441     [_webView _downloadURL:_hitTestResult.absoluteMediaURL()];
442 }
443
444 #pragma mark Text actions
445
446 - (NSArray *)_defaultMenuItemsForText
447 {
448     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:WebActionMenuItemTagCopyText];
449     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:WebActionMenuItemTagLookupText];
450
451     return @[ copyTextItem.get(), lookupTextItem.get() ];
452 }
453
454 - (NSArray *)_defaultMenuItemsForEditableText
455 {
456     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:WebActionMenuItemTagCopyText];
457     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:WebActionMenuItemTagLookupText];
458     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:WebActionMenuItemTagPaste];
459
460     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
461 }
462
463 - (NSArray *)_defaultMenuItemsForEditableTextWithSuggestions
464 {
465     Frame* frame = core([_webView _selectedOrMainFrame]);
466     if (!frame)
467         return @[ ];
468
469     NSDictionary *options = nil;
470     RefPtr<Range> lookupRange = rangeForDictionaryLookupAtHitTestResult(_hitTestResult, &options);
471     if (!lookupRange)
472         return @[ ];
473
474     String lookupText = lookupRange->text();
475     TextCheckerClient* textChecker = frame->editor().textChecker();
476     if (!textChecker)
477         return @[ ];
478
479     Vector<TextCheckingResult> results = textChecker->checkTextOfParagraph(lookupText, NSTextCheckingTypeSpelling);
480     if (results.isEmpty())
481         return @[ ];
482
483     Vector<String> guesses;
484     frame->editor().textChecker()->getGuessesForWord(lookupText, String(), guesses);
485     if (guesses.isEmpty())
486         return @[ ];
487
488     RetainPtr<NSMenu> spellingSubMenu = adoptNS([[NSMenu alloc] init]);
489     for (const auto& guess : guesses) {
490         RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:guess action:@selector(_changeSelectionToSuggestion:) keyEquivalent:@""]);
491         [item setRepresentedObject:guess];
492         [item setTarget:self];
493         [spellingSubMenu addItem:item.get()];
494     }
495
496     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:WebActionMenuItemTagCopyText];
497     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:WebActionMenuItemTagLookupText];
498     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:WebActionMenuItemTagPaste];
499     RetainPtr<NSMenuItem> textSuggestionsItem = [self _createActionMenuItemForTag:WebActionMenuItemTagTextSuggestions];
500
501     [textSuggestionsItem setSubmenu:spellingSubMenu.get()];
502
503     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get(), textSuggestionsItem.get() ];
504 }
505
506 - (void)_selectDataDetectedText
507 {
508     [_webView _mainCoreFrame]->selection().setSelectedRange(_currentDetectedDataRange.get(), DOWNSTREAM, true);
509 }
510
511 - (NSArray *)_defaultMenuItemsForDataDetectedText
512 {
513     RefPtr<Range> detectedDataRange;
514     FloatRect detectedDataBoundingBox;
515     RetainPtr<DDActionContext> actionContext;
516
517     if ([[_webView UIDelegate] respondsToSelector:@selector(_webView:actionContextForHitTestResult:range:)]) {
518         RetainPtr<WebElementDictionary> hitTestDictionary = adoptNS([[WebElementDictionary alloc] initWithHitTestResult:_hitTestResult]);
519
520         DOMRange *customDataDetectorsRange;
521         actionContext = [[_webView UIDelegate] _webView:_webView actionContextForHitTestResult:hitTestDictionary.get() range:&customDataDetectorsRange];
522
523         if (actionContext && customDataDetectorsRange)
524             detectedDataRange = core(customDataDetectorsRange);
525     }
526
527     // If the client didn't give us an action context, try to scan around the hit point.
528     if (!actionContext || !detectedDataRange)
529         actionContext = DataDetection::detectItemAroundHitTestResult(_hitTestResult, detectedDataBoundingBox, detectedDataRange);
530
531     if (!actionContext || !detectedDataRange)
532         return @[ ];
533
534     // FIXME: We should hide/show the yellow highlight here.
535     _currentActionContext = [actionContext contextForView:_webView altMode:YES interactionStartedHandler:^() {
536     } interactionChangedHandler:^() {
537     } interactionStoppedHandler:^() {
538     }];
539     _currentDetectedDataRange = detectedDataRange;
540
541     [_currentActionContext setHighlightFrame:[_webView.window convertRectToScreen:[_webView convertRect:detectedDataBoundingBox toView:nil]]];
542
543     return [[getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()];
544 }
545
546 - (void)_copySelection:(id)sender
547 {
548     [_webView _executeCoreCommandByName:@"copy" value:nil];
549 }
550
551 - (void)_lookupText:(id)sender
552 {
553     Frame* frame = core([_webView _selectedOrMainFrame]);
554     if (!frame)
555         return;
556
557     DictionaryPopupInfo popupInfo = performDictionaryLookupForSelection(frame, frame->selection().selection());
558     if (!popupInfo.attributedString)
559         return;
560
561     NSPoint textBaselineOrigin = popupInfo.origin;
562
563     // Convert to screen coordinates.
564     textBaselineOrigin = [_webView.window convertRectToScreen:NSMakeRect(textBaselineOrigin.x, textBaselineOrigin.y, 0, 0)].origin;
565
566     WKShowWordDefinitionWindow(popupInfo.attributedString.get(), textBaselineOrigin, popupInfo.options.get());
567 }
568
569 - (void)_paste:(id)sender
570 {
571     [_webView _executeCoreCommandByName:@"paste" value:nil];
572 }
573
574 - (void)_selectLookupText
575 {
576     NSDictionary *options = nil;
577     RefPtr<Range> lookupRange = rangeForDictionaryLookupAtHitTestResult(_hitTestResult, &options);
578     if (!lookupRange)
579         return;
580
581     Frame* frame = _hitTestResult.innerNode()->document().frame();
582     if (!frame)
583         return;
584
585     frame->selection().setSelectedRange(lookupRange.get(), DOWNSTREAM, true);
586 }
587
588 - (void)_changeSelectionToSuggestion:(id)sender
589 {
590     NSString *selectedCorrection = [sender representedObject];
591     if (!selectedCorrection)
592         return;
593
594     ASSERT([selectedCorrection isKindOfClass:[NSString class]]);
595
596     WebHTMLView *documentView = [[[_webView _selectedOrMainFrame] frameView] documentView];
597     [documentView _changeSpellingToWord:selectedCorrection];
598 }
599
600 static DictionaryPopupInfo performDictionaryLookupForSelection(Frame* frame, const VisibleSelection& selection)
601 {
602     NSDictionary *options = nil;
603     DictionaryPopupInfo popupInfo;
604     RefPtr<Range> selectedRange = rangeForDictionaryLookupForSelection(selection, &options);
605     if (selectedRange)
606         popupInfo = performDictionaryLookupForRange(frame, *selectedRange, options);
607     return popupInfo;
608 }
609
610 static DictionaryPopupInfo performDictionaryLookupForRange(Frame* frame, Range& range, NSDictionary *options)
611 {
612     DictionaryPopupInfo popupInfo;
613     if (range.text().stripWhiteSpace().isEmpty())
614         return popupInfo;
615     
616     RenderObject* renderer = range.startContainer()->renderer();
617     const RenderStyle& style = renderer->style();
618
619     Vector<FloatQuad> quads;
620     range.textQuads(quads);
621     if (quads.isEmpty())
622         return popupInfo;
623
624     IntRect rangeRect = frame->view()->contentsToWindow(quads[0].enclosingBoundingBox());
625
626     popupInfo.origin = NSMakePoint(rangeRect.x(), rangeRect.y() + (style.fontMetrics().descent() * frame->page()->pageScaleFactor()));
627     popupInfo.options = options;
628
629     NSAttributedString *nsAttributedString = editingAttributedStringFromRange(range);
630     RetainPtr<NSMutableAttributedString> scaledNSAttributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:[nsAttributedString string]]);
631     NSFontManager *fontManager = [NSFontManager sharedFontManager];
632
633     [nsAttributedString enumerateAttributesInRange:NSMakeRange(0, [nsAttributedString length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
634         RetainPtr<NSMutableDictionary> scaledAttributes = adoptNS([attributes mutableCopy]);
635
636         NSFont *font = [scaledAttributes objectForKey:NSFontAttributeName];
637         if (font) {
638             font = [fontManager convertFont:font toSize:[font pointSize] * frame->page()->pageScaleFactor()];
639             [scaledAttributes setObject:font forKey:NSFontAttributeName];
640         }
641
642         [scaledNSAttributedString addAttributes:scaledAttributes.get() range:range];
643     }];
644
645     popupInfo.attributedString = scaledNSAttributedString.get();
646     return popupInfo;
647 }
648
649 #pragma mark Whitespace actions
650
651 - (NSArray *)_defaultMenuItemsForWhitespaceInEditableArea
652 {
653     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:WebActionMenuItemTagPaste];
654
655     return @[ [NSMenuItem separatorItem], [NSMenuItem separatorItem], pasteItem.get() ];
656 }
657
658 #pragma mark NSSharingServicePickerDelegate implementation
659
660 - (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)mask proposedSharingServices:(NSArray *)proposedServices
661 {
662     RetainPtr<NSMutableArray> services = adoptNS([[NSMutableArray alloc] initWithCapacity:proposedServices.count]);
663
664     for (NSSharingService *service in proposedServices) {
665         if ([service.name isEqualToString:NSSharingServiceNameAddToIPhoto])
666             continue;
667         [services addObject:service];
668     }
669
670     return services.autorelease();
671 }
672
673 - (id <NSSharingServiceDelegate>)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker delegateForSharingService:(NSSharingService *)sharingService
674 {
675     return self;
676 }
677
678 #pragma mark NSSharingServiceDelegate implementation
679
680 - (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
681 {
682     return _webView.window;
683 }
684
685 #pragma mark Menu Items
686
687 - (RetainPtr<NSMenuItem>)_createActionMenuItemForTag:(uint32_t)tag
688 {
689     SEL selector = nullptr;
690     NSString *title = nil;
691     NSImage *image = nil;
692
693     switch (tag) {
694     case WebActionMenuItemTagOpenLinkInDefaultBrowser:
695         selector = @selector(_openURLFromActionMenu:);
696         title = WEB_UI_STRING_KEY("Open", "Open (action menu item)", "action menu item");
697         image = [NSImage imageNamed:@"NSActionMenuOpenInNewWindow"];
698         break;
699
700     case WebActionMenuItemTagPreviewLink:
701         selector = @selector(_quickLookURLFromActionMenu:);
702         title = WEB_UI_STRING_KEY("Preview", "Preview (action menu item)", "action menu item");
703         image = [NSImage imageNamed:@"NSActionMenuQuickLook"];
704         break;
705
706     case WebActionMenuItemTagAddLinkToSafariReadingList:
707         selector = @selector(_addToReadingListFromActionMenu:);
708         title = WEB_UI_STRING_KEY("Add to Reading List", "Add to Reading List (action menu item)", "action menu item");
709         image = [NSImage imageNamed:@"NSActionMenuAddToReadingList"];
710         break;
711
712     case WebActionMenuItemTagCopyText:
713         selector = @selector(_copySelection:);
714         title = WEB_UI_STRING_KEY("Copy", "Copy (text action menu item)", "text action menu item");
715         image = [NSImage imageNamed:@"NSActionMenuCopy"];
716         break;
717
718     case WebActionMenuItemTagLookupText:
719         selector = @selector(_lookupText:);
720         title = WEB_UI_STRING_KEY("Look Up", "Look Up (action menu item)", "action menu item");
721         image = [NSImage imageNamed:@"NSActionMenuLookup"];
722         break;
723
724     case WebActionMenuItemTagPaste:
725         selector = @selector(_paste:);
726         title = WEB_UI_STRING_KEY("Paste", "Paste (action menu item)", "action menu item");
727         image = [NSImage imageNamed:@"NSActionMenuPaste"];
728         break;
729
730     case WebActionMenuItemTagTextSuggestions:
731         title = WEB_UI_STRING_KEY("Suggestions", "Suggestions (action menu item)", "action menu item");
732         image = [NSImage imageNamed:@"NSActionMenuSpelling"];
733         break;
734
735     case WebActionMenuItemTagCopyImage:
736         selector = @selector(_copyImage:);
737         title = WEB_UI_STRING_KEY("Copy", "Copy (image action menu item)", "image action menu item");
738         image = [NSImage imageNamed:@"NSActionMenuCopy"];
739         break;
740
741     case WebActionMenuItemTagAddImageToPhotos:
742         selector = @selector(_addImageToPhotos:);
743         title = WEB_UI_STRING_KEY("Add to Photos", "Add to Photos (action menu item)", "action menu item");
744         image = [NSImage imageNamed:@"NSActionMenuAddToPhotos"];
745         break;
746
747     case WebActionMenuItemTagSaveImageToDownloads:
748         selector = @selector(_saveImageToDownloads:);
749         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (image action menu item)", "image action menu item");
750         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
751         break;
752
753     case WebActionMenuItemTagShareImage:
754         title = WEB_UI_STRING_KEY("Share (image action menu item)", "Share (image action menu item)", "image action menu item");
755         image = [NSImage imageNamed:@"NSActionMenuShare"];
756         break;
757
758     case WebActionMenuItemTagCopyVideoURL:
759         selector = @selector(_copyVideoURL:);
760         title = WEB_UI_STRING_KEY("Copy", "Copy (video action menu item)", "video action menu item");
761         image = [NSImage imageNamed:@"NSActionMenuCopy"];
762         break;
763
764     case WebActionMenuItemTagSaveVideoToDownloads:
765         selector = @selector(_saveVideoToDownloads:);
766         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (video action menu item)", "video action menu item");
767         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
768         break;
769
770     case WebActionMenuItemTagShareVideo:
771         title = WEB_UI_STRING_KEY("Share", "Share (video action menu item)", "video action menu item");
772         image = [NSImage imageNamed:@"NSActionMenuShare"];
773         break;
774
775     default:
776         ASSERT_NOT_REACHED();
777         return nil;
778     }
779
780     RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]);
781     [item setImage:image];
782     [item setTarget:self];
783     [item setTag:tag];
784     return item;
785 }
786
787 - (NSArray *)_defaultMenuItems
788 {
789     NSURL *url = _hitTestResult.absoluteLinkURL();
790     if (url && WebCore::protocolIsInHTTPFamily([url absoluteString])) {
791         _type = WebActionMenuLink;
792         return [self _defaultMenuItemsForLink];
793     }
794
795     if (url && WebCore::protocolIs([url absoluteString], "mailto")) {
796         _type = WebActionMenuMailtoLink;
797         return [self _defaultMenuItemsForMailtoLink];
798     }
799
800     if (!_hitTestResult.absoluteMediaURL().isEmpty()) {
801         _type = WebActionMenuVideo;
802         return [self _defaultMenuItemsForVideo];
803     }
804
805     if (_hitTestResult.image() && !_hitTestResult.absoluteImageURL().isEmpty()) {
806         _type = WebActionMenuImage;
807         return [self _defaultMenuItemsForImage];
808     }
809
810     Node* node = _hitTestResult.innerNode();
811     if (node && node->isTextNode()) {
812         NSArray *dataDetectorMenuItems = [self _defaultMenuItemsForDataDetectedText];
813         if (dataDetectorMenuItems.count) {
814             _type = WebActionMenuDataDetectedItem;
815             return dataDetectorMenuItems;
816         }
817
818         if (_hitTestResult.isContentEditable()) {
819             NSArray *editableTextWithSuggestions = [self _defaultMenuItemsForEditableTextWithSuggestions];
820             if (editableTextWithSuggestions.count) {
821                 _type = WebActionMenuEditableTextWithSuggestions;
822                 return editableTextWithSuggestions;
823             }
824
825             _type = WebActionMenuEditableText;
826             return [self _defaultMenuItemsForEditableText];
827         }
828
829         _type = WebActionMenuReadOnlyText;
830         return [self _defaultMenuItemsForText];
831     }
832
833     if (_hitTestResult.isContentEditable()) {
834         _type = WebActionMenuWhitespaceInEditableArea;
835         return [self _defaultMenuItemsForWhitespaceInEditableArea];
836     }
837
838     _type = WebActionMenuNone;
839     return @[ ];
840 }
841
842 @end