If the action menu hit test times out, a menu with a single separator appears
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / WKActionMenuController.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 "config.h"
27 #import "WKActionMenuController.h"
28
29 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
30
31 #import "WKNSURLExtras.h"
32 #import "WKViewInternal.h"
33 #import "WKWebView.h"
34 #import "WKWebViewInternal.h"
35 #import "WebContext.h"
36 #import "WebKitSystemInterface.h"
37 #import "WebPageMessages.h"
38 #import "WebPageProxy.h"
39 #import "WebPageProxyMessages.h"
40 #import "WebProcessProxy.h"
41 #import <Foundation/Foundation.h>
42 #import <ImageIO/ImageIO.h>
43 #import <ImageKit/ImageKit.h>
44 #import <WebCore/DataDetectorsSPI.h>
45 #import <WebCore/GeometryUtilities.h>
46 #import <WebCore/LocalizedStrings.h>
47 #import <WebCore/LookupSPI.h>
48 #import <WebCore/NSMenuSPI.h>
49 #import <WebCore/NSSharingServiceSPI.h>
50 #import <WebCore/NSSharingServicePickerSPI.h>
51 #import <WebCore/NSViewSPI.h>
52 #import <WebCore/QuickLookMacSPI.h>
53 #import <WebCore/SoftLinking.h>
54 #import <WebCore/TextIndicator.h>
55 #import <WebCore/URL.h>
56
57 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, QuickLookUI)
58 SOFT_LINK_CLASS(QuickLookUI, QLPreviewMenuItem)
59
60 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(Quartz, ImageKit)
61 SOFT_LINK_CLASS(ImageKit, IKSlideshow)
62
63 using namespace WebCore;
64 using namespace WebKit;
65
66 @interface WKActionMenuController () <NSSharingServiceDelegate, NSSharingServicePickerDelegate, NSPopoverDelegate, QLPreviewMenuItemDelegate>
67 - (void)_updateActionMenuItems;
68 - (BOOL)_canAddMediaToPhotos;
69 - (void)_showTextIndicator;
70 - (void)_hideTextIndicator;
71 - (void)_clearActionMenuState;
72 @end
73
74 @interface WKView (WKDeprecatedSPI)
75 - (NSArray *)_actionMenuItemsForHitTestResult:(WKHitTestResultRef)hitTestResult defaultActionMenuItems:(NSArray *)defaultMenuItems;
76 @end
77
78 #if WK_API_ENABLED
79
80 static const CGFloat previewViewInset = 3;
81 static const CGFloat previewViewTitleHeight = 34;
82
83 @class WKPagePreviewViewController;
84
85 @protocol WKPagePreviewViewControllerDelegate <NSObject>
86 - (NSView *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController viewForPreviewingURL:(NSURL *)url initialFrameSize:(NSSize)initialFrameSize;
87 - (NSString *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController titleForPreviewOfURL:(NSURL *)url;
88 - (void)pagePreviewViewControllerWasClicked:(WKPagePreviewViewController *)pagePreviewViewController;
89 @end
90
91 @interface WKPagePreviewViewController : NSViewController {
92 @public
93     NSSize _mainViewSize;
94     RetainPtr<NSURL> _url;
95     RetainPtr<NSView> _previewView;
96     RetainPtr<NSTextField> _titleTextField;
97     RetainPtr<NSString> _previewTitle;
98     id <WKPagePreviewViewControllerDelegate> _delegate;
99     CGFloat _popoverToViewScale;
100 }
101
102 @property (nonatomic, copy) NSString *previewTitle;
103
104 - (instancetype)initWithPageURL:(NSURL *)URL mainViewSize:(NSSize)size popoverToViewScale:(CGFloat)scale;
105
106 + (NSSize)previewPadding;
107
108 @end
109
110 @implementation WKPagePreviewViewController
111
112 - (instancetype)initWithPageURL:(NSURL *)URL mainViewSize:(NSSize)size popoverToViewScale:(CGFloat)scale
113 {
114     if (!(self = [super init]))
115         return nil;
116
117     _url = URL;
118     _mainViewSize = size;
119     _popoverToViewScale = scale;
120
121     return self;
122 }
123
124 - (NSString *)previewTitle
125 {
126     return _previewTitle.get();
127 }
128
129 - (void)setPreviewTitle:(NSString *)previewTitle
130 {
131     if ([_previewTitle isEqualToString:previewTitle])
132         return;
133
134     // Keep a separate copy around in case this is received before the view hierarchy is created.
135     _previewTitle = adoptNS([previewTitle copy]);
136     [_titleTextField setStringValue:previewTitle ? previewTitle : @""];
137 }
138
139 + (NSSize)previewPadding
140 {
141     return NSMakeSize(2 * previewViewInset, previewViewTitleHeight + 2 * previewViewInset);
142 }
143
144 - (void)loadView
145 {
146     NSRect defaultFrame = NSMakeRect(0, 0, _mainViewSize.width, _mainViewSize.height);
147     _previewView = [_delegate pagePreviewViewController:self viewForPreviewingURL:_url.get() initialFrameSize:defaultFrame.size];
148     if (!_previewView) {
149         RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:defaultFrame]);
150         [webView _setIgnoresNonWheelEvents:YES];
151         if (_url) {
152             NSURLRequest *request = [NSURLRequest requestWithURL:_url.get()];
153             [webView loadRequest:request];
154         }
155         _previewView = webView;
156     }
157
158     RetainPtr<NSClickGestureRecognizer> clickRecognizer = adoptNS([[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(_clickRecognized:)]);
159     [_previewView addGestureRecognizer:clickRecognizer.get()];
160
161     NSRect previewFrame = [_previewView frame];
162     NSRect containerFrame = previewFrame;
163     NSSize totalPadding = [[self class] previewPadding];
164     containerFrame.size.width += totalPadding.width;
165     containerFrame.size.height += totalPadding.height;
166     previewFrame = NSOffsetRect(previewFrame, previewViewInset, previewViewInset);
167
168     RetainPtr<NSView> containerView = adoptNS([[NSView alloc] initWithFrame:containerFrame]);
169     [containerView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
170     [containerView addSubview:_previewView.get()];
171     [_previewView setFrame:previewFrame];
172     [_previewView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
173
174     _titleTextField = adoptNS([[NSTextField alloc] init]);
175     [_titleTextField setWantsLayer:YES];
176     [_titleTextField setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
177     [_titleTextField setEditable:NO];
178     [_titleTextField setBezeled:NO];
179     [_titleTextField setDrawsBackground:NO];
180     [_titleTextField setAlignment:NSCenterTextAlignment];
181     [_titleTextField setUsesSingleLineMode:YES];
182     [_titleTextField setLineBreakMode:NSLineBreakByTruncatingTail];
183     [_titleTextField setTextColor:[NSColor labelColor]];
184
185     NSString *title = _previewTitle.get();
186     if (!title)
187         title = [_delegate pagePreviewViewController:self titleForPreviewOfURL:_url.get()];
188     if (!title)
189         title = [_url absoluteString];
190
191     [_titleTextField setStringValue:title ? title : @""];
192
193     [_titleTextField sizeToFit];
194     NSSize titleFittingSize = [_titleTextField frame].size;
195     CGFloat textFieldCenteringOffset = (NSMaxY(containerFrame) - NSMaxY(previewFrame) - titleFittingSize.height) / 2;
196
197     NSRect titleFrame = previewFrame;
198     titleFrame.size.height = titleFittingSize.height;
199     titleFrame.origin.y = NSMaxY(previewFrame) + textFieldCenteringOffset;
200     [_titleTextField setFrame:titleFrame];
201     [containerView addSubview:_titleTextField.get()];
202
203     // Setting the webView bounds will scale it to 75% of the _mainViewSize.
204     [_previewView setBounds:NSMakeRect(0, 0, _mainViewSize.width / _popoverToViewScale, _mainViewSize.height / _popoverToViewScale)];
205
206     self.view = containerView.get();
207 }
208
209 - (void)_clickRecognized:(NSGestureRecognizer *)gestureRecognizer
210 {
211     if (gestureRecognizer.state == NSGestureRecognizerStateBegan)
212         [_delegate pagePreviewViewControllerWasClicked:self];
213 }
214
215 @end
216
217 @interface WKActionMenuController () <WKPagePreviewViewControllerDelegate>
218 @end
219
220 #endif
221
222 @implementation WKActionMenuController
223
224 - (instancetype)initWithPage:(WebPageProxy&)page view:(WKView *)wkView
225 {
226     self = [super init];
227
228     if (!self)
229         return nil;
230
231     _page = &page;
232     _wkView = wkView;
233     _type = kWKActionMenuNone;
234
235     return self;
236 }
237
238 - (void)willDestroyView:(WKView *)view
239 {
240     _page = nullptr;
241     _wkView = nil;
242     _hitTestResult = ActionMenuHitTestResult();
243     _currentActionContext = nil;
244 }
245
246 - (void)wkView:(WKView *)wkView willHandleMouseDown:(NSEvent *)event
247 {
248     [self _clearActionMenuState];
249 }
250
251 - (void)prepareForMenu:(NSMenu *)menu withEvent:(NSEvent *)event
252 {
253     if (menu != _wkView.actionMenu)
254         return;
255
256     [self dismissActionMenuPopovers];
257
258     _eventLocationInView = [_wkView convertPoint:event.locationInWindow fromView:nil];
259     _page->performActionMenuHitTestAtLocation(_eventLocationInView);
260
261     _state = ActionMenuState::Pending;
262     [self _updateActionMenuItems];
263
264     _shouldKeepPreviewPopoverOpen = NO;
265 }
266
267 - (BOOL)isMenuForTextContent
268 {
269     return _type == kWKActionMenuReadOnlyText || _type == kWKActionMenuEditableText || _type == kWKActionMenuEditableTextWithSuggestions;
270 }
271
272 - (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event
273 {
274     if (menu != _wkView.actionMenu)
275         return;
276
277     if (!menu.numberOfItems)
278         return;
279
280     if (_type == kWKActionMenuDataDetectedItem) {
281         if (menu.numberOfItems == 1)
282             _page->clearSelection();
283         else
284             _page->selectLastActionMenuRange();
285         return;
286     }
287
288     if (_type == kWKActionMenuWhitespaceInEditableArea) {
289         _page->focusAndSelectLastActionMenuHitTestResult();
290         return;
291     }
292
293 #if WK_API_ENABLED
294     if (_type == kWKActionMenuLink)
295         [self _createPreviewPopover];
296 #endif
297
298     if (![self isMenuForTextContent]) {
299         _page->clearSelection();
300         return;
301     }
302
303     // Action menus for text should highlight the text so that it is clear what the action menu actions
304     // will apply to. If the text is already selected, the menu will use the existing selection.
305     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
306     if (!hitTestResult->isSelected())
307         _page->selectLastActionMenuRange();
308 }
309
310 - (void)didCloseMenu:(NSMenu *)menu withEvent:(NSEvent *)event
311 {
312     if (menu != _wkView.actionMenu)
313         return;
314
315     [_previewPopover setBehavior:NSPopoverBehaviorTransient];
316     if (!_shouldKeepPreviewPopoverOpen)
317         [self _clearPreviewPopover];
318
319     [self _clearActionMenuState];
320 }
321
322 - (void)_clearActionMenuState
323 {
324     if (_currentActionContext && _hasActivatedActionContext) {
325         [getDDActionsManagerClass() didUseActions];
326         _hasActivatedActionContext = NO;
327     }
328
329     _state = ActionMenuState::None;
330     _hitTestResult = ActionMenuHitTestResult();
331     _type = kWKActionMenuNone;
332     _sharingServicePicker = nil;
333     _currentActionContext = nil;
334     _userData = nil;
335 }
336
337 - (void)didPerformActionMenuHitTest:(const ActionMenuHitTestResult&)hitTestResult userData:(API::Object*)userData
338 {
339     // FIXME: This needs to use the WebKit2 callback mechanism to avoid out-of-order replies.
340     _state = ActionMenuState::Ready;
341     _hitTestResult = hitTestResult;
342     _userData = userData;
343
344     [self _updateActionMenuItems];
345 }
346
347 - (void)dismissActionMenuPopovers
348 {
349     DDActionsManager *actionsManager = [getDDActionsManagerClass() sharedManager];
350     if ([actionsManager respondsToSelector:@selector(requestBubbleClosureUnanchorOnFailure:)])
351         [actionsManager requestBubbleClosureUnanchorOnFailure:YES];
352
353     [self _hideTextIndicator];
354     [self _clearPreviewPopover];
355 }
356
357 - (void)setPreviewTitle:(NSString *)previewTitle
358 {
359 #if WK_API_ENABLED
360     [_previewViewController setPreviewTitle:previewTitle];
361 #endif
362 }
363
364 #pragma mark Text Indicator
365
366 - (void)_showTextIndicator
367 {
368     if (_isShowingTextIndicator)
369         return;
370
371     if (_hitTestResult.detectedDataTextIndicator) {
372         _page->setTextIndicator(_hitTestResult.detectedDataTextIndicator->data(), false);
373         _isShowingTextIndicator = YES;
374     }
375 }
376
377 - (void)_hideTextIndicator
378 {
379     if (!_isShowingTextIndicator)
380         return;
381
382     _page->clearTextIndicator();
383     _isShowingTextIndicator = NO;
384 }
385
386 #pragma mark Link actions
387
388 - (NSArray *)_defaultMenuItemsForLink
389 {
390     RetainPtr<NSMenuItem> openLinkItem = [self _createActionMenuItemForTag:kWKContextActionItemTagOpenLinkInDefaultBrowser];
391
392     BOOL shouldUseStandardQuickLookPreview = [_wkView _shouldUseStandardQuickLookPreview] && [NSMenuItem respondsToSelector:@selector(standardQuickLookMenuItem)];
393     RetainPtr<NSMenuItem> previewLinkItem;
394     RetainPtr<QLPreviewMenuItem> qlPreviewLinkItem;
395     if (shouldUseStandardQuickLookPreview) {
396         qlPreviewLinkItem = [NSMenuItem standardQuickLookMenuItem];
397         [qlPreviewLinkItem setPreviewStyle:QLPreviewStylePopover];
398         [qlPreviewLinkItem setDelegate:self];
399     } else {
400 #if WK_API_ENABLED
401         previewLinkItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPreviewLink];
402 #else
403         previewLinkItem = [NSMenuItem separatorItem];
404 #endif
405     }
406
407     RetainPtr<NSMenuItem> readingListItem = [self _createActionMenuItemForTag:kWKContextActionItemTagAddLinkToSafariReadingList];
408
409     return @[ openLinkItem.get(), shouldUseStandardQuickLookPreview ? qlPreviewLinkItem.get() : previewLinkItem.get(), [NSMenuItem separatorItem], readingListItem.get() ];
410 }
411
412 - (void)_openURLFromActionMenu:(id)sender
413 {
414     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
415     [[NSWorkspace sharedWorkspace] openURL:[NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()]];
416 }
417
418 - (void)_addToReadingListFromActionMenu:(id)sender
419 {
420     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
421     NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameAddToSafariReadingList];
422     [service performWithItems:@[ [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()] ]];
423 }
424
425 #if WK_API_ENABLED
426 - (void)_keepPreviewOpenFromActionMenu:(id)sender
427 {
428     _shouldKeepPreviewPopoverOpen = YES;
429 }
430
431 - (void)_previewURLFromActionMenu:(id)sender
432 {
433     ASSERT(_previewPopover);
434
435     // We might already have a preview showing if the menu item was highlighted earlier.
436     if ([_previewPopover isShown])
437         return;
438
439     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
440     dispatch_async(dispatch_get_main_queue(), ^{
441         [_previewPopover showRelativeToRect:_popoverOriginRect ofView:_wkView preferredEdge:NSMaxYEdge];
442     });
443 }
444
445 - (void)_createPreviewPopover
446 {
447     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
448     NSURL *url = [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()];
449     _popoverOriginRect = hitTestResult->elementBoundingBox();
450
451     NSSize previewPadding = [WKPagePreviewViewController previewPadding];
452     NSSize popoverSize = [self _preferredPopoverSizeWithPreviewPadding:previewPadding];
453     CGFloat actualPopoverToViewScale = popoverSize.width / NSWidth(_wkView.bounds);
454     popoverSize.width += previewPadding.width;
455     popoverSize.height += previewPadding.height;
456
457     _previewViewController = adoptNS([[WKPagePreviewViewController alloc] initWithPageURL:url mainViewSize:_wkView.bounds.size popoverToViewScale:actualPopoverToViewScale]);
458     _previewViewController->_delegate = self;
459     [_previewViewController loadView];
460
461     _previewPopover = adoptNS([[NSPopover alloc] init]);
462     [_previewPopover setBehavior:NSPopoverBehaviorApplicationDefined];
463     [_previewPopover setContentSize:popoverSize];
464     [_previewPopover setContentViewController:_previewViewController.get()];
465     [_previewPopover setDelegate:self];
466 }
467
468 static bool targetSizeFitsInAvailableSpace(NSSize targetSize, NSSize availableSpace)
469 {
470     return targetSize.width <= availableSpace.width && targetSize.height <= availableSpace.height;
471 }
472
473 - (NSSize)largestPopoverSize
474 {
475     NSSize screenSize = _wkView.window.screen.frame.size;
476
477     if (screenSize.width == 1280 && screenSize.height == 800)
478         return NSMakeSize(1240, 674);
479
480     if (screenSize.width == 1366 && screenSize.height == 768)
481         return NSMakeSize(1264, 642);
482
483     if (screenSize.width == 1440 && screenSize.height == 900)
484         return NSMakeSize(1264, 760);
485
486     if (screenSize.width == 1680 && screenSize.height == 1050)
487         return NSMakeSize(1324, 910);
488
489     return NSMakeSize(1324, 940);
490 }
491
492 - (NSSize)_preferredPopoverSizeWithPreviewPadding:(NSSize)previewPadding
493 {
494     static const CGFloat preferredPopoverToViewScale = 0.75;
495     static const NSSize screenPadding = {40, 40};
496     static const NSSize smallestPopoverSize = NSMakeSize(500, 300);
497
498     const NSSize effectivePadding = NSMakeSize(screenPadding.width + previewPadding.width, screenPadding.height + previewPadding.height);
499
500     NSWindow *window = _wkView.window;
501     NSRect originScreenRect = [window convertRectToScreen:[_wkView convertRect:_popoverOriginRect toView:nil]];
502     NSRect screenFrame = window.screen.visibleFrame;
503
504     NSRect wkViewBounds = _wkView.bounds;
505     NSSize targetSize = NSMakeSize(NSWidth(wkViewBounds) * preferredPopoverToViewScale, NSHeight(wkViewBounds) * preferredPopoverToViewScale);
506     NSSize largestPopoverSize = [self largestPopoverSize];
507
508     CGFloat availableSpaceAbove = NSMaxY(screenFrame) - NSMaxY(originScreenRect);
509     CGFloat availableSpaceBelow = NSMinY(originScreenRect) - NSMinY(screenFrame);
510     CGFloat maxAvailableVerticalSpace = fmax(availableSpaceAbove, availableSpaceBelow) - effectivePadding.height;
511     NSSize maxSpaceAvailableOnYEdge = NSMakeSize(screenFrame.size.width - effectivePadding.height, maxAvailableVerticalSpace);
512     if (targetSizeFitsInAvailableSpace(targetSize, maxSpaceAvailableOnYEdge) && targetSizeFitsInAvailableSpace(targetSize, largestPopoverSize))
513         return targetSize;
514
515     CGFloat availableSpaceAtLeft = NSMinX(originScreenRect) - NSMinX(screenFrame);
516     CGFloat availableSpaceAtRight = NSMaxX(screenFrame) - NSMaxX(originScreenRect);
517     CGFloat maxAvailableHorizontalSpace = fmax(availableSpaceAtLeft, availableSpaceAtRight) - effectivePadding.width;
518     NSSize maxSpaceAvailableOnXEdge = NSMakeSize(maxAvailableHorizontalSpace, screenFrame.size.height - effectivePadding.width);
519     if (targetSizeFitsInAvailableSpace(targetSize, maxSpaceAvailableOnXEdge) && targetSizeFitsInAvailableSpace(targetSize, largestPopoverSize))
520         return targetSize;
521
522     // Adjust the maximum space available if it is larger than the largest popover size.
523     if (maxSpaceAvailableOnYEdge.width > largestPopoverSize.width && maxSpaceAvailableOnYEdge.height > largestPopoverSize.height)
524         maxSpaceAvailableOnYEdge = largestPopoverSize;
525     if (maxSpaceAvailableOnXEdge.width > largestPopoverSize.width && maxSpaceAvailableOnXEdge.height > largestPopoverSize.height)
526         maxSpaceAvailableOnXEdge = largestPopoverSize;
527
528     // 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.
529     CGFloat aspectRatio = wkViewBounds.size.width / wkViewBounds.size.height;
530     FloatRect maxVerticalTargetSizePreservingAspectRatioRect = largestRectWithAspectRatioInsideRect(aspectRatio, FloatRect(0, 0, maxSpaceAvailableOnYEdge.width, maxSpaceAvailableOnYEdge.height));
531     FloatRect maxHorizontalTargetSizePreservingAspectRatioRect = largestRectWithAspectRatioInsideRect(aspectRatio, FloatRect(0, 0, maxSpaceAvailableOnXEdge.width, maxSpaceAvailableOnXEdge.height));
532
533     NSSize maxVerticalTargetSizePreservingAspectRatio = NSMakeSize(maxVerticalTargetSizePreservingAspectRatioRect.width(), maxVerticalTargetSizePreservingAspectRatioRect.height());
534     NSSize maxHortizontalTargetSizePreservingAspectRatio = NSMakeSize(maxHorizontalTargetSizePreservingAspectRatioRect.width(), maxHorizontalTargetSizePreservingAspectRatioRect.height());
535
536     NSSize computedTargetSize;
537     if ((maxVerticalTargetSizePreservingAspectRatio.width * maxVerticalTargetSizePreservingAspectRatio.height) > (maxHortizontalTargetSizePreservingAspectRatio.width * maxHortizontalTargetSizePreservingAspectRatio.height))
538         computedTargetSize = maxVerticalTargetSizePreservingAspectRatio;
539     computedTargetSize = maxHortizontalTargetSizePreservingAspectRatio;
540
541     // Now make sure what we've computed isn't too small.
542     if (computedTargetSize.width < smallestPopoverSize.width && computedTargetSize.height < smallestPopoverSize.height) {
543         float limitWidth = smallestPopoverSize.width > computedTargetSize.width ? smallestPopoverSize.width : computedTargetSize.width;
544         float limitHeight = smallestPopoverSize.height > computedTargetSize.height ? smallestPopoverSize.height : computedTargetSize.height;
545         FloatRect targetRectLargerThanMinSize = largestRectWithAspectRatioInsideRect(aspectRatio, FloatRect(0, 0, limitWidth, limitHeight));
546         computedTargetSize = NSMakeSize(targetRectLargerThanMinSize.size().width(), targetRectLargerThanMinSize.size().height());
547
548         // If our orignal computedTargetSize was so small that we had to get here and make a new computedTargetSize that is
549         // larger than the minimum, then the elementBoundingBox of the _hitTestResult is probably huge. So we should use
550         // the event origin as the popover origin in this case and not worry about obscuring the _hitTestResult.
551         _popoverOriginRect.origin = _eventLocationInView;
552         _popoverOriginRect.size = NSMakeSize(1, 1);
553     }
554
555     return computedTargetSize;
556 }
557
558 #endif // WK_API_ENABLED
559
560 - (void)_clearPreviewPopover
561 {
562 #if WK_API_ENABLED
563     if (_previewViewController) {
564         _previewViewController->_delegate = nil;
565         [_wkView _finishPreviewingURL:_previewViewController->_url.get() withPreviewView:_previewViewController->_previewView.get()];
566         _previewViewController = nil;
567     }
568 #endif
569
570     [_previewPopover close];
571     [_previewPopover setDelegate:nil];
572     _previewPopover = nil;
573 }
574
575 #pragma mark Video actions
576
577 - (NSArray *)_defaultMenuItemsForVideo
578 {
579     RetainPtr<NSMenuItem> copyVideoURLItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyVideoURL];
580
581     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
582     RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagSaveVideoToDownloads];
583     RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:kWKContextActionItemTagShareVideo];
584
585     String urlToShare = hitTestResult->absoluteMediaURL();
586     if (!hitTestResult->isDownloadableMedia()) {
587         [saveToDownloadsItem setEnabled:NO];
588         urlToShare = _page->mainFrame()->url();
589     }
590
591     if (!urlToShare.isEmpty()) {
592         _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ urlToShare ]]);
593         [_sharingServicePicker setDelegate:self];
594         [shareItem setSubmenu:[_sharingServicePicker menu]];
595     }
596
597     return @[ copyVideoURLItem.get(), [NSMenuItem separatorItem], saveToDownloadsItem.get(), shareItem.get() ];
598 }
599
600 - (void)_copyVideoURL:(id)sender
601 {
602     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
603     String urlToCopy = hitTestResult->absoluteMediaURL();
604     if (!hitTestResult->isDownloadableMedia())
605         urlToCopy = _page->mainFrame()->url();
606
607     [[NSPasteboard generalPasteboard] clearContents];
608     [[NSPasteboard generalPasteboard] writeObjects:@[ urlToCopy ]];
609 }
610
611 - (void)_saveVideoToDownloads:(id)sender
612 {
613     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
614     _page->process().context().download(_page, hitTestResult->absoluteMediaURL());
615 }
616
617 #pragma mark Image actions
618
619 - (NSImage *)_hitTestResultImage
620 {
621     RefPtr<SharedMemory> imageSharedMemory = _hitTestResult.imageSharedMemory;
622     if (!imageSharedMemory)
623         return nil;
624
625     RetainPtr<NSImage> nsImage = adoptNS([[NSImage alloc] initWithData:[NSData dataWithBytes:imageSharedMemory->data() length:imageSharedMemory->size()]]);
626     return nsImage.autorelease();
627 }
628
629 - (NSArray *)_defaultMenuItemsForImage
630 {
631     RetainPtr<NSMenuItem> copyImageItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyImage];
632     RetainPtr<NSMenuItem> addToPhotosItem;
633     if ([self _canAddMediaToPhotos])
634         addToPhotosItem = [self _createActionMenuItemForTag:kWKContextActionItemTagAddImageToPhotos];
635     else
636         addToPhotosItem = [NSMenuItem separatorItem];
637     RetainPtr<NSMenuItem> saveToDownloadsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagSaveImageToDownloads];
638     RetainPtr<NSMenuItem> shareItem = [self _createActionMenuItemForTag:kWKContextActionItemTagShareImage];
639
640     if (RetainPtr<NSImage> image = [self _hitTestResultImage]) {
641         _sharingServicePicker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ image.get() ]]);
642         [_sharingServicePicker setDelegate:self];
643         [shareItem setSubmenu:[_sharingServicePicker menu]];
644     }
645
646     return @[ copyImageItem.get(), addToPhotosItem.get(), saveToDownloadsItem.get(), shareItem.get() ];
647 }
648
649 - (void)_copyImage:(id)sender
650 {
651     RetainPtr<NSImage> image = [self _hitTestResultImage];
652     if (!image)
653         return;
654
655     [[NSPasteboard generalPasteboard] clearContents];
656     [[NSPasteboard generalPasteboard] writeObjects:@[ image.get() ]];
657 }
658
659 - (void)_saveImageToDownloads:(id)sender
660 {
661     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
662     _page->process().context().download(_page, hitTestResult->absoluteImageURL());
663 }
664
665 // FIXME: We should try to share this with WebPageProxyMac's similar PDF functions.
666 static NSString *temporaryPhotosDirectoryPath()
667 {
668     static NSString *temporaryPhotosDirectoryPath;
669
670     if (!temporaryPhotosDirectoryPath) {
671         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPhotos-XXXXXX"];
672         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
673
674         if (mkdtemp(templateRepresentation.mutableData()))
675             temporaryPhotosDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
676     }
677
678     return temporaryPhotosDirectoryPath;
679 }
680
681 static NSString *pathToPhotoOnDisk(NSString *suggestedFilename)
682 {
683     NSString *photoDirectoryPath = temporaryPhotosDirectoryPath();
684     if (!photoDirectoryPath) {
685         WTFLogAlways("Cannot create temporary photo download directory.");
686         return nil;
687     }
688
689     NSString *path = [photoDirectoryPath stringByAppendingPathComponent:suggestedFilename];
690
691     NSFileManager *fileManager = [NSFileManager defaultManager];
692     if ([fileManager fileExistsAtPath:path]) {
693         NSString *pathTemplatePrefix = [photoDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
694         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:suggestedFilename];
695         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
696
697         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
698         if (fd < 0) {
699             WTFLogAlways("Cannot create photo file in the temporary directory (%@).", suggestedFilename);
700             return nil;
701         }
702
703         close(fd);
704         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
705     }
706
707     return path;
708 }
709
710 - (BOOL)_canAddMediaToPhotos
711 {
712     return [getIKSlideshowClass() canExportToApplication:@"com.apple.Photos"];
713 }
714
715 - (void)_addImageToPhotos:(id)sender
716 {
717     if (![self _canAddMediaToPhotos])
718         return;
719
720     RefPtr<SharedMemory> imageSharedMemory = _hitTestResult.imageSharedMemory;
721     if (!imageSharedMemory->size() || _hitTestResult.imageExtension.isEmpty())
722         return;
723
724     RetainPtr<NSData> imageData = adoptNS([[NSData alloc] initWithBytes:imageSharedMemory->data() length:imageSharedMemory->size()]);
725     RetainPtr<NSString> suggestedFilename = [[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:_hitTestResult.imageExtension];
726
727     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
728         NSString *filePath = pathToPhotoOnDisk(suggestedFilename.get());
729         if (!filePath)
730             return;
731
732         NSURL *fileURL = [NSURL fileURLWithPath:filePath];
733         [imageData writeToURL:fileURL atomically:NO];
734
735         dispatch_async(dispatch_get_main_queue(), ^{
736             // This API provides no way to report failure, but if 18420778 is fixed so that it does, we should handle this.
737             [getIKSlideshowClass() exportSlideshowItem:filePath toApplication:@"com.apple.Photos"];
738         });
739     });
740 }
741
742 #pragma mark Text actions
743
744 - (NSArray *)_defaultMenuItemsForDataDetectedText
745 {
746     DDActionContext *actionContext = _hitTestResult.actionContext.get();
747     if (!actionContext)
748         return @[ ];
749
750     actionContext.altMode = YES;
751     if ([[getDDActionsManagerClass() sharedManager] respondsToSelector:@selector(hasActionsForResult:actionContext:)]) {
752         if (![[getDDActionsManagerClass() sharedManager] hasActionsForResult:actionContext.mainResult actionContext:actionContext])
753             return @[ ];
754     }
755
756     // Ref our WebPageProxy for use in the blocks below.
757     RefPtr<WebPageProxy> page = _page;
758     PageOverlay::PageOverlayID overlayID = _hitTestResult.detectedDataOriginatingPageOverlay;
759     _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
760         page->send(Messages::WebPage::DataDetectorsDidPresentUI(overlayID));
761     } interactionChangedHandler:^() {
762         [self _showTextIndicator];
763         page->send(Messages::WebPage::DataDetectorsDidChangeUI(overlayID));
764     } interactionStoppedHandler:^() {
765         [self _hideTextIndicator];
766         page->send(Messages::WebPage::DataDetectorsDidHideUI(overlayID));
767     }];
768
769     [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
770
771     NSArray *menuItems = [[getDDActionsManagerClass() sharedManager] menuItemsForResult:[_currentActionContext mainResult] actionContext:_currentActionContext.get()];
772     if (menuItems.count == 1 && _hitTestResult.detectedDataTextIndicator)
773         _hitTestResult.detectedDataTextIndicator->setPresentationTransition(TextIndicatorPresentationTransition::Bounce);
774     return menuItems;
775 }
776
777 - (NSArray *)_defaultMenuItemsForText
778 {
779     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
780     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
781
782     return @[ copyTextItem.get(), lookupTextItem.get() ];
783 }
784
785 - (NSArray *)_defaultMenuItemsForEditableText
786 {
787     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
788     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
789     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
790
791     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
792 }
793
794 - (NSArray *)_defaultMenuItemsForEditableTextWithSuggestions
795 {
796     if (_hitTestResult.lookupText.isEmpty())
797         return @[ ];
798
799     Vector<TextCheckingResult> results;
800     _page->checkTextOfParagraph(_hitTestResult.lookupText, NSTextCheckingTypeSpelling, results);
801     if (results.isEmpty())
802         return @[ ];
803
804     Vector<String> guesses;
805     _page->getGuessesForWord(_hitTestResult.lookupText, String(), guesses);
806     if (guesses.isEmpty())
807         return @[ ];
808
809     RetainPtr<NSMenu> spellingSubMenu = adoptNS([[NSMenu alloc] init]);
810     for (const auto& guess : guesses) {
811         RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:guess action:@selector(_changeSelectionToSuggestion:) keyEquivalent:@""]);
812         [item setRepresentedObject:guess];
813         [item setTarget:self];
814         [spellingSubMenu addItem:item.get()];
815     }
816
817     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
818     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
819     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
820     RetainPtr<NSMenuItem> textSuggestionsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagTextSuggestions];
821
822     [textSuggestionsItem setSubmenu:spellingSubMenu.get()];
823
824     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get(), textSuggestionsItem.get() ];
825 }
826
827 - (void)_copySelection:(id)sender
828 {
829     _page->executeEditCommand("copy");
830 }
831
832 - (void)_paste:(id)sender
833 {
834     _page->executeEditCommand("paste");
835 }
836
837 - (void)_lookupText:(id)sender
838 {
839     _page->performDictionaryLookupOfCurrentSelection();
840 }
841
842 - (void)_changeSelectionToSuggestion:(id)sender
843 {
844     NSString *selectedCorrection = [sender representedObject];
845     if (!selectedCorrection)
846         return;
847
848     ASSERT([selectedCorrection isKindOfClass:[NSString class]]);
849
850     _page->changeSpellingToWord(selectedCorrection);
851 }
852
853 #pragma mark Whitespace actions
854
855 - (NSArray *)_defaultMenuItemsForWhitespaceInEditableArea
856 {
857     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
858
859     return @[ [NSMenuItem separatorItem], [NSMenuItem separatorItem], pasteItem.get() ];
860 }
861
862 #pragma mark mailto: and tel: Link actions
863
864 - (NSArray *)_defaultMenuItemsForDataDetectableLink
865 {
866     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
867     RetainPtr<DDActionContext> actionContext = [[getDDActionContextClass() alloc] init];
868
869     // FIXME: Should this show a yellow highlight?
870     _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
871     } interactionChangedHandler:^() {
872     } interactionStoppedHandler:^() {
873     }];
874
875     [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
876
877     return [[getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:hitTestResult->absoluteLinkURL() actionContext:_currentActionContext.get()];
878 }
879
880 #pragma mark NSMenuDelegate implementation
881
882 - (void)menuNeedsUpdate:(NSMenu *)menu
883 {
884     if (menu != _wkView.actionMenu)
885         return;
886
887     ASSERT(_state != ActionMenuState::None);
888
889     // FIXME: We need to be able to cancel this if the menu goes away.
890     // FIXME: Connection can be null if the process is closed; we should clean up better in that case.
891     if (_state == ActionMenuState::Pending) {
892         if (auto* connection = _page->process().connection()) {
893             bool receivedReply = connection->waitForAndDispatchImmediately<Messages::WebPageProxy::DidPerformActionMenuHitTest>(_page->pageID(), std::chrono::milliseconds(500));
894             if (!receivedReply)
895                 _state = ActionMenuState::TimedOut;
896         }
897     }
898
899     if (_state != ActionMenuState::Ready)
900         [self _updateActionMenuItems];
901
902     if (_currentActionContext) {
903         _hasActivatedActionContext = YES;
904         if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()]) {
905             [menu cancelTracking];
906             [menu removeAllItems];
907         }
908     }
909 }
910
911 - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
912 {
913 #if WK_API_ENABLED
914     if (item.tag != kWKContextActionItemTagPreviewLink)
915         return;
916     [self _previewURLFromActionMenu:item];
917 #endif
918 }
919
920 #pragma mark NSSharingServicePickerDelegate implementation
921
922 - (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)mask proposedSharingServices:(NSArray *)proposedServices
923 {
924     RetainPtr<NSMutableArray> services = adoptNS([[NSMutableArray alloc] initWithCapacity:proposedServices.count]);
925
926     for (NSSharingService *service in proposedServices) {
927         if ([service.name isEqualToString:NSSharingServiceNameAddToIPhoto])
928             continue;
929         [services addObject:service];
930     }
931
932     return services.autorelease();
933 }
934
935 - (id <NSSharingServiceDelegate>)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker delegateForSharingService:(NSSharingService *)sharingService
936 {
937     return self;
938 }
939
940 #pragma mark NSSharingServiceDelegate implementation
941
942 - (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
943 {
944     return _wkView.window;
945 }
946
947 #pragma mark NSPopoverDelegate implementation
948
949 - (void)popoverWillClose:(NSNotification *)notification
950 {
951     _shouldKeepPreviewPopoverOpen = NO;
952     [self _clearPreviewPopover];
953 }
954
955 #pragma mark QLPreviewMenuItemDelegate implementation
956
957 - (NSView *)menuItem:(NSMenuItem *)menuItem viewAtScreenPoint:(NSPoint)screenPoint
958 {
959     return _wkView;
960 }
961
962 - (id<QLPreviewItem>)menuItem:(NSMenuItem *)menuItem previewItemAtPoint:(NSPoint)point
963 {
964     if (!_wkView)
965         return nil;
966
967     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
968     return [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()];
969 }
970
971 - (NSRectEdge)menuItem:(NSMenuItem *)menuItem preferredEdgeForPoint:(NSPoint)point
972 {
973     return NSMaxYEdge;
974 }
975
976 #pragma mark Menu Items
977
978 - (RetainPtr<NSMenuItem>)_createActionMenuItemForTag:(uint32_t)tag
979 {
980     SEL selector = nullptr;
981     NSString *title = nil;
982     NSImage *image = nil;
983     bool enabled = true;
984
985     switch (tag) {
986     case kWKContextActionItemTagOpenLinkInDefaultBrowser:
987         selector = @selector(_openURLFromActionMenu:);
988         title = WEB_UI_STRING_KEY("Open", "Open (action menu item)", "action menu item");
989         image = [NSImage imageNamed:@"NSActionMenuOpenInNewWindow"];
990         break;
991
992 #if WK_API_ENABLED
993     case kWKContextActionItemTagPreviewLink:
994         selector = @selector(_keepPreviewOpenFromActionMenu:);
995         title = @"";
996         image = [NSImage imageNamed:@"NSActionMenuQuickLook"];
997         break;
998 #endif
999
1000     case kWKContextActionItemTagAddLinkToSafariReadingList:
1001         selector = @selector(_addToReadingListFromActionMenu:);
1002         title = WEB_UI_STRING_KEY("Add to Reading List", "Add to Reading List (action menu item)", "action menu item");
1003         image = [NSImage imageNamed:@"NSActionMenuAddToReadingList"];
1004         break;
1005
1006     case kWKContextActionItemTagCopyImage:
1007         selector = @selector(_copyImage:);
1008         title = WEB_UI_STRING_KEY("Copy", "Copy (image action menu item)", "image action menu item");
1009         image = [NSImage imageNamed:@"NSActionMenuCopy"];
1010         break;
1011
1012     case kWKContextActionItemTagAddImageToPhotos:
1013         selector = @selector(_addImageToPhotos:);
1014         title = WEB_UI_STRING_KEY("Add to Photos", "Add to Photos (action menu item)", "action menu item");
1015         image = [NSImage imageNamed:@"NSActionMenuAddToPhotos"];
1016         break;
1017
1018     case kWKContextActionItemTagSaveImageToDownloads:
1019         selector = @selector(_saveImageToDownloads:);
1020         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (image action menu item)", "image action menu item");
1021         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
1022         break;
1023
1024     case kWKContextActionItemTagShareImage:
1025         title = WEB_UI_STRING_KEY("Share (image action menu item)", "Share (image action menu item)", "image action menu item");
1026         image = [NSImage imageNamed:@"NSActionMenuShare"];
1027         break;
1028
1029     case kWKContextActionItemTagCopyText:
1030         selector = @selector(_copySelection:);
1031         title = WEB_UI_STRING_KEY("Copy", "Copy (text action menu item)", "text action menu item");
1032         image = [NSImage imageNamed:@"NSActionMenuCopy"];
1033         break;
1034
1035     case kWKContextActionItemTagLookupText:
1036         selector = @selector(_lookupText:);
1037         title = WEB_UI_STRING_KEY("Look Up", "Look Up (action menu item)", "action menu item");
1038         image = [NSImage imageNamed:@"NSActionMenuLookup"];
1039         enabled = getLULookupDefinitionModuleClass();
1040         break;
1041
1042     case kWKContextActionItemTagPaste:
1043         selector = @selector(_paste:);
1044         title = WEB_UI_STRING_KEY("Paste", "Paste (action menu item)", "action menu item");
1045         image = [NSImage imageNamed:@"NSActionMenuPaste"];
1046         break;
1047
1048     case kWKContextActionItemTagTextSuggestions:
1049         title = WEB_UI_STRING_KEY("Suggestions", "Suggestions (action menu item)", "action menu item");
1050         image = [NSImage imageNamed:@"NSActionMenuSpelling"];
1051         break;
1052
1053     case kWKContextActionItemTagCopyVideoURL:
1054         selector = @selector(_copyVideoURL:);
1055         title = WEB_UI_STRING_KEY("Copy", "Copy (video action menu item)", "video action menu item");
1056         image = [NSImage imageNamed:@"NSActionMenuCopy"];
1057         break;
1058
1059     case kWKContextActionItemTagSaveVideoToDownloads:
1060         selector = @selector(_saveVideoToDownloads:);
1061         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (video action menu item)", "video action menu item");
1062         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
1063         break;
1064
1065     case kWKContextActionItemTagShareVideo:
1066         title = WEB_UI_STRING_KEY("Share", "Share (video action menu item)", "video action menu item");
1067         image = [NSImage imageNamed:@"NSActionMenuShare"];
1068         break;
1069
1070     default:
1071         ASSERT_NOT_REACHED();
1072         return nil;
1073     }
1074
1075     RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]);
1076     [item setImage:image];
1077     [item setTarget:self];
1078     [item setTag:tag];
1079     [item setEnabled:enabled];
1080     return item;
1081 }
1082
1083 - (PassRefPtr<WebHitTestResult>)_webHitTestResult
1084 {
1085     RefPtr<WebHitTestResult> hitTestResult;
1086     if (_state == ActionMenuState::Ready)
1087         hitTestResult = WebHitTestResult::create(_hitTestResult.hitTestResult);
1088     else
1089         hitTestResult = _page->lastMouseMoveHitTestResult();
1090
1091     return hitTestResult.release();
1092 }
1093
1094 - (NSArray *)_defaultMenuItems
1095 {
1096     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
1097     if (!hitTestResult) {
1098         _type = kWKActionMenuNone;
1099         return _state == ActionMenuState::Pending ? @[ [NSMenuItem separatorItem] ] : @[ ];
1100     }
1101
1102     String absoluteLinkURL = hitTestResult->absoluteLinkURL();
1103     if (!absoluteLinkURL.isEmpty()) {
1104         if (WebCore::protocolIsInHTTPFamily(absoluteLinkURL)) {
1105             _type = kWKActionMenuLink;
1106             return [self _defaultMenuItemsForLink];
1107         }
1108
1109         if (protocolIs(absoluteLinkURL, "mailto")) {
1110             _type = kWKActionMenuMailtoLink;
1111             return [self _defaultMenuItemsForDataDetectableLink];
1112         }
1113
1114         if (protocolIs(absoluteLinkURL, "tel")) {
1115             _type = kWKActionMenuTelLink;
1116             return [self _defaultMenuItemsForDataDetectableLink];
1117         }
1118     }
1119
1120     if (!hitTestResult->absoluteMediaURL().isEmpty()) {
1121         _type = kWKActionMenuVideo;
1122         return [self _defaultMenuItemsForVideo];
1123     }
1124
1125     if (!hitTestResult->absoluteImageURL().isEmpty() && _hitTestResult.imageSharedMemory && !_hitTestResult.imageExtension.isEmpty()) {
1126         _type = kWKActionMenuImage;
1127         return [self _defaultMenuItemsForImage];
1128     }
1129
1130     if (hitTestResult->isTextNode() || hitTestResult->isOverTextInsideFormControlElement()) {
1131         NSArray *dataDetectorMenuItems = [self _defaultMenuItemsForDataDetectedText];
1132         if (_currentActionContext) {
1133             // If this is a data detected item with no menu items, we should not fall back to regular text options.
1134             if (!dataDetectorMenuItems.count) {
1135                 _type = kWKActionMenuNone;
1136                 return @[ ];
1137             }
1138             _type = kWKActionMenuDataDetectedItem;
1139             return dataDetectorMenuItems;
1140         }
1141
1142         if (hitTestResult->isContentEditable()) {
1143             NSArray *editableTextWithSuggestions = [self _defaultMenuItemsForEditableTextWithSuggestions];
1144             if (editableTextWithSuggestions.count) {
1145                 _type = kWKActionMenuEditableTextWithSuggestions;
1146                 return editableTextWithSuggestions;
1147             }
1148
1149             _type = kWKActionMenuEditableText;
1150             return [self _defaultMenuItemsForEditableText];
1151         }
1152
1153         _type = kWKActionMenuReadOnlyText;
1154         return [self _defaultMenuItemsForText];
1155     }
1156
1157     if (hitTestResult->isContentEditable()) {
1158         _type = kWKActionMenuWhitespaceInEditableArea;
1159         return [self _defaultMenuItemsForWhitespaceInEditableArea];
1160     }
1161
1162     if (hitTestResult->isSelected()) {
1163         // A selection should present the read-only text menu. It might make more sense to present a new
1164         // type of menu with just copy, but for the time being, we should stay consistent with text.
1165         _type = kWKActionMenuReadOnlyText;
1166         return [self _defaultMenuItemsForText];
1167     }
1168
1169     _type = kWKActionMenuNone;
1170     return _state == ActionMenuState::Pending ? @[ [NSMenuItem separatorItem] ] : @[ ];
1171 }
1172
1173 - (void)_updateActionMenuItems
1174 {
1175     [_wkView.actionMenu removeAllItems];
1176
1177     NSArray *menuItems = [self _defaultMenuItems];
1178     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
1179
1180     if ([_wkView respondsToSelector:@selector(_actionMenuItemsForHitTestResult:defaultActionMenuItems:)])
1181         menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) defaultActionMenuItems:menuItems];
1182     else
1183         menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) withType:_type defaultActionMenuItems:menuItems userData:toAPI(_userData.get())];
1184
1185     for (NSMenuItem *item in menuItems)
1186         [_wkView.actionMenu addItem:item];
1187
1188     if (!_wkView.actionMenu.numberOfItems)
1189         [_wkView.actionMenu cancelTracking];
1190 }
1191
1192 #if WK_API_ENABLED
1193
1194 #pragma mark WKPagePreviewViewControllerDelegate
1195
1196 - (NSView *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController viewForPreviewingURL:(NSURL *)url initialFrameSize:(NSSize)initialFrameSize
1197 {
1198     return [_wkView _viewForPreviewingURL:url initialFrameSize:initialFrameSize];
1199 }
1200
1201 - (NSString *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController titleForPreviewOfURL:(NSURL *)url
1202 {
1203     return [_wkView _titleForPreviewOfURL:url];
1204 }
1205
1206 - (void)pagePreviewViewControllerWasClicked:(WKPagePreviewViewController *)pagePreviewViewController
1207 {
1208     if (NSURL *url = pagePreviewViewController->_url.get())
1209         [_wkView _handleClickInPreviewView:pagePreviewViewController->_previewView.get() URL:url];
1210 }
1211
1212 #endif
1213
1214 @end
1215
1216 #endif // PLATFORM(MAC)