Stop using ContextMenuItem::shareMenuItem in the UI process
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / WebContextMenuProxyMac.mm
1 /*
2  * Copyright (C) 2010 Apple 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WebContextMenuProxyMac.h"
28
29 #if PLATFORM(MAC)
30
31 #import "APIContextMenuClient.h"
32 #import "DataReference.h"
33 #import "MenuUtilities.h"
34 #import "PageClientImpl.h"
35 #import "ServicesController.h"
36 #import "ShareableBitmap.h"
37 #import "StringUtilities.h"
38 #import "WKSharingServicePickerDelegate.h"
39 #import "WKView.h"
40 #import "WebContextMenuItem.h"
41 #import "WebContextMenuItemData.h"
42 #import "WebProcessProxy.h"
43 #import <WebCore/GraphicsContext.h>
44 #import <WebCore/IntRect.h>
45 #import <WebCore/NSMenuSPI.h>
46 #import <WebCore/NSSharingServicePickerSPI.h>
47 #import <WebCore/NSSharingServiceSPI.h>
48 #import <WebKitSystemInterface.h>
49 #import <wtf/RetainPtr.h>
50
51 using namespace WebCore;
52
53 @interface WKUserDataWrapper : NSObject {
54     RefPtr<API::Object> _webUserData;
55 }
56 - (id)initWithUserData:(API::Object*)userData;
57 - (API::Object*)userData;
58 @end
59
60 @implementation WKUserDataWrapper
61
62 - (id)initWithUserData:(API::Object*)userData
63 {
64     self = [super init];
65     if (!self)
66         return nil;
67     
68     _webUserData = userData;
69     return self;
70 }
71
72 - (API::Object*)userData
73 {
74     return _webUserData.get();
75 }
76
77 @end
78
79 @interface WKSelectionHandlerWrapper : NSObject {
80     std::function<void ()> _selectionHandler;
81 }
82 - (id)initWithSelectionHandler:(std::function<void ()>)selectionHandler;
83 - (void)executeSelectionHandler;
84 @end
85
86 @implementation WKSelectionHandlerWrapper
87 - (id)initWithSelectionHandler:(std::function<void ()>)selectionHandler
88 {
89     self = [super init];
90     if (!self)
91         return nil;
92     
93     _selectionHandler = selectionHandler;
94     return self;
95 }
96
97 - (void)executeSelectionHandler
98 {
99     if (_selectionHandler)
100         _selectionHandler();
101 }
102 @end
103
104 @interface WKMenuTarget : NSObject {
105     WebKit::WebContextMenuProxyMac* _menuProxy;
106 }
107 + (WKMenuTarget *)sharedMenuTarget;
108 - (WebKit::WebContextMenuProxyMac*)menuProxy;
109 - (void)setMenuProxy:(WebKit::WebContextMenuProxyMac*)menuProxy;
110 - (void)forwardContextMenuAction:(id)sender;
111 @end
112
113 @implementation WKMenuTarget
114
115 + (WKMenuTarget*)sharedMenuTarget
116 {
117     static WKMenuTarget* target = [[WKMenuTarget alloc] init];
118     return target;
119 }
120
121 - (WebKit::WebContextMenuProxyMac*)menuProxy
122 {
123     return _menuProxy;
124 }
125
126 - (void)setMenuProxy:(WebKit::WebContextMenuProxyMac*)menuProxy
127 {
128     _menuProxy = menuProxy;
129 }
130
131 - (void)forwardContextMenuAction:(id)sender
132 {
133     id representedObject = [sender representedObject];
134
135     // NSMenuItems with a represented selection handler belong solely to the UI process
136     // and don't need any further processing after the selection handler is called.
137     if ([representedObject isKindOfClass:[WKSelectionHandlerWrapper class]]) {
138         [representedObject executeSelectionHandler];
139         return;
140     }
141
142     WebKit::WebContextMenuItemData item(ActionType, static_cast<ContextMenuAction>([sender tag]), [sender title], [sender isEnabled], [sender state] == NSOnState);
143     if (representedObject) {
144         ASSERT([representedObject isKindOfClass:[WKUserDataWrapper class]]);
145         item.setUserData([static_cast<WKUserDataWrapper *>(representedObject) userData]);
146     }
147
148     _menuProxy->contextMenuItemSelected(item);
149 }
150
151 @end
152
153 namespace WebKit {
154
155 WebContextMenuProxyMac::WebContextMenuProxyMac(WKView* webView, WebPageProxy& page, const ContextMenuContextData& context, const UserData& userData)
156     : WebContextMenuProxy(context, userData)
157     , m_webView(webView)
158     , m_page(page)
159 {
160 }
161
162 WebContextMenuProxyMac::~WebContextMenuProxyMac()
163 {
164     [m_menu cancelTracking];
165 }
166
167 void WebContextMenuProxyMac::contextMenuItemSelected(const WebContextMenuItemData& item)
168 {
169 #if ENABLE(SERVICE_CONTROLS)
170     clearServicesMenu();
171 #endif
172
173     m_page.contextMenuItemSelected(item);
174 }
175
176 #if ENABLE(SERVICE_CONTROLS)
177 void WebContextMenuProxyMac::setupServicesMenu()
178 {
179     bool includeEditorServices = m_context.controlledDataIsEditable();
180     bool hasControlledImage = m_context.controlledImage();
181     NSArray *items = nil;
182     if (hasControlledImage) {
183         RefPtr<ShareableBitmap> image = m_context.controlledImage();
184         if (!image)
185             return;
186
187         auto cgImage = image->makeCGImage();
188         auto nsImage = adoptNS([[NSImage alloc] initWithCGImage:cgImage.get() size:image->size()]);
189
190 #ifdef __LP64__
191         auto itemProvider = adoptNS([[NSItemProvider alloc] initWithItem:[nsImage TIFFRepresentation] typeIdentifier:(__bridge NSString *)kUTTypeTIFF]);
192         items = @[ itemProvider.get() ];
193 #else
194         items = @[ ];
195 #endif
196     } else if (!m_context.controlledSelectionData().isEmpty()) {
197         auto selectionData = adoptNS([[NSData alloc] initWithBytes:static_cast<const void*>(m_context.controlledSelectionData().data()) length:m_context.controlledSelectionData().size()]);
198         auto selection = adoptNS([[NSAttributedString alloc] initWithRTFD:selectionData.get() documentAttributes:nil]);
199
200         items = @[ selection.get() ];
201     } else {
202         LOG_ERROR("No service controlled item represented in the context");
203         return;
204     }
205
206     RetainPtr<NSSharingServicePicker> picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:items]);
207     [picker setStyle:hasControlledImage ? NSSharingServicePickerStyleRollover : NSSharingServicePickerStyleTextSelection];
208     [picker setDelegate:[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate]];
209     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setPicker:picker.get()];
210     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setFiltersEditingServices:!includeEditorServices];
211     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setHandlesEditingReplacement:includeEditorServices];
212
213     m_menu = adoptNS([[picker menu] copy]);
214
215     if (!hasControlledImage)
216         [m_menu setShowsStateColumn:YES];
217
218     // Explicitly add a menu item for each telephone number that is in the selection.
219     Vector<RetainPtr<NSMenuItem>> telephoneNumberMenuItems;
220
221     for (auto& telephoneNumber : m_context.selectedTelephoneNumbers()) {
222         if (NSMenuItem *item = menuItemForTelephoneNumber(telephoneNumber)) {
223             [item setIndentationLevel:1];
224             telephoneNumberMenuItems.append(item);
225         }
226     }
227
228     if (!telephoneNumberMenuItems.isEmpty()) {
229         if (m_menu)
230             [m_menu insertItem:[NSMenuItem separatorItem] atIndex:0];
231         else
232             m_menu = adoptNS([[NSMenu alloc] init]);
233         int itemPosition = 0;
234         NSMenuItem *groupEntry = [[NSMenuItem alloc] initWithTitle:menuItemTitleForTelephoneNumberGroup() action:nil keyEquivalent:@""];
235         [groupEntry setEnabled:NO];
236         [m_menu insertItem:groupEntry atIndex:itemPosition++];
237         for (auto& menuItem : telephoneNumberMenuItems)
238             [m_menu insertItem:menuItem.get() atIndex:itemPosition++];
239     }
240
241     // If there is no services menu, then the existing services on the system have changed, so refresh that list of services.
242     // If <rdar://problem/17954709> is resolved then we can more accurately keep the list up to date without this call.
243     if (!m_menu)
244         ServicesController::singleton().refreshExistingServices();
245 }
246
247 void WebContextMenuProxyMac::showServicesMenu()
248 {
249     setupServicesMenu();
250
251     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setMenuProxy:this];
252     [m_menu popUpMenuPositioningItem:nil atLocation:m_context.menuLocation() inView:m_webView];
253 }
254
255 void WebContextMenuProxyMac::clearServicesMenu()
256 {
257     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setPicker:nullptr];
258     m_menu = nullptr;
259 }
260
261 RetainPtr<NSMenuItem> WebContextMenuProxyMac::createShareMenuItem()
262 {
263     if (![[NSMenuItem class] respondsToSelector:@selector(standardShareMenuItemWithItems:)])
264         return nil;
265
266     const WebHitTestResultData& hitTestData = m_context.webHitTestResultData();
267
268     auto items = adoptNS([[NSMutableArray alloc] init]);
269
270     if (!hitTestData.absoluteLinkURL.isEmpty()) {
271         NSURL *absoluteLinkURL = [NSURL URLWithString:hitTestData.absoluteLinkURL];
272         [items addObject:absoluteLinkURL];
273     }
274
275     if (hitTestData.isDownloadableMedia && !hitTestData.absoluteMediaURL.isEmpty()) {
276         NSURL *downloadableMediaURL = [NSURL URLWithString:hitTestData.absoluteMediaURL];
277         [items addObject:downloadableMediaURL];
278     }
279
280     if (hitTestData.imageSharedMemory && hitTestData.imageSize) {
281         auto image = adoptNS([[NSImage alloc] initWithData:[NSData dataWithBytes:(unsigned char*)hitTestData.imageSharedMemory->data() length:hitTestData.imageSize]]);
282         [items addObject:image.get()];
283     }
284
285     RetainPtr<NSMenuItem> item = [NSMenuItem standardShareMenuItemWithItems:items.get()];
286     if (!item)
287         return nil;
288
289     NSSharingServicePicker *sharingServicePicker = [item representedObject];
290     sharingServicePicker.delegate = [WKSharingServicePickerDelegate sharedSharingServicePickerDelegate];
291
292     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setFiltersEditingServices:NO];
293     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setHandlesEditingReplacement:NO];
294     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setMenuProxy:this];
295
296     // Setting the picker lets the delegate retain it to keep it alive, but this picker is kept alive by the menu item.
297     [[WKSharingServicePickerDelegate sharedSharingServicePickerDelegate] setPicker:nil];
298
299     return item;
300 }
301 #endif
302
303 void WebContextMenuProxyMac::show()
304 {
305     Ref<WebPageProxy> protect(m_page);
306
307 #if ENABLE(SERVICE_CONTROLS)
308     if (m_context.isServicesMenu()) {
309         showServicesMenu();
310         return;
311     }
312 #endif
313
314     showContextMenu();
315 }
316
317 RetainPtr<NSMenu> WebContextMenuProxyMac::createContextMenuFromItems(const Vector<WebContextMenuItemData>& items)
318 {
319     auto menu = adoptNS([[NSMenu alloc] initWithTitle:@""]);
320     [menu setAutoenablesItems:NO];
321
322     for (auto& item : items) {
323         if (auto menuItem = createContextMenuItem(item))
324             [menu addItem:menuItem.get()];
325     }
326
327     return menu;
328 }
329
330 RetainPtr<NSMenuItem> WebContextMenuProxyMac::createContextMenuItem(const WebContextMenuItemData& item)
331 {
332 #if ENABLE(SERVICE_CONTROLS)
333     if (item.action() == ContextMenuItemTagShareMenu)
334         return createShareMenuItem();
335 #endif
336
337     switch (item.type()) {
338     case ActionType:
339     case CheckableActionType: {
340         auto menuItem = adoptNS([[NSMenuItem alloc] initWithTitle:item.title() action:@selector(forwardContextMenuAction:) keyEquivalent:@""]);
341
342         [menuItem setTag:item.action()];
343         [menuItem setEnabled:item.enabled()];
344         [menuItem setState:item.checked() ? NSOnState : NSOffState];
345         [menuItem setTarget:[WKMenuTarget sharedMenuTarget]];
346
347         if (item.userData()) {
348             auto wrapper = adoptNS([[WKUserDataWrapper alloc] initWithUserData:item.userData()]);
349             [menuItem setRepresentedObject:wrapper.get()];
350         }
351
352         return menuItem;
353     }
354
355     case SeparatorType:
356         return [NSMenuItem separatorItem];
357
358     case SubmenuType: {
359         auto menuItem = adoptNS([[NSMenuItem alloc] initWithTitle:item.title() action:nullptr keyEquivalent:@""]);
360         [menuItem setEnabled:item.enabled()];
361         [menuItem setSubmenu:createContextMenuFromItems(item.submenu()).get()];
362
363         return menuItem;
364     }
365     }
366 }
367
368 void WebContextMenuProxyMac::showContextMenu()
369 {
370     Vector<RefPtr<WebContextMenuItem>> proposedAPIItems;
371     for (auto& item : m_context.menuItems())
372         proposedAPIItems.append(WebContextMenuItem::create(item));
373
374     Vector<RefPtr<WebContextMenuItem>> clientItems;
375     bool useProposedItems = true;
376
377     // FIXME: Get rid of this once we don't need the C SPI.
378     if (m_page.contextMenuClient().getContextMenuFromProposedMenu(m_page, proposedAPIItems, clientItems, m_context.webHitTestResultData(), m_page.process().transformHandlesToObjects(m_userData.object()).get()))
379         useProposedItems = false;
380
381     Vector<WebContextMenuItemData> items;
382     for (auto& item : (useProposedItems ? proposedAPIItems : clientItems))
383         items.append(item->data());
384
385     if (items.isEmpty())
386         return;
387
388     auto menu = createContextMenuFromItems(items);
389     m_menu = m_page.contextMenuClient().menuFromProposedMenu(m_page, menu.get(), m_context.webHitTestResultData());
390
391     [[WKMenuTarget sharedMenuTarget] setMenuProxy:this];
392     [m_menu popUpMenuPositioningItem:nil atLocation:m_context.menuLocation() inView:m_webView];
393 }
394
395 NSWindow *WebContextMenuProxyMac::window() const
396 {
397     return [m_webView window];
398 }
399
400 } // namespace WebKit
401
402 #endif // PLATFORM(MAC)