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