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