WebContextMenuClient.mm   [plain text]


/*
 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "WebContextMenuClient.h"

#import "WebDelegateImplementationCaching.h"
#import "WebElementDictionary.h"
#import "WebFrame.h"
#import "WebFrameInternal.h"
#import "WebHTMLView.h"
#import "WebHTMLViewInternal.h"
#import "WebKitVersionChecks.h"
#import "WebNSPasteboardExtras.h"
#import "WebUIDelegate.h"
#import "WebUIDelegatePrivate.h"
#import "WebView.h"
#import "WebViewFactory.h"
#import "WebViewInternal.h"
#import <WebCore/ContextMenu.h>
#import <WebCore/KURL.h>
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebKit/DOMPrivate.h>

using namespace WebCore;

@interface NSApplication (AppKitSecretsIKnowAbout)
- (void)speakString:(NSString *)string;
@end

WebContextMenuClient::WebContextMenuClient(WebView *webView) 
    : m_webView(webView)
{
}

void WebContextMenuClient::contextMenuDestroyed()
{
    delete this;
}

static BOOL isPreVersion3Client(void)
{
    static BOOL preVersion3Client = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_3_0_CONTEXT_MENU_TAGS);
    return preVersion3Client;
}

static BOOL isPreInspectElementTagClient(void)
{
    static BOOL preInspectElementTagClient = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_INSPECT_ELEMENT_MENU_TAG);
    return preInspectElementTagClient;
}

static NSMutableArray *fixMenusToSendToOldClients(NSMutableArray *defaultMenuItems)
{
    NSMutableArray *savedItems = nil;

    unsigned defaultItemsCount = [defaultMenuItems count];

    if (isPreInspectElementTagClient() && defaultItemsCount >= 2) {
        NSMenuItem *secondToLastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 2];
        NSMenuItem *lastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 1];

        if ([secondToLastItem isSeparatorItem] && [lastItem tag] == WebMenuItemTagInspectElement) {
            savedItems = [NSMutableArray arrayWithCapacity:2];
            [savedItems addObject:secondToLastItem];
            [savedItems addObject:lastItem];

            [defaultMenuItems removeObject:secondToLastItem];
            [defaultMenuItems removeObject:lastItem];
            defaultItemsCount -= 2;
        }
    }

    BOOL preVersion3Client = isPreVersion3Client();
    if (!preVersion3Client)
        return savedItems;
        
    BOOL isMail = applicationIsAppleMail();
    for (unsigned i = 0; i < defaultItemsCount; ++i) {
        NSMenuItem *item = [defaultMenuItems objectAtIndex:i];
        int tag = [item tag];
        int oldStyleTag = tag;
        
        if (preVersion3Client && isMail && tag == WebMenuItemTagOpenLink) {
            // Tiger Mail changes our "Open Link in New Window" item to "Open Link"
            // and doesn't expect us to include an "Open Link" item at all. (5011905)
            [defaultMenuItems removeObjectAtIndex:i];
            i--;
            defaultItemsCount--;
            continue;
        }
        
        if (tag >= WEBMENUITEMTAG_WEBKIT_3_0_SPI_START) {
            // Change all editing-related SPI tags listed in WebUIDelegatePrivate.h to WebMenuItemTagOther
            // to match our old WebKit context menu behavior.
            oldStyleTag = WebMenuItemTagOther;
        } else {
            // All items are expected to have useful tags coming into this method.
            ASSERT(tag != WebMenuItemTagOther);
            
            // Use the pre-3.0 tags for the few items that changed tags as they moved from SPI to API. We
            // do this only for old clients; new Mail already expects the new symbols in this case.
            if (preVersion3Client) {
                switch (tag) {
                    case WebMenuItemTagSearchInSpotlight:
                        oldStyleTag = OldWebMenuItemTagSearchInSpotlight;
                        break;
                    case WebMenuItemTagSearchWeb:
                        oldStyleTag = OldWebMenuItemTagSearchWeb;
                        break;
                    case WebMenuItemTagLookUpInDictionary:
                        oldStyleTag = OldWebMenuItemTagLookUpInDictionary;
                        break;
                    default:
                        break;
                }
            }
        }

        if (oldStyleTag != tag)
            [item setTag:oldStyleTag];
    }

    return savedItems;
}

static void fixMenusReceivedFromOldClients(NSMutableArray *newMenuItems, NSMutableArray *savedItems)
{   
    if (savedItems)
        [newMenuItems addObjectsFromArray:savedItems];

    BOOL preVersion3Client = isPreVersion3Client();
    if (!preVersion3Client)
        return;
    
    // Restore the modern tags to the menu items whose tags we altered in fixMenusToSendToOldClients. 
    unsigned newItemsCount = [newMenuItems count];
    for (unsigned i = 0; i < newItemsCount; ++i) {
        NSMenuItem *item = [newMenuItems objectAtIndex:i];
        
        int tag = [item tag];
        int modernTag = tag;
        
        if (tag == WebMenuItemTagOther) {
            // Restore the specific tag for items on which we temporarily set WebMenuItemTagOther to match old behavior.
            NSString *title = [item title];
            if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagOpenLink]])
                modernTag = WebMenuItemTagOpenLink;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagIgnoreGrammar]])
                modernTag = WebMenuItemTagIgnoreGrammar;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSpellingMenu]])
                modernTag = WebMenuItemTagSpellingMenu;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagShowSpellingPanel:true]]
                     || [title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagShowSpellingPanel:false]])
                modernTag = WebMenuItemTagShowSpellingPanel;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagCheckSpelling]])
                modernTag = WebMenuItemTagCheckSpelling;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagCheckSpellingWhileTyping]])
                modernTag = WebMenuItemTagCheckSpellingWhileTyping;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagCheckGrammarWithSpelling]])
                modernTag = WebMenuItemTagCheckGrammarWithSpelling;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagFontMenu]])
                modernTag = WebMenuItemTagFontMenu;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagShowFonts]])
                modernTag = WebMenuItemTagShowFonts;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagBold]])
                modernTag = WebMenuItemTagBold;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagItalic]])
                modernTag = WebMenuItemTagItalic;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagUnderline]])
                modernTag = WebMenuItemTagUnderline;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagOutline]])
                modernTag = WebMenuItemTagOutline;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagStyles]])
                modernTag = WebMenuItemTagStyles;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagShowColors]])
                modernTag = WebMenuItemTagShowColors;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSpeechMenu]])
                modernTag = WebMenuItemTagSpeechMenu;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagStartSpeaking]])
                modernTag = WebMenuItemTagStartSpeaking;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagStopSpeaking]])
                modernTag = WebMenuItemTagStopSpeaking;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagWritingDirectionMenu]])
                modernTag = WebMenuItemTagWritingDirectionMenu;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagDefaultDirection]])
                modernTag = WebMenuItemTagDefaultDirection;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagLeftToRight]])
                modernTag = WebMenuItemTagLeftToRight;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagRightToLeft]])
                modernTag = WebMenuItemTagRightToLeft;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagInspectElement]])
                modernTag = WebMenuItemTagInspectElement;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagCorrectSpellingAutomatically]])
                modernTag = WebMenuItemTagCorrectSpellingAutomatically;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSubstitutionsMenu]])
                modernTag = WebMenuItemTagSubstitutionsMenu;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagShowSubstitutions:true]]
                     || [title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagShowSubstitutions:false]])
                modernTag = WebMenuItemTagShowSubstitutions;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSmartCopyPaste]])
                modernTag = WebMenuItemTagSmartCopyPaste;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSmartQuotes]])
                modernTag = WebMenuItemTagSmartQuotes;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSmartDashes]])
                modernTag = WebMenuItemTagSmartDashes;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagSmartLinks]])
                modernTag = WebMenuItemTagSmartLinks;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagTextReplacement]])
                modernTag = WebMenuItemTagTextReplacement;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagTransformationsMenu]])
                modernTag = WebMenuItemTagTransformationsMenu;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagMakeUpperCase]])
                modernTag = WebMenuItemTagMakeUpperCase;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagMakeLowerCase]])
                modernTag = WebMenuItemTagMakeLowerCase;
            else if ([title isEqualToString:[[WebViewFactory sharedFactory] contextMenuItemTagCapitalize]])
                modernTag = WebMenuItemTagCapitalize;
            else {
            // We don't expect WebMenuItemTagOther for any items other than the ones we explicitly handle.
            // There's nothing to prevent an app from applying this tag, but they are supposed to only
            // use tags in the range starting with WebMenuItemBaseApplicationTag=10000
                ASSERT_NOT_REACHED();
            }
        } else if (preVersion3Client) {
            // Restore the new API tag for items on which we temporarily set the old SPI tag. The old SPI tag was
            // needed to avoid confusing clients linked against earlier WebKits; the new API tag is needed for
            // WebCore to handle the menu items appropriately (without needing to know about the old SPI tags).
            switch (tag) {
                case OldWebMenuItemTagSearchInSpotlight:
                    modernTag = WebMenuItemTagSearchInSpotlight;
                    break;
                case OldWebMenuItemTagSearchWeb:
                    modernTag = WebMenuItemTagSearchWeb;
                    break;
                case OldWebMenuItemTagLookUpInDictionary:
                    modernTag = WebMenuItemTagLookUpInDictionary;
                    break;
                default:
                    break;
            }
        }
        
        if (modernTag != tag)
            [item setTag:modernTag];        
    }
}

NSMutableArray* WebContextMenuClient::getCustomMenuFromDefaultItems(ContextMenu* defaultMenu)
{
    id delegate = [m_webView UIDelegate];
    SEL selector = @selector(webView:contextMenuItemsForElement:defaultMenuItems:);
    if (![delegate respondsToSelector:selector])
        return defaultMenu->platformDescription();

    NSDictionary *element = [[[WebElementDictionary alloc] initWithHitTestResult:defaultMenu->hitTestResult()] autorelease];

    BOOL preVersion3Client = isPreVersion3Client();
    if (preVersion3Client) {
        DOMNode *node = [element objectForKey:WebElementDOMNodeKey];
        if ([node isKindOfClass:[DOMHTMLInputElement class]] && [(DOMHTMLInputElement *)node _isTextField])
            return defaultMenu->platformDescription();
        if ([node isKindOfClass:[DOMHTMLTextAreaElement class]])
            return defaultMenu->platformDescription();
    }

    NSMutableArray *defaultMenuItems = defaultMenu->platformDescription();

    unsigned defaultItemsCount = [defaultMenuItems count];
    for (unsigned i = 0; i < defaultItemsCount; ++i)
        [[defaultMenuItems objectAtIndex:i] setRepresentedObject:element];

    NSMutableArray *savedItems = [fixMenusToSendToOldClients(defaultMenuItems) retain];
    NSArray *delegateSuppliedItems = CallUIDelegate(m_webView, selector, element, defaultMenuItems);
    NSMutableArray *newMenuItems = [delegateSuppliedItems mutableCopy];
    fixMenusReceivedFromOldClients(newMenuItems, savedItems);
    [savedItems release];
    return [newMenuItems autorelease];
}

void WebContextMenuClient::contextMenuItemSelected(ContextMenuItem* item, const ContextMenu* parentMenu)
{
    id delegate = [m_webView UIDelegate];
    SEL selector = @selector(webView:contextMenuItemSelected:forElement:);
    if ([delegate respondsToSelector:selector]) {
        NSDictionary *element = [[WebElementDictionary alloc] initWithHitTestResult:parentMenu->hitTestResult()];
        NSMenuItem *platformItem = item->releasePlatformDescription();

        CallUIDelegate(m_webView, selector, platformItem, element);

        [element release];
        [platformItem release];
    }
}

void WebContextMenuClient::downloadURL(const KURL& url)
{
    [m_webView _downloadURL:url];
}

void WebContextMenuClient::searchWithSpotlight()
{
    [m_webView _searchWithSpotlightFromMenu:nil];
}

void WebContextMenuClient::searchWithGoogle(const Frame*)
{
    [m_webView _searchWithGoogleFromMenu:nil];
}

void WebContextMenuClient::lookUpInDictionary(Frame* frame)
{
    WebHTMLView* htmlView = (WebHTMLView*)[[kit(frame) frameView] documentView];
    if(![htmlView isKindOfClass:[WebHTMLView class]])
        return;
    [htmlView _lookUpInDictionaryFromMenu:nil];
}

bool WebContextMenuClient::isSpeaking()
{
    return [NSApp isSpeaking];
}

void WebContextMenuClient::speak(const String& string)
{
    [NSApp speakString:[[(NSString*)string copy] autorelease]];
}

void WebContextMenuClient::stopSpeaking()
{
    [NSApp stopSpeaking:nil];
}