Should have copy and paste items in all text menus
[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     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
782     [pasteItem setEnabled:NO];
783
784     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
785 }
786
787 - (NSArray *)_defaultMenuItemsForEditableText
788 {
789     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
790     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
791     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
792
793     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get() ];
794 }
795
796 - (NSArray *)_defaultMenuItemsForEditableTextWithSuggestions
797 {
798     if (_hitTestResult.lookupText.isEmpty())
799         return @[ ];
800
801     Vector<TextCheckingResult> results;
802     _page->checkTextOfParagraph(_hitTestResult.lookupText, NSTextCheckingTypeSpelling, results);
803     if (results.isEmpty())
804         return @[ ];
805
806     Vector<String> guesses;
807     _page->getGuessesForWord(_hitTestResult.lookupText, String(), guesses);
808     if (guesses.isEmpty())
809         return @[ ];
810
811     RetainPtr<NSMenu> spellingSubMenu = adoptNS([[NSMenu alloc] init]);
812     for (const auto& guess : guesses) {
813         RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:guess action:@selector(_changeSelectionToSuggestion:) keyEquivalent:@""]);
814         [item setRepresentedObject:guess];
815         [item setTarget:self];
816         [spellingSubMenu addItem:item.get()];
817     }
818
819     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
820     RetainPtr<NSMenuItem> lookupTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagLookupText];
821     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
822     RetainPtr<NSMenuItem> textSuggestionsItem = [self _createActionMenuItemForTag:kWKContextActionItemTagTextSuggestions];
823
824     [textSuggestionsItem setSubmenu:spellingSubMenu.get()];
825
826     return @[ copyTextItem.get(), lookupTextItem.get(), pasteItem.get(), textSuggestionsItem.get() ];
827 }
828
829 - (void)_copySelection:(id)sender
830 {
831     _page->executeEditCommand("copy");
832 }
833
834 - (void)_paste:(id)sender
835 {
836     _page->executeEditCommand("paste");
837 }
838
839 - (void)_lookupText:(id)sender
840 {
841     _page->performDictionaryLookupOfCurrentSelection();
842 }
843
844 - (void)_changeSelectionToSuggestion:(id)sender
845 {
846     NSString *selectedCorrection = [sender representedObject];
847     if (!selectedCorrection)
848         return;
849
850     ASSERT([selectedCorrection isKindOfClass:[NSString class]]);
851
852     _page->changeSpellingToWord(selectedCorrection);
853 }
854
855 #pragma mark Whitespace actions
856
857 - (NSArray *)_defaultMenuItemsForWhitespaceInEditableArea
858 {
859     RetainPtr<NSMenuItem> copyTextItem = [self _createActionMenuItemForTag:kWKContextActionItemTagCopyText];
860     RetainPtr<NSMenuItem> pasteItem = [self _createActionMenuItemForTag:kWKContextActionItemTagPaste];
861     [copyTextItem setEnabled:NO];
862
863     return @[ copyTextItem.get(), [NSMenuItem separatorItem], pasteItem.get() ];
864 }
865
866 #pragma mark mailto: and tel: Link actions
867
868 - (NSArray *)_defaultMenuItemsForDataDetectableLink
869 {
870     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
871     RetainPtr<DDActionContext> actionContext = [allocDDActionContextInstance() init];
872
873     // FIXME: Should this show a yellow highlight?
874     _currentActionContext = [actionContext contextForView:_wkView altMode:YES interactionStartedHandler:^() {
875     } interactionChangedHandler:^() {
876     } interactionStoppedHandler:^() {
877     }];
878
879     [_currentActionContext setHighlightFrame:[_wkView.window convertRectToScreen:[_wkView convertRect:_hitTestResult.detectedDataBoundingBox toView:nil]]];
880
881     return [[getDDActionsManagerClass() sharedManager] menuItemsForTargetURL:hitTestResult->absoluteLinkURL() actionContext:_currentActionContext.get()];
882 }
883
884 #pragma mark NSMenuDelegate implementation
885
886 - (void)menuNeedsUpdate:(NSMenu *)menu
887 {
888     if (menu != _wkView.actionMenu)
889         return;
890
891     ASSERT(_state != ActionMenuState::None);
892
893     // FIXME: We need to be able to cancel this if the menu goes away.
894     // FIXME: Connection can be null if the process is closed; we should clean up better in that case.
895     if (_state == ActionMenuState::Pending) {
896         if (auto* connection = _page->process().connection()) {
897             bool receivedReply = connection->waitForAndDispatchImmediately<Messages::WebPageProxy::DidPerformActionMenuHitTest>(_page->pageID(), std::chrono::milliseconds(500));
898             if (!receivedReply)
899                 _state = ActionMenuState::TimedOut;
900         }
901     }
902
903     if (_state != ActionMenuState::Ready)
904         [self _updateActionMenuItems];
905
906     if (_currentActionContext) {
907         _hasActivatedActionContext = YES;
908         if (![getDDActionsManagerClass() shouldUseActionsWithContext:_currentActionContext.get()]) {
909             [menu cancelTracking];
910             [menu removeAllItems];
911         }
912     }
913 }
914
915 - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
916 {
917 #if WK_API_ENABLED
918     if (item.tag != kWKContextActionItemTagPreviewLink)
919         return;
920     [self _previewURLFromActionMenu:item];
921 #endif
922 }
923
924 #pragma mark NSSharingServicePickerDelegate implementation
925
926 - (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)mask proposedSharingServices:(NSArray *)proposedServices
927 {
928     RetainPtr<NSMutableArray> services = adoptNS([[NSMutableArray alloc] initWithCapacity:proposedServices.count]);
929
930     for (NSSharingService *service in proposedServices) {
931         if ([service.name isEqualToString:NSSharingServiceNameAddToIPhoto])
932             continue;
933         [services addObject:service];
934     }
935
936     return services.autorelease();
937 }
938
939 - (id <NSSharingServiceDelegate>)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker delegateForSharingService:(NSSharingService *)sharingService
940 {
941     return self;
942 }
943
944 #pragma mark NSSharingServiceDelegate implementation
945
946 - (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
947 {
948     return _wkView.window;
949 }
950
951 #pragma mark NSPopoverDelegate implementation
952
953 - (void)popoverWillClose:(NSNotification *)notification
954 {
955     _shouldKeepPreviewPopoverOpen = NO;
956     [self _clearPreviewPopover];
957 }
958
959 #pragma mark QLPreviewMenuItemDelegate implementation
960
961 - (NSView *)menuItem:(NSMenuItem *)menuItem viewAtScreenPoint:(NSPoint)screenPoint
962 {
963     return _wkView;
964 }
965
966 - (id<QLPreviewItem>)menuItem:(NSMenuItem *)menuItem previewItemAtPoint:(NSPoint)point
967 {
968     if (!_wkView)
969         return nil;
970
971     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
972     return [NSURL _web_URLWithWTFString:hitTestResult->absoluteLinkURL()];
973 }
974
975 - (NSRectEdge)menuItem:(NSMenuItem *)menuItem preferredEdgeForPoint:(NSPoint)point
976 {
977     return NSMaxYEdge;
978 }
979
980 #pragma mark Menu Items
981
982 - (RetainPtr<NSMenuItem>)_createActionMenuItemForTag:(uint32_t)tag
983 {
984     SEL selector = nullptr;
985     NSString *title = nil;
986     NSImage *image = nil;
987     bool enabled = true;
988     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
989
990     switch (tag) {
991     case kWKContextActionItemTagOpenLinkInDefaultBrowser:
992         selector = @selector(_openURLFromActionMenu:);
993         title = WEB_UI_STRING_KEY("Open", "Open (action menu item)", "action menu item");
994         image = [NSImage imageNamed:@"NSActionMenuOpenInNewWindow"];
995         break;
996
997 #if WK_API_ENABLED
998     case kWKContextActionItemTagPreviewLink:
999         selector = @selector(_keepPreviewOpenFromActionMenu:);
1000         title = @"";
1001         image = [NSImage imageNamed:@"NSActionMenuQuickLook"];
1002         break;
1003 #endif
1004
1005     case kWKContextActionItemTagAddLinkToSafariReadingList:
1006         selector = @selector(_addToReadingListFromActionMenu:);
1007         title = WEB_UI_STRING_KEY("Add to Reading List", "Add to Reading List (action menu item)", "action menu item");
1008         image = [NSImage imageNamed:@"NSActionMenuAddToReadingList"];
1009         break;
1010
1011     case kWKContextActionItemTagCopyImage:
1012         selector = @selector(_copyImage:);
1013         title = WEB_UI_STRING_KEY("Copy", "Copy (image action menu item)", "image action menu item");
1014         image = [NSImage imageNamed:@"NSActionMenuCopy"];
1015         break;
1016
1017     case kWKContextActionItemTagAddImageToPhotos:
1018         selector = @selector(_addImageToPhotos:);
1019         title = WEB_UI_STRING_KEY("Add to Photos", "Add to Photos (action menu item)", "action menu item");
1020         image = [NSImage imageNamed:@"NSActionMenuAddToPhotos"];
1021         break;
1022
1023     case kWKContextActionItemTagSaveImageToDownloads:
1024         selector = @selector(_saveImageToDownloads:);
1025         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (image action menu item)", "image action menu item");
1026         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
1027         break;
1028
1029     case kWKContextActionItemTagShareImage:
1030         title = WEB_UI_STRING_KEY("Share (image action menu item)", "Share (image action menu item)", "image action menu item");
1031         image = [NSImage imageNamed:@"NSActionMenuShare"];
1032         break;
1033
1034     case kWKContextActionItemTagCopyText:
1035         selector = @selector(_copySelection:);
1036         title = WEB_UI_STRING_KEY("Copy", "Copy (text action menu item)", "text action menu item");
1037         image = [NSImage imageNamed:@"NSActionMenuCopy"];
1038         enabled = hitTestResult->allowsCopy();
1039         break;
1040
1041     case kWKContextActionItemTagLookupText:
1042         selector = @selector(_lookupText:);
1043         title = WEB_UI_STRING_KEY("Look Up", "Look Up (action menu item)", "action menu item");
1044         image = [NSImage imageNamed:@"NSActionMenuLookup"];
1045         enabled = getLULookupDefinitionModuleClass() && hitTestResult->allowsCopy();
1046         break;
1047
1048     case kWKContextActionItemTagPaste:
1049         selector = @selector(_paste:);
1050         title = WEB_UI_STRING_KEY("Paste", "Paste (action menu item)", "action menu item");
1051         image = [NSImage imageNamed:@"NSActionMenuPaste"];
1052         break;
1053
1054     case kWKContextActionItemTagTextSuggestions:
1055         title = WEB_UI_STRING_KEY("Suggestions", "Suggestions (action menu item)", "action menu item");
1056         image = [NSImage imageNamed:@"NSActionMenuSpelling"];
1057         break;
1058
1059     case kWKContextActionItemTagCopyVideoURL:
1060         selector = @selector(_copyVideoURL:);
1061         title = WEB_UI_STRING_KEY("Copy", "Copy (video action menu item)", "video action menu item");
1062         image = [NSImage imageNamed:@"NSActionMenuCopy"];
1063         break;
1064
1065     case kWKContextActionItemTagSaveVideoToDownloads:
1066         selector = @selector(_saveVideoToDownloads:);
1067         title = WEB_UI_STRING_KEY("Save to Downloads", "Save to Downloads (video action menu item)", "video action menu item");
1068         image = [NSImage imageNamed:@"NSActionMenuSaveToDownloads"];
1069         break;
1070
1071     case kWKContextActionItemTagShareVideo:
1072         title = WEB_UI_STRING_KEY("Share", "Share (video action menu item)", "video action menu item");
1073         image = [NSImage imageNamed:@"NSActionMenuShare"];
1074         break;
1075
1076     default:
1077         ASSERT_NOT_REACHED();
1078         return nil;
1079     }
1080
1081     RetainPtr<NSMenuItem> item = adoptNS([[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]);
1082     [item setImage:image];
1083     [item setTarget:self];
1084     [item setTag:tag];
1085     [item setEnabled:enabled];
1086     return item;
1087 }
1088
1089 - (PassRefPtr<WebHitTestResult>)_webHitTestResult
1090 {
1091     RefPtr<WebHitTestResult> hitTestResult;
1092     if (_state == ActionMenuState::Ready)
1093         hitTestResult = WebHitTestResult::create(_hitTestResult.hitTestResult);
1094     else
1095         hitTestResult = _page->lastMouseMoveHitTestResult();
1096
1097     return hitTestResult.release();
1098 }
1099
1100 - (NSArray *)_defaultMenuItems
1101 {
1102     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
1103     if (!hitTestResult) {
1104         _type = kWKActionMenuNone;
1105         return _state == ActionMenuState::Pending ? @[ [NSMenuItem separatorItem] ] : @[ ];
1106     }
1107
1108     String absoluteLinkURL = hitTestResult->absoluteLinkURL();
1109     if (!absoluteLinkURL.isEmpty()) {
1110         if (WebCore::protocolIsInHTTPFamily(absoluteLinkURL)) {
1111             _type = kWKActionMenuLink;
1112             return [self _defaultMenuItemsForLink];
1113         }
1114
1115         if (protocolIs(absoluteLinkURL, "mailto")) {
1116             _type = kWKActionMenuMailtoLink;
1117             return [self _defaultMenuItemsForDataDetectableLink];
1118         }
1119
1120         if (protocolIs(absoluteLinkURL, "tel")) {
1121             _type = kWKActionMenuTelLink;
1122             return [self _defaultMenuItemsForDataDetectableLink];
1123         }
1124     }
1125
1126     if (!hitTestResult->absoluteMediaURL().isEmpty()) {
1127         _type = kWKActionMenuVideo;
1128         return [self _defaultMenuItemsForVideo];
1129     }
1130
1131     if (!hitTestResult->absoluteImageURL().isEmpty() && _hitTestResult.imageSharedMemory && !_hitTestResult.imageExtension.isEmpty()) {
1132         _type = kWKActionMenuImage;
1133         return [self _defaultMenuItemsForImage];
1134     }
1135
1136     if (hitTestResult->isTextNode() || hitTestResult->isOverTextInsideFormControlElement()) {
1137         NSArray *dataDetectorMenuItems = [self _defaultMenuItemsForDataDetectedText];
1138         if (_currentActionContext) {
1139             // If this is a data detected item with no menu items, we should not fall back to regular text options.
1140             if (!dataDetectorMenuItems.count) {
1141                 _type = kWKActionMenuNone;
1142                 return @[ ];
1143             }
1144             _type = kWKActionMenuDataDetectedItem;
1145             return dataDetectorMenuItems;
1146         }
1147
1148         if (hitTestResult->isContentEditable()) {
1149             NSArray *editableTextWithSuggestions = [self _defaultMenuItemsForEditableTextWithSuggestions];
1150             if (editableTextWithSuggestions.count) {
1151                 _type = kWKActionMenuEditableTextWithSuggestions;
1152                 return editableTextWithSuggestions;
1153             }
1154
1155             _type = kWKActionMenuEditableText;
1156             return [self _defaultMenuItemsForEditableText];
1157         }
1158
1159         _type = kWKActionMenuReadOnlyText;
1160         return [self _defaultMenuItemsForText];
1161     }
1162
1163     if (hitTestResult->isContentEditable()) {
1164         _type = kWKActionMenuWhitespaceInEditableArea;
1165         return [self _defaultMenuItemsForWhitespaceInEditableArea];
1166     }
1167
1168     if (hitTestResult->isSelected()) {
1169         // A selection should present the read-only text menu. It might make more sense to present a new
1170         // type of menu with just copy, but for the time being, we should stay consistent with text.
1171         _type = kWKActionMenuReadOnlyText;
1172         return [self _defaultMenuItemsForText];
1173     }
1174
1175     _type = kWKActionMenuNone;
1176     return _state == ActionMenuState::Pending ? @[ [NSMenuItem separatorItem] ] : @[ ];
1177 }
1178
1179 - (void)_updateActionMenuItems
1180 {
1181     [_wkView.actionMenu removeAllItems];
1182
1183     NSArray *menuItems = [self _defaultMenuItems];
1184     RefPtr<WebHitTestResult> hitTestResult = [self _webHitTestResult];
1185
1186     if ([_wkView respondsToSelector:@selector(_actionMenuItemsForHitTestResult:defaultActionMenuItems:)])
1187         menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) defaultActionMenuItems:menuItems];
1188     else
1189         menuItems = [_wkView _actionMenuItemsForHitTestResult:toAPI(hitTestResult.get()) withType:_type defaultActionMenuItems:menuItems userData:toAPI(_userData.get())];
1190
1191     for (NSMenuItem *item in menuItems)
1192         [_wkView.actionMenu addItem:item];
1193
1194     if (!_wkView.actionMenu.numberOfItems)
1195         [_wkView.actionMenu cancelTracking];
1196 }
1197
1198 #if WK_API_ENABLED
1199
1200 #pragma mark WKPagePreviewViewControllerDelegate
1201
1202 - (NSView *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController viewForPreviewingURL:(NSURL *)url initialFrameSize:(NSSize)initialFrameSize
1203 {
1204     return [_wkView _viewForPreviewingURL:url initialFrameSize:initialFrameSize];
1205 }
1206
1207 - (NSString *)pagePreviewViewController:(WKPagePreviewViewController *)pagePreviewViewController titleForPreviewOfURL:(NSURL *)url
1208 {
1209     return [_wkView _titleForPreviewOfURL:url];
1210 }
1211
1212 - (void)pagePreviewViewControllerWasClicked:(WKPagePreviewViewController *)pagePreviewViewController
1213 {
1214     if (NSURL *url = pagePreviewViewController->_url.get())
1215         [_wkView _handleClickInPreviewView:pagePreviewViewController->_previewView.get() URL:url];
1216 }
1217
1218 #endif
1219
1220 @end
1221
1222 #endif // PLATFORM(MAC)