Remove dead context menu code
[WebKit-https.git] / Source / WebKit / mac / WebCoreSupport / WebContextMenuClient.mm
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2015 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #if !PLATFORM(IOS)
30
31 #import "WebContextMenuClient.h"
32
33 #import "WebDelegateImplementationCaching.h"
34 #import "WebElementDictionary.h"
35 #import "WebFrame.h"
36 #import "WebFrameInternal.h"
37 #import "WebHTMLView.h"
38 #import "WebHTMLViewInternal.h"
39 #import "WebKitVersionChecks.h"
40 #import "WebNSPasteboardExtras.h"
41 #import "WebSharingServicePickerController.h"
42 #import "WebUIDelegate.h"
43 #import "WebUIDelegatePrivate.h"
44 #import "WebView.h"
45 #import "WebViewInternal.h"
46 #import <WebCore/BitmapImage.h>
47 #import <WebCore/ContextMenu.h>
48 #import <WebCore/ContextMenuController.h>
49 #import <WebCore/Document.h>
50 #import <WebCore/Frame.h>
51 #import <WebCore/FrameView.h>
52 #import <WebCore/GraphicsContext.h>
53 #import <WebCore/ImageBuffer.h>
54 #import <WebCore/LocalizedStrings.h>
55 #import <WebCore/NSSharingServicePickerSPI.h>
56 #import <WebCore/Page.h>
57 #import <WebCore/RenderBox.h>
58 #import <WebCore/RenderObject.h>
59 #import <WebCore/SharedBuffer.h>
60 #import <WebCore/RuntimeApplicationChecks.h>
61 #import <WebCore/URL.h>
62 #import <WebKitLegacy/DOMPrivate.h>
63
64 using namespace WebCore;
65
66 @interface NSApplication (AppKitSecretsIKnowAbout)
67 - (void)speakString:(NSString *)string;
68 @end
69
70 WebContextMenuClient::WebContextMenuClient(WebView *webView)
71 #if ENABLE(SERVICE_CONTROLS)
72     : WebSharingServicePickerClient(webView)
73 #else
74     : m_webView(webView)
75 #endif
76 {
77 }
78
79 WebContextMenuClient::~WebContextMenuClient()
80 {
81 #if ENABLE(SERVICE_CONTROLS)
82     if (m_sharingServicePickerController)
83         [m_sharingServicePickerController clear];
84 #endif
85 }
86
87 void WebContextMenuClient::contextMenuDestroyed()
88 {
89     delete this;
90 }
91
92 void WebContextMenuClient::contextMenuItemSelected(ContextMenuItem* item, const ContextMenu* parentMenu)
93 {
94     id delegate = [m_webView UIDelegate];
95     SEL selector = @selector(webView:contextMenuItemSelected:forElement:);
96     if ([delegate respondsToSelector:selector]) {
97         NSDictionary *element = [[WebElementDictionary alloc] initWithHitTestResult:[m_webView page]->contextMenuController().hitTestResult()];
98
99         CallUIDelegate(m_webView, selector, item->platformDescription(), element);
100
101         [element release];
102     }
103 }
104
105 void WebContextMenuClient::downloadURL(const URL& url)
106 {
107     [m_webView _downloadURL:url];
108 }
109
110 void WebContextMenuClient::searchWithSpotlight()
111 {
112     [m_webView _searchWithSpotlightFromMenu:nil];
113 }
114
115 void WebContextMenuClient::searchWithGoogle(const Frame*)
116 {
117     [m_webView _searchWithGoogleFromMenu:nil];
118 }
119
120 void WebContextMenuClient::lookUpInDictionary(Frame* frame)
121 {
122     WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView];
123     if(![htmlView isKindOfClass:[WebHTMLView class]])
124         return;
125     [htmlView _lookUpInDictionaryFromMenu:nil];
126 }
127
128 bool WebContextMenuClient::isSpeaking()
129 {
130     return [NSApp isSpeaking];
131 }
132
133 void WebContextMenuClient::speak(const String& string)
134 {
135     [NSApp speakString:[[(NSString*)string copy] autorelease]];
136 }
137
138 void WebContextMenuClient::stopSpeaking()
139 {
140     [NSApp stopSpeaking:nil];
141 }
142
143 ContextMenuItem WebContextMenuClient::shareMenuItem(const HitTestResult& hitTestResult)
144 {
145     if (![[NSMenuItem class] respondsToSelector:@selector(standardShareMenuItemWithItems:)])
146         return ContextMenuItem();
147
148     Node* node = hitTestResult.innerNonSharedNode();
149     if (!node)
150         return ContextMenuItem();
151
152     Frame* frame = node->document().frame();
153     if (!frame)
154         return ContextMenuItem();
155
156     URL downloadableMediaURL;
157     if (!hitTestResult.absoluteMediaURL().isEmpty() && hitTestResult.isDownloadableMedia())
158         downloadableMediaURL = hitTestResult.absoluteMediaURL();
159
160     RetainPtr<NSImage> nsImage;
161     if (Image* image = hitTestResult.image()) {
162         if (RefPtr<SharedBuffer> buffer = image->data())
163             nsImage = adoptNS([[NSImage alloc] initWithData:[NSData dataWithBytes:buffer->data() length:buffer->size()]]);
164     }
165
166     return ContextMenuItem::shareMenuItem(hitTestResult.absoluteLinkURL(), downloadableMediaURL, nsImage.get(), hitTestResult.selectedText());
167 }
168
169 bool WebContextMenuClient::clientFloatRectForNode(Node& node, FloatRect& rect) const
170 {
171     RenderObject* renderer = node.renderer();
172     if (!renderer) {
173         // This method shouldn't be called in cases where the controlled node hasn't rendered.
174         ASSERT_NOT_REACHED();
175         return false;
176     }
177
178     if (!is<RenderBox>(*renderer))
179         return false;
180     auto& renderBox = downcast<RenderBox>(*renderer);
181
182     LayoutRect layoutRect = renderBox.clientBoxRect();
183     FloatQuad floatQuad = renderBox.localToAbsoluteQuad(FloatQuad(layoutRect));
184     rect = floatQuad.boundingBox();
185
186     return true;
187 }
188
189 #if ENABLE(SERVICE_CONTROLS)
190 void WebContextMenuClient::sharingServicePickerWillBeDestroyed(WebSharingServicePickerController &)
191 {
192     m_sharingServicePickerController = nil;
193 }
194
195 WebCore::FloatRect WebContextMenuClient::screenRectForCurrentSharingServicePickerItem(WebSharingServicePickerController &)
196 {
197     Page* page = [m_webView page];
198     if (!page)
199         return NSZeroRect;
200
201     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
202     if (!node)
203         return NSZeroRect;
204
205     FrameView* frameView = node->document().view();
206     if (!frameView) {
207         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
208         ASSERT_NOT_REACHED();
209         return NSZeroRect;
210     }
211
212     FloatRect rect;
213     if (!clientFloatRectForNode(*node, rect))
214         return NSZeroRect;
215
216     // FIXME: https://webkit.org/b/132915
217     // Ideally we'd like to convert the content rect to screen coordinates without the lossy float -> int conversion.
218     // Creating a rounded int rect works well in practice, but might still lead to off-by-one-pixel problems in edge cases.
219     IntRect intRect = roundedIntRect(rect);
220     return frameView->contentsToScreen(intRect);
221 }
222
223 RetainPtr<NSImage> WebContextMenuClient::imageForCurrentSharingServicePickerItem(WebSharingServicePickerController &)
224 {
225     Page* page = [m_webView page];
226     if (!page)
227         return nil;
228
229     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
230     if (!node)
231         return nil;
232
233     FrameView* frameView = node->document().view();
234     if (!frameView) {
235         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
236         ASSERT_NOT_REACHED();
237         return nil;
238     }
239
240     FloatRect rect;
241     if (!clientFloatRectForNode(*node, rect))
242         return nil;
243
244     // This is effectively a snapshot, and will be painted in an unaccelerated fashion in line with FrameSnapshotting.
245     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(rect.size(), Unaccelerated);
246     if (!buffer)
247         return nil;
248
249     VisibleSelection oldSelection = frameView->frame().selection().selection();
250     RefPtr<Range> range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
251     frameView->frame().selection().setSelection(VisibleSelection(*range), FrameSelection::DoNotSetFocus);
252
253     PaintBehavior oldPaintBehavior = frameView->paintBehavior();
254     frameView->setPaintBehavior(PaintBehaviorSelectionOnly);
255
256     buffer->context().translate(-toFloatSize(rect.location()));
257     frameView->paintContents(buffer->context(), roundedIntRect(rect));
258
259     frameView->frame().selection().setSelection(oldSelection);
260     frameView->setPaintBehavior(oldPaintBehavior);
261
262     RefPtr<Image> image = buffer->copyImage(DontCopyBackingStore);
263     if (!image)
264         return nil;
265
266     return [[image->getNSImage() retain] autorelease];
267 }
268 #endif
269
270 NSMenu *WebContextMenuClient::contextMenuForEvent(NSEvent *event, NSView *view, bool& isServicesMenu)
271 {
272     isServicesMenu = false;
273
274     Page* page = [m_webView page];
275     if (!page)
276         return nil;
277
278 #if ENABLE(SERVICE_CONTROLS) && defined(__LP64__)
279     if (Image* image = page->contextMenuController().context().controlledImage()) {
280         ASSERT(page->contextMenuController().context().hitTestResult().innerNode());
281
282         RetainPtr<NSItemProvider> itemProvider = adoptNS([[NSItemProvider alloc] initWithItem:image->getNSImage() typeIdentifier:@"public.image"]);
283
284         bool isContentEditable = page->contextMenuController().context().hitTestResult().innerNode()->isContentEditable();
285         m_sharingServicePickerController = adoptNS([[WebSharingServicePickerController alloc] initWithItems:@[ itemProvider.get() ] includeEditorServices:isContentEditable client:this style:NSSharingServicePickerStyleRollover]);
286
287         isServicesMenu = true;
288         return [m_sharingServicePickerController menu];
289     }
290 #endif
291
292     return [view menuForEvent:event];
293 }
294
295 void WebContextMenuClient::showContextMenu()
296 {
297     Page* page = [m_webView page];
298     if (!page)
299         return;
300     Frame* frame = page->contextMenuController().hitTestResult().innerNodeFrame();
301     if (!frame)
302         return;
303     FrameView* frameView = frame->view();
304     if (!frameView)
305         return;
306
307     NSView* view = frameView->documentView();
308     IntPoint point = frameView->contentsToWindow(page->contextMenuController().hitTestResult().roundedPointInInnerNodeFrame());
309     NSEvent* event = [NSEvent mouseEventWithType:NSRightMouseDown location:point modifierFlags:0 timestamp:0 windowNumber:[[view window] windowNumber] context:0 eventNumber:0 clickCount:1 pressure:1];
310
311     // Show the contextual menu for this event.
312     bool isServicesMenu;
313     if (NSMenu *menu = contextMenuForEvent(event, view, isServicesMenu)) {
314         if (isServicesMenu)
315             [menu popUpMenuPositioningItem:nil atLocation:[view convertPoint:point toView:nil] inView:view];
316         else
317             [NSMenu popUpContextMenu:menu withEvent:event forView:view];
318     }
319 }
320
321 #endif // !PLATFORM(IOS)