Reviewed by Darin
[WebKit-https.git] / WebKit / DefaultDelegates / WebDefaultContextMenuDelegate.m
1 /*
2  * Copyright (C) 2005 Apple Computer, 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 <WebKit/WebDefaultContextMenuDelegate.h>
30
31 #import <JavaScriptCore/Assertions.h>
32 #import <WebKit/DOM.h>
33 #import <WebKit/DOMPrivate.h>
34 #import <WebKit/WebFrameBridge.h>
35 #import <WebKit/WebDataSourcePrivate.h>
36 #import <WebKit/WebDefaultUIDelegate.h>
37 #import <WebKit/WebDOMOperations.h>
38 #import <WebKit/WebFrameInternal.h>
39 #import <WebKit/WebHTMLViewPrivate.h>
40 #import <WebKit/WebLocalizableStrings.h>
41 #import <WebKit/WebNSPasteboardExtras.h>
42 #import <WebKit/WebNSURLRequestExtras.h>
43 #import <WebKit/WebFrameView.h>
44 #import <WebKit/WebPolicyDelegate.h>
45 #import <WebKit/WebViewInternal.h>
46 #import <WebKit/WebUIDelegate.h>
47 #import <WebKit/WebUIDelegatePrivate.h>
48
49 #import <WebCore/WebCoreFrameBridge.h>
50
51 #import <Foundation/NSURLConnection.h>
52 #import <Foundation/NSURLRequest.h>
53
54 @implementation WebDefaultUIDelegate (WebContextMenu)
55
56 static NSString *localizedMenuTitleFromAppKit(NSString *key, NSString *comment)
57 {
58     NSBundle *appKitBundle = [NSBundle bundleWithIdentifier:@"com.apple.AppKit"];
59     if (!appKitBundle) {
60         return key;
61     }
62     NSString *result = NSLocalizedStringFromTableInBundle(key, @"MenuCommands", appKitBundle, comment);
63     if (result == nil) {
64         return key;
65     }
66     return result;
67 }
68
69 - (NSMenuItem *)menuItemWithTag:(int)tag target:(id)target representedObject:(id)representedObject
70 {
71     NSMenuItem *menuItem = [[[NSMenuItem alloc] init] autorelease];
72     [menuItem setTag:tag];
73     [menuItem setTarget:target]; // can be nil
74     [menuItem setRepresentedObject:representedObject];
75     
76     NSString *title = nil;
77     SEL action = NULL;
78     
79     switch(tag) {
80         case WebMenuItemTagOpenLinkInNewWindow:
81             title = UI_STRING("Open Link in New Window", "Open in New Window context menu item");
82             action = @selector(openLinkInNewWindow:);
83             break;
84         case WebMenuItemTagDownloadLinkToDisk:
85             title = UI_STRING("Download Linked File", "Download Linked File context menu item");
86             action = @selector(downloadLinkToDisk:);
87             break;
88         case WebMenuItemTagCopyLinkToClipboard:
89             title = UI_STRING("Copy Link", "Copy Link context menu item");
90             action = @selector(copyLinkToClipboard:);
91             break;
92         case WebMenuItemTagOpenImageInNewWindow:
93             title = UI_STRING("Open Image in New Window", "Open Image in New Window context menu item");
94             action = @selector(openImageInNewWindow:);
95             break;
96         case WebMenuItemTagDownloadImageToDisk:
97             title = UI_STRING("Download Image", "Download Image context menu item");
98             action = @selector(downloadImageToDisk:);
99             break;
100         case WebMenuItemTagCopyImageToClipboard:
101             title = UI_STRING("Copy Image", "Copy Image context menu item");
102             action = @selector(copyImageToClipboard:);
103             break;
104         case WebMenuItemTagOpenFrameInNewWindow:
105             title = UI_STRING("Open Frame in New Window", "Open Frame in New Window context menu item");
106             action = @selector(openFrameInNewWindow:);
107             break;
108         case WebMenuItemTagCopy:
109             title = UI_STRING("Copy", "Copy context menu item");
110             action = @selector(copy:);
111             break;
112         case WebMenuItemTagGoBack:
113             title = UI_STRING("Back", "Back context menu item");
114             action = @selector(goBack:);
115             break;
116         case WebMenuItemTagGoForward:
117             title = UI_STRING("Forward", "Forward context menu item");
118             action = @selector(goForward:);
119             break;
120         case WebMenuItemTagStop:
121             title = UI_STRING("Stop", "Stop context menu item");
122             action = @selector(stopLoading:);
123             break;
124         case WebMenuItemTagReload:
125             title = UI_STRING("Reload", "Reload context menu item");
126             action = @selector(reload:);
127             break;
128         case WebMenuItemTagCut:
129             title = UI_STRING("Cut", "Cut context menu item");
130             action = @selector(cut:);
131             break;
132         case WebMenuItemTagPaste:
133             title = UI_STRING("Paste", "Paste context menu item");
134             action = @selector(paste:);
135             break;
136         case WebMenuItemTagSpellingGuess:
137             action = @selector(_changeSpellingFromMenu:);
138             break;
139         case WebMenuItemTagNoGuessesFound:
140             title = UI_STRING("No Guesses Found", "No Guesses Found context menu item");
141             break;
142         case WebMenuItemTagIgnoreSpelling:
143             title = UI_STRING("Ignore Spelling", "Ignore Spelling context menu item");
144             action = @selector(_ignoreSpellingFromMenu:);
145             break;
146         case WebMenuItemTagLearnSpelling:
147             title = UI_STRING("Learn Spelling", "Learn Spelling context menu item");
148             action = @selector(_learnSpellingFromMenu:);
149             break;
150         case WebMenuItemTagSearchInSpotlight:
151             // FIXME: Perhaps move this string into WebKit directly when we're not in localization freeze
152             title = localizedMenuTitleFromAppKit(@"Search in Spotlight", @"Search in Spotlight menu title.");
153             action = @selector(_searchWithSpotlightFromMenu:);
154             break;
155         case WebMenuItemTagSearchWeb:
156             // FIXME: Perhaps move this string into WebKit directly when we're not in localization freeze
157             title = localizedMenuTitleFromAppKit(@"Search in Google", @"Search in Google menu title.");
158             action = @selector(_searchWithGoogleFromMenu:);
159             break;
160         case WebMenuItemTagLookUpInDictionary:
161             // FIXME: Perhaps move this string into WebKit directly when we're not in localization freeze
162             title = localizedMenuTitleFromAppKit(@"Look Up in Dictionary", @"Look Up in Dictionary menu title.");
163             action = @selector(_lookUpInDictionaryFromMenu:);
164             break;
165         default:
166             return nil;
167     }
168
169     if (title != nil) {
170         [menuItem setTitle:title];
171     }
172     [menuItem setAction:action];
173     
174     return menuItem;
175 }
176
177 - (void)appendDefaultItems:(NSArray *)defaultItems toArray:(NSMutableArray *)menuItems
178 {
179     ASSERT_ARG(menuItems, menuItems != nil);
180     if ([defaultItems count] > 0) {
181         ASSERT(![[menuItems lastObject] isSeparatorItem]);
182         if (![[defaultItems objectAtIndex:0] isSeparatorItem]) {
183             [menuItems addObject:[NSMenuItem separatorItem]];
184             
185             NSEnumerator *e = [defaultItems objectEnumerator];
186             NSMenuItem *item;
187             while ((item = [e nextObject]) != nil) {
188                 [menuItems addObject:item];
189             }
190         }
191     }
192 }
193
194 - (NSArray *)contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
195 {
196     // The defaultMenuItems here are ones supplied by the WebDocumentView protocol implementation. WebPDFView is
197     // one case that has non-nil default items here.
198     NSMutableArray *menuItems = [NSMutableArray array];
199     
200     NSURL *linkURL = [element objectForKey:WebElementLinkURLKey];
201     
202     if (linkURL) {
203         if([WebView _canHandleRequest:[NSURLRequest requestWithURL:linkURL]]) {
204             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagOpenLinkInNewWindow target:self representedObject:element]];
205             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagDownloadLinkToDisk target:self representedObject:element]];
206         }
207         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagCopyLinkToClipboard target:self representedObject:element]];
208     }
209     
210     WebFrame *webFrame = [element objectForKey:WebElementFrameKey];
211     NSURL *imageURL = [element objectForKey:WebElementImageURLKey];
212     
213     if (imageURL) {
214         if (linkURL) {
215             [menuItems addObject:[NSMenuItem separatorItem]];
216         }
217         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagOpenImageInNewWindow target:self representedObject:element]];
218         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagDownloadImageToDisk target:self representedObject:element]];
219         if ([imageURL isFileURL] || [[webFrame dataSource] _fileWrapperForURL:imageURL]) {
220             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagCopyImageToClipboard target:self representedObject:element]];
221         }
222     }
223     
224     if (!imageURL && !linkURL) {
225         if ([[element objectForKey:WebElementIsSelectedKey] boolValue]) {
226             // The Spotlight and Google items are implemented in WebView, and require that the
227             // current document view conforms to WebDocumentText
228             ASSERT([[[webFrame frameView] documentView] conformsToProtocol:@protocol(WebDocumentText)]);
229             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchInSpotlight target:nil representedObject:element]];
230             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchWeb target:nil representedObject:element]];
231             [menuItems addObject:[NSMenuItem separatorItem]];
232
233             // FIXME 4184640: The Look Up in Dictionary item is only implemented in WebHTMLView, and so is present but
234             // dimmed for other cases where WebElementIsSelectedKey is present. It would probably 
235             // be better not to include it in the menu if the documentView isn't a WebHTMLView, but that could break 
236             // existing clients that have code that relies on it being present (unlikely for clients outside of Apple, 
237             // but Safari has such code).
238             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagLookUpInDictionary target:nil representedObject:element]];            
239             [menuItems addObject:[NSMenuItem separatorItem]];
240             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagCopy target:nil representedObject:element]];
241         } else {
242             WebView *wv = [webFrame webView];
243             if ([wv canGoBack]) {
244                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagGoBack target:wv representedObject:element]];
245             }
246             if ([wv canGoForward]) {
247                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagGoForward target:wv representedObject:element]];
248             }
249             if ([wv isLoading]) {
250                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagStop target:wv representedObject:element]];
251             } else {
252                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagReload target:wv representedObject:element]];
253             }
254             
255             if (webFrame != [wv mainFrame]) {
256                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagOpenFrameInNewWindow target:self representedObject:element]];
257             }
258         }
259     }
260     
261     // Add the default items at the end, if any, after a separator
262     [self appendDefaultItems:defaultMenuItems toArray:menuItems];
263
264     return menuItems;
265 }
266
267 - (NSArray *)editingContextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
268 {
269     NSMutableArray *menuItems = [NSMutableArray array];
270     NSMenuItem *item;
271     WebHTMLView *HTMLView = (WebHTMLView *)[[[element objectForKey:WebElementFrameKey] frameView] documentView];
272     ASSERT([HTMLView isKindOfClass:[WebHTMLView class]]);
273     BOOL inPasswordField = [HTMLView _isSelectionInPasswordField];
274
275     // Add spelling-related context menu items.
276     if ([HTMLView _isSelectionMisspelled] && !inPasswordField) {
277         NSArray *guesses = [HTMLView _guessesForMisspelledSelection];
278         unsigned count = [guesses count];
279         if (count > 0) {
280             NSEnumerator *enumerator = [guesses objectEnumerator];
281             NSString *guess;
282             while ((guess = [enumerator nextObject]) != nil) {
283                 item = [self menuItemWithTag:WebMenuItemTagSpellingGuess target:nil representedObject:element];
284                 [item setTitle:guess];
285                 [menuItems addObject:item];
286             }
287         } else {
288             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagNoGuessesFound target:nil representedObject:element]];
289         }
290         [menuItems addObject:[NSMenuItem separatorItem]];
291         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagIgnoreSpelling target:nil representedObject:element]];
292         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagLearnSpelling target:nil representedObject:element]];
293         [menuItems addObject:[NSMenuItem separatorItem]];
294     }
295
296     if ([[element objectForKey:WebElementIsSelectedKey] boolValue] && !inPasswordField) {
297         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchInSpotlight target:nil representedObject:element]];
298         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchWeb target:nil representedObject:element]];
299         [menuItems addObject:[NSMenuItem separatorItem]];
300
301         // FIXME: The NSTextView behavior for looking text up in the dictionary is different if
302         // there was a selection before you clicked than if the selection was created as part of
303         // the click. This is desired by the dictionary folks apparently, though it seems bizarre.
304         // It might be tricky to pull this off in WebKit.
305         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagLookUpInDictionary target:nil representedObject:element]];
306         [menuItems addObject:[NSMenuItem separatorItem]];
307     }
308     
309     // Load our NSTextView-like context menu nib.
310     if (defaultMenu == nil) {
311         static NSNib *textViewMenuNib = nil;
312         if (!textViewMenuNib) {
313 #if BUILDING_ON_TIGER
314             NSString *contextMenuNibName = @"WebViewEditingContextMenuOld";
315 #else
316             NSString *contextMenuNibName = @"WebViewEditingContextMenu";
317 #endif
318             textViewMenuNib = [[NSNib alloc] initWithNibNamed:contextMenuNibName bundle:[NSBundle bundleForClass:[self class]]];
319         }
320         [textViewMenuNib instantiateNibWithOwner:self topLevelObjects:nil];
321         ASSERT(defaultMenu != nil);
322     }
323     
324     // Add tags to the menu items because this is what the WebUIDelegate expects.
325     // Also remove any future-looking items if appropriate.
326     NSEnumerator *enumerator = [[defaultMenu itemArray] objectEnumerator];
327     while ((item = [enumerator nextObject]) != nil) {
328         item = [item copy];
329         SEL action = [item action];
330         int tag;
331         bool validForPassword = true;
332         if (action == @selector(cut:)) {
333             tag = WebMenuItemTagCut;
334         } else if (action == @selector(copy:)) {
335             tag = WebMenuItemTagCopy;
336         } else if (action == @selector(paste:)) {
337             tag = WebMenuItemTagPaste;
338         } else {
339             // FIXME 4158153: we should supply tags for each known item so clients can make
340             // sensible decisions, like we do with PDF context menu items (see WebPDFView.mm).
341             // FIXME: This ignores the contents of submenus entirely.
342
343             // Once we have other tag names, we should reconsider if any of them are valid for password fields.
344             tag = WebMenuItemTagOther;
345             validForPassword = false;
346         }
347         
348         if (!inPasswordField || validForPassword) {
349             [item setTag:tag];
350             [menuItems addObject:item];
351         }
352         [item release];
353     }
354     
355     // Add the default items at the end, if any, after a separator
356     [self appendDefaultItems:defaultMenuItems toArray:menuItems];
357         
358     return menuItems;
359 }
360
361 - (NSArray *)webView:(WebView *)wv contextMenuItemsForElement:(NSDictionary *)element  defaultMenuItems:(NSArray *)defaultMenuItems
362 {
363     BOOL contentEditible = NO;
364     id domElement = [element objectForKey:WebElementDOMNodeKey];
365     if ([wv isEditable] || [domElement isKindOfClass:[DOMHTMLTextAreaElement class]] || [domElement isKindOfClass:[DOMHTMLIsIndexElement class]])
366         contentEditible = YES;
367     else if ([domElement isKindOfClass:[DOMHTMLInputElement class]])
368         contentEditible = [(DOMHTMLInputElement *)domElement _isTextField];
369     else if ([domElement isKindOfClass:[DOMNode class]])
370         contentEditible = [(DOMNode *)domElement isContentEditable];
371
372     NSView *documentView = [[[element objectForKey:WebElementFrameKey] frameView] documentView];
373     if ([documentView isKindOfClass:[WebHTMLView class]] && contentEditible) {
374         return [self editingContextMenuItemsForElement:element defaultMenuItems:defaultMenuItems];
375     } else {
376         return [self contextMenuItemsForElement:element defaultMenuItems:defaultMenuItems];
377     }
378 }
379
380 - (void)openNewWindowWithURL:(NSURL *)URL element:(NSDictionary *)element
381 {
382     WebFrame *webFrame = [element objectForKey:WebElementFrameKey];
383     WebView *webView = [webFrame webView];
384     
385     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
386     NSString *referrer = [[webFrame _bridge] referrer];
387     if (referrer) {
388         [request _web_setHTTPReferrer:referrer];
389     }
390     
391     [webView _openNewWindowWithRequest:request];
392 }
393
394 - (void)downloadURL:(NSURL *)URL element:(NSDictionary *)element
395 {
396     WebFrame *webFrame = [element objectForKey:WebElementFrameKey];
397     WebView *webView = [webFrame webView];
398     [webView _downloadURL:URL];
399 }
400
401 - (void)openLinkInNewWindow:(id)sender
402 {
403     NSDictionary *element = [sender representedObject];
404     [self openNewWindowWithURL:[element objectForKey:WebElementLinkURLKey] element:element];
405 }
406
407 - (void)downloadLinkToDisk:(id)sender
408 {
409     NSDictionary *element = [sender representedObject];
410     [self downloadURL:[element objectForKey:WebElementLinkURLKey] element:element];
411 }
412
413 - (void)copyLinkToClipboard:(id)sender
414 {
415     NSDictionary *element = [sender representedObject];
416     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
417     NSArray *types = [NSPasteboard _web_writableTypesForURL];
418     [pasteboard declareTypes:types owner:self];    
419     [[[element objectForKey:WebElementFrameKey] webView] _writeLinkElement:element 
420                                                        withPasteboardTypes:types
421                                                               toPasteboard:pasteboard];
422 }
423
424 - (void)openImageInNewWindow:(id)sender
425 {
426     NSDictionary *element = [sender representedObject];
427     [self openNewWindowWithURL:[element objectForKey:WebElementImageURLKey] element:element];
428 }
429
430 - (void)downloadImageToDisk:(id)sender
431 {
432     NSDictionary *element = [sender representedObject];
433     [self downloadURL:[element objectForKey:WebElementImageURLKey] element:element];
434 }
435
436 - (void)copyImageToClipboard:(id)sender
437 {
438     NSDictionary *element = [sender representedObject];
439     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
440     NSArray *types = [NSPasteboard _web_writableTypesForImageIncludingArchive:([element objectForKey:WebElementDOMNodeKey] != nil)];
441     [pasteboard declareTypes:types owner:self];
442     [[[element objectForKey:WebElementFrameKey] webView] _writeImageForElement:element 
443                                                         withPasteboardTypes:types 
444                                                                toPasteboard:pasteboard];
445 }
446
447 - (void)openFrameInNewWindow:(id)sender
448 {
449     NSDictionary *element = [sender representedObject];
450     WebDataSource *dataSource = [[element objectForKey:WebElementFrameKey] dataSource];
451     NSURL *URL = [dataSource unreachableURL];
452     if (URL == nil) {
453         URL = [[dataSource request] URL];
454     }    
455     [self openNewWindowWithURL:URL element:element];
456 }
457
458 @end