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