[iOS] Remove all usages of UIItemProvider, UIItemProviderReading, and related classes
[WebKit-https.git] / Source / WebCore / platform / ios / WebItemProviderPasteboard.mm
1 /*
2  * Copyright (C) 2017-2018 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) || PLATFORM(IOSMAC)
30
31 #import <Foundation/NSItemProvider.h>
32 #import <Foundation/NSProgress.h>
33 #import <MobileCoreServices/MobileCoreServices.h>
34 #import <UIKit/NSItemProvider+UIKitAdditions.h>
35 #import <UIKit/UIColor.h>
36 #import <UIKit/UIImage.h>
37 #import <WebCore/FileSystem.h>
38 #import <WebCore/Pasteboard.h>
39 #import <pal/spi/ios/UIKitSPI.h>
40 #import <wtf/BlockPtr.h>
41 #import <wtf/OSObjectPtr.h>
42 #import <wtf/RetainPtr.h>
43 #import <wtf/SoftLinking.h>
44
45 SOFT_LINK_FRAMEWORK(UIKit)
46 SOFT_LINK_CLASS(UIKit, UIColor)
47 SOFT_LINK_CLASS(UIKit, UIImage)
48
49 typedef void(^ItemProviderDataLoadCompletionHandler)(NSData *, NSError *);
50 typedef void(^ItemProviderFileLoadCompletionHandler)(NSURL *, BOOL, NSError *);
51 typedef NSMutableDictionary<NSString *, NSURL *> TypeToFileURLMap;
52
53 using WebCore::Pasteboard;
54 using WebCore::PasteboardCustomData;
55
56 @interface WebItemProviderDataRegistrar : NSObject <WebItemProviderRegistrar>
57 - (instancetype)initWithData:(NSData *)data type:(NSString *)utiType;
58 @property (nonatomic, readonly) NSString *typeIdentifier;
59 @property (nonatomic, readonly) NSData *data;
60 @end
61
62 @implementation WebItemProviderDataRegistrar {
63     RetainPtr<NSString> _typeIdentifier;
64     RetainPtr<NSData> _data;
65 }
66
67 - (instancetype)initWithData:(NSData *)data type:(NSString *)utiType
68 {
69     if (!(self = [super init]))
70         return nil;
71
72     _data = data;
73     _typeIdentifier = utiType;
74     return self;
75 }
76
77 - (NSString *)typeIdentifier
78 {
79     return _typeIdentifier.get();
80 }
81
82 - (NSData *)data
83 {
84     return _data.get();
85 }
86
87 - (NSString *)typeIdentifierForClient
88 {
89     return self.typeIdentifier;
90 }
91
92 - (NSData *)dataForClient
93 {
94     return self.data;
95 }
96
97 - (void)registerItemProvider:(NSItemProvider *)itemProvider
98 {
99     [itemProvider registerDataRepresentationForTypeIdentifier:self.typeIdentifier visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[itemData = _data] (ItemProviderDataLoadCompletionHandler completionHandler) -> NSProgress * {
100         completionHandler(itemData.get(), nil);
101         return nil;
102     }];
103 }
104
105 - (NSString *)description
106 {
107     return [NSString stringWithFormat:@"(%@ => %tu bytes)", _typeIdentifier.get(), [_data length]];
108 }
109
110 @end
111
112 @interface WebItemProviderWritableObjectRegistrar : NSObject <WebItemProviderRegistrar>
113 - (instancetype)initWithObject:(id <NSItemProviderWriting>)representingObject;
114 @property (nonatomic, readonly) id <NSItemProviderWriting> representingObject;
115 @end
116
117 @implementation WebItemProviderWritableObjectRegistrar {
118     RetainPtr<id <NSItemProviderWriting>> _representingObject;
119 }
120
121 - (instancetype)initWithObject:(id <NSItemProviderWriting>)representingObject
122 {
123     if (!(self = [super init]))
124         return nil;
125
126     _representingObject = representingObject;
127     return self;
128 }
129
130 - (id <NSItemProviderWriting>)representingObject
131 {
132     return _representingObject.get();
133 }
134
135 - (id <NSItemProviderWriting>)representingObjectForClient
136 {
137     return self.representingObject;
138 }
139
140 - (void)registerItemProvider:(NSItemProvider *)itemProvider
141 {
142     [itemProvider registerObject:self.representingObject visibility:NSItemProviderRepresentationVisibilityAll];
143 }
144
145 - (NSString *)description
146 {
147     return [NSString stringWithFormat:@"(%@)", [_representingObject class]];
148 }
149
150 @end
151
152 @interface WebItemProviderPromisedFileRegistrar : NSObject <WebItemProviderRegistrar>
153 - (instancetype)initWithType:(NSString *)utiType callback:(void(^)(WebItemProviderFileCallback))callback;
154 @property (nonatomic, readonly) NSString *typeIdentifier;
155 @end
156
157 @implementation WebItemProviderPromisedFileRegistrar {
158     RetainPtr<NSString> _typeIdentifier;
159     BlockPtr<void(WebItemProviderFileCallback)> _callback;
160 }
161
162 - (instancetype)initWithType:(NSString *)utiType callback:(void(^)(WebItemProviderFileCallback))callback
163 {
164     if (!(self = [super init]))
165         return nil;
166
167     _typeIdentifier = utiType;
168     _callback = callback;
169     return self;
170 }
171
172 - (void)registerItemProvider:(NSItemProvider *)itemProvider
173 {
174     [itemProvider registerFileRepresentationForTypeIdentifier:_typeIdentifier.get() fileOptions:0 visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[callback = _callback] (ItemProviderFileLoadCompletionHandler completionHandler) -> NSProgress * {
175         callback([protectedCompletionHandler = makeBlockPtr(completionHandler)] (NSURL *fileURL, NSError *error) {
176             protectedCompletionHandler(fileURL, NO, error);
177         });
178         return nil;
179     }];
180 }
181
182 - (NSString *)typeIdentifier
183 {
184     return _typeIdentifier.get();
185 }
186
187 - (NSString *)description
188 {
189     return [NSString stringWithFormat:@"(file promise: %@)", _typeIdentifier.get()];
190 }
191
192 @end
193
194 @interface WebItemProviderRegistrationInfoList ()
195 {
196     RetainPtr<NSMutableArray> _representations;
197 }
198 @end
199
200 @implementation WebItemProviderRegistrationInfoList
201
202 - (instancetype)init
203 {
204     if (self = [super init]) {
205         _representations = adoptNS([[NSMutableArray alloc] init]);
206         _preferredPresentationSize = CGSizeZero;
207         _preferredPresentationStyle = WebPreferredPresentationStyleUnspecified;
208     }
209
210     return self;
211 }
212
213 - (void)dealloc
214 {
215     [_suggestedName release];
216     [_teamData release];
217     [super dealloc];
218 }
219
220 - (void)addData:(NSData *)data forType:(NSString *)typeIdentifier
221 {
222     auto representation = adoptNS([[WebItemProviderDataRegistrar alloc] initWithData:data type:typeIdentifier]);
223     [_representations addObject:representation.get()];
224 }
225
226 - (void)addRepresentingObject:(id <NSItemProviderWriting>)object
227 {
228     ASSERT([object conformsToProtocol:@protocol(NSItemProviderWriting)]);
229     auto representation = adoptNS([[WebItemProviderWritableObjectRegistrar alloc] initWithObject:object]);
230     [_representations addObject:representation.get()];
231 }
232
233 - (void)addPromisedType:(NSString *)typeIdentifier fileCallback:(void(^)(WebItemProviderFileCallback))callback
234 {
235     auto representation = adoptNS([[WebItemProviderPromisedFileRegistrar alloc] initWithType:typeIdentifier callback:callback]);
236     [_representations addObject:representation.get()];
237 }
238
239 - (NSUInteger)numberOfItems
240 {
241     return [_representations count];
242 }
243
244 - (id <WebItemProviderRegistrar>)itemAtIndex:(NSUInteger)index
245 {
246     if (index >= self.numberOfItems)
247         return nil;
248
249     return [_representations objectAtIndex:index];
250 }
251
252 - (void)enumerateItems:(void (^)(id <WebItemProviderRegistrar>, NSUInteger))block
253 {
254     for (NSUInteger index = 0; index < self.numberOfItems; ++index)
255         block([self itemAtIndex:index], index);
256 }
257
258 #if !PLATFORM(IOSMAC)
259 static UIPreferredPresentationStyle uiPreferredPresentationStyle(WebPreferredPresentationStyle style)
260 {
261     switch (style) {
262     case WebPreferredPresentationStyleUnspecified:
263         return UIPreferredPresentationStyleUnspecified;
264     case WebPreferredPresentationStyleInline:
265         return UIPreferredPresentationStyleInline;
266     case WebPreferredPresentationStyleAttachment:
267         return UIPreferredPresentationStyleAttachment;
268     default:
269         ASSERT_NOT_REACHED();
270         return UIPreferredPresentationStyleUnspecified;
271     }
272 }
273 #endif
274
275 - (NSItemProvider *)itemProvider
276 {
277     if (!self.numberOfItems)
278         return nil;
279
280     auto itemProvider = adoptNS([NSItemProvider new]);
281     for (id <WebItemProviderRegistrar> representation in _representations.get())
282         [representation registerItemProvider:itemProvider.get()];
283     [itemProvider setSuggestedName:self.suggestedName];
284 #if !PLATFORM(IOSMAC)
285     [itemProvider setPreferredPresentationSize:self.preferredPresentationSize];
286     [itemProvider setPreferredPresentationStyle:uiPreferredPresentationStyle(self.preferredPresentationStyle)];
287     [itemProvider setTeamData:self.teamData];
288 #endif
289     return itemProvider.autorelease();
290 }
291
292 - (NSString *)description
293 {
294     __block NSMutableString *description = [NSMutableString string];
295     [description appendFormat:@"<%@: %p", [self class], self];
296     [self enumerateItems:^(id <WebItemProviderRegistrar> item, NSUInteger index) {
297         if (index)
298             [description appendString:@", "];
299         [description appendString:[item description]];
300     }];
301     [description appendString:@">"];
302     return description;
303 }
304
305 @end
306
307 @interface WebItemProviderLoadResult : NSObject
308
309 - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad;
310 - (NSURL *)fileURLForType:(NSString *)type;
311 - (void)setFileURL:(NSURL *)url forType:(NSString *)type;
312 @property (nonatomic, readonly) NSArray<NSURL *> *loadedFileURLs;
313 @property (nonatomic, readonly) NSArray<NSString *> *loadedTypeIdentifiers;
314 @property (nonatomic, readonly) BOOL canBeRepresentedAsFileUpload;
315 @property (nonatomic, readonly) NSItemProvider *itemProvider;
316 @property (nonatomic, readonly) NSArray<NSString *> *typesToLoad;
317
318 @end
319
320 @implementation WebItemProviderLoadResult {
321     RetainPtr<TypeToFileURLMap> _fileURLs;
322     RetainPtr<NSItemProvider> _itemProvider;
323     RetainPtr<NSArray<NSString *>> _typesToLoad;
324 }
325
326 + (instancetype)loadResultWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad
327 {
328     return [[[self alloc] initWithItemProvider:itemProvider typesToLoad:typesToLoad] autorelease];
329 }
330
331 - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad
332 {
333     if (!(self = [super init]))
334         return nil;
335
336     _fileURLs = adoptNS([[NSMutableDictionary alloc] init]);
337     _itemProvider = itemProvider;
338     _typesToLoad = typesToLoad;
339
340     return self;
341 }
342
343 - (BOOL)canBeRepresentedAsFileUpload
344 {
345     return [_itemProvider preferredPresentationStyle] != UIPreferredPresentationStyleInline;
346 }
347
348 - (NSArray<NSString *> *)typesToLoad
349 {
350     return _typesToLoad.get();
351 }
352
353 - (NSURL *)fileURLForType:(NSString *)type
354 {
355     return [_fileURLs objectForKey:type];
356 }
357
358 - (void)setFileURL:(NSURL *)url forType:(NSString *)type
359 {
360     [_fileURLs setObject:url forKey:type];
361 }
362
363 - (NSArray<NSURL *> *)loadedFileURLs
364 {
365     return [_fileURLs allValues];
366 }
367
368 - (NSArray<NSString *> *)loadedTypeIdentifiers
369 {
370     return [_fileURLs allKeys];
371 }
372
373 - (NSItemProvider *)itemProvider
374 {
375     return _itemProvider.get();
376 }
377
378 - (NSString *)description
379 {
380     __block NSMutableString *description = [NSMutableString string];
381     [description appendFormat:@"<%@: %p typesToLoad: [ ", [self class], self];
382     [_typesToLoad enumerateObjectsUsingBlock:^(NSString *type, NSUInteger index, BOOL *) {
383         [description appendString:type];
384         if (index + 1 < [_typesToLoad count])
385             [description appendString:@", "];
386     }];
387     [description appendFormat:@" ] fileURLs: { "];
388     __block NSUInteger index = 0;
389     [_fileURLs enumerateKeysAndObjectsUsingBlock:^(NSString *type, NSURL *url, BOOL *) {
390         [description appendFormat:@"%@ => \"%@\"", type, url.path];
391         if (++index < [_fileURLs count])
392             [description appendString:@", "];
393     }];
394     [description appendFormat:@" }>"];
395     return description;
396 }
397
398 @end
399
400 @interface WebItemProviderPasteboard ()
401
402 @property (nonatomic) NSInteger numberOfItems;
403 @property (nonatomic) NSInteger changeCount;
404 @property (nonatomic) NSInteger pendingOperationCount;
405
406 @end
407
408 @implementation WebItemProviderPasteboard {
409     // FIXME: These ivars should be refactored to be Vector<RetainPtr<Type>> instead of generic NSArrays.
410     RetainPtr<NSArray> _itemProviders;
411     RetainPtr<NSArray> _supportedTypeIdentifiers;
412     RetainPtr<WebItemProviderRegistrationInfoList> _stagedRegistrationInfoList;
413
414     Vector<RetainPtr<WebItemProviderLoadResult>> _loadResults;
415 }
416
417 + (instancetype)sharedInstance
418 {
419     static WebItemProviderPasteboard *sharedPasteboard = nil;
420     static dispatch_once_t onceToken;
421     dispatch_once(&onceToken, ^() {
422         sharedPasteboard = [[WebItemProviderPasteboard alloc] init];
423     });
424     return sharedPasteboard;
425 }
426
427 - (instancetype)init
428 {
429     if (self = [super init]) {
430         _itemProviders = adoptNS([[NSArray alloc] init]);
431         _changeCount = 0;
432         _pendingOperationCount = 0;
433         _supportedTypeIdentifiers = nil;
434         _stagedRegistrationInfoList = nil;
435         _loadResults = { };
436     }
437     return self;
438 }
439
440 - (void)updateSupportedTypeIdentifiers:(NSArray<NSString *> *)types
441 {
442     _supportedTypeIdentifiers = types;
443 }
444
445 - (NSArray<NSString *> *)pasteboardTypesByFidelityForItemAtIndex:(NSUInteger)index
446 {
447     return [self itemProviderAtIndex:index].registeredTypeIdentifiers ?: @[ ];
448 }
449
450 - (NSArray<NSString *> *)pasteboardTypes
451 {
452     NSMutableSet<NSString *> *allTypes = [NSMutableSet set];
453     NSMutableArray<NSString *> *allTypesInOrder = [NSMutableArray array];
454     for (NSItemProvider *provider in _itemProviders.get()) {
455         for (NSString *typeIdentifier in provider.registeredTypeIdentifiers) {
456             if ([allTypes containsObject:typeIdentifier])
457                 continue;
458
459             [allTypes addObject:typeIdentifier];
460             [allTypesInOrder addObject:typeIdentifier];
461         }
462     }
463     return allTypesInOrder;
464 }
465
466 - (NSArray<__kindof NSItemProvider *> *)itemProviders
467 {
468     return _itemProviders.get();
469 }
470
471 - (void)setItemProviders:(NSArray<__kindof NSItemProvider *> *)itemProviders
472 {
473     itemProviders = itemProviders ?: [NSArray array];
474     if (_itemProviders == itemProviders || [_itemProviders isEqualToArray:itemProviders])
475         return;
476
477     _itemProviders = itemProviders;
478     _changeCount++;
479
480     if (!itemProviders.count)
481         _loadResults = { };
482 }
483
484 - (NSInteger)numberOfItems
485 {
486     return [_itemProviders count];
487 }
488
489 - (NSData *)_preLoadedDataConformingToType:(NSString *)typeIdentifier forItemProviderAtIndex:(NSUInteger)index
490 {
491     if (_loadResults.size() != [_itemProviders count]) {
492         ASSERT_NOT_REACHED();
493         return nil;
494     }
495
496     WebItemProviderLoadResult *loadResult = _loadResults[index].get();
497     for (NSString *loadedType in loadResult.loadedTypeIdentifiers) {
498         if (!UTTypeConformsTo((CFStringRef)loadedType, (CFStringRef)typeIdentifier))
499             continue;
500
501         // We've already loaded data relevant for this UTI type onto disk, so there's no need to ask the NSItemProvider for the same data again.
502         if (NSData *result = [NSData dataWithContentsOfURL:[loadResult fileURLForType:loadedType] options:NSDataReadingMappedIfSafe error:nil])
503             return result;
504     }
505     return nil;
506 }
507
508 - (NSData *)dataForPasteboardType:(NSString *)pasteboardType
509 {
510     return [self dataForPasteboardType:pasteboardType inItemSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.numberOfItems)]].firstObject;
511 }
512
513 - (NSArray *)dataForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet
514 {
515     if (_loadResults.isEmpty())
516         return @[ ];
517
518     auto values = adoptNS([[NSMutableArray alloc] init]);
519     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
520     [itemSet enumerateIndexesUsingBlock:[retainedSelf, pasteboardType, values] (NSUInteger index, BOOL *) {
521         NSItemProvider *provider = [retainedSelf itemProviderAtIndex:index];
522         if (!provider)
523             return;
524
525         if (NSData *loadedData = [retainedSelf _preLoadedDataConformingToType:pasteboardType forItemProviderAtIndex:index])
526             [values addObject:loadedData];
527     }];
528     return values.autorelease();
529 }
530
531 static NSArray<Class<NSItemProviderReading>> *allLoadableClasses()
532 {
533     return @[ [getUIColorClass() class], [getUIImageClass() class], [NSURL class], [NSString class], [NSAttributedString class] ];
534 }
535
536 static Class classForTypeIdentifier(NSString *typeIdentifier, NSString *&outTypeIdentifierToLoad)
537 {
538     outTypeIdentifierToLoad = typeIdentifier;
539
540     // First, try to load a platform NSItemProviderReading-conformant object as-is.
541     for (Class<NSItemProviderReading> loadableClass in allLoadableClasses()) {
542         if ([[loadableClass readableTypeIdentifiersForItemProvider] containsObject:(NSString *)typeIdentifier])
543             return loadableClass;
544     }
545
546     // If we were unable to load any object, check if the given type identifier is still something
547     // WebKit knows how to handle.
548     if ([typeIdentifier isEqualToString:(NSString *)kUTTypeHTML]) {
549         // Load kUTTypeHTML as a plain text HTML string.
550         outTypeIdentifierToLoad = (NSString *)kUTTypePlainText;
551         return [NSString class];
552     }
553
554     return nil;
555 }
556
557 - (NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet
558 {
559     if (_loadResults.isEmpty())
560         return @[ ];
561
562     auto values = adoptNS([[NSMutableArray alloc] init]);
563     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
564     [itemSet enumerateIndexesUsingBlock:[retainedSelf, pasteboardType, values] (NSUInteger index, BOOL *) {
565         NSItemProvider *provider = [retainedSelf itemProviderAtIndex:index];
566         if (!provider)
567             return;
568
569         NSString *typeIdentifierToLoad;
570         Class readableClass = classForTypeIdentifier(pasteboardType, typeIdentifierToLoad);
571         if (!readableClass)
572             return;
573
574         NSData *preloadedData = [retainedSelf _preLoadedDataConformingToType:pasteboardType forItemProviderAtIndex:index];
575         if (!preloadedData)
576             return;
577
578         if (id <NSItemProviderReading> readObject = [readableClass objectWithItemProviderData:preloadedData typeIdentifier:(NSString *)typeIdentifierToLoad error:nil])
579             [values addObject:readObject];
580     }];
581
582     return values.autorelease();
583 }
584
585 - (NSInteger)changeCount
586 {
587     return _changeCount;
588 }
589
590 - (NSURL *)preferredFileUploadURLAtIndex:(NSUInteger)index fileType:(NSString **)outFileType
591 {
592     if (outFileType)
593         *outFileType = nil;
594
595     if (index >= _loadResults.size())
596         return nil;
597
598     auto result = _loadResults[index];
599     if (![result canBeRepresentedAsFileUpload])
600         return nil;
601
602     NSItemProvider *itemProvider = [result itemProvider];
603     for (NSString *registeredTypeIdentifier in itemProvider.registeredTypeIdentifiers) {
604         // Search for the highest fidelity non-private type identifier we loaded from the item provider.
605         if (!UTTypeIsDeclared((CFStringRef)registeredTypeIdentifier) && !UTTypeIsDynamic((CFStringRef)registeredTypeIdentifier))
606             continue;
607
608         for (NSString *loadedTypeIdentifier in [result loadedTypeIdentifiers]) {
609             if (!UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, (CFStringRef)loadedTypeIdentifier))
610                 continue;
611
612             if (outFileType)
613                 *outFileType = loadedTypeIdentifier;
614             return [result fileURLForType:loadedTypeIdentifier];
615         }
616     }
617
618     return nil;
619 }
620
621 - (NSArray<NSURL *> *)allDroppedFileURLs
622 {
623     NSMutableArray<NSURL *> *fileURLs = [NSMutableArray array];
624     for (auto loadResult : _loadResults) {
625         if ([loadResult canBeRepresentedAsFileUpload])
626             [fileURLs addObjectsFromArray:[loadResult loadedFileURLs]];
627     }
628     return fileURLs;
629 }
630
631 static BOOL typeConformsToTypes(NSString *type, NSArray *conformsToTypes)
632 {
633     // A type is considered appropriate to load if it conforms to one or more supported types.
634     for (NSString *conformsToType in conformsToTypes) {
635         if (UTTypeConformsTo((CFStringRef)type, (CFStringRef)conformsToType))
636             return YES;
637     }
638     return NO;
639 }
640
641 - (NSInteger)numberOfFiles
642 {
643     NSArray *supportedFileTypes = Pasteboard::supportedFileUploadPasteboardTypes();
644     NSInteger numberOfFiles = 0;
645     for (NSItemProvider *itemProvider in _itemProviders.get()) {
646         if (itemProvider.preferredPresentationStyle == UIPreferredPresentationStyleInline)
647             continue;
648
649         for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
650             if (!typeConformsToTypes(identifier, supportedFileTypes))
651                 continue;
652             ++numberOfFiles;
653             break;
654         }
655     }
656     return numberOfFiles;
657 }
658
659 static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, NSString *suggestedName, NSString *typeIdentifier)
660 {
661     static NSString *defaultDropFolderName = @"folder";
662     static NSString *defaultDropFileName = @"file";
663     static NSString *dataInteractionDirectoryPrefix = @"data-interaction";
664     if (!url)
665         return nil;
666
667     NSString *temporaryDataInteractionDirectory = WebCore::FileSystem::createTemporaryDirectory(dataInteractionDirectoryPrefix);
668     if (!temporaryDataInteractionDirectory)
669         return nil;
670
671     NSURL *destination = nil;
672     BOOL isFolder = UTTypeConformsTo((CFStringRef)typeIdentifier, kUTTypeFolder);
673     NSFileManager *fileManager = [NSFileManager defaultManager];
674
675     if (!suggestedName)
676         suggestedName = url.lastPathComponent ?: (isFolder ? defaultDropFolderName : defaultDropFileName);
677
678     if (![suggestedName containsString:@"."] && !isFolder)
679         suggestedName = [suggestedName stringByAppendingPathExtension:url.pathExtension];
680
681     destination = [NSURL fileURLWithPath:[temporaryDataInteractionDirectory stringByAppendingPathComponent:suggestedName]];
682     return [fileManager linkItemAtURL:url toURL:destination error:nil] ? destination : nil;
683 }
684
685 - (NSArray<NSString *> *)typeIdentifiersToLoadForRegisteredTypeIdentifiers:(NSArray<NSString *> *)registeredTypeIdentifiers
686 {
687     auto typesToLoad = adoptNS([[NSMutableOrderedSet alloc] init]);
688     NSString *highestFidelitySupportedType = nil;
689     NSString *highestFidelityContentType = nil;
690
691     BOOL containsFlatRTFD = [registeredTypeIdentifiers containsObject:(NSString *)kUTTypeFlatRTFD];
692     // First, search for the highest fidelity supported type or the highest fidelity generic content type.
693     for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) {
694         if (containsFlatRTFD && [registeredTypeIdentifier isEqualToString:(NSString *)kUTTypeRTFD]) {
695             // In the case where attachments are enabled and we're accepting all types of content using attachment
696             // elements as a fallback representation, if the source writes attributed strings to the pasteboard with
697             // com.apple.rtfd at a higher fidelity than com.apple.flat-rtfd, we'll end up loading only com.apple.rtfd
698             // and dropping the text as an attachment element because we cannot convert the dropped content to markup.
699             // Instead, if flat RTFD is present in the item provider, always prefer that over RTFD so that dropping as
700             // regular web content isn't overridden by enabling attachment elements.
701             continue;
702         }
703
704         if (!highestFidelitySupportedType && typeConformsToTypes(registeredTypeIdentifier, _supportedTypeIdentifiers.get()))
705             highestFidelitySupportedType = registeredTypeIdentifier;
706
707         if (!highestFidelityContentType && UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeContent))
708             highestFidelityContentType = registeredTypeIdentifier;
709     }
710
711     // For compatibility with DataTransfer APIs, additionally load web-exposed types. Since this is the only chance to
712     // fault in any data at all, we need to load up front any information that the page may ask for later down the line.
713     NSString *customPasteboardDataUTI = @(PasteboardCustomData::cocoaType());
714     for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) {
715         if ([registeredTypeIdentifier isEqualToString:highestFidelityContentType]
716             || [registeredTypeIdentifier isEqualToString:highestFidelitySupportedType]
717             || [registeredTypeIdentifier isEqualToString:customPasteboardDataUTI]
718             || UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeURL)
719             || UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeHTML)
720             || UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypePlainText))
721             [typesToLoad addObject:registeredTypeIdentifier];
722     }
723
724     return [typesToLoad array];
725 }
726
727 - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action
728 {
729     [self doAfterLoadingProvidedContentIntoFileURLs:action synchronousTimeout:0];
730 }
731
732 - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action synchronousTimeout:(NSTimeInterval)synchronousTimeout
733 {
734     _loadResults.clear();
735
736     auto changeCountBeforeLoading = _changeCount;
737     auto loadResults = adoptNS([[NSMutableArray alloc] initWithCapacity:[_itemProviders count]]);
738
739     // First, figure out which item providers we want to try and load files from.
740     BOOL foundAnyDataToLoad = NO;
741     RetainPtr<WebItemProviderPasteboard> protectedSelf = self;
742     for (NSItemProvider *itemProvider in _itemProviders.get()) {
743         NSArray<NSString *> *typeIdentifiersToLoad = [protectedSelf typeIdentifiersToLoadForRegisteredTypeIdentifiers:itemProvider.registeredTypeIdentifiers];
744         foundAnyDataToLoad |= typeIdentifiersToLoad.count;
745         [loadResults addObject:[WebItemProviderLoadResult loadResultWithItemProvider:itemProvider typesToLoad:typeIdentifiersToLoad]];
746     }
747
748     if (!foundAnyDataToLoad) {
749         action(@[ ]);
750         return;
751     }
752
753     auto setFileURLsLock = adoptNS([[NSLock alloc] init]);
754     auto synchronousFileLoadingGroup = adoptOSObject(dispatch_group_create());
755     auto fileLoadingGroup = adoptOSObject(dispatch_group_create());
756     for (WebItemProviderLoadResult *loadResult in loadResults.get()) {
757         for (NSString *typeToLoad in loadResult.typesToLoad) {
758             dispatch_group_enter(fileLoadingGroup.get());
759             dispatch_group_enter(synchronousFileLoadingGroup.get());
760             [loadResult.itemProvider loadFileRepresentationForTypeIdentifier:typeToLoad completionHandler:[protectedLoadResult = retainPtr(loadResult), synchronousFileLoadingGroup, setFileURLsLock, protectedTypeToLoad = retainPtr(typeToLoad), fileLoadingGroup] (NSURL *url, NSError *) {
761                 // After executing this completion block, UIKit removes the file at the given URL. However, we need this data to persist longer for the web content process.
762                 // To address this, we hard link the given URL to a new temporary file in the temporary directory. This follows the same flow as regular file upload, in
763                 // WKFileUploadPanel.mm. The temporary files are cleaned up by the system at a later time.
764                 if (NSURL *destination = linkTemporaryItemProviderFilesToDropStagingDirectory(url, [protectedLoadResult itemProvider].suggestedName, protectedTypeToLoad.get())) {
765                     [setFileURLsLock lock];
766                     [protectedLoadResult setFileURL:destination forType:protectedTypeToLoad.get()];
767                     [setFileURLsLock unlock];
768                 }
769                 dispatch_group_leave(fileLoadingGroup.get());
770                 dispatch_group_leave(synchronousFileLoadingGroup.get());
771             }];
772         }
773     }
774
775     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
776     auto itemLoadCompletion = [retainedSelf, synchronousFileLoadingGroup, fileLoadingGroup, loadResults, completionBlock = makeBlockPtr(action), changeCountBeforeLoading] {
777         if (changeCountBeforeLoading == retainedSelf->_changeCount) {
778             for (WebItemProviderLoadResult *loadResult in loadResults.get())
779                 retainedSelf->_loadResults.append(loadResult);
780         }
781
782         completionBlock([retainedSelf allDroppedFileURLs]);
783     };
784
785     if (synchronousTimeout > 0 && !dispatch_group_wait(synchronousFileLoadingGroup.get(), dispatch_time(DISPATCH_TIME_NOW, synchronousTimeout * NSEC_PER_SEC))) {
786         itemLoadCompletion();
787         return;
788     }
789
790     dispatch_group_notify(fileLoadingGroup.get(), dispatch_get_main_queue(), itemLoadCompletion);
791 }
792
793 - (NSItemProvider *)itemProviderAtIndex:(NSUInteger)index
794 {
795     return index < [_itemProviders count] ? [_itemProviders objectAtIndex:index] : nil;
796 }
797
798 - (BOOL)hasPendingOperation
799 {
800     return _pendingOperationCount;
801 }
802
803 - (void)incrementPendingOperationCount
804 {
805     _pendingOperationCount++;
806 }
807
808 - (void)decrementPendingOperationCount
809 {
810     _pendingOperationCount--;
811 }
812
813 - (void)enumerateItemProvidersWithBlock:(void (^)(__kindof NSItemProvider *itemProvider, NSUInteger index, BOOL *stop))block
814 {
815     [_itemProviders enumerateObjectsUsingBlock:block];
816 }
817
818 - (void)stageRegistrationList:(nullable WebItemProviderRegistrationInfoList *)info
819 {
820     _stagedRegistrationInfoList = info.numberOfItems ? info : nil;
821 }
822
823 - (WebItemProviderRegistrationInfoList *)takeRegistrationList
824 {
825     auto stagedRegistrationInfoList = _stagedRegistrationInfoList;
826     _stagedRegistrationInfoList = nil;
827     return stagedRegistrationInfoList.autorelease();
828 }
829
830 @end
831
832 #endif // ENABLE(DATA_INTERACTION) || PLATFORM(IOSMAC)