* DefaultDelegates/WebDefaultContextMenuDelegate.m:
[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             // The Spotlight and Google items are implemented in WebView, and require that the
226             // current document view conforms to WebDocumentText
227             ASSERT([[[webFrame frameView] documentView] conformsToProtocol:@protocol(WebDocumentText)]);
228             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchInSpotlight target:nil representedObject:element]];
229             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchWeb target:nil representedObject:element]];
230             [menuItems addObject:[NSMenuItem separatorItem]];
231
232             // FIXME 4184640: The Look Up in Dictionary item is only implemented in WebHTMLView, and so is present but
233             // dimmed for other cases where WebElementIsSelectedKey is present. It would probably 
234             // be better not to include it in the menu if the documentView isn't a WebHTMLView, but that could break 
235             // existing clients that have code that relies on it being present (unlikely for clients outside of Apple, 
236             // but Safari has such code).
237             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagLookUpInDictionary target:nil representedObject:element]];            
238             [menuItems addObject:[NSMenuItem separatorItem]];
239             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagCopy target:nil representedObject:element]];
240         } else {
241             WebView *wv = [webFrame webView];
242             if ([wv canGoBack]) {
243                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagGoBack target:wv representedObject:element]];
244             }
245             if ([wv canGoForward]) {
246                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagGoForward target:wv representedObject:element]];
247             }
248             if ([wv isLoading]) {
249                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagStop target:wv representedObject:element]];
250             } else {
251                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagReload target:wv representedObject:element]];
252             }
253             
254             if (webFrame != [wv mainFrame]) {
255                 [menuItems addObject:[self menuItemWithTag:WebMenuItemTagOpenFrameInNewWindow target:self representedObject:element]];
256             }
257         }
258     }
259     
260     // Add the default items at the end, if any, after a separator
261     [self appendDefaultItems:defaultMenuItems toArray:menuItems];
262
263     return menuItems;
264 }
265
266 - (NSArray *)editingContextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
267 {
268     NSMutableArray *menuItems = [NSMutableArray array];
269     NSMenuItem *item;
270     WebHTMLView *HTMLView = (WebHTMLView *)[[[element objectForKey:WebElementFrameKey] frameView] documentView];
271     ASSERT([HTMLView isKindOfClass:[WebHTMLView class]]);
272     
273     // Add spelling-related context menu items.
274     if ([HTMLView _isSelectionMisspelled]) {
275         NSArray *guesses = [HTMLView _guessesForMisspelledSelection];
276         unsigned count = [guesses count];
277         if (count > 0) {
278             NSEnumerator *enumerator = [guesses objectEnumerator];
279             NSString *guess;
280             while ((guess = [enumerator nextObject]) != nil) {
281                 item = [self menuItemWithTag:WebMenuItemTagSpellingGuess target:nil representedObject:element];
282                 [item setTitle:guess];
283                 [menuItems addObject:item];
284             }
285         } else {
286             [menuItems addObject:[self menuItemWithTag:WebMenuItemTagNoGuessesFound target:nil representedObject:element]];
287         }
288         [menuItems addObject:[NSMenuItem separatorItem]];
289         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagIgnoreSpelling target:nil representedObject:element]];
290         [menuItems addObject:[self menuItemWithTag:WebMenuItemTagLearnSpelling target:nil representedObject:element]];
291         [menuItems addObject:[NSMenuItem separatorItem]];
292     }
293     
294     // Add items that aren't in our nib, originally because they were Tiger-only.
295     // FIXME: We should update the nib to include these.
296     [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchInSpotlight target:nil representedObject:element]];
297     [menuItems addObject:[self menuItemWithTag:WebMenuItemTagSearchWeb target:nil representedObject:element]];
298     [menuItems addObject:[NSMenuItem separatorItem]];
299     // FIXME: The NSTextView behavior for looking text up in the dictionary is different if
300     // there was a selection before you clicked than if the selection was created as part of
301     // the click. This is desired by the dictionary folks apparently, though it seems bizarre.
302     // It might be tricky to pull this off in WebKit.
303     [menuItems addObject:[self menuItemWithTag:WebMenuItemTagLookUpInDictionary target:nil representedObject:element]];
304     [menuItems addObject:[NSMenuItem separatorItem]];
305     
306     // Load our NSTextView-like context menu nib.
307     if (defaultMenu == nil) {
308         static NSNib *textViewMenuNib = nil;
309         if (textViewMenuNib == nil) {
310             textViewMenuNib = [[NSNib alloc] initWithNibNamed:@"WebViewEditingContextMenu" bundle:[NSBundle bundleForClass:[self class]]];
311         }
312         [textViewMenuNib instantiateNibWithOwner:self topLevelObjects:nil];
313         ASSERT(defaultMenu != nil);
314     }
315     
316     // Add tags to the menu items because this is what the WebUIDelegate expects.
317     NSEnumerator *enumerator = [[defaultMenu itemArray] objectEnumerator];
318     while ((item = [enumerator nextObject]) != nil) {
319         item = [item copy];
320         SEL action = [item action];
321         int tag;
322         if (action == @selector(cut:)) {
323             tag = WebMenuItemTagCut;
324         } else if (action == @selector(copy:)) {
325             tag = WebMenuItemTagCopy;
326         } else if (action == @selector(paste:)) {
327             tag = WebMenuItemTagPaste;
328         } else {
329             // FIXME 4158153: we should supply tags for each known item so clients can make
330             // sensible decisions, like we do with PDF context menu items (see WebPDFView.m)
331             tag = WebMenuItemTagOther;
332         }
333         [item setTag:tag];
334         [menuItems addObject:item];
335         [item release];
336     }
337     
338     // Add the default items at the end, if any, after a separator
339     [self appendDefaultItems:defaultMenuItems toArray:menuItems];
340         
341     return menuItems;
342 }
343
344 - (NSArray *)webView:(WebView *)wv contextMenuItemsForElement:(NSDictionary *)element  defaultMenuItems:(NSArray *)defaultMenuItems
345 {
346     NSView *documentView = [[[element objectForKey:WebElementFrameKey] frameView] documentView];
347     if ([documentView isKindOfClass:[WebHTMLView class]] && [(WebHTMLView *)documentView _isEditable]) {
348         return [self editingContextMenuItemsForElement:element defaultMenuItems:defaultMenuItems];
349     } else {
350         return [self contextMenuItemsForElement:element defaultMenuItems:defaultMenuItems];
351     }
352 }
353
354 - (void)openNewWindowWithURL:(NSURL *)URL element:(NSDictionary *)element
355 {
356     WebFrame *webFrame = [element objectForKey:WebElementFrameKey];
357     WebView *webView = [webFrame webView];
358     
359     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
360     NSString *referrer = [[webFrame _bridge] referrer];
361     if (referrer) {
362         [request _web_setHTTPReferrer:referrer];
363     }
364     
365     [webView _openNewWindowWithRequest:request];
366 }
367
368 - (void)downloadURL:(NSURL *)URL element:(NSDictionary *)element
369 {
370     WebFrame *webFrame = [element objectForKey:WebElementFrameKey];
371     WebView *webView = [webFrame webView];
372     [webView _downloadURL:URL];
373 }
374
375 - (void)openLinkInNewWindow:(id)sender
376 {
377     NSDictionary *element = [sender representedObject];
378     [self openNewWindowWithURL:[element objectForKey:WebElementLinkURLKey] element:element];
379 }
380
381 - (void)downloadLinkToDisk:(id)sender
382 {
383     NSDictionary *element = [sender representedObject];
384     [self downloadURL:[element objectForKey:WebElementLinkURLKey] element:element];
385 }
386
387 - (void)copyLinkToClipboard:(id)sender
388 {
389     NSDictionary *element = [sender representedObject];
390     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
391     NSArray *types = [NSPasteboard _web_writableTypesForURL];
392     [pasteboard declareTypes:types owner:self];    
393     [[[element objectForKey:WebElementFrameKey] webView] _writeLinkElement:element 
394                                                        withPasteboardTypes:types
395                                                               toPasteboard:pasteboard];
396 }
397
398 - (void)openImageInNewWindow:(id)sender
399 {
400     NSDictionary *element = [sender representedObject];
401     [self openNewWindowWithURL:[element objectForKey:WebElementImageURLKey] element:element];
402 }
403
404 - (void)downloadImageToDisk:(id)sender
405 {
406     NSDictionary *element = [sender representedObject];
407     [self downloadURL:[element objectForKey:WebElementImageURLKey] element:element];
408 }
409
410 - (void)copyImageToClipboard:(id)sender
411 {
412     NSDictionary *element = [sender representedObject];
413     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
414     NSArray *types = [NSPasteboard _web_writableTypesForImageIncludingArchive:([element objectForKey:WebElementDOMNodeKey] != nil)];
415     [pasteboard declareTypes:types owner:self];
416     [[[element objectForKey:WebElementFrameKey] webView] _writeImageForElement:element 
417                                                         withPasteboardTypes:types 
418                                                                toPasteboard:pasteboard];
419 }
420
421 - (void)openFrameInNewWindow:(id)sender
422 {
423     NSDictionary *element = [sender representedObject];
424     WebDataSource *dataSource = [[element objectForKey:WebElementFrameKey] dataSource];
425     NSURL *URL = [dataSource unreachableURL];
426     if (URL == nil) {
427         URL = [[dataSource request] URL];
428     }    
429     [self openNewWindowWithURL:URL element:element];
430 }
431
432 @end