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