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