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