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