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