Home | History | Annotate | Download | only in WebCoreSupport
      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 
     52 using namespace WebCore;
     53 
     54 @interface NSApplication (AppKitSecretsIKnowAbout)
     55 - (void)speakString:(NSString *)string;
     56 @end
     57 
     58 WebContextMenuClient::WebContextMenuClient(WebView *webView)
     59     : m_webView(webView)
     60 {
     61 }
     62 
     63 void WebContextMenuClient::contextMenuDestroyed()
     64 {
     65     delete this;
     66 }
     67 
     68 static BOOL isPreVersion3Client(void)
     69 {
     70     static BOOL preVersion3Client = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_3_0_CONTEXT_MENU_TAGS);
     71     return preVersion3Client;
     72 }
     73 
     74 static BOOL isPreInspectElementTagClient(void)
     75 {
     76     static BOOL preInspectElementTagClient = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_INSPECT_ELEMENT_MENU_TAG);
     77     return preInspectElementTagClient;
     78 }
     79 
     80 static 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 
    154 static 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 
    276 NSMutableArray* 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 
    308 void 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 
    323 void WebContextMenuClient::downloadURL(const KURL& url)
    324 {
    325     [m_webView _downloadURL:url];
    326 }
    327 
    328 void WebContextMenuClient::searchWithSpotlight()
    329 {
    330     [m_webView _searchWithSpotlightFromMenu:nil];
    331 }
    332 
    333 void WebContextMenuClient::searchWithGoogle(const Frame*)
    334 {
    335     [m_webView _searchWithGoogleFromMenu:nil];
    336 }
    337 
    338 void 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 
    346 bool WebContextMenuClient::isSpeaking()
    347 {
    348     return [NSApp isSpeaking];
    349 }
    350 
    351 void WebContextMenuClient::speak(const String& string)
    352 {
    353     [NSApp speakString:[[(NSString*)string copy] autorelease]];
    354 }
    355 
    356 void WebContextMenuClient::stopSpeaking()
    357 {
    358     [NSApp stopSpeaking:nil];
    359 }
    360