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