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