WebItemProviderPasteboard should use -registerLoadHandlersToItemProvider: when creati...
[WebKit-https.git] / Source / WebCore / platform / ios / WebItemProviderPasteboard.mm
1 /*
2  * Copyright (C) 2017 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 #include "config.h"
27 #import "WebItemProviderPasteboard.h"
28
29 #if ENABLE(DATA_INTERACTION)
30
31 #import "SoftLinking.h"
32 #import "UIKitSPI.h"
33 #import <Foundation/NSProgress.h>
34 #import <MobileCoreServices/MobileCoreServices.h>
35 #import <UIKit/UIColor.h>
36 #import <UIKit/UIImage.h>
37 #import <UIKit/UIItemProviderWriting.h>
38 #import <wtf/RetainPtr.h>
39
40 SOFT_LINK_FRAMEWORK(UIKit)
41 SOFT_LINK_CLASS(UIKit, UIColor)
42 SOFT_LINK_CLASS(UIKit, UIImage)
43 SOFT_LINK_CLASS(UIKit, UIItemProvider)
44
45 #define MATCHES_UTI_TYPE(type, suffix) [type isEqualToString:(__bridge NSString *)kUTType ## suffix]
46 #define MATCHES_UIKIT_TYPE(type, suffix) [type isEqualToString:@"com.apple.uikit. ## suffix ##"]
47
48 static BOOL isRichTextType(NSString *type)
49 {
50     return MATCHES_UTI_TYPE(type, RTF) || MATCHES_UTI_TYPE(type, RTFD) || MATCHES_UTI_TYPE(type, HTML);
51 }
52
53 static BOOL isStringType(NSString *type)
54 {
55     return MATCHES_UTI_TYPE(type, Text) || MATCHES_UTI_TYPE(type, UTF8PlainText) || MATCHES_UTI_TYPE(type, UTF16PlainText);
56 }
57
58 static BOOL isURLType(NSString *type)
59 {
60     return MATCHES_UTI_TYPE(type, URL);
61 }
62
63 static BOOL isColorType(NSString *type)
64 {
65     return MATCHES_UIKIT_TYPE(type, color);
66 }
67
68 static BOOL isImageType(NSString *type)
69 {
70     return MATCHES_UTI_TYPE(type, PNG) || MATCHES_UTI_TYPE(type, JPEG) || MATCHES_UTI_TYPE(type, GIF) || MATCHES_UIKIT_TYPE(type, image);
71 }
72
73 @interface WebItemProviderPasteboard ()
74
75 @property (nonatomic) NSInteger numberOfItems;
76 @property (nonatomic) NSInteger changeCount;
77 @property (nonatomic) NSInteger pendingOperationCount;
78
79 @end
80
81 @implementation WebItemProviderPasteboard {
82     RetainPtr<NSArray> _itemProviders;
83 }
84
85 + (instancetype)sharedInstance
86 {
87     static WebItemProviderPasteboard *sharedPasteboard = nil;
88     static dispatch_once_t onceToken;
89     dispatch_once(&onceToken, ^() {
90         sharedPasteboard = [[WebItemProviderPasteboard alloc] init];
91     });
92     return sharedPasteboard;
93 }
94
95 - (instancetype)init
96 {
97     if (self = [super init]) {
98         _itemProviders = adoptNS([[NSArray alloc] init]);
99         _changeCount = 0;
100         _pendingOperationCount = 0;
101     }
102     return self;
103 }
104
105 - (NSArray<NSString *> *)pasteboardTypes
106 {
107     NSMutableSet<NSString *> *allTypes = [NSMutableSet set];
108     for (UIItemProvider *provider in _itemProviders.get())
109         [allTypes addObjectsFromArray:provider.registeredTypeIdentifiers];
110     return allTypes.allObjects;
111 }
112
113 - (NSArray<UIItemProvider *> *)itemProviders
114 {
115     return _itemProviders.get();
116 }
117
118 - (void)setItemProviders:(NSArray<UIItemProvider *> *)itemProviders
119 {
120     itemProviders = itemProviders ?: [NSArray array];
121     if (_itemProviders == itemProviders || [_itemProviders isEqualToArray:itemProviders])
122         return;
123
124     _itemProviders = itemProviders;
125     _changeCount++;
126 }
127
128 - (NSInteger)numberOfItems
129 {
130     return [_itemProviders count];
131 }
132
133 - (void)setItems:(NSArray *)items
134 {
135     NSMutableArray *providers = [NSMutableArray array];
136     for (NSDictionary *item in items) {
137         if (!item.count)
138             continue;
139         RetainPtr<UIItemProvider> itemProvider = adoptNS([[getUIItemProviderClass() alloc] init]);
140         RetainPtr<NSMutableDictionary> itemRepresentationsCopy = adoptNS([item mutableCopy]);
141         // First, let the platform write all the default object types it can recognize, such as NSString and NSURL.
142         for (NSString *typeIdentifier in [itemRepresentationsCopy allKeys]) {
143             id representingObject = [itemRepresentationsCopy objectForKey:typeIdentifier];
144             if (![representingObject conformsToProtocol:@protocol(UIItemProviderWriting)])
145                 continue;
146
147             id <UIItemProviderWriting> objectToWrite = (id <UIItemProviderWriting>)representingObject;
148             if (![objectToWrite.writableTypeIdentifiersForItemProvider containsObject:typeIdentifier])
149                 continue;
150
151             [itemRepresentationsCopy removeObjectForKey:typeIdentifier];
152             [objectToWrite registerLoadHandlersToItemProvider:itemProvider.get()];
153         }
154
155         // Secondly, WebKit uses some custom type representations and/or type identifiers, so we need to write these as well.
156         for (NSString *typeIdentifier in itemRepresentationsCopy.get()) {
157             [itemProvider registerDataRepresentationForTypeIdentifier:typeIdentifier options:nil loadHandler:^NSProgress *(UIItemProviderDataLoadCompletionBlock completionBlock)
158             {
159                 completionBlock([itemRepresentationsCopy objectForKey:typeIdentifier], nil);
160                 return [NSProgress discreteProgressWithTotalUnitCount:100];
161             }];
162         }
163         [providers addObject:itemProvider.get()];
164     }
165     _changeCount++;
166     _itemProviders = providers;
167 }
168
169 - (NSArray *)dataForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet
170 {
171     __block NSMutableArray *values = [NSMutableArray array];
172     [itemSet enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *) {
173         UIItemProvider *provider = [self itemProviderAtIndex:index];
174         if (!provider)
175             return;
176
177         NSData *data = [provider copyDataRepresentationForTypeIdentifier:pasteboardType error:nil];
178         if (data)
179             [values addObject:data];
180     }];
181
182     return values;
183 }
184
185 - (NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet
186 {
187     __block NSMutableArray *values = [NSMutableArray array];
188     [itemSet enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *) {
189         UIItemProvider *provider = [self itemProviderAtIndex:index];
190         if (!provider)
191             return;
192
193         // FIXME: These should be refactored to use asynchronous calls.
194         if (isColorType(pasteboardType) && [self _tryToCreateAndAppendObjectOfClass:[getUIColorClass() class] toArray:values usingProvider:provider])
195             return;
196
197         if (isImageType(pasteboardType) && [self _tryToCreateAndAppendObjectOfClass:[getUIImageClass() class] toArray:values usingProvider:provider])
198             return;
199
200         if (isURLType(pasteboardType) && [self _tryToCreateAndAppendObjectOfClass:[NSURL class] toArray:values usingProvider:provider])
201             return;
202
203         if (isRichTextType(pasteboardType) && [self _tryToCreateAndAppendObjectOfClass:[NSAttributedString class] toArray:values usingProvider:provider])
204             return;
205
206         if (isStringType(pasteboardType) && [self _tryToCreateAndAppendObjectOfClass:[NSString class] toArray:values usingProvider:provider])
207             return;
208
209         WTFLogAlways("Failed to instantiate object for type: '%s' at index: %tu", pasteboardType.UTF8String, index);
210     }];
211     return values;
212 }
213
214 - (BOOL)_tryToCreateAndAppendObjectOfClass:(Class)objectClass toArray:(NSMutableArray *)array usingProvider:(UIItemProvider *)provider
215 {
216     if (![provider canCreateObjectOfClass:objectClass])
217         return NO;
218
219     id object = [provider createObjectOfClass:objectClass error:nil];
220     if (object)
221         [array addObject:object];
222
223     return !!object;
224 }
225
226 - (NSInteger)changeCount
227 {
228     return _changeCount;
229 }
230
231 - (UIItemProvider *)itemProviderAtIndex:(NSInteger)index
232 {
233     return 0 <= index && index < (NSInteger)[_itemProviders count] ? [_itemProviders objectAtIndex:index] : nil;
234 }
235
236 - (BOOL)hasPendingOperation
237 {
238     return _pendingOperationCount;
239 }
240
241 - (void)incrementPendingOperationCount
242 {
243     _pendingOperationCount++;
244 }
245
246 - (void)decrementPendingOperationCount
247 {
248     _pendingOperationCount--;
249 }
250
251 @end
252
253 #endif // ENABLE(DATA_INTERACTION)