Build fix for 32-bit Yosemite
[WebKit-https.git] / Source / WebKit / mac / WebCoreSupport / WebContextMenuClient.mm
1 /*
2  * Copyright (C) 2006, 2007, 2008 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 <WebCore/BitmapImage.h>
34 #import "WebDelegateImplementationCaching.h"
35 #import "WebElementDictionary.h"
36 #import "WebFrame.h"
37 #import "WebFrameInternal.h"
38 #import "WebHTMLView.h"
39 #import "WebHTMLViewInternal.h"
40 #import "WebKitVersionChecks.h"
41 #import "WebNSPasteboardExtras.h"
42 #import "WebSharingServicePickerController.h"
43 #import "WebUIDelegate.h"
44 #import "WebUIDelegatePrivate.h"
45 #import "WebView.h"
46 #import "WebViewInternal.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/SharedBuffer.h>
59 #import <WebCore/RuntimeApplicationChecks.h>
60 #import <WebCore/URL.h>
61 #import <WebKitLegacy/DOMPrivate.h>
62
63 using namespace WebCore;
64
65 @interface NSApplication (AppKitSecretsIKnowAbout)
66 - (void)speakString:(NSString *)string;
67 @end
68
69 WebContextMenuClient::WebContextMenuClient(WebView *webView) 
70     : m_webView(webView)
71 {
72 }
73
74 WebContextMenuClient::~WebContextMenuClient()
75 {
76 #if ENABLE(SERVICE_CONTROLS)
77     if (m_sharingServicePickerController)
78         [m_sharingServicePickerController clear];
79 #endif
80 }
81
82 void WebContextMenuClient::contextMenuDestroyed()
83 {
84     delete this;
85 }
86
87 static BOOL isPreVersion3Client(void)
88 {
89     static BOOL preVersion3Client = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_3_0_CONTEXT_MENU_TAGS);
90     return preVersion3Client;
91 }
92
93 static BOOL isPreInspectElementTagClient(void)
94 {
95     static BOOL preInspectElementTagClient = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_INSPECT_ELEMENT_MENU_TAG);
96     return preInspectElementTagClient;
97 }
98
99 static NSMutableArray *fixMenusToSendToOldClients(NSMutableArray *defaultMenuItems)
100 {
101     NSMutableArray *savedItems = nil;
102
103     unsigned defaultItemsCount = [defaultMenuItems count];
104
105     if (isPreInspectElementTagClient() && defaultItemsCount >= 2) {
106         NSMenuItem *secondToLastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 2];
107         NSMenuItem *lastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 1];
108
109         if ([secondToLastItem isSeparatorItem] && [lastItem tag] == WebMenuItemTagInspectElement) {
110             savedItems = [NSMutableArray arrayWithCapacity:2];
111             [savedItems addObject:secondToLastItem];
112             [savedItems addObject:lastItem];
113
114             [defaultMenuItems removeObject:secondToLastItem];
115             [defaultMenuItems removeObject:lastItem];
116             defaultItemsCount -= 2;
117         }
118     }
119
120     BOOL preVersion3Client = isPreVersion3Client();
121     if (!preVersion3Client)
122         return savedItems;
123         
124     for (unsigned i = 0; i < defaultItemsCount; ++i) {
125         NSMenuItem *item = [defaultMenuItems objectAtIndex:i];
126         int tag = [item tag];
127         int oldStyleTag = tag;
128
129         if (tag >= WEBMENUITEMTAG_WEBKIT_3_0_SPI_START) {
130             // Change all editing-related SPI tags listed in WebUIDelegatePrivate.h to WebMenuItemTagOther
131             // to match our old WebKit context menu behavior.
132             oldStyleTag = WebMenuItemTagOther;
133         } else {
134             // All items are expected to have useful tags coming into this method.
135             ASSERT(tag != WebMenuItemTagOther);
136             
137             // Use the pre-3.0 tags for the few items that changed tags as they moved from SPI to API. We
138             // do this only for old clients; new Mail already expects the new symbols in this case.
139             if (preVersion3Client) {
140                 switch (tag) {
141                     case WebMenuItemTagSearchInSpotlight:
142                         oldStyleTag = OldWebMenuItemTagSearchInSpotlight;
143                         break;
144                     case WebMenuItemTagSearchWeb:
145                         oldStyleTag = OldWebMenuItemTagSearchWeb;
146                         break;
147                     case WebMenuItemTagLookUpInDictionary:
148                         oldStyleTag = OldWebMenuItemTagLookUpInDictionary;
149                         break;
150                     default:
151                         break;
152                 }
153             }
154         }
155
156         if (oldStyleTag != tag)
157             [item setTag:oldStyleTag];
158     }
159
160     return savedItems;
161 }
162
163 static void fixMenusReceivedFromOldClients(NSMutableArray *newMenuItems, NSMutableArray *savedItems)
164 {   
165     if (savedItems)
166         [newMenuItems addObjectsFromArray:savedItems];
167
168     BOOL preVersion3Client = isPreVersion3Client();
169     if (!preVersion3Client)
170         return;
171     
172     // Restore the modern tags to the menu items whose tags we altered in fixMenusToSendToOldClients. 
173     unsigned newItemsCount = [newMenuItems count];
174     for (unsigned i = 0; i < newItemsCount; ++i) {
175         NSMenuItem *item = [newMenuItems objectAtIndex:i];
176         
177         int tag = [item tag];
178         int modernTag = tag;
179         
180         if (tag == WebMenuItemTagOther) {
181             // Restore the specific tag for items on which we temporarily set WebMenuItemTagOther to match old behavior.
182             NSString *title = [item title];
183             if ([title isEqualToString:contextMenuItemTagOpenLink()])
184                 modernTag = WebMenuItemTagOpenLink;
185             else if ([title isEqualToString:contextMenuItemTagIgnoreGrammar()])
186                 modernTag = WebMenuItemTagIgnoreGrammar;
187             else if ([title isEqualToString:contextMenuItemTagSpellingMenu()])
188                 modernTag = WebMenuItemTagSpellingMenu;
189             else if ([title isEqualToString:contextMenuItemTagShowSpellingPanel(true)]
190                      || [title isEqualToString:contextMenuItemTagShowSpellingPanel(false)])
191                 modernTag = WebMenuItemTagShowSpellingPanel;
192             else if ([title isEqualToString:contextMenuItemTagCheckSpelling()])
193                 modernTag = WebMenuItemTagCheckSpelling;
194             else if ([title isEqualToString:contextMenuItemTagCheckSpellingWhileTyping()])
195                 modernTag = WebMenuItemTagCheckSpellingWhileTyping;
196             else if ([title isEqualToString:contextMenuItemTagCheckGrammarWithSpelling()])
197                 modernTag = WebMenuItemTagCheckGrammarWithSpelling;
198             else if ([title isEqualToString:contextMenuItemTagFontMenu()])
199                 modernTag = WebMenuItemTagFontMenu;
200             else if ([title isEqualToString:contextMenuItemTagShowFonts()])
201                 modernTag = WebMenuItemTagShowFonts;
202             else if ([title isEqualToString:contextMenuItemTagBold()])
203                 modernTag = WebMenuItemTagBold;
204             else if ([title isEqualToString:contextMenuItemTagItalic()])
205                 modernTag = WebMenuItemTagItalic;
206             else if ([title isEqualToString:contextMenuItemTagUnderline()])
207                 modernTag = WebMenuItemTagUnderline;
208             else if ([title isEqualToString:contextMenuItemTagOutline()])
209                 modernTag = WebMenuItemTagOutline;
210             else if ([title isEqualToString:contextMenuItemTagStyles()])
211                 modernTag = WebMenuItemTagStyles;
212             else if ([title isEqualToString:contextMenuItemTagShowColors()])
213                 modernTag = WebMenuItemTagShowColors;
214             else if ([title isEqualToString:contextMenuItemTagSpeechMenu()])
215                 modernTag = WebMenuItemTagSpeechMenu;
216             else if ([title isEqualToString:contextMenuItemTagStartSpeaking()])
217                 modernTag = WebMenuItemTagStartSpeaking;
218             else if ([title isEqualToString:contextMenuItemTagStopSpeaking()])
219                 modernTag = WebMenuItemTagStopSpeaking;
220             else if ([title isEqualToString:contextMenuItemTagWritingDirectionMenu()])
221                 modernTag = WebMenuItemTagWritingDirectionMenu;
222             else if ([title isEqualToString:contextMenuItemTagDefaultDirection()])
223                 modernTag = WebMenuItemTagDefaultDirection;
224             else if ([title isEqualToString:contextMenuItemTagLeftToRight()])
225                 modernTag = WebMenuItemTagLeftToRight;
226             else if ([title isEqualToString:contextMenuItemTagRightToLeft()])
227                 modernTag = WebMenuItemTagRightToLeft;
228             else if ([title isEqualToString:contextMenuItemTagInspectElement()])
229                 modernTag = WebMenuItemTagInspectElement;
230             else if ([title isEqualToString:contextMenuItemTagCorrectSpellingAutomatically()])
231                 modernTag = WebMenuItemTagCorrectSpellingAutomatically;
232             else if ([title isEqualToString:contextMenuItemTagSubstitutionsMenu()])
233                 modernTag = WebMenuItemTagSubstitutionsMenu;
234             else if ([title isEqualToString:contextMenuItemTagShowSubstitutions(true)]
235                      || [title isEqualToString:contextMenuItemTagShowSubstitutions(false)])
236                 modernTag = WebMenuItemTagShowSubstitutions;
237             else if ([title isEqualToString:contextMenuItemTagSmartCopyPaste()])
238                 modernTag = WebMenuItemTagSmartCopyPaste;
239             else if ([title isEqualToString:contextMenuItemTagSmartQuotes()])
240                 modernTag = WebMenuItemTagSmartQuotes;
241             else if ([title isEqualToString:contextMenuItemTagSmartDashes()])
242                 modernTag = WebMenuItemTagSmartDashes;
243             else if ([title isEqualToString:contextMenuItemTagSmartLinks()])
244                 modernTag = WebMenuItemTagSmartLinks;
245             else if ([title isEqualToString:contextMenuItemTagTextReplacement()])
246                 modernTag = WebMenuItemTagTextReplacement;
247             else if ([title isEqualToString:contextMenuItemTagTransformationsMenu()])
248                 modernTag = WebMenuItemTagTransformationsMenu;
249             else if ([title isEqualToString:contextMenuItemTagMakeUpperCase()])
250                 modernTag = WebMenuItemTagMakeUpperCase;
251             else if ([title isEqualToString:contextMenuItemTagMakeLowerCase()])
252                 modernTag = WebMenuItemTagMakeLowerCase;
253             else if ([title isEqualToString:contextMenuItemTagCapitalize()])
254                 modernTag = WebMenuItemTagCapitalize;
255             else {
256             // We don't expect WebMenuItemTagOther for any items other than the ones we explicitly handle.
257             // There's nothing to prevent an app from applying this tag, but they are supposed to only
258             // use tags in the range starting with WebMenuItemBaseApplicationTag=10000
259                 ASSERT_NOT_REACHED();
260             }
261         } else if (preVersion3Client) {
262             // Restore the new API tag for items on which we temporarily set the old SPI tag. The old SPI tag was
263             // needed to avoid confusing clients linked against earlier WebKits; the new API tag is needed for
264             // WebCore to handle the menu items appropriately (without needing to know about the old SPI tags).
265             switch (tag) {
266                 case OldWebMenuItemTagSearchInSpotlight:
267                     modernTag = WebMenuItemTagSearchInSpotlight;
268                     break;
269                 case OldWebMenuItemTagSearchWeb:
270                     modernTag = WebMenuItemTagSearchWeb;
271                     break;
272                 case OldWebMenuItemTagLookUpInDictionary:
273                     modernTag = WebMenuItemTagLookUpInDictionary;
274                     break;
275                 default:
276                     break;
277             }
278         }
279         
280         if (modernTag != tag)
281             [item setTag:modernTag];        
282     }
283 }
284
285 NSMutableArray* WebContextMenuClient::getCustomMenuFromDefaultItems(ContextMenu* defaultMenu)
286 {
287     id delegate = [m_webView UIDelegate];
288     SEL selector = @selector(webView:contextMenuItemsForElement:defaultMenuItems:);
289     if (![delegate respondsToSelector:selector])
290         return defaultMenu->platformDescription();
291
292     NSDictionary *element = [[[WebElementDictionary alloc] initWithHitTestResult:[m_webView page]->contextMenuController().hitTestResult()] autorelease];
293
294     BOOL preVersion3Client = isPreVersion3Client();
295     if (preVersion3Client) {
296         DOMNode *node = [element objectForKey:WebElementDOMNodeKey];
297         if ([node isKindOfClass:[DOMHTMLInputElement class]] && [(DOMHTMLInputElement *)node _isTextField])
298             return defaultMenu->platformDescription();
299         if ([node isKindOfClass:[DOMHTMLTextAreaElement class]])
300             return defaultMenu->platformDescription();
301     }
302
303     NSMutableArray *defaultMenuItems = defaultMenu->platformDescription();
304
305     unsigned defaultItemsCount = [defaultMenuItems count];
306     for (unsigned i = 0; i < defaultItemsCount; ++i)
307         [[defaultMenuItems objectAtIndex:i] setRepresentedObject:element];
308
309     NSMutableArray *savedItems = [fixMenusToSendToOldClients(defaultMenuItems) retain];
310     NSArray *delegateSuppliedItems = CallUIDelegate(m_webView, selector, element, defaultMenuItems);
311     NSMutableArray *newMenuItems = [delegateSuppliedItems mutableCopy];
312     fixMenusReceivedFromOldClients(newMenuItems, savedItems);
313     [savedItems release];
314     return [newMenuItems autorelease];
315 }
316
317 void WebContextMenuClient::contextMenuItemSelected(ContextMenuItem* item, const ContextMenu* parentMenu)
318 {
319     id delegate = [m_webView UIDelegate];
320     SEL selector = @selector(webView:contextMenuItemSelected:forElement:);
321     if ([delegate respondsToSelector:selector]) {
322         NSDictionary *element = [[WebElementDictionary alloc] initWithHitTestResult:[m_webView page]->contextMenuController().hitTestResult()];
323         NSMenuItem *platformItem = item->releasePlatformDescription();
324
325         CallUIDelegate(m_webView, selector, platformItem, element);
326
327         [element release];
328         [platformItem release];
329     }
330 }
331
332 void WebContextMenuClient::downloadURL(const URL& url)
333 {
334     [m_webView _downloadURL:url];
335 }
336
337 void WebContextMenuClient::searchWithSpotlight()
338 {
339     [m_webView _searchWithSpotlightFromMenu:nil];
340 }
341
342 void WebContextMenuClient::searchWithGoogle(const Frame*)
343 {
344     [m_webView _searchWithGoogleFromMenu:nil];
345 }
346
347 void WebContextMenuClient::lookUpInDictionary(Frame* frame)
348 {
349     WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView];
350     if(![htmlView isKindOfClass:[WebHTMLView class]])
351         return;
352     [htmlView _lookUpInDictionaryFromMenu:nil];
353 }
354
355 bool WebContextMenuClient::isSpeaking()
356 {
357     return [NSApp isSpeaking];
358 }
359
360 void WebContextMenuClient::speak(const String& string)
361 {
362     [NSApp speakString:[[(NSString*)string copy] autorelease]];
363 }
364
365 void WebContextMenuClient::stopSpeaking()
366 {
367     [NSApp stopSpeaking:nil];
368 }
369
370 bool WebContextMenuClient::clientFloatRectForNode(Node& node, FloatRect& rect) const
371 {
372     RenderObject* renderer = node.renderer();
373     if (!renderer) {
374         // This method shouldn't be called in cases where the controlled node hasn't rendered.
375         ASSERT_NOT_REACHED();
376         return false;
377     }
378
379     if (!is<RenderBox>(*renderer))
380         return false;
381     auto& renderBox = downcast<RenderBox>(*renderer);
382
383     LayoutRect layoutRect = renderBox.clientBoxRect();
384     FloatQuad floatQuad = renderBox.localToAbsoluteQuad(FloatQuad(layoutRect));
385     rect = floatQuad.boundingBox();
386
387     return true;
388 }
389
390 #if ENABLE(SERVICE_CONTROLS)
391 void WebContextMenuClient::sharingServicePickerWillBeDestroyed(WebSharingServicePickerController &)
392 {
393     m_sharingServicePickerController = nil;
394 }
395
396 WebCore::Page* WebContextMenuClient::pageForSharingServicePicker(WebSharingServicePickerController &)
397 {
398     return [m_webView page];
399 }
400
401 RetainPtr<NSWindow> WebContextMenuClient::windowForSharingServicePicker(WebSharingServicePickerController &)
402 {
403     return [m_webView window];
404 }
405
406 WebCore::FloatRect WebContextMenuClient::screenRectForCurrentSharingServicePickerItem(WebSharingServicePickerController &)
407 {
408     Page* page = [m_webView page];
409     if (!page)
410         return NSZeroRect;
411
412     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
413     if (!node)
414         return NSZeroRect;
415
416     FrameView* frameView = node->document().view();
417     if (!frameView) {
418         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
419         ASSERT_NOT_REACHED();
420         return NSZeroRect;
421     }
422
423     FloatRect rect;
424     if (!clientFloatRectForNode(*node, rect))
425         return NSZeroRect;
426
427     // FIXME: https://webkit.org/b/132915
428     // Ideally we'd like to convert the content rect to screen coordinates without the lossy float -> int conversion.
429     // Creating a rounded int rect works well in practice, but might still lead to off-by-one-pixel problems in edge cases.
430     IntRect intRect = roundedIntRect(rect);
431     return frameView->contentsToScreen(intRect);
432 }
433
434 RetainPtr<NSImage> WebContextMenuClient::imageForCurrentSharingServicePickerItem(WebSharingServicePickerController &)
435 {
436     Page* page = [m_webView page];
437     if (!page)
438         return nil;
439
440     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
441     if (!node)
442         return nil;
443
444     FrameView* frameView = node->document().view();
445     if (!frameView) {
446         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
447         ASSERT_NOT_REACHED();
448         return nil;
449     }
450
451     FloatRect rect;
452     if (!clientFloatRectForNode(*node, rect))
453         return nil;
454
455     std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(rect.size());
456     if (!buffer)
457         return nil;
458
459     VisibleSelection oldSelection = frameView->frame().selection().selection();
460     RefPtr<Range> range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
461     frameView->frame().selection().setSelection(VisibleSelection(range.get()), FrameSelection::DoNotSetFocus);
462
463     PaintBehavior oldPaintBehavior = frameView->paintBehavior();
464     frameView->setPaintBehavior(PaintBehaviorSelectionOnly);
465
466     buffer->context()->translate(-toFloatSize(rect.location()));
467     frameView->paintContents(buffer->context(), roundedIntRect(rect));
468
469     frameView->frame().selection().setSelection(oldSelection);
470     frameView->setPaintBehavior(oldPaintBehavior);
471
472     RefPtr<Image> image = buffer->copyImage(DontCopyBackingStore);
473     return image->getNSImage();
474 }
475 #endif
476
477 NSMenu *WebContextMenuClient::contextMenuForEvent(NSEvent *event, NSView *view, bool& isServicesMenu)
478 {
479     isServicesMenu = false;
480
481     Page* page = [m_webView page];
482     if (!page)
483         return nil;
484
485 #if ENABLE(SERVICE_CONTROLS) && defined(__LP64__)
486     if (Image* image = page->contextMenuController().context().controlledImage()) {
487         ASSERT(page->contextMenuController().context().hitTestResult().innerNode());
488
489         RetainPtr<NSItemProvider> itemProvider = adoptNS([[NSItemProvider alloc] initWithItem:image->getNSImage() typeIdentifier:@"public.image"]);
490
491         bool isContentEditable = page->contextMenuController().context().hitTestResult().innerNode()->isContentEditable();
492         m_sharingServicePickerController = adoptNS([[WebSharingServicePickerController alloc] initWithItems:@[ itemProvider.get() ] includeEditorServices:isContentEditable client:this style:NSSharingServicePickerStyleRollover]);
493
494         isServicesMenu = true;
495         return [m_sharingServicePickerController menu];
496     }
497 #endif
498
499     return [view menuForEvent:event];
500 }
501
502 void WebContextMenuClient::showContextMenu()
503 {
504     Page* page = [m_webView page];
505     if (!page)
506         return;
507     Frame* frame = page->contextMenuController().hitTestResult().innerNodeFrame();
508     if (!frame)
509         return;
510     FrameView* frameView = frame->view();
511     if (!frameView)
512         return;
513
514     NSView* view = frameView->documentView();
515     IntPoint point = frameView->contentsToWindow(page->contextMenuController().hitTestResult().roundedPointInInnerNodeFrame());
516     NSEvent* event = [NSEvent mouseEventWithType:NSRightMouseDown location:point modifierFlags:0 timestamp:0 windowNumber:[[view window] windowNumber] context:0 eventNumber:0 clickCount:1 pressure:1];
517
518     // Show the contextual menu for this event.
519     bool isServicesMenu;
520     if (NSMenu *menu = contextMenuForEvent(event, view, isServicesMenu)) {
521         if (isServicesMenu)
522             [menu popUpMenuPositioningItem:nil atLocation:[view convertPoint:point toView:nil] inView:view];
523         else
524             [NSMenu popUpContextMenu:menu withEvent:event forView:view];
525     }
526 }
527
528 #endif // !PLATFORM(IOS)