0ec1223ca141074e31adbf8e6c3c93002f8c2706
[WebKit-https.git] / Source / WebKitLegacy / 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/Page.h>
56 #import <WebCore/RenderBox.h>
57 #import <WebCore/RenderObject.h>
58 #import <WebCore/RuntimeApplicationChecks.h>
59 #import <WebCore/SharedBuffer.h>
60 #import <WebCore/URL.h>
61 #import <WebKitLegacy/DOMPrivate.h>
62 #import <pal/spi/mac/NSSharingServicePickerSPI.h>
63
64 using namespace WebCore;
65
66 @interface NSApplication ()
67 - (BOOL)isSpeaking;
68 - (void)speakString:(NSString *)string;
69 - (void)stopSpeaking:(id)sender;
70 @end
71
72 WebContextMenuClient::WebContextMenuClient(WebView *webView)
73 #if ENABLE(SERVICE_CONTROLS)
74     : WebSharingServicePickerClient(webView)
75 #else
76     : m_webView(webView)
77 #endif
78 {
79 }
80
81 WebContextMenuClient::~WebContextMenuClient()
82 {
83 #if ENABLE(SERVICE_CONTROLS)
84     if (m_sharingServicePickerController)
85         [m_sharingServicePickerController clear];
86 #endif
87 }
88
89 void WebContextMenuClient::contextMenuDestroyed()
90 {
91     delete this;
92 }
93
94 void WebContextMenuClient::downloadURL(const URL& url)
95 {
96     [m_webView _downloadURL:url];
97 }
98
99 void WebContextMenuClient::searchWithSpotlight()
100 {
101     [m_webView _searchWithSpotlightFromMenu:nil];
102 }
103
104 void WebContextMenuClient::searchWithGoogle(const Frame*)
105 {
106     [m_webView _searchWithGoogleFromMenu:nil];
107 }
108
109 void WebContextMenuClient::lookUpInDictionary(Frame* frame)
110 {
111     WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView];
112     if(![htmlView isKindOfClass:[WebHTMLView class]])
113         return;
114     [htmlView _lookUpInDictionaryFromMenu:nil];
115 }
116
117 bool WebContextMenuClient::isSpeaking()
118 {
119     return [NSApp isSpeaking];
120 }
121
122 void WebContextMenuClient::speak(const String& string)
123 {
124     [NSApp speakString:[[(NSString*)string copy] autorelease]];
125 }
126
127 void WebContextMenuClient::stopSpeaking()
128 {
129     [NSApp stopSpeaking:nil];
130 }
131
132 bool WebContextMenuClient::clientFloatRectForNode(Node& node, FloatRect& rect) const
133 {
134     RenderObject* renderer = node.renderer();
135     if (!renderer) {
136         // This method shouldn't be called in cases where the controlled node hasn't rendered.
137         ASSERT_NOT_REACHED();
138         return false;
139     }
140
141     if (!is<RenderBox>(*renderer))
142         return false;
143     auto& renderBox = downcast<RenderBox>(*renderer);
144
145     LayoutRect layoutRect = renderBox.clientBoxRect();
146     FloatQuad floatQuad = renderBox.localToAbsoluteQuad(FloatQuad(layoutRect));
147     rect = floatQuad.boundingBox();
148
149     return true;
150 }
151
152 #if ENABLE(SERVICE_CONTROLS)
153 void WebContextMenuClient::sharingServicePickerWillBeDestroyed(WebSharingServicePickerController &)
154 {
155     m_sharingServicePickerController = nil;
156 }
157
158 WebCore::FloatRect WebContextMenuClient::screenRectForCurrentSharingServicePickerItem(WebSharingServicePickerController &)
159 {
160     Page* page = [m_webView page];
161     if (!page)
162         return NSZeroRect;
163
164     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
165     if (!node)
166         return NSZeroRect;
167
168     FrameView* frameView = node->document().view();
169     if (!frameView) {
170         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
171         ASSERT_NOT_REACHED();
172         return NSZeroRect;
173     }
174
175     FloatRect rect;
176     if (!clientFloatRectForNode(*node, rect))
177         return NSZeroRect;
178
179     // FIXME: https://webkit.org/b/132915
180     // Ideally we'd like to convert the content rect to screen coordinates without the lossy float -> int conversion.
181     // Creating a rounded int rect works well in practice, but might still lead to off-by-one-pixel problems in edge cases.
182     IntRect intRect = roundedIntRect(rect);
183     return frameView->contentsToScreen(intRect);
184 }
185
186 RetainPtr<NSImage> WebContextMenuClient::imageForCurrentSharingServicePickerItem(WebSharingServicePickerController &)
187 {
188     Page* page = [m_webView page];
189     if (!page)
190         return nil;
191
192     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
193     if (!node)
194         return nil;
195
196     FrameView* frameView = node->document().view();
197     if (!frameView) {
198         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
199         ASSERT_NOT_REACHED();
200         return nil;
201     }
202
203     FloatRect rect;
204     if (!clientFloatRectForNode(*node, rect))
205         return nil;
206
207     // This is effectively a snapshot, and will be painted in an unaccelerated fashion in line with FrameSnapshotting.
208     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(rect.size(), Unaccelerated);
209     if (!buffer)
210         return nil;
211
212     VisibleSelection oldSelection = frameView->frame().selection().selection();
213     RefPtr<Range> range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
214     frameView->frame().selection().setSelection(VisibleSelection(*range), FrameSelection::DoNotSetFocus);
215
216     PaintBehavior oldPaintBehavior = frameView->paintBehavior();
217     frameView->setPaintBehavior(PaintBehaviorSelectionOnly);
218
219     buffer->context().translate(-toFloatSize(rect.location()));
220     frameView->paintContents(buffer->context(), roundedIntRect(rect));
221
222     frameView->frame().selection().setSelection(oldSelection);
223     frameView->setPaintBehavior(oldPaintBehavior);
224
225     RefPtr<Image> image = ImageBuffer::sinkIntoImage(WTFMove(buffer));
226     if (!image)
227         return nil;
228
229     return image->snapshotNSImage();
230 }
231 #endif
232
233 NSMenu *WebContextMenuClient::contextMenuForEvent(NSEvent *event, NSView *view, bool& isServicesMenu)
234 {
235     isServicesMenu = false;
236
237     Page* page = [m_webView page];
238     if (!page)
239         return nil;
240
241 #if ENABLE(SERVICE_CONTROLS) && defined(__LP64__)
242     if (Image* image = page->contextMenuController().context().controlledImage()) {
243         ASSERT(page->contextMenuController().context().hitTestResult().innerNode());
244
245         RetainPtr<NSItemProvider> itemProvider = adoptNS([[NSItemProvider alloc] initWithItem:image->snapshotNSImage().get() typeIdentifier:@"public.image"]);
246
247         bool isContentEditable = page->contextMenuController().context().hitTestResult().innerNode()->isContentEditable();
248         m_sharingServicePickerController = adoptNS([[WebSharingServicePickerController alloc] initWithItems:@[ itemProvider.get() ] includeEditorServices:isContentEditable client:this style:NSSharingServicePickerStyleRollover]);
249
250         isServicesMenu = true;
251         return [m_sharingServicePickerController menu];
252     }
253 #endif
254
255     return [view menuForEvent:event];
256 }
257
258 void WebContextMenuClient::showContextMenu()
259 {
260     Page* page = [m_webView page];
261     if (!page)
262         return;
263     Frame* frame = page->contextMenuController().hitTestResult().innerNodeFrame();
264     if (!frame)
265         return;
266     FrameView* frameView = frame->view();
267     if (!frameView)
268         return;
269
270     NSView* view = frameView->documentView();
271     IntPoint point = frameView->contentsToWindow(page->contextMenuController().hitTestResult().roundedPointInInnerNodeFrame());
272     NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:point modifierFlags:0 timestamp:0 windowNumber:[[view window] windowNumber] context:0 eventNumber:0 clickCount:1 pressure:1];
273
274     // Show the contextual menu for this event.
275     bool isServicesMenu;
276     if (NSMenu *menu = contextMenuForEvent(event, view, isServicesMenu)) {
277         if (isServicesMenu)
278             [menu popUpMenuPositioningItem:nil atLocation:[view convertPoint:point toView:nil] inView:view];
279         else
280             [NSMenu popUpContextMenu:menu withEvent:event forView:view];
281     }
282 }
283
284 #endif // !PLATFORM(IOS)