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