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