da74f5a5fc85feef75875d736650fb8929a9465c
[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/URL.h>
51 #import <WebCore/LocalizedStrings.h>
52 #import <WebCore/Page.h>
53 #import <WebCore/RenderObject.h>
54 #import <WebCore/SharedBuffer.h>
55 #import <WebCore/Frame.h>
56 #import <WebCore/FrameView.h>
57 #import <WebCore/RuntimeApplicationChecks.h>
58 #import <WebKitLegacy/DOMPrivate.h>
59
60 using namespace WebCore;
61
62 @interface NSApplication (AppKitSecretsIKnowAbout)
63 - (void)speakString:(NSString *)string;
64 @end
65
66 WebContextMenuClient::WebContextMenuClient(WebView *webView) 
67     : m_webView(webView)
68 {
69 }
70
71 WebContextMenuClient::~WebContextMenuClient()
72 {
73 #if ENABLE(SERVICE_CONTROLS)
74     if (m_sharingServicePickerController)
75         [m_sharingServicePickerController clear];
76 #endif
77 }
78
79 void WebContextMenuClient::contextMenuDestroyed()
80 {
81     delete this;
82 }
83
84 static BOOL isPreVersion3Client(void)
85 {
86     static BOOL preVersion3Client = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_3_0_CONTEXT_MENU_TAGS);
87     return preVersion3Client;
88 }
89
90 static BOOL isPreInspectElementTagClient(void)
91 {
92     static BOOL preInspectElementTagClient = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_INSPECT_ELEMENT_MENU_TAG);
93     return preInspectElementTagClient;
94 }
95
96 static NSMutableArray *fixMenusToSendToOldClients(NSMutableArray *defaultMenuItems)
97 {
98     NSMutableArray *savedItems = nil;
99
100     unsigned defaultItemsCount = [defaultMenuItems count];
101
102     if (isPreInspectElementTagClient() && defaultItemsCount >= 2) {
103         NSMenuItem *secondToLastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 2];
104         NSMenuItem *lastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 1];
105
106         if ([secondToLastItem isSeparatorItem] && [lastItem tag] == WebMenuItemTagInspectElement) {
107             savedItems = [NSMutableArray arrayWithCapacity:2];
108             [savedItems addObject:secondToLastItem];
109             [savedItems addObject:lastItem];
110
111             [defaultMenuItems removeObject:secondToLastItem];
112             [defaultMenuItems removeObject:lastItem];
113             defaultItemsCount -= 2;
114         }
115     }
116
117     BOOL preVersion3Client = isPreVersion3Client();
118     if (!preVersion3Client)
119         return savedItems;
120         
121     for (unsigned i = 0; i < defaultItemsCount; ++i) {
122         NSMenuItem *item = [defaultMenuItems objectAtIndex:i];
123         int tag = [item tag];
124         int oldStyleTag = tag;
125
126         if (tag >= WEBMENUITEMTAG_WEBKIT_3_0_SPI_START) {
127             // Change all editing-related SPI tags listed in WebUIDelegatePrivate.h to WebMenuItemTagOther
128             // to match our old WebKit context menu behavior.
129             oldStyleTag = WebMenuItemTagOther;
130         } else {
131             // All items are expected to have useful tags coming into this method.
132             ASSERT(tag != WebMenuItemTagOther);
133             
134             // Use the pre-3.0 tags for the few items that changed tags as they moved from SPI to API. We
135             // do this only for old clients; new Mail already expects the new symbols in this case.
136             if (preVersion3Client) {
137                 switch (tag) {
138                     case WebMenuItemTagSearchInSpotlight:
139                         oldStyleTag = OldWebMenuItemTagSearchInSpotlight;
140                         break;
141                     case WebMenuItemTagSearchWeb:
142                         oldStyleTag = OldWebMenuItemTagSearchWeb;
143                         break;
144                     case WebMenuItemTagLookUpInDictionary:
145                         oldStyleTag = OldWebMenuItemTagLookUpInDictionary;
146                         break;
147                     default:
148                         break;
149                 }
150             }
151         }
152
153         if (oldStyleTag != tag)
154             [item setTag:oldStyleTag];
155     }
156
157     return savedItems;
158 }
159
160 static void fixMenusReceivedFromOldClients(NSMutableArray *newMenuItems, NSMutableArray *savedItems)
161 {   
162     if (savedItems)
163         [newMenuItems addObjectsFromArray:savedItems];
164
165     BOOL preVersion3Client = isPreVersion3Client();
166     if (!preVersion3Client)
167         return;
168     
169     // Restore the modern tags to the menu items whose tags we altered in fixMenusToSendToOldClients. 
170     unsigned newItemsCount = [newMenuItems count];
171     for (unsigned i = 0; i < newItemsCount; ++i) {
172         NSMenuItem *item = [newMenuItems objectAtIndex:i];
173         
174         int tag = [item tag];
175         int modernTag = tag;
176         
177         if (tag == WebMenuItemTagOther) {
178             // Restore the specific tag for items on which we temporarily set WebMenuItemTagOther to match old behavior.
179             NSString *title = [item title];
180             if ([title isEqualToString:contextMenuItemTagOpenLink()])
181                 modernTag = WebMenuItemTagOpenLink;
182             else if ([title isEqualToString:contextMenuItemTagIgnoreGrammar()])
183                 modernTag = WebMenuItemTagIgnoreGrammar;
184             else if ([title isEqualToString:contextMenuItemTagSpellingMenu()])
185                 modernTag = WebMenuItemTagSpellingMenu;
186             else if ([title isEqualToString:contextMenuItemTagShowSpellingPanel(true)]
187                      || [title isEqualToString:contextMenuItemTagShowSpellingPanel(false)])
188                 modernTag = WebMenuItemTagShowSpellingPanel;
189             else if ([title isEqualToString:contextMenuItemTagCheckSpelling()])
190                 modernTag = WebMenuItemTagCheckSpelling;
191             else if ([title isEqualToString:contextMenuItemTagCheckSpellingWhileTyping()])
192                 modernTag = WebMenuItemTagCheckSpellingWhileTyping;
193             else if ([title isEqualToString:contextMenuItemTagCheckGrammarWithSpelling()])
194                 modernTag = WebMenuItemTagCheckGrammarWithSpelling;
195             else if ([title isEqualToString:contextMenuItemTagFontMenu()])
196                 modernTag = WebMenuItemTagFontMenu;
197             else if ([title isEqualToString:contextMenuItemTagShowFonts()])
198                 modernTag = WebMenuItemTagShowFonts;
199             else if ([title isEqualToString:contextMenuItemTagBold()])
200                 modernTag = WebMenuItemTagBold;
201             else if ([title isEqualToString:contextMenuItemTagItalic()])
202                 modernTag = WebMenuItemTagItalic;
203             else if ([title isEqualToString:contextMenuItemTagUnderline()])
204                 modernTag = WebMenuItemTagUnderline;
205             else if ([title isEqualToString:contextMenuItemTagOutline()])
206                 modernTag = WebMenuItemTagOutline;
207             else if ([title isEqualToString:contextMenuItemTagStyles()])
208                 modernTag = WebMenuItemTagStyles;
209             else if ([title isEqualToString:contextMenuItemTagShowColors()])
210                 modernTag = WebMenuItemTagShowColors;
211             else if ([title isEqualToString:contextMenuItemTagSpeechMenu()])
212                 modernTag = WebMenuItemTagSpeechMenu;
213             else if ([title isEqualToString:contextMenuItemTagStartSpeaking()])
214                 modernTag = WebMenuItemTagStartSpeaking;
215             else if ([title isEqualToString:contextMenuItemTagStopSpeaking()])
216                 modernTag = WebMenuItemTagStopSpeaking;
217             else if ([title isEqualToString:contextMenuItemTagWritingDirectionMenu()])
218                 modernTag = WebMenuItemTagWritingDirectionMenu;
219             else if ([title isEqualToString:contextMenuItemTagDefaultDirection()])
220                 modernTag = WebMenuItemTagDefaultDirection;
221             else if ([title isEqualToString:contextMenuItemTagLeftToRight()])
222                 modernTag = WebMenuItemTagLeftToRight;
223             else if ([title isEqualToString:contextMenuItemTagRightToLeft()])
224                 modernTag = WebMenuItemTagRightToLeft;
225             else if ([title isEqualToString:contextMenuItemTagInspectElement()])
226                 modernTag = WebMenuItemTagInspectElement;
227             else if ([title isEqualToString:contextMenuItemTagCorrectSpellingAutomatically()])
228                 modernTag = WebMenuItemTagCorrectSpellingAutomatically;
229             else if ([title isEqualToString:contextMenuItemTagSubstitutionsMenu()])
230                 modernTag = WebMenuItemTagSubstitutionsMenu;
231             else if ([title isEqualToString:contextMenuItemTagShowSubstitutions(true)]
232                      || [title isEqualToString:contextMenuItemTagShowSubstitutions(false)])
233                 modernTag = WebMenuItemTagShowSubstitutions;
234             else if ([title isEqualToString:contextMenuItemTagSmartCopyPaste()])
235                 modernTag = WebMenuItemTagSmartCopyPaste;
236             else if ([title isEqualToString:contextMenuItemTagSmartQuotes()])
237                 modernTag = WebMenuItemTagSmartQuotes;
238             else if ([title isEqualToString:contextMenuItemTagSmartDashes()])
239                 modernTag = WebMenuItemTagSmartDashes;
240             else if ([title isEqualToString:contextMenuItemTagSmartLinks()])
241                 modernTag = WebMenuItemTagSmartLinks;
242             else if ([title isEqualToString:contextMenuItemTagTextReplacement()])
243                 modernTag = WebMenuItemTagTextReplacement;
244             else if ([title isEqualToString:contextMenuItemTagTransformationsMenu()])
245                 modernTag = WebMenuItemTagTransformationsMenu;
246             else if ([title isEqualToString:contextMenuItemTagMakeUpperCase()])
247                 modernTag = WebMenuItemTagMakeUpperCase;
248             else if ([title isEqualToString:contextMenuItemTagMakeLowerCase()])
249                 modernTag = WebMenuItemTagMakeLowerCase;
250             else if ([title isEqualToString:contextMenuItemTagCapitalize()])
251                 modernTag = WebMenuItemTagCapitalize;
252             else {
253             // We don't expect WebMenuItemTagOther for any items other than the ones we explicitly handle.
254             // There's nothing to prevent an app from applying this tag, but they are supposed to only
255             // use tags in the range starting with WebMenuItemBaseApplicationTag=10000
256                 ASSERT_NOT_REACHED();
257             }
258         } else if (preVersion3Client) {
259             // Restore the new API tag for items on which we temporarily set the old SPI tag. The old SPI tag was
260             // needed to avoid confusing clients linked against earlier WebKits; the new API tag is needed for
261             // WebCore to handle the menu items appropriately (without needing to know about the old SPI tags).
262             switch (tag) {
263                 case OldWebMenuItemTagSearchInSpotlight:
264                     modernTag = WebMenuItemTagSearchInSpotlight;
265                     break;
266                 case OldWebMenuItemTagSearchWeb:
267                     modernTag = WebMenuItemTagSearchWeb;
268                     break;
269                 case OldWebMenuItemTagLookUpInDictionary:
270                     modernTag = WebMenuItemTagLookUpInDictionary;
271                     break;
272                 default:
273                     break;
274             }
275         }
276         
277         if (modernTag != tag)
278             [item setTag:modernTag];        
279     }
280 }
281
282 NSMutableArray* WebContextMenuClient::getCustomMenuFromDefaultItems(ContextMenu* defaultMenu)
283 {
284     id delegate = [m_webView UIDelegate];
285     SEL selector = @selector(webView:contextMenuItemsForElement:defaultMenuItems:);
286     if (![delegate respondsToSelector:selector])
287         return defaultMenu->platformDescription();
288
289     NSDictionary *element = [[[WebElementDictionary alloc] initWithHitTestResult:[m_webView page]->contextMenuController().hitTestResult()] autorelease];
290
291     BOOL preVersion3Client = isPreVersion3Client();
292     if (preVersion3Client) {
293         DOMNode *node = [element objectForKey:WebElementDOMNodeKey];
294         if ([node isKindOfClass:[DOMHTMLInputElement class]] && [(DOMHTMLInputElement *)node _isTextField])
295             return defaultMenu->platformDescription();
296         if ([node isKindOfClass:[DOMHTMLTextAreaElement class]])
297             return defaultMenu->platformDescription();
298     }
299
300     NSMutableArray *defaultMenuItems = defaultMenu->platformDescription();
301
302     unsigned defaultItemsCount = [defaultMenuItems count];
303     for (unsigned i = 0; i < defaultItemsCount; ++i)
304         [[defaultMenuItems objectAtIndex:i] setRepresentedObject:element];
305
306     NSMutableArray *savedItems = [fixMenusToSendToOldClients(defaultMenuItems) retain];
307     NSArray *delegateSuppliedItems = CallUIDelegate(m_webView, selector, element, defaultMenuItems);
308     NSMutableArray *newMenuItems = [delegateSuppliedItems mutableCopy];
309     fixMenusReceivedFromOldClients(newMenuItems, savedItems);
310     [savedItems release];
311     return [newMenuItems autorelease];
312 }
313
314 void WebContextMenuClient::contextMenuItemSelected(ContextMenuItem* item, const ContextMenu* parentMenu)
315 {
316     id delegate = [m_webView UIDelegate];
317     SEL selector = @selector(webView:contextMenuItemSelected:forElement:);
318     if ([delegate respondsToSelector:selector]) {
319         NSDictionary *element = [[WebElementDictionary alloc] initWithHitTestResult:[m_webView page]->contextMenuController().hitTestResult()];
320         NSMenuItem *platformItem = item->releasePlatformDescription();
321
322         CallUIDelegate(m_webView, selector, platformItem, element);
323
324         [element release];
325         [platformItem release];
326     }
327 }
328
329 void WebContextMenuClient::downloadURL(const URL& url)
330 {
331     [m_webView _downloadURL:url];
332 }
333
334 void WebContextMenuClient::searchWithSpotlight()
335 {
336     [m_webView _searchWithSpotlightFromMenu:nil];
337 }
338
339 void WebContextMenuClient::searchWithGoogle(const Frame*)
340 {
341     [m_webView _searchWithGoogleFromMenu:nil];
342 }
343
344 void WebContextMenuClient::lookUpInDictionary(Frame* frame)
345 {
346     WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView];
347     if(![htmlView isKindOfClass:[WebHTMLView class]])
348         return;
349     [htmlView _lookUpInDictionaryFromMenu:nil];
350 }
351
352 bool WebContextMenuClient::isSpeaking()
353 {
354     return [NSApp isSpeaking];
355 }
356
357 void WebContextMenuClient::speak(const String& string)
358 {
359     [NSApp speakString:[[(NSString*)string copy] autorelease]];
360 }
361
362 void WebContextMenuClient::stopSpeaking()
363 {
364     [NSApp stopSpeaking:nil];
365 }
366
367 IntRect WebContextMenuClient::screenRectForHitTestNode() const
368 {
369     Page* page = [m_webView page];
370     if (!page)
371         return IntRect();
372
373     Node* node = page->contextMenuController().context().hitTestResult().innerNode();
374     if (!node)
375         return IntRect();
376
377     RenderObject* renderer = node->renderer();
378     if (!renderer) {
379         // This method shouldn't be called in cases where the controlled node hasn't rendered.
380         ASSERT_NOT_REACHED();
381         return IntRect();
382     }
383
384     IntRect rect = renderer->absoluteBoundingBoxRect();
385     FrameView* frameView = node->document().view();
386     if (!frameView) {
387         // This method shouldn't be called in cases where the controlled node isn't in a rendered view.
388         ASSERT_NOT_REACHED();
389         return IntRect();
390     }
391
392     return frameView->contentsToScreen(rect);
393 }
394
395 NSMenu *WebContextMenuClient::contextMenuForEvent(NSEvent *event, NSView *view)
396 {
397     Page* page = [m_webView page];
398     if (!page)
399         return nil;
400
401 #if ENABLE(SERVICE_CONTROLS)
402     if (Image* image = page->contextMenuController().context().controlledImage()) {
403         ASSERT(page->contextMenuController().context().hitTestResult().innerNode());
404
405         RefPtr<SharedBuffer> data = image->data();
406         ASSERT(data);
407         RetainPtr<CFDataRef> cfData = data->createCFData();
408
409         bool isContentEditable = page->contextMenuController().context().hitTestResult().innerNode()->isContentEditable();
410         m_sharingServicePickerController = adoptNS([[WebSharingServicePickerController alloc] initWithData:(NSData *)cfData.get() includeEditorServices:isContentEditable menuClient:this]);
411         
412         return [m_sharingServicePickerController menu];
413     }
414 #endif
415
416     return [view menuForEvent:event];
417 }
418
419 void WebContextMenuClient::showContextMenu()
420 {
421     Page* page = [m_webView page];
422     if (!page)
423         return;
424     Frame* frame = page->contextMenuController().hitTestResult().innerNodeFrame();
425     if (!frame)
426         return;
427     FrameView* frameView = frame->view();
428     if (!frameView)
429         return;
430
431     NSView* view = frameView->documentView();
432     IntPoint point = frameView->contentsToRootView(page->contextMenuController().hitTestResult().roundedPointInInnerNodeFrame());
433     NSPoint nsScreenPoint = [view convertPoint:point toView:nil];
434     NSEvent* event = [NSEvent mouseEventWithType:NSRightMouseDown location:nsScreenPoint modifierFlags:0 timestamp:0 windowNumber:[[view window] windowNumber] context:0 eventNumber:0 clickCount:1 pressure:1];
435
436     // Show the contextual menu for this event.
437     if (NSMenu *menu = contextMenuForEvent(event, view))
438         [NSMenu popUpContextMenu:menu withEvent:event forView:view];
439 }
440
441 #if ENABLE(SERVICE_CONTROLS)
442 void WebContextMenuClient::clearSharingServicePickerController()
443 {
444     m_sharingServicePickerController = nil;
445 }
446 #endif
447
448 #endif // !PLATFORM(IOS)