| /* |
| * Copyright (C) 2006, 2007, 2008, 2015 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #if !PLATFORM(IOS_FAMILY) |
| |
| #import "WebContextMenuClient.h" |
| |
| #import "WebDelegateImplementationCaching.h" |
| #import "WebElementDictionary.h" |
| #import "WebFrameInternal.h" |
| #import "WebFrameView.h" |
| #import "WebHTMLViewInternal.h" |
| #import "WebKitVersionChecks.h" |
| #import "WebNSPasteboardExtras.h" |
| #import "WebSharingServicePickerController.h" |
| #import "WebUIDelegatePrivate.h" |
| #import "WebViewInternal.h" |
| #import <WebCore/BitmapImage.h> |
| #import <WebCore/ContextMenu.h> |
| #import <WebCore/ContextMenuController.h> |
| #import <WebCore/Document.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameView.h> |
| #import <WebCore/GraphicsContext.h> |
| #import <WebCore/ImageBuffer.h> |
| #import <WebCore/LocalizedStrings.h> |
| #import <WebCore/Page.h> |
| #import <WebCore/RenderBox.h> |
| #import <WebCore/RenderObject.h> |
| #import <WebCore/RuntimeApplicationChecks.h> |
| #import <WebCore/SharedBuffer.h> |
| #import <WebKitLegacy/DOMPrivate.h> |
| #import <pal/spi/mac/NSSharingServicePickerSPI.h> |
| #import <wtf/URL.h> |
| |
| using namespace WebCore; |
| |
| @interface NSApplication () |
| - (BOOL)isSpeaking; |
| - (void)speakString:(NSString *)string; |
| - (void)stopSpeaking:(id)sender; |
| @end |
| |
| WebContextMenuClient::WebContextMenuClient(WebView *webView) |
| #if ENABLE(SERVICE_CONTROLS) |
| : WebSharingServicePickerClient(webView) |
| #else |
| : m_webView(webView) |
| #endif |
| { |
| } |
| |
| WebContextMenuClient::~WebContextMenuClient() |
| { |
| #if ENABLE(SERVICE_CONTROLS) |
| if (m_sharingServicePickerController) |
| [m_sharingServicePickerController clear]; |
| #endif |
| } |
| |
| void WebContextMenuClient::contextMenuDestroyed() |
| { |
| delete this; |
| } |
| |
| void WebContextMenuClient::downloadURL(const URL& url) |
| { |
| [m_webView _downloadURL:url]; |
| } |
| |
| void WebContextMenuClient::searchWithSpotlight() |
| { |
| [m_webView _searchWithSpotlightFromMenu:nil]; |
| } |
| |
| void WebContextMenuClient::searchWithGoogle(const Frame*) |
| { |
| [m_webView _searchWithGoogleFromMenu:nil]; |
| } |
| |
| void WebContextMenuClient::lookUpInDictionary(Frame* frame) |
| { |
| WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView]; |
| if(![htmlView isKindOfClass:[WebHTMLView class]]) |
| return; |
| [htmlView _lookUpInDictionaryFromMenu:nil]; |
| } |
| |
| bool WebContextMenuClient::isSpeaking() |
| { |
| return [NSApp isSpeaking]; |
| } |
| |
| void WebContextMenuClient::speak(const String& string) |
| { |
| [NSApp speakString:(NSString *)string]; |
| } |
| |
| void WebContextMenuClient::stopSpeaking() |
| { |
| [NSApp stopSpeaking:nil]; |
| } |
| |
| bool WebContextMenuClient::clientFloatRectForNode(Node& node, FloatRect& rect) const |
| { |
| RenderObject* renderer = node.renderer(); |
| if (!renderer) { |
| // This method shouldn't be called in cases where the controlled node hasn't rendered. |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| if (!is<RenderBox>(*renderer)) |
| return false; |
| auto& renderBox = downcast<RenderBox>(*renderer); |
| |
| LayoutRect layoutRect = renderBox.clientBoxRect(); |
| FloatQuad floatQuad = renderBox.localToAbsoluteQuad(FloatQuad(layoutRect)); |
| rect = floatQuad.boundingBox(); |
| |
| return true; |
| } |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| void WebContextMenuClient::sharingServicePickerWillBeDestroyed(WebSharingServicePickerController &) |
| { |
| m_sharingServicePickerController = nil; |
| } |
| |
| WebCore::FloatRect WebContextMenuClient::screenRectForCurrentSharingServicePickerItem(WebSharingServicePickerController &) |
| { |
| Page* page = [m_webView page]; |
| if (!page) |
| return NSZeroRect; |
| |
| Node* node = page->contextMenuController().context().hitTestResult().innerNode(); |
| if (!node) |
| return NSZeroRect; |
| |
| FrameView* frameView = node->document().view(); |
| if (!frameView) { |
| // This method shouldn't be called in cases where the controlled node isn't in a rendered view. |
| ASSERT_NOT_REACHED(); |
| return NSZeroRect; |
| } |
| |
| FloatRect rect; |
| if (!clientFloatRectForNode(*node, rect)) |
| return NSZeroRect; |
| |
| // FIXME: https://webkit.org/b/132915 |
| // Ideally we'd like to convert the content rect to screen coordinates without the lossy float -> int conversion. |
| // Creating a rounded int rect works well in practice, but might still lead to off-by-one-pixel problems in edge cases. |
| IntRect intRect = roundedIntRect(rect); |
| return frameView->contentsToScreen(intRect); |
| } |
| |
| RetainPtr<NSImage> WebContextMenuClient::imageForCurrentSharingServicePickerItem(WebSharingServicePickerController &) |
| { |
| Page* page = [m_webView page]; |
| if (!page) |
| return nil; |
| |
| Node* node = page->contextMenuController().context().hitTestResult().innerNode(); |
| if (!node) |
| return nil; |
| |
| FrameView* frameView = node->document().view(); |
| if (!frameView) { |
| // This method shouldn't be called in cases where the controlled node isn't in a rendered view. |
| ASSERT_NOT_REACHED(); |
| return nil; |
| } |
| |
| FloatRect rect; |
| if (!clientFloatRectForNode(*node, rect)) |
| return nil; |
| |
| // This is effectively a snapshot, and will be painted in an unaccelerated fashion in line with FrameSnapshotting. |
| std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(rect.size(), Unaccelerated); |
| if (!buffer) |
| return nil; |
| |
| VisibleSelection oldSelection = frameView->frame().selection().selection(); |
| auto range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor)); |
| frameView->frame().selection().setSelection(VisibleSelection(range.get()), FrameSelection::DoNotSetFocus); |
| |
| OptionSet<PaintBehavior> oldPaintBehavior = frameView->paintBehavior(); |
| frameView->setPaintBehavior(PaintBehavior::SelectionOnly); |
| |
| buffer->context().translate(-toFloatSize(rect.location())); |
| frameView->paintContents(buffer->context(), roundedIntRect(rect)); |
| |
| frameView->frame().selection().setSelection(oldSelection); |
| frameView->setPaintBehavior(oldPaintBehavior); |
| |
| RefPtr<Image> image = ImageBuffer::sinkIntoImage(WTFMove(buffer)); |
| if (!image) |
| return nil; |
| |
| return image->snapshotNSImage(); |
| } |
| #endif |
| |
| NSMenu *WebContextMenuClient::contextMenuForEvent(NSEvent *event, NSView *view, bool& isServicesMenu) |
| { |
| isServicesMenu = false; |
| |
| Page* page = [m_webView page]; |
| if (!page) |
| return nil; |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| if (Image* image = page->contextMenuController().context().controlledImage()) { |
| ASSERT(page->contextMenuController().context().hitTestResult().innerNode()); |
| |
| RetainPtr<NSItemProvider> itemProvider = adoptNS([[NSItemProvider alloc] initWithItem:image->snapshotNSImage().get() typeIdentifier:@"public.image"]); |
| |
| bool isContentEditable = page->contextMenuController().context().hitTestResult().innerNode()->isContentEditable(); |
| m_sharingServicePickerController = adoptNS([[WebSharingServicePickerController alloc] initWithItems:@[ itemProvider.get() ] includeEditorServices:isContentEditable client:this style:NSSharingServicePickerStyleRollover]); |
| |
| isServicesMenu = true; |
| return [m_sharingServicePickerController menu]; |
| } |
| #endif |
| |
| return [view menuForEvent:event]; |
| } |
| |
| void WebContextMenuClient::showContextMenu() |
| { |
| Page* page = [m_webView page]; |
| if (!page) |
| return; |
| Frame* frame = page->contextMenuController().hitTestResult().innerNodeFrame(); |
| if (!frame) |
| return; |
| FrameView* frameView = frame->view(); |
| if (!frameView) |
| return; |
| |
| NSView* view = frameView->documentView(); |
| IntPoint point = frameView->contentsToWindow(page->contextMenuController().hitTestResult().roundedPointInInnerNodeFrame()); |
| NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:point modifierFlags:0 timestamp:0 windowNumber:[[view window] windowNumber] context:0 eventNumber:0 clickCount:1 pressure:1]; |
| |
| // Show the contextual menu for this event. |
| bool isServicesMenu; |
| if (NSMenu *menu = contextMenuForEvent(event, view, isServicesMenu)) { |
| if (isServicesMenu) |
| [menu popUpMenuPositioningItem:nil atLocation:[view convertPoint:point toView:nil] inView:view]; |
| else |
| [NSMenu popUpContextMenu:menu withEvent:event forView:view]; |
| } |
| } |
| |
| #endif // !PLATFORM(IOS_FAMILY) |