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