1/*
2 * Copyright (C) 2014 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 "WebSharingServicePickerController.h"
27
28#if ENABLE(SERVICE_CONTROLS)
29
30#import "WebContextMenuClient.h"
31#import "WebViewInternal.h"
32#import <AppKit/NSSharingService.h>
33#import <WebCore/BitmapImage.h>
34#import <WebCore/Document.h>
35#import <WebCore/Editor.h>
36#import <WebCore/FocusController.h>
37#import <WebCore/Frame.h>
38#import <WebCore/FrameSelection.h>
39#import <WebCore/ContextMenuController.h>
40#import <WebCore/Page.h>
41
42#if __has_include(<AppKit/NSSharingService_Private.h>)
43#import <AppKit/NSSharingService_Private.h>
44#else
45typedef enum {
46    NSSharingServicePickerStyleRollover = 1
47} NSSharingServicePickerStyle;
48
49@interface NSSharingServicePicker (Private)
50@property NSSharingServicePickerStyle style;
51- (NSMenu *)menu;
52@end
53#endif
54
55#if __has_include(<AppKit/NSItemProvider.h>)
56#import <AppKit/NSItemProvider.h>
57#else
58// FIXME: We should properly disable code that interacts with NSItemProvider/NSSharingServicePicker in 32-bit.
59@interface NSItemProvider : NSObject
60@property (copy, readonly) NSArray *registeredTypeIdentifiers;
61- (instancetype)initWithItem:(id <NSSecureCoding>)item typeIdentifier:(NSString *)typeIdentifier;
62- (void)loadItemForTypeIdentifier:(NSString *)typeIdentifier options:(NSDictionary *)options completionHandler:(void (^)(id <NSSecureCoding> item, NSError *error))completionHandler;
63@end
64#endif
65
66static NSString *serviceControlsPasteboardName = @"WebKitServiceControlsPasteboard";
67
68using namespace WebCore;
69
70@implementation WebSharingServicePickerController
71
72- (instancetype)initWithData:(NSData *)data includeEditorServices:(BOOL)includeEditorServices menuClient:(WebContextMenuClient*)menuClient
73{
74#ifndef __LP64__
75    return nil;
76#else
77    if (!(self = [super init]))
78        return nil;
79
80    RetainPtr<NSItemProvider> itemProvider = adoptNS([[NSItemProvider alloc] initWithItem:data typeIdentifier:@"public.data"]);
81
82    _picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ itemProvider.get() ]]);
83    [_picker setStyle:NSSharingServicePickerStyleRollover];
84    [_picker setDelegate:self];
85
86    _includeEditorServices = includeEditorServices;
87    _menuClient = menuClient;
88
89    return self;
90#endif
91}
92
93- (void)clear
94{
95    // Protect self from being dealloc'ed partway through this method.
96    RetainPtr<WebSharingServicePickerController> protector(self);
97
98    if (_menuClient)
99        _menuClient->clearSharingServicePickerController();
100
101    _picker = nullptr;
102    _menuClient = nullptr;
103}
104
105- (NSMenu *)menu
106{
107    return [_picker menu];
108}
109
110- (void)didShareImageData:(NSData *)data confirmDataIsValidTIFFData:(BOOL)confirmData
111{
112    Page* page = [_menuClient->webView() page];
113    if (!page)
114        return;
115
116    if (confirmData) {
117        RetainPtr<NSImage> nsImage = adoptNS([[NSImage alloc] initWithData:data]);
118        if (!nsImage) {
119            LOG_ERROR("Shared image data cannot create a valid NSImage");
120            return;
121        }
122
123        data = [nsImage TIFFRepresentation];
124    }
125
126    NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:serviceControlsPasteboardName];
127    [pasteboard declareTypes:@[ NSPasteboardTypeTIFF ] owner:nil];
128    [pasteboard setData:data forType:NSPasteboardTypeTIFF];
129
130    if (Node* node = page->contextMenuController().context().hitTestResult().innerNode()) {
131        if (Frame* frame = node->document().frame())
132            frame->editor().replaceNodeFromPasteboard(node, serviceControlsPasteboardName);
133    }
134
135    [self clear];
136}
137
138#pragma mark NSSharingServicePickerDelegate methods
139
140- (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)mask proposedSharingServices:(NSArray *)proposedServices
141{
142    if (_includeEditorServices)
143        return proposedServices;
144
145    NSMutableArray *services = [[NSMutableArray alloc] initWithCapacity:[proposedServices count]];
146
147    for (NSSharingService *service in proposedServices) {
148        if (service.type != NSSharingServiceTypeEditor)
149            [services addObject:service];
150    }
151
152    return services;
153}
154
155- (id <NSSharingServiceDelegate>)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker delegateForSharingService:(NSSharingService *)sharingService
156{
157    return self;
158}
159
160- (void)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker didChooseSharingService:(NSSharingService *)service
161{
162    if (!service)
163        _menuClient->clearSharingServicePickerController();
164}
165
166#pragma mark NSSharingServiceDelegate methods
167
168- (void)sharingService:(NSSharingService *)sharingService didShareItems:(NSArray *)items
169{
170    // We only send one item, so we should only get one item back.
171    if ([items count] != 1)
172        return;
173
174    id item = [items objectAtIndex:0];
175
176    if ([item isKindOfClass:[NSImage class]])
177        [self didShareImageData:[item TIFFRepresentation] confirmDataIsValidTIFFData:NO];
178#ifdef __LP64__
179    else if ([item isKindOfClass:[NSItemProvider class]]) {
180        NSItemProvider *itemProvider = (NSItemProvider *)item;
181        NSString *itemUTI = itemProvider.registeredTypeIdentifiers.firstObject;
182
183        [itemProvider loadItemForTypeIdentifier:itemUTI options:nil completionHandler:^(id receivedData, NSError *dataError) {
184            if (!receivedData) {
185                LOG_ERROR("Did not receive data from NSItemProvider");
186                return;
187            }
188
189            if (![receivedData isKindOfClass:[NSData class]]) {
190                LOG_ERROR("Data received from NSItemProvider is not of type NSData");
191                return;
192            }
193
194            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
195                [self didShareImageData:receivedData confirmDataIsValidTIFFData:YES];
196            }];
197
198        }];
199    }
200#endif
201    else
202        LOG_ERROR("sharingService:didShareItems: - Unknown item type returned");
203}
204
205- (void)sharingService:(NSSharingService *)sharingService didFailToShareItems:(NSArray *)items error:(NSError *)error
206{
207    [self clear];
208}
209
210- (NSRect)sharingService:(NSSharingService *)sharingService sourceFrameOnScreenForShareItem:(id <NSPasteboardWriting>)item
211{
212    if (!_menuClient)
213        return NSZeroRect;
214
215    return _menuClient->screenRectForHitTestNode();
216}
217
218- (NSImage *)sharingService:(NSSharingService *)sharingService transitionImageForShareItem:(id <NSPasteboardWriting>)item contentRect:(NSRect *)contentRect
219{
220    if (!_menuClient)
221        return nil;
222
223    return _menuClient->renderedImageForControlledImage();
224}
225
226- (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope
227{
228    return [_menuClient->webView() window];
229}
230
231@end
232
233#endif
234