2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "WKActionMenuController.h"
29 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
31 #import "TextIndicator.h"
32 #import "WKNSURLExtras.h"
33 #import "WKViewInternal.h"
35 #import "WKWebViewInternal.h"
36 #import "WebContext.h"
37 #import "WebKitSystemInterface.h"
38 #import "WebPageMessages.h"
39 #import "WebPageProxy.h"
40 #import "WebPageProxyMessages.h"
41 #import "WebProcessProxy.h"
42 #import <Foundation/Foundation.h>
43 #import <ImageIO/ImageIO.h>
44 #import <ImageKit/ImageKit.h>
45 #import <WebCore/DataDetectorsSPI.h>
46 #import <WebCore/GeometryUtilities.h>
47 #import <WebCore/LocalizedStrings.h>
48 #import <WebCore/NSSharingServiceSPI.h>
49 #import <WebCore/NSSharingServicePickerSPI.h>
50 #import <WebCore/NSViewSPI.h>
51 #import <WebCore/SoftLinking.h>
52 #import <WebCore/URL.h>
54 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, ImageKit)
55 SOFT_LINK_CLASS(ImageKit, IKSlideshow)
57 static bool hasDataDetectorsCompletionAPI() {
58 static bool hasCompletionAPI;
59 static dispatch_once_t onceToken;
60 dispatch_once(&onceToken, ^{
61 hasCompletionAPI = [getDDActionsManagerClass() respondsToSelector:@selector(shouldUseActionsWithContext:)] && [getDDActionsManagerClass() respondsToSelector:@selector(didUseActions)];
63 return hasCompletionAPI;
66 using namespace WebCore;
67 using namespace WebKit;
69 @interface WKActionMenuController () <NSSharingServiceDelegate, NSSharingServicePickerDelegate, NSPopoverDelegate>
70 - (void)_updateActionMenuItems;
71 - (BOOL)_canAddMediaToPhotos;
72 - (void)_showTextIndicator;
73 - (void)_hideTextIndicator;
76 @interface WKView (WKDeprecatedSPI)
77 - (NSArray *)_actionMenuItemsForHitTestResult:(WKHitTestResultRef)hitTestResult defaultActionMenuItems:(NSArray *)defaultMenuItems;
82 @class WKPagePreviewViewController;
84 @protocol WKPagePreviewViewControllerDelegate <NSObject>
85 - (NSView *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController viewForPreviewingURL:(NSURL *)url initialFrameSize:(NSSize)initialFrameSize;
86 - (void)pagePreviewViewControllerWasClicked:(WKPagePreviewViewController *)pagePreviewViewController;
89 @interface WKPagePreviewViewController : NSViewController {
92 RetainPtr<NSURL> _url;
93 id <WKPagePreviewViewControllerDelegate> _delegate;
94 CGFloat _popoverToViewScale;
97 - (instancetype)initWithPageURL:(NSURL *)URL mainViewSize:(NSSize)size popoverToViewScale:(CGFloat)scale;
101 @implementation WKPagePreviewViewController
103 - (instancetype)initWithPageURL:(NSURL *)URL mainViewSize:(NSSize)size popoverToViewScale:(CGFloat)scale
105 if (!(self = [super init]))
109 _mainViewSize = size;
110 _popoverToViewScale = scale;
117 NSRect defaultFrame = NSMakeRect(0, 0, _mainViewSize.width, _mainViewSize.height);
118 RetainPtr<NSView> previewView = [_delegate pagePreviewViewController:self viewForPreviewingURL:_url.get() initialFrameSize:defaultFrame.size];
120 RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:defaultFrame]);
121 [webView _setIgnoresNonWheelMouseEvents:YES];
123 NSURLRequest *request = [NSURLRequest requestWithURL:_url.get()];
124 [webView loadRequest:request];
126 previewView = webView;
129 // Setting the webView bounds will scale it to 75% of the _mainViewSize.
130 [previewView setBounds:NSMakeRect(0, 0, _mainViewSize.width / _popoverToViewScale, _mainViewSize.height / _popoverToViewScale)];
132 RetainPtr<NSClickGestureRecognizer> clickRecognizer = adoptNS([[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(_clickRecognized:)]);
133 [previewView addGestureRecognizer:clickRecognizer.get()];
134 self.view = previewView.get();
137 - (void)_clickRecognized:(NSGestureRecognizer *)gestureRecognizer
139 [_delegate pagePreviewViewControllerWasClicked:self];
144 @interface WKActionMenuController () <WKPagePreviewViewControllerDelegate>
149 @implementation WKActionMenuController
151 - (instancetype)initWithPage:(WebPageProxy&)page view:(WKView *)wkView
160 _type = kWKActionMenuNone;
165 - (void)willDestroyView:(WKView *)view
169 _hitTestResult = ActionMenuHitTestResult();
170 _currentActionContext = nil;
173 - (void)prepareForMenu:(NSMenu *)menu withEvent:(NSEvent *)event
175 if (menu != _wkView.actionMenu)
178 if (_wkView._shouldIgnoreMouseEvents) {
179 [menu cancelTracking];
183 [self dismissActionMenuPopovers];
185 _page->performActionMenuHitTestAtLocation([_wkView convertPoint:event.locationInWindow fromView:nil]);
187 _state = ActionMenuState::Pending;
188 [self _updateActionMenuItems];
190 _shouldKeepPreviewPopoverOpen = NO;
193 - (BOOL)isMenuForTextContent
195 return _type == kWKActionMenuReadOnlyText || _type == kWKActionMenuEditableText || _type == kWKActionMenuEditableTextWithSuggestions || _type == kWKActionMenuWhitespaceInEditableArea;
198 - (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event
200 if (menu != _wkView.actionMenu)
203 if (_type == kWKActionMenuDataDetectedItem) {
204 if (_currentActionContext && hasDataDetectorsCompletionAPI()) {
205 if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()]) {
206 [menu cancelTracking];
210 if (menu.numberOfItems == 1) {
211 _page->clearSelection();
212 if (!hasDataDetectorsCompletionAPI())
213 [self _showTextIndicator];
215 _page->selectLastActionMenuRange();
219 if (![self isMenuForTextContent]) {
220 _page->clearSelection();
224 // Action menus for text should highlight the text so that it is clear what the action menu actions
225 // will apply to. If the text is already selected, the menu will use the existing selection.
226 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
227 if (!hitTestResult->isSelected())
228 _page->selectLastActionMenuRange();
231 - (void)didCloseMenu:(NSMenu *)menu withEvent:(NSEvent *)event
233 if (menu != _wkView.actionMenu)
236 if (_type == kWKActionMenuDataDetectedItem) {
237 if (hasDataDetectorsCompletionAPI()) {
238 if (_currentActionContext)
239 [getDDActionsManagerClass() didUseActions];
241 if (menu.numberOfItems > 1)
242 [self _hideTextIndicator];
246 if (!_shouldKeepPreviewPopoverOpen)
247 [self _clearPreviewPopover];
249 _state = ActionMenuState::None;
250 _hitTestResult = ActionMenuHitTestResult();
251 _type = kWKActionMenuNone;
252 _sharingServicePicker = nil;
253 _currentActionContext = nil;
256 - (void)didPerformActionMenuHitTest:(const ActionMenuHitTestResult&)hitTestResult userData:(API::Object*)userData
258 // FIXME: This needs to use the WebKit2 callback mechanism to avoid out-of-order replies.
259 _state = ActionMenuState::Ready;
260 _hitTestResult = hitTestResult;
261 _userData = userData;
263 [self _updateActionMenuItems];
266 - (void)dismissActionMenuPopovers
268 DDActionsManager *actionsManager = [getDDActionsManagerClass() sharedManager];
269 if ([actionsManager respondsToSelector:@selector(requestBubbleClosureUnanchorOnFailure:)])
270 [actionsManager requestBubbleClosureUnanchorOnFailure:YES];
272 [self _hideTextIndicator];
273 [self _clearPreviewPopover];
276 #pragma mark Text Indicator
278 - (void)_showTextIndicator
280 if (_isShowingTextIndicator)
283 if (_hitTestResult.detectedDataTextIndicator) {
284 _page->setTextIndicator(_hitTestResult.detectedDataTextIndicator->data(), false, true);
285 _isShowingTextIndicator = YES;
289 - (void)_hideTextIndicator
291 if (!_isShowingTextIndicator)
294 _page->clearTextIndicator(false, true);
295 _isShowingTextIndicator = NO;
298 #pragma mark Link actions
300 - (NSArray *)_defaultMenuItemsForLink
302 RetainPtr<NSMenuItem> openLinkItem = [self _createActionMenuItemForTag:kWKContextActionItemTagOpenLinkInDefaultBrowser];
304 RetainPtr<NSMenuItem> previewLinkItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPreviewLink];
306 RetainPtr<NSMenuItem> previewLinkItem = [NSMenuItem separatorItem];
308 RetainPtr<NSMenuItem> readingListItem = [self _createActionMenuItemForTag:kWKContextActionItemTagAddLinkToSafariReadingList];
310 return @[ openLinkItem.get(), previewLinkItem.get(), [NSMenuItem separatorItem], readingListItem.get() ];
313 - (void)_openURLFromActionMenu:(id)sender
315 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
316 [[NSWorkspace sharedWorkspace] openURL:[NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()]];
319 - (void)_addToReadingListFromActionMenu:(id)sender
321 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
322 NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameAddToSafariReadingList];
323 [service performWithItems:@[ [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()] ]];
327 - (void)_keepPreviewOpenFromActionMenu:(id)sender
329 _shouldKeepPreviewPopoverOpen = YES;
332 - (void)_previewURLFromActionMenu:(id)sender
334 // We might already have a preview showing if the menu item was highlighted earlier.
338 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
339 NSURL *url = [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()];
340 NSRect originRect = hitTestResult->elementBoundingBox();
341 [self _createPreviewPopoverForURL:url originRect:originRect];
342 [_previewPopover showRelativeToRect:originRect ofView:_wkView preferredEdge:NSMaxYEdge];
345 - (void)_createPreviewPopoverForURL:(NSURL *)url originRect:(NSRect)originRect
347 NSSize popoverSize = [self _preferredSizeForPopoverPresentedFromOriginRect:originRect];
348 CGFloat actualPopoverToViewScale = popoverSize.width / NSWidth(_wkView.bounds);
349 _previewViewController = adoptNS([[WKPagePreviewViewController alloc] initWithPageURL:url mainViewSize:_wkView.bounds.size popoverToViewScale:actualPopoverToViewScale]);
350 _previewViewController->_delegate = self;
352 _previewPopover = adoptNS([[NSPopover alloc] init]);
353 [_previewPopover setBehavior:NSPopoverBehaviorTransient];
354 [_previewPopover setContentSize:popoverSize];
355 [_previewPopover setContentViewController:_previewViewController.get()];
356 [_previewPopover setDelegate:self];
359 static bool targetSizeFitsInAvailableSpace(NSSize targetSize, NSSize availableSpace)
361 return targetSize.width <= availableSpace.width && targetSize.height <= availableSpace.height;
364 - (NSSize)_preferredSizeForPopoverPresentedFromOriginRect:(NSRect)originRect
366 static const CGFloat preferredPopoverToViewScale = 0.75;
367 static const CGFloat screenPadding = 40;
369 NSWindow *window = _wkView.window;
370 NSRect originScreenRect = [window convertRectToScreen:[_wkView convertRect:originRect toView:nil]];
371 NSRect screenFrame = window.screen.visibleFrame;
373 NSRect wkViewBounds = _wkView.bounds;
374 NSSize targetSize = NSMakeSize(NSWidth(wkViewBounds) * preferredPopoverToViewScale, NSHeight(wkViewBounds) * preferredPopoverToViewScale);
376 CGFloat availableSpaceAbove = NSMaxY(screenFrame) - NSMaxY(originScreenRect);
377 CGFloat availableSpaceBelow = NSMinY(originScreenRect) - NSMinY(screenFrame);
378 CGFloat maxAvailableVerticalSpace = fmax(availableSpaceAbove, availableSpaceBelow) - screenPadding;
379 NSSize maxSpaceAvailableOnYEdge = NSMakeSize(screenFrame.size.width - screenPadding, maxAvailableVerticalSpace);
380 if (targetSizeFitsInAvailableSpace(targetSize, maxSpaceAvailableOnYEdge))
383 CGFloat availableSpaceAtLeft = NSMinX(originScreenRect) - NSMinX(screenFrame);
384 CGFloat availableSpaceAtRight = NSMaxX(screenFrame) - NSMaxX(originScreenRect);
385 CGFloat maxAvailableHorizontalSpace = fmax(availableSpaceAtLeft, availableSpaceAtRight) - screenPadding;
386 NSSize maxSpaceAvailableOnXEdge = NSMakeSize(maxAvailableHorizontalSpace, screenFrame.size.height - screenPadding);
387 if (targetSizeFitsInAvailableSpace(targetSize, maxSpaceAvailableOnXEdge))
390 // If the target size doesn't fit anywhere, we'll find the largest rect that does fit that also maintains the original view's aspect ratio.
391 CGFloat aspectRatio = wkViewBounds.size.width / wkViewBounds.size.height;
392 FloatRect maxVerticalTargetSizePreservingAspectRatioRect = largestRectWithAspectRatioInsideRect(aspectRatio, FloatRect(0, 0, maxSpaceAvailableOnYEdge.width, maxSpaceAvailableOnYEdge.height));
393 FloatRect maxHorizontalTargetSizePreservingAspectRatioRect = largestRectWithAspectRatioInsideRect(aspectRatio, FloatRect(0, 0, maxSpaceAvailableOnXEdge.width, maxSpaceAvailableOnXEdge.height));
395 NSSize maxVerticalTargetSizePreservingAspectRatio = NSMakeSize(maxVerticalTargetSizePreservingAspectRatioRect.width(), maxVerticalTargetSizePreservingAspectRatioRect.height());
396 NSSize maxHortizontalTargetSizePreservingAspectRatio = NSMakeSize(maxHorizontalTargetSizePreservingAspectRatioRect.width(), maxHorizontalTargetSizePreservingAspectRatioRect.height());
398 if ((maxVerticalTargetSizePreservingAspectRatio.width * maxVerticalTargetSizePreservingAspectRatio.height) > (maxHortizontalTargetSizePreservingAspectRatio.width * maxHortizontalTargetSizePreservingAspectRatio.height))
399 return maxVerticalTargetSizePreservingAspectRatio;
400 return maxHortizontalTargetSizePreservingAspectRatio;
403 #endif // WK_API_ENABLED
405 - (void)_clearPreviewPopover
408 if (_previewViewController) {
409 _previewViewController->_delegate = nil;
410 [_wkView _finishPreviewingURL:_previewViewController->_url.get() withPreviewView:[_previewViewController view]];
411 _previewViewController = nil;
415 [_previewPopover close];
416 [_previewPopover setDelegate:nil];
417 _previewPopover = nil;
420 #pragma mark Video actions
422 - (NSArray *)_defaultMenuItemsForVideo
424 RetainPtr<NSMenuItem> copyVideoURLItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyVideoURL];
426 RetainPtr<NSMenuItem> saveToDownloadsItem;
427 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
428 if (hitTestResult->isDownloadableMedia())
429 saveToDownloadsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagSaveVideoToDownloads];
431 saveToDownloadsItem = [NSMenuItem separatorItem];
433 RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:kWKContextActionItemTagShareVideo];
434 String videoURL = hitTestResult->absoluteMediaURL();
435 if (!videoURL.isEmpty()) {
436 _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ videoURL ]]);
437 [_sharingServicePicker setDelegate:self];
438 [shareItem setSubmenu:[_sharingServicePicker menu]];
441 return @[ copyVideoURLItem.get(), [NSMenuItem separatorItem], saveToDownloadsItem.get(), shareItem.get() ];
444 - (void)_copyVideoURL:(id)sender
446 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
448 [[NSPasteboard generalPasteboard] clearContents];
449 [[NSPasteboard generalPasteboard] writeObjects:@[ hitTestResult->absoluteMediaURL() ]];
452 - (void)_saveVideoToDownloads:(id)sender
454 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
455 _page->process().context().download(_page, hitTestResult->absoluteMediaURL());
458 #pragma mark Image actions
460 - (NSArray *)_defaultMenuItemsForImage
462 RetainPtr<NSMenuItem> copyImageItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyImage];
463 RetainPtr<NSMenuItem> addToPhotosItem;
464 if ([self _canAddMediaToPhotos])
465 addToPhotosItem = [self _createActionMenuItemForTag:kWKContextActionItemTagAddImageToPhotos];
467 addToPhotosItem = [NSMenuItem separatorItem];
468 RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagSaveImageToDownloads];
469 RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:kWKContextActionItemTagShareImage];
471 if (RefPtr<ShareableBitmap> bitmap = _hitTestResult.image) {
472 RetainPtr<CGImageRef> image = bitmap->makeCGImage();
473 RetainPtr<NSImage> nsImage = adoptNS([[NSImage alloc] initWithCGImage:image.get() size:NSZeroSize]);
474 _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ nsImage.get() ]]);
475 [_sharingServicePicker setDelegate:self];
476 [shareItem setSubmenu:[_sharingServicePicker menu]];
479 return @[ copyImageItem.get(), addToPhotosItem.get(), saveToDownloadsItem.get(), shareItem.get() ];
482 - (void)_copyImage:(id)sender
484 RefPtr<ShareableBitmap> bitmap = _hitTestResult.image;
488 RetainPtr<CGImageRef> image = bitmap->makeCGImage();
489 RetainPtr<NSImage> nsImage = adoptNS([[NSImage alloc] initWithCGImage:image.get() size:NSZeroSize]);
490 [[NSPasteboard generalPasteboard] clearContents];
491 [[NSPasteboard generalPasteboard] writeObjects:@[ nsImage.get() ]];
494 - (void)_saveImageToDownloads:(id)sender
496 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
497 _page->process().context().download(_page, hitTestResult->absoluteImageURL());
500 // FIXME: We should try to share this with WebPageProxyMac's similar PDF functions.
501 static NSString *temporaryPhotosDirectoryPath()
503 static NSString *temporaryPhotosDirectoryPath;
505 if (!temporaryPhotosDirectoryPath) {
506 NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPhotos-XXXXXX"];
507 CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
509 if (mkdtemp(templateRepresentation.mutableData()))
510 temporaryPhotosDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
513 return temporaryPhotosDirectoryPath;
516 static NSString *pathToPhotoOnDisk(NSString *suggestedFilename)
518 NSString *photoDirectoryPath = temporaryPhotosDirectoryPath();
519 if (!photoDirectoryPath) {
520 WTFLogAlways("Cannot create temporary photo download directory.");
524 NSString *path = [photoDirectoryPath stringByAppendingPathComponent:suggestedFilename];
526 NSFileManager *fileManager = [NSFileManager defaultManager];
527 if ([fileManager fileExistsAtPath:path]) {
528 NSString *pathTemplatePrefix = [photoDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
529 NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
530 CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
532 int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
534 WTFLogAlways("Cannot create photo file in the temporary directory (%@).", suggestedFilename);
539 path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
545 - (BOOL)_canAddMediaToPhotos
547 return [getIKSlideshowClass() canExportToApplication:@"com.apple.Photos"];
550 - (void)_addImageToPhotos:(id)sender
552 if (![self _canAddMediaToPhotos])
555 RefPtr<ShareableBitmap> bitmap = _hitTestResult.image;
558 RetainPtr<CGImageRef> image = bitmap->makeCGImage();
560 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
561 NSString * const suggestedFilename = @"image.jpg";
563 NSString *filePath = pathToPhotoOnDisk(suggestedFilename);
567 NSURL *fileURL = [NSURL fileURLWithPath:filePath];
568 auto dest = adoptCF(CGImageDestinationCreateWithURL((CFURLRef)fileURL, kUTTypeJPEG, 1, nullptr));
569 CGImageDestinationAddImage(dest.get(), image.get(), nullptr);
570 CGImageDestinationFinalize(dest.get());
572 dispatch_async(dispatch_get_main_queue(), ^{
573 // This API provides no way to report failure, but if 18420778 is fixed so that it does, we should handle this.
574 [getIKSlideshowClass() exportSlideshowItem:filePath toApplication:@"com.apple.Photos"];
579 #pragma mark Text actions
581 - (NSArray *)_defaultMenuItemsForDataDetectedText
583 DDActionContext *actionContext = _hitTestResult.actionContext.get();
587 if (hasDataDetectorsCompletionAPI()) {
588 _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
589 } interactionChangedHandler:^() {
590 [self _showTextIndicator];
591 } interactionStoppedHandler:^() {
592 [self _hideTextIndicator];
595 _currentActionContext = actionContext;
597 [_currentActionContext setCompletionHandler:^() {
598 [self _hideTextIndicator];
601 [_currentActionContext setForActionMenuContent:YES];
604 [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
606 return [[getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()];
609 - (NSArray *)_defaultMenuItemsForText
611 RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
612 RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
614 return @[ copyTextItem.get(), lookupTextItem.get() ];
617 - (NSArray *)_defaultMenuItemsForEditableText
619 RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
620 RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
621 RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
623 return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
626 - (NSArray *)_defaultMenuItemsForEditableTextWithSuggestions
628 if (_hitTestResult.lookupText.isEmpty())
631 Vector<TextCheckingResult> results;
632 _page->checkTextOfParagraph(_hitTestResult.lookupText, NSTextCheckingTypeSpelling, results);
633 if (results.isEmpty())
636 Vector<String> guesses;
637 _page->getGuessesForWord(_hitTestResult.lookupText, String(), guesses);
638 if (guesses.isEmpty())
641 RetainPtr<NSMenu> spellingSubMenu = adoptNS([[NSMenu alloc] init]);
642 for (const auto& guess : guesses) {
643 RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:guess action:@selector(_changeSelectionToSuggestion:) keyEquivalent:@""]);
644 [item setRepresentedObject:guess];
645 [item setTarget:self];
646 [spellingSubMenu addItem:item.get()];
649 RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
650 RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
651 RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
652 RetainPtr<NSMenuItem> textSuggestionsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagTextSuggestions];
654 [textSuggestionsItem setSubmenu:spellingSubMenu.get()];
656 return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get(), textSuggestionsItem.get() ];
659 - (void)_copySelection:(id)sender
661 _page->executeEditCommand("copy");
664 - (void)_paste:(id)sender
666 _page->executeEditCommand("paste");
669 - (void)_lookupText:(id)sender
671 _page->performDictionaryLookupOfCurrentSelection();
674 - (void)_changeSelectionToSuggestion:(id)sender
676 NSString *selectedCorrection = [sender representedObject];
677 if (!selectedCorrection)
680 ASSERT([selectedCorrection isKindOfClass:[NSString class]]);
682 _page->changeSpellingToWord(selectedCorrection);
685 #pragma mark Whitespace actions
687 - (NSArray *)_defaultMenuItemsForWhitespaceInEditableArea
689 RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
691 return @[ [NSMenuItem separatorItem], [NSMenuItem separatorItem], pasteItem.get() ];
694 #pragma mark Mailto Link actions
696 - (NSArray *)_defaultMenuItemsForMailtoLink
698 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
700 RetainPtr<DDActionContext> actionContext = [[getDDActionContextClass() alloc] init];
701 [actionContext setForActionMenuContent:YES];
702 [actionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:hitTestResult->elementBoundingBox() toView:nil]]];
703 return [[getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:hitTestResult->absoluteLinkURL() actionContext:actionContext.get()];
706 #pragma mark NSMenuDelegate implementation
708 - (void)menuNeedsUpdate:(NSMenu *)menu
710 if (menu != _wkView.actionMenu)
713 ASSERT(_state != ActionMenuState::None);
715 // FIXME: We need to be able to cancel this if the menu goes away.
716 // FIXME: Connection can be null if the process is closed; we should clean up better in that case.
717 if (_state == ActionMenuState::Pending) {
718 if (auto* connection = _page->process().connection())
719 connection->waitForAndDispatchImmediately<Messages::WebPageProxy::DidPerformActionMenuHitTest>(_page->pageID(), std::chrono::milliseconds(500));
722 if (_state != ActionMenuState::Ready)
723 [self _updateActionMenuItems];
726 - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
729 if (item.tag != kWKContextActionItemTagPreviewLink)
731 [self _previewURLFromActionMenu:item];
735 #pragma mark NSSharingServicePickerDelegate implementation
737 - (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)mask proposedSharingServices:(NSArray *)proposedServices
739 RetainPtr<NSMutableArray> services = adoptNS([[NSMutableArray alloc] initWithCapacity:proposedServices.count]);
741 for (NSSharingService *service in proposedServices) {
742 if ([service.name isEqualToString:NSSharingServiceNameAddToIPhoto])
744 [services addObject:service];
747 return services.autorelease();
750 - (id <NSSharingServiceDelegate>)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker delegateForSharingService:(NSSharingService *)sharingService
755 #pragma mark NSSharingServiceDelegate implementation
757 - (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
759 return _wkView.window;
762 #pragma mark NSPopoverDelegate implementation
764 - (void)popoverWillClose:(NSNotification *)notification
766 _shouldKeepPreviewPopoverOpen = NO;
767 [self _clearPreviewPopover];
770 #pragma mark Menu Items
772 - (RetainPtr<NSMenuItem>)_createActionMenuItemForTag:(uint32_t)tag
774 SEL selector = nullptr;
775 NSString *title = nil;
776 NSImage *image = nil;
779 case kWKContextActionItemTagOpenLinkInDefaultBrowser:
780 selector = @selector(_openURLFromActionMenu:);
781 title = WEB_UI_STRING_KEY("Open", "Open (action menu item)", "action menu item");
782 image = [NSImage imageNamed:@"NSActionMenuOpenInNewWindow"];
786 case kWKContextActionItemTagPreviewLink:
787 selector = @selector(_keepPreviewOpenFromActionMenu:);
788 title = WEB_UI_STRING_KEY("Preview", "Preview (action menu item)", "action menu item");
789 image = [NSImage imageNamed:@"NSActionMenuQuickLook"];
793 case kWKContextActionItemTagAddLinkToSafariReadingList:
794 selector = @selector(_addToReadingListFromActionMenu:);
795 title = WEB_UI_STRING_KEY("Add to Reading List", "Add to Reading List (action menu item)", "action menu item");
796 image = [NSImage imageNamed:@"NSActionMenuAddToReadingList"];
799 case kWKContextActionItemTagCopyImage:
800 selector = @selector(_copyImage:);
801 title = WEB_UI_STRING_KEY("Copy", "Copy (image action menu item)", "image action menu item");
802 image = [NSImage imageNamed:@"NSActionMenuCopy"];
805 case kWKContextActionItemTagAddImageToPhotos:
806 selector = @selector(_addImageToPhotos:);
807 title = WEB_UI_STRING_KEY("Add to Photos", "Add to Photos (action menu item)", "action menu item");
808 image = [NSImage imageNamed:@"NSActionMenuAddToPhotos"];
811 case kWKContextActionItemTagSaveImageToDownloads:
812 selector = @selector(_saveImageToDownloads:);
813 title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (image action menu item)", "image action menu item");
814 image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
817 case kWKContextActionItemTagShareImage:
818 title = WEB_UI_STRING_KEY("Share (image action menu item)", "Share (image action menu item)", "image action menu item");
819 image = [NSImage imageNamed:@"NSActionMenuShare"];
822 case kWKContextActionItemTagCopyText:
823 selector = @selector(_copySelection:);
824 title = WEB_UI_STRING_KEY("Copy", "Copy (text action menu item)", "text action menu item");
825 image = [NSImage imageNamed:@"NSActionMenuCopy"];
828 case kWKContextActionItemTagLookupText:
829 selector = @selector(_lookupText:);
830 title = WEB_UI_STRING_KEY("Look Up", "Look Up (action menu item)", "action menu item");
831 image = [NSImage imageNamed:@"NSActionMenuLookup"];
834 case kWKContextActionItemTagPaste:
835 selector = @selector(_paste:);
836 title = WEB_UI_STRING_KEY("Paste", "Paste (action menu item)", "action menu item");
837 image = [NSImage imageNamed:@"NSActionMenuPaste"];
840 case kWKContextActionItemTagTextSuggestions:
841 title = WEB_UI_STRING_KEY("Suggestions", "Suggestions (action menu item)", "action menu item");
842 image = [NSImage imageNamed:@"NSActionMenuSpelling"];
845 case kWKContextActionItemTagCopyVideoURL:
846 selector = @selector(_copyVideoURL:);
847 title = WEB_UI_STRING_KEY("Copy", "Copy (video action menu item)", "video action menu item");
848 image = [NSImage imageNamed:@"NSActionMenuCopy"];
851 case kWKContextActionItemTagSaveVideoToDownloads:
852 selector = @selector(_saveVideoToDownloads:);
853 title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (video action menu item)", "video action menu item");
854 image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
857 case kWKContextActionItemTagShareVideo:
858 title = WEB_UI_STRING_KEY("Share", "Share (video action menu item)", "video action menu item");
859 image = [NSImage imageNamed:@"NSActionMenuShare"];
863 ASSERT_NOT_REACHED();
867 RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]);
868 [item setImage:image];
869 [item setTarget:self];
874 - (PassRefPtr<WebHitTestResult>)_webHitTestResult
876 RefPtr<WebHitTestResult> hitTestResult;
877 if (_state == ActionMenuState::Ready)
878 hitTestResult = WebHitTestResult::create(_hitTestResult.hitTestResult);
880 hitTestResult = _page->lastMouseMoveHitTestResult();
882 return hitTestResult.release();
885 - (NSArray *)_defaultMenuItems
887 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
888 if (!hitTestResult) {
889 _type = kWKActionMenuNone;
890 return _state != ActionMenuState::Ready ? @[ [NSMenuItem separatorItem] ] : @[ ];
893 String absoluteLinkURL = hitTestResult->absoluteLinkURL();
894 if (!absoluteLinkURL.isEmpty()) {
895 if (WebCore::protocolIsInHTTPFamily(absoluteLinkURL)) {
896 _type = kWKActionMenuLink;
897 return [self _defaultMenuItemsForLink];
900 if (protocolIs(absoluteLinkURL, "mailto")) {
901 _type = kWKActionMenuMailtoLink;
902 return [self _defaultMenuItemsForMailtoLink];
906 if (!hitTestResult->absoluteMediaURL().isEmpty()) {
907 _type = kWKActionMenuVideo;
908 return [self _defaultMenuItemsForVideo];
911 if (!hitTestResult->absoluteImageURL().isEmpty() && _hitTestResult.image) {
912 _type = kWKActionMenuImage;
913 return [self _defaultMenuItemsForImage];
916 if (hitTestResult->isTextNode()) {
917 NSArray *dataDetectorMenuItems = [self _defaultMenuItemsForDataDetectedText];
918 if (dataDetectorMenuItems.count) {
919 _type = kWKActionMenuDataDetectedItem;
920 return dataDetectorMenuItems;
923 if (hitTestResult->isContentEditable()) {
924 NSArray *editableTextWithSuggestions = [self _defaultMenuItemsForEditableTextWithSuggestions];
925 if (editableTextWithSuggestions.count) {
926 _type = kWKActionMenuEditableTextWithSuggestions;
927 return editableTextWithSuggestions;
930 _type = kWKActionMenuEditableText;
931 return [self _defaultMenuItemsForEditableText];
934 _type = kWKActionMenuReadOnlyText;
935 return [self _defaultMenuItemsForText];
938 if (hitTestResult->isContentEditable()) {
939 _type = kWKActionMenuWhitespaceInEditableArea;
940 return [self _defaultMenuItemsForWhitespaceInEditableArea];
943 _type = kWKActionMenuNone;
944 return _state != ActionMenuState::Ready ? @[ [NSMenuItem separatorItem] ] : @[ ];
947 - (void)_updateActionMenuItems
949 [_wkView.actionMenu removeAllItems];
951 NSArray *menuItems = [self _defaultMenuItems];
952 RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
954 if ([_wkView respondsToSelector:@selector(_actionMenuItemsForHitTestResult:defaultActionMenuItems:)])
955 menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) defaultActionMenuItems:menuItems];
957 menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) withType:_type defaultActionMenuItems:menuItems userData:toAPI(_userData.get())];
959 for (NSMenuItem *item in menuItems)
960 [_wkView.actionMenu addItem:item];
962 if (_state == ActionMenuState::Ready && !_wkView.actionMenu.numberOfItems)
963 [_wkView.actionMenu cancelTracking];
968 #pragma mark WKPagePreviewViewControllerDelegate
970 - (NSView *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController viewForPreviewingURL:(NSURL *)url initialFrameSize:(NSSize)initialFrameSize
972 return [_wkView _viewForPreviewingURL:url initialFrameSize:initialFrameSize];
975 - (void)pagePreviewViewControllerWasClicked:(WKPagePreviewViewController *)pagePreviewViewController
977 if (NSURL *url = pagePreviewViewController->_url.get())
978 [[NSWorkspace sharedWorkspace] openURL:url];
985 #endif // PLATFORM(MAC)