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