2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
31 #import "WebContextMenuClient.h"
33 #import <WebCore/BitmapImage.h>
34 #import "WebDelegateImplementationCaching.h"
35 #import "WebElementDictionary.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"
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>
58 using namespace WebCore;
60 @interface NSApplication (AppKitSecretsIKnowAbout)
61 - (void)speakString:(NSString *)string;
64 WebContextMenuClient::WebContextMenuClient(WebView *webView)
69 void WebContextMenuClient::contextMenuDestroyed()
74 static BOOL isPreVersion3Client(void)
76 static BOOL preVersion3Client = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_3_0_CONTEXT_MENU_TAGS);
77 return preVersion3Client;
80 static BOOL isPreInspectElementTagClient(void)
82 static BOOL preInspectElementTagClient = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_INSPECT_ELEMENT_MENU_TAG);
83 return preInspectElementTagClient;
86 static NSMutableArray *fixMenusToSendToOldClients(NSMutableArray *defaultMenuItems)
88 NSMutableArray *savedItems = nil;
90 unsigned defaultItemsCount = [defaultMenuItems count];
92 if (isPreInspectElementTagClient() && defaultItemsCount >= 2) {
93 NSMenuItem *secondToLastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 2];
94 NSMenuItem *lastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 1];
96 if ([secondToLastItem isSeparatorItem] && [lastItem tag] == WebMenuItemTagInspectElement) {
97 savedItems = [NSMutableArray arrayWithCapacity:2];
98 [savedItems addObject:secondToLastItem];
99 [savedItems addObject:lastItem];
101 [defaultMenuItems removeObject:secondToLastItem];
102 [defaultMenuItems removeObject:lastItem];
103 defaultItemsCount -= 2;
107 BOOL preVersion3Client = isPreVersion3Client();
108 if (!preVersion3Client)
111 for (unsigned i = 0; i < defaultItemsCount; ++i) {
112 NSMenuItem *item = [defaultMenuItems objectAtIndex:i];
113 int tag = [item tag];
114 int oldStyleTag = tag;
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;
121 // All items are expected to have useful tags coming into this method.
122 ASSERT(tag != WebMenuItemTagOther);
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) {
128 case WebMenuItemTagSearchInSpotlight:
129 oldStyleTag = OldWebMenuItemTagSearchInSpotlight;
131 case WebMenuItemTagSearchWeb:
132 oldStyleTag = OldWebMenuItemTagSearchWeb;
134 case WebMenuItemTagLookUpInDictionary:
135 oldStyleTag = OldWebMenuItemTagLookUpInDictionary;
143 if (oldStyleTag != tag)
144 [item setTag:oldStyleTag];
150 static void fixMenusReceivedFromOldClients(NSMutableArray *newMenuItems, NSMutableArray *savedItems)
153 [newMenuItems addObjectsFromArray:savedItems];
155 BOOL preVersion3Client = isPreVersion3Client();
156 if (!preVersion3Client)
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];
164 int tag = [item tag];
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;
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();
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).
253 case OldWebMenuItemTagSearchInSpotlight:
254 modernTag = WebMenuItemTagSearchInSpotlight;
256 case OldWebMenuItemTagSearchWeb:
257 modernTag = WebMenuItemTagSearchWeb;
259 case OldWebMenuItemTagLookUpInDictionary:
260 modernTag = WebMenuItemTagLookUpInDictionary;
267 if (modernTag != tag)
268 [item setTag:modernTag];
272 NSMutableArray* WebContextMenuClient::getCustomMenuFromDefaultItems(ContextMenu* defaultMenu)
274 id delegate = [m_webView UIDelegate];
275 SEL selector = @selector(webView:contextMenuItemsForElement:defaultMenuItems:);
276 if (![delegate respondsToSelector:selector])
277 return defaultMenu->platformDescription();
279 NSDictionary *element = [[[WebElementDictionary alloc] initWithHitTestResult:[m_webView page]->contextMenuController().hitTestResult()] autorelease];
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();
290 NSMutableArray *defaultMenuItems = defaultMenu->platformDescription();
292 unsigned defaultItemsCount = [defaultMenuItems count];
293 for (unsigned i = 0; i < defaultItemsCount; ++i)
294 [[defaultMenuItems objectAtIndex:i] setRepresentedObject:element];
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];
304 void WebContextMenuClient::contextMenuItemSelected(ContextMenuItem* item, const ContextMenu* parentMenu)
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();
312 CallUIDelegate(m_webView, selector, platformItem, element);
315 [platformItem release];
319 void WebContextMenuClient::downloadURL(const URL& url)
321 [m_webView _downloadURL:url];
324 void WebContextMenuClient::searchWithSpotlight()
326 [m_webView _searchWithSpotlightFromMenu:nil];
329 void WebContextMenuClient::searchWithGoogle(const Frame*)
331 [m_webView _searchWithGoogleFromMenu:nil];
334 void WebContextMenuClient::lookUpInDictionary(Frame* frame)
336 WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView];
337 if(![htmlView isKindOfClass:[WebHTMLView class]])
339 [htmlView _lookUpInDictionaryFromMenu:nil];
342 bool WebContextMenuClient::isSpeaking()
344 return [NSApp isSpeaking];
347 void WebContextMenuClient::speak(const String& string)
349 [NSApp speakString:[[(NSString*)string copy] autorelease]];
352 void WebContextMenuClient::stopSpeaking()
354 [NSApp stopSpeaking:nil];
357 NSMenu *WebContextMenuClient::contextMenuForEvent(NSEvent *event, NSView *view)
359 Page* page = [m_webView page];
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]);
369 return [m_sharingServicePickerController menu];
373 return [view menuForEvent:event];
376 void WebContextMenuClient::showContextMenu()
378 Page* page = [m_webView page];
381 Frame* frame = page->contextMenuController().hitTestResult().innerNodeFrame();
384 FrameView* frameView = frame->view();
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];
393 // Show the contextual menu for this event.
394 if (NSMenu *menu = contextMenuForEvent(event, view))
395 [NSMenu popUpContextMenu:menu withEvent:event forView:view];
398 #if ENABLE(SERVICE_CONTROLS)
399 void WebContextMenuClient::clearSharingServicePickerController()
401 m_sharingServicePickerController = nil;
405 #endif // !PLATFORM(IOS)