[Attachment Support] Create attachment elements when dropping files on iOS
[WebKit-https.git] / Source / WebCore / platform / ios / WebItemProviderPasteboard.mm
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #import "WebItemProviderPasteboard.h"
28
29 #if ENABLE(DATA_INTERACTION)
30
31 #import <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 NSMutableDictionary<NSString *, NSURL *> TypeToFileURLMap;
52
53 using WebCore::Pasteboard;
54 using WebCore::PasteboardCustomData;
55 @interface WebItemProviderRegistrationInfo ()
56 {
57     RetainPtr<id <UIItemProviderWriting>> _representingObject;
58     RetainPtr<NSString> _typeIdentifier;
59     RetainPtr<NSData> _data;
60 }
61 @end
62
63 @implementation WebItemProviderRegistrationInfo
64
65 - (instancetype)initWithRepresentingObject:(id <UIItemProviderWriting>)representingObject typeIdentifier:(NSString *)typeIdentifier data:(NSData *)data
66 {
67     if (representingObject)
68         ASSERT(!typeIdentifier && !data);
69     else
70         ASSERT(typeIdentifier && data);
71
72     if (self = [super init]) {
73         _representingObject = representingObject;
74         _typeIdentifier = typeIdentifier;
75         _data = data;
76     }
77     return self;
78 }
79
80 - (id <UIItemProviderWriting>)representingObject
81 {
82     return _representingObject.get();
83 }
84
85 - (NSString *)typeIdentifier
86 {
87     return _typeIdentifier.get();
88 }
89
90 - (NSData *)data
91 {
92     return _data.get();
93 }
94
95 @end
96
97 @interface WebItemProviderRegistrationInfoList ()
98 {
99     RetainPtr<NSMutableArray> _representations;
100 }
101 @end
102
103 @implementation WebItemProviderRegistrationInfoList
104
105 - (instancetype)init
106 {
107     if (self = [super init]) {
108         _representations = adoptNS([[NSMutableArray alloc] init]);
109         _preferredPresentationSize = CGSizeZero;
110         _preferredPresentationStyle = WebPreferredPresentationStyleUnspecified;
111     }
112
113     return self;
114 }
115
116 - (void)dealloc
117 {
118     [_suggestedName release];
119     [super dealloc];
120 }
121
122 - (void)addData:(NSData *)data forType:(NSString *)typeIdentifier
123 {
124     [_representations addObject:[[[WebItemProviderRegistrationInfo alloc] initWithRepresentingObject:nil typeIdentifier:typeIdentifier data:data] autorelease]];
125 }
126
127 - (void)addRepresentingObject:(id <UIItemProviderWriting>)object
128 {
129     ASSERT([object conformsToProtocol:@protocol(UIItemProviderWriting)]);
130     [_representations addObject:[[[WebItemProviderRegistrationInfo alloc] initWithRepresentingObject:object typeIdentifier:nil data:nil] autorelease]];
131 }
132
133 - (NSUInteger)numberOfItems
134 {
135     return [_representations count];
136 }
137
138 - (WebItemProviderRegistrationInfo *)itemAtIndex:(NSUInteger)index
139 {
140     if (index >= self.numberOfItems)
141         return nil;
142
143     return [_representations objectAtIndex:index];
144 }
145
146 - (void)enumerateItems:(void (^)(WebItemProviderRegistrationInfo *, NSUInteger))block
147 {
148     for (NSUInteger index = 0; index < self.numberOfItems; ++index)
149         block([self itemAtIndex:index], index);
150 }
151
152 static UIPreferredPresentationStyle uiPreferredPresentationStyle(WebPreferredPresentationStyle style)
153 {
154     switch (style) {
155     case WebPreferredPresentationStyleUnspecified:
156         return UIPreferredPresentationStyleUnspecified;
157     case WebPreferredPresentationStyleInline:
158         return UIPreferredPresentationStyleInline;
159     case WebPreferredPresentationStyleAttachment:
160         return UIPreferredPresentationStyleAttachment;
161     default:
162         ASSERT_NOT_REACHED();
163         return UIPreferredPresentationStyleUnspecified;
164     }
165 }
166
167 - (UIItemProvider *)itemProvider
168 {
169     if (!self.numberOfItems)
170         return nil;
171
172     auto itemProvider = adoptNS([allocUIItemProviderInstance() init]);
173     for (WebItemProviderRegistrationInfo *representation in _representations.get()) {
174         if (representation.representingObject) {
175             [itemProvider registerObject:representation.representingObject visibility:UIItemProviderRepresentationOptionsVisibilityAll];
176             continue;
177         }
178
179         if (!representation.typeIdentifier.length || !representation.data.length)
180             continue;
181
182         RetainPtr<NSData> itemData = representation.data;
183         [itemProvider registerDataRepresentationForTypeIdentifier:representation.typeIdentifier visibility:UIItemProviderRepresentationOptionsVisibilityAll loadHandler:[itemData] (ItemProviderDataLoadCompletionHandler completionHandler) -> NSProgress * {
184             completionHandler(itemData.get(), nil);
185             return nil;
186         }];
187     }
188     [itemProvider setPreferredPresentationSize:self.preferredPresentationSize];
189     [itemProvider setSuggestedName:self.suggestedName];
190     [itemProvider setPreferredPresentationStyle:uiPreferredPresentationStyle(self.preferredPresentationStyle)];
191     [itemProvider setTeamData:self.teamData];
192     return itemProvider.autorelease();
193 }
194
195 - (NSString *)description
196 {
197     __block NSMutableString *description = [NSMutableString string];
198     [description appendFormat:@"<%@: %p", [self class], self];
199     [self enumerateItems:^(WebItemProviderRegistrationInfo *item, NSUInteger index) {
200         if (index)
201             [description appendString:@","];
202
203         if (item.representingObject)
204             [description appendFormat:@" (%@: %p)", [item.representingObject class], item.representingObject];
205         else
206             [description appendFormat:@" ('%@' => %tu bytes)", item.typeIdentifier, item.data.length];
207     }];
208     [description appendString:@">"];
209     return description;
210 }
211
212 @end
213
214 @interface WebItemProviderLoadResult : NSObject
215
216 - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad;
217 - (NSURL *)fileURLForType:(NSString *)type;
218 - (void)setFileURL:(NSURL *)url forType:(NSString *)type;
219 @property (nonatomic, readonly) NSArray<NSURL *> *loadedFileURLs;
220 @property (nonatomic, readonly) NSArray<NSString *> *loadedTypeIdentifiers;
221 @property (nonatomic, readonly) BOOL canBeRepresentedAsFileUpload;
222 @property (nonatomic, readonly) NSItemProvider *itemProvider;
223 @property (nonatomic, readonly) NSArray<NSString *> *typesToLoad;
224
225 @end
226
227 @implementation WebItemProviderLoadResult {
228     RetainPtr<TypeToFileURLMap> _fileURLs;
229     RetainPtr<NSItemProvider> _itemProvider;
230     RetainPtr<NSArray<NSString *>> _typesToLoad;
231 }
232
233 + (instancetype)loadResultWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad
234 {
235     return [[[self alloc] initWithItemProvider:itemProvider typesToLoad:typesToLoad] autorelease];
236 }
237
238 - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad
239 {
240     if (!(self = [super init]))
241         return nil;
242
243     _fileURLs = adoptNS([[NSMutableDictionary alloc] init]);
244     _itemProvider = itemProvider;
245     _typesToLoad = typesToLoad;
246
247     return self;
248 }
249
250 - (BOOL)canBeRepresentedAsFileUpload
251 {
252     return [_itemProvider preferredPresentationStyle] != UIPreferredPresentationStyleInline;
253 }
254
255 - (NSArray<NSString *> *)typesToLoad
256 {
257     return _typesToLoad.get();
258 }
259
260 - (NSURL *)fileURLForType:(NSString *)type
261 {
262     return [_fileURLs objectForKey:type];
263 }
264
265 - (void)setFileURL:(NSURL *)url forType:(NSString *)type
266 {
267     [_fileURLs setObject:url forKey:type];
268 }
269
270 - (NSArray<NSURL *> *)loadedFileURLs
271 {
272     return [_fileURLs allValues];
273 }
274
275 - (NSArray<NSString *> *)loadedTypeIdentifiers
276 {
277     return [_fileURLs allKeys];
278 }
279
280 - (NSItemProvider *)itemProvider
281 {
282     return _itemProvider.get();
283 }
284
285 - (NSString *)description
286 {
287     __block NSMutableString *description = [NSMutableString string];
288     [description appendFormat:@"<%@: %p typesToLoad: [ ", [self class], self];
289     [_typesToLoad enumerateObjectsUsingBlock:^(NSString *type, NSUInteger index, BOOL *) {
290         [description appendString:type];
291         if (index + 1 < [_typesToLoad count])
292             [description appendString:@", "];
293     }];
294     [description appendFormat:@" ] fileURLs: { "];
295     __block NSUInteger index = 0;
296     [_fileURLs enumerateKeysAndObjectsUsingBlock:^(NSString *type, NSURL *url, BOOL *) {
297         [description appendFormat:@"%@ => \"%@\"", type, url.path];
298         if (++index < [_fileURLs count])
299             [description appendString:@", "];
300     }];
301     [description appendFormat:@" }>"];
302     return description;
303 }
304
305 @end
306
307 @interface WebItemProviderPasteboard ()
308
309 @property (nonatomic) NSInteger numberOfItems;
310 @property (nonatomic) NSInteger changeCount;
311 @property (nonatomic) NSInteger pendingOperationCount;
312
313 @end
314
315 @implementation WebItemProviderPasteboard {
316     // FIXME: These ivars should be refactored to be Vector<RetainPtr<Type>> instead of generic NSArrays.
317     RetainPtr<NSArray> _itemProviders;
318     RetainPtr<NSArray> _supportedTypeIdentifiers;
319     RetainPtr<WebItemProviderRegistrationInfoList> _stagedRegistrationInfoList;
320
321     Vector<RetainPtr<WebItemProviderLoadResult>> _loadResults;
322 }
323
324 + (instancetype)sharedInstance
325 {
326     static WebItemProviderPasteboard *sharedPasteboard = nil;
327     static dispatch_once_t onceToken;
328     dispatch_once(&onceToken, ^() {
329         sharedPasteboard = [[WebItemProviderPasteboard alloc] init];
330     });
331     return sharedPasteboard;
332 }
333
334 - (instancetype)init
335 {
336     if (self = [super init]) {
337         _itemProviders = adoptNS([[NSArray alloc] init]);
338         _changeCount = 0;
339         _pendingOperationCount = 0;
340         _supportedTypeIdentifiers = nil;
341         _stagedRegistrationInfoList = nil;
342         _loadResults = { };
343     }
344     return self;
345 }
346
347 - (void)updateSupportedTypeIdentifiers:(NSArray<NSString *> *)types
348 {
349     _supportedTypeIdentifiers = types;
350 }
351
352 - (NSArray<NSString *> *)pasteboardTypesByFidelityForItemAtIndex:(NSUInteger)index
353 {
354     return [self itemProviderAtIndex:index].registeredTypeIdentifiers ?: @[ ];
355 }
356
357 - (NSArray<NSString *> *)pasteboardTypes
358 {
359     NSMutableSet<NSString *> *allTypes = [NSMutableSet set];
360     NSMutableArray<NSString *> *allTypesInOrder = [NSMutableArray array];
361     for (UIItemProvider *provider in _itemProviders.get()) {
362         for (NSString *typeIdentifier in provider.registeredTypeIdentifiers) {
363             if ([allTypes containsObject:typeIdentifier])
364                 continue;
365
366             [allTypes addObject:typeIdentifier];
367             [allTypesInOrder addObject:typeIdentifier];
368         }
369     }
370     return allTypesInOrder;
371 }
372
373 - (NSArray<__kindof NSItemProvider *> *)itemProviders
374 {
375     return _itemProviders.get();
376 }
377
378 - (void)setItemProviders:(NSArray<__kindof NSItemProvider *> *)itemProviders
379 {
380     itemProviders = itemProviders ?: [NSArray array];
381     if (_itemProviders == itemProviders || [_itemProviders isEqualToArray:itemProviders])
382         return;
383
384     _itemProviders = itemProviders;
385     _changeCount++;
386
387     if (!itemProviders.count)
388         _loadResults = { };
389 }
390
391 - (NSInteger)numberOfItems
392 {
393     return [_itemProviders count];
394 }
395
396 - (NSData *)_preLoadedDataConformingToType:(NSString *)typeIdentifier forItemProviderAtIndex:(NSUInteger)index
397 {
398     if (_loadResults.size() != [_itemProviders count]) {
399         ASSERT_NOT_REACHED();
400         return nil;
401     }
402
403     WebItemProviderLoadResult *loadResult = _loadResults[index].get();
404     for (NSString *loadedType in loadResult.loadedTypeIdentifiers) {
405         if (!UTTypeConformsTo((CFStringRef)loadedType, (CFStringRef)typeIdentifier))
406             continue;
407
408         // 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.
409         if (NSData *result = [NSData dataWithContentsOfURL:[loadResult fileURLForType:loadedType] options:NSDataReadingMappedIfSafe error:nil])
410             return result;
411     }
412     return nil;
413 }
414
415 - (NSData *)dataForPasteboardType:(NSString *)pasteboardType
416 {
417     return [self dataForPasteboardType:pasteboardType inItemSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.numberOfItems)]].firstObject;
418 }
419
420 - (NSArray *)dataForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet
421 {
422     if (_loadResults.isEmpty())
423         return @[ ];
424
425     auto values = adoptNS([[NSMutableArray alloc] init]);
426     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
427     [itemSet enumerateIndexesUsingBlock:[retainedSelf, pasteboardType, values] (NSUInteger index, BOOL *) {
428         UIItemProvider *provider = [retainedSelf itemProviderAtIndex:index];
429         if (!provider)
430             return;
431
432         if (NSData *loadedData = [retainedSelf _preLoadedDataConformingToType:pasteboardType forItemProviderAtIndex:index])
433             [values addObject:loadedData];
434     }];
435     return values.autorelease();
436 }
437
438 static NSArray<Class<UIItemProviderReading>> *allLoadableClasses()
439 {
440     return @[ [getUIColorClass() class], [getUIImageClass() class], [NSURL class], [NSString class], [NSAttributedString class] ];
441 }
442
443 static Class classForTypeIdentifier(NSString *typeIdentifier, NSString *&outTypeIdentifierToLoad)
444 {
445     outTypeIdentifierToLoad = typeIdentifier;
446
447     // First, try to load a platform UIItemProviderReading-conformant object as-is.
448     for (Class<UIItemProviderReading> loadableClass in allLoadableClasses()) {
449         if ([[loadableClass readableTypeIdentifiersForItemProvider] containsObject:(NSString *)typeIdentifier])
450             return loadableClass;
451     }
452
453     // If we were unable to load any object, check if the given type identifier is still something
454     // WebKit knows how to handle.
455     if ([typeIdentifier isEqualToString:(NSString *)kUTTypeHTML]) {
456         // Load kUTTypeHTML as a plain text HTML string.
457         outTypeIdentifierToLoad = (NSString *)kUTTypePlainText;
458         return [NSString class];
459     }
460
461     return nil;
462 }
463
464 - (NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet
465 {
466     if (_loadResults.isEmpty())
467         return @[ ];
468
469     auto values = adoptNS([[NSMutableArray alloc] init]);
470     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
471     [itemSet enumerateIndexesUsingBlock:[retainedSelf, pasteboardType, values] (NSUInteger index, BOOL *) {
472         UIItemProvider *provider = [retainedSelf itemProviderAtIndex:index];
473         if (!provider)
474             return;
475
476         NSString *typeIdentifierToLoad;
477         Class readableClass = classForTypeIdentifier(pasteboardType, typeIdentifierToLoad);
478         if (!readableClass)
479             return;
480
481         NSData *preloadedData = [retainedSelf _preLoadedDataConformingToType:pasteboardType forItemProviderAtIndex:index];
482         if (!preloadedData)
483             return;
484
485         if (id <NSItemProviderReading> readObject = [readableClass objectWithItemProviderData:preloadedData typeIdentifier:(NSString *)typeIdentifierToLoad error:nil])
486             [values addObject:readObject];
487     }];
488
489     return values.autorelease();
490 }
491
492 - (NSInteger)changeCount
493 {
494     return _changeCount;
495 }
496
497 - (NSURL *)preferredFileUploadURLAtIndex:(NSUInteger)index fileType:(NSString **)outFileType
498 {
499     if (outFileType)
500         *outFileType = nil;
501
502     if (index >= _loadResults.size())
503         return nil;
504
505     auto result = _loadResults[index];
506     if (![result canBeRepresentedAsFileUpload])
507         return nil;
508
509     NSItemProvider *itemProvider = [result itemProvider];
510     for (NSString *registeredTypeIdentifier in itemProvider.registeredTypeIdentifiers) {
511         // Search for the highest fidelity non-private type identifier we loaded from the item provider.
512         if (!UTTypeIsDeclared((CFStringRef)registeredTypeIdentifier) && !UTTypeIsDynamic((CFStringRef)registeredTypeIdentifier))
513             continue;
514
515         for (NSString *loadedTypeIdentifier in [result loadedTypeIdentifiers]) {
516             if (!UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, (CFStringRef)loadedTypeIdentifier))
517                 continue;
518
519             if (outFileType)
520                 *outFileType = loadedTypeIdentifier;
521             return [result fileURLForType:loadedTypeIdentifier];
522         }
523     }
524
525     return nil;
526 }
527
528 - (NSArray<NSURL *> *)allDroppedFileURLs
529 {
530     NSMutableArray<NSURL *> *fileURLs = [NSMutableArray array];
531     for (auto loadResult : _loadResults) {
532         if ([loadResult canBeRepresentedAsFileUpload])
533             [fileURLs addObjectsFromArray:[loadResult loadedFileURLs]];
534     }
535     return fileURLs;
536 }
537
538 static BOOL typeConformsToTypes(NSString *type, NSArray *conformsToTypes)
539 {
540     // A type is considered appropriate to load if it conforms to one or more supported types.
541     for (NSString *conformsToType in conformsToTypes) {
542         if (UTTypeConformsTo((CFStringRef)type, (CFStringRef)conformsToType))
543             return YES;
544     }
545     return NO;
546 }
547
548 - (NSInteger)numberOfFiles
549 {
550     NSArray *supportedFileTypes = Pasteboard::supportedFileUploadPasteboardTypes();
551     NSInteger numberOfFiles = 0;
552     for (UIItemProvider *itemProvider in _itemProviders.get()) {
553         if (itemProvider.preferredPresentationStyle == UIPreferredPresentationStyleInline)
554             continue;
555
556         for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
557             if (!typeConformsToTypes(identifier, supportedFileTypes))
558                 continue;
559             ++numberOfFiles;
560             break;
561         }
562     }
563     return numberOfFiles;
564 }
565
566 static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, NSString *suggestedName, NSString *typeIdentifier)
567 {
568     static NSString *defaultDropFolderName = @"folder";
569     static NSString *defaultDropFileName = @"file";
570     static NSString *dataInteractionDirectoryPrefix = @"data-interaction";
571     if (!url)
572         return nil;
573
574     NSString *temporaryDataInteractionDirectory = WebCore::FileSystem::createTemporaryDirectory(dataInteractionDirectoryPrefix);
575     if (!temporaryDataInteractionDirectory)
576         return nil;
577
578     NSURL *destination = nil;
579     BOOL isFolder = UTTypeConformsTo((CFStringRef)typeIdentifier, kUTTypeFolder);
580     NSFileManager *fileManager = [NSFileManager defaultManager];
581
582     if (!suggestedName)
583         suggestedName = url.lastPathComponent ?: (isFolder ? defaultDropFolderName : defaultDropFileName);
584
585     if (![suggestedName containsString:@"."] && !isFolder)
586         suggestedName = [suggestedName stringByAppendingPathExtension:url.pathExtension];
587
588     destination = [NSURL fileURLWithPath:[temporaryDataInteractionDirectory stringByAppendingPathComponent:suggestedName]];
589     return [fileManager linkItemAtURL:url toURL:destination error:nil] ? destination : nil;
590 }
591
592 - (NSArray<NSString *> *)typeIdentifiersToLoadForRegisteredTypeIdentfiers:(NSArray<NSString *> *)registeredTypeIdentifiers
593 {
594     NSMutableSet *typesToLoad = [NSMutableSet set];
595     NSString *highestFidelityContentType = nil;
596
597     BOOL containsFlatRTFD = [registeredTypeIdentifiers containsObject:(NSString *)kUTTypeFlatRTFD];
598     // First, we want to either load the highest fidelity supported type or the highest fidelity generic content type.
599     for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) {
600         if (containsFlatRTFD && [registeredTypeIdentifier isEqualToString:(NSString *)kUTTypeRTFD]) {
601             // In the case where attachments are enabled and we're accepting all types of content using attachment
602             // elements as a fallback representation, if the source writes attributed strings to the pasteboard with
603             // com.apple.rtfd at a higher fidelity than com.apple.flat-rtfd, we'll end up loading only com.apple.rtfd
604             // and dropping the text as an attachment element because we cannot convert the dropped content to markup.
605             // Instead, if flat RTFD is present in the item provider, always prefer that over RTFD so that dropping as
606             // regular web content isn't overridden by enabling attachment elements.
607             continue;
608         }
609
610         if (typeConformsToTypes(registeredTypeIdentifier, _supportedTypeIdentifiers.get())) {
611             [typesToLoad addObject:registeredTypeIdentifier];
612             break;
613         }
614
615         if (!highestFidelityContentType && UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeContent))
616             highestFidelityContentType = registeredTypeIdentifier;
617     }
618     if (!typesToLoad.count && highestFidelityContentType)
619         [typesToLoad addObject:highestFidelityContentType];
620
621     // For compatibility with DataTransfer APIs, additionally load web-exposed types. Since this is the only chance to
622     // fault in any data at all, we need to load up front any information that the page may ask for later down the line.
623     NSString *customPasteboardDataUTI = @(PasteboardCustomData::cocoaType());
624     for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) {
625         if ([typesToLoad containsObject:registeredTypeIdentifier])
626             continue;
627         if ([registeredTypeIdentifier isEqualToString:customPasteboardDataUTI])
628             [typesToLoad addObject:customPasteboardDataUTI];
629         if (UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeURL))
630             [typesToLoad addObject:(NSString *)kUTTypeURL];
631         if (UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeHTML))
632             [typesToLoad addObject:(NSString *)kUTTypeHTML];
633         if (UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypePlainText))
634             [typesToLoad addObject:(NSString *)kUTTypePlainText];
635     }
636
637     return typesToLoad.allObjects;
638 }
639
640 - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action
641 {
642     [self doAfterLoadingProvidedContentIntoFileURLs:action synchronousTimeout:0];
643 }
644
645 - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action synchronousTimeout:(NSTimeInterval)synchronousTimeout
646 {
647     _loadResults.clear();
648
649     auto changeCountBeforeLoading = _changeCount;
650     auto loadResults = adoptNS([[NSMutableArray alloc] initWithCapacity:[_itemProviders count]]);
651
652     // First, figure out which item providers we want to try and load files from.
653     BOOL foundAnyDataToLoad = NO;
654     RetainPtr<WebItemProviderPasteboard> protectedSelf = self;
655     for (NSItemProvider *itemProvider in _itemProviders.get()) {
656         NSArray<NSString *> *typeIdentifiersToLoad = [protectedSelf typeIdentifiersToLoadForRegisteredTypeIdentfiers:itemProvider.registeredTypeIdentifiers];
657         foundAnyDataToLoad |= typeIdentifiersToLoad.count;
658         [loadResults addObject:[WebItemProviderLoadResult loadResultWithItemProvider:itemProvider typesToLoad:typeIdentifiersToLoad]];
659     }
660
661     if (!foundAnyDataToLoad) {
662         action(@[ ]);
663         return;
664     }
665
666     auto setFileURLsLock = adoptNS([[NSLock alloc] init]);
667     auto synchronousFileLoadingGroup = adoptOSObject(dispatch_group_create());
668     auto fileLoadingGroup = adoptOSObject(dispatch_group_create());
669     for (WebItemProviderLoadResult *loadResult in loadResults.get()) {
670         for (NSString *typeToLoad in loadResult.typesToLoad) {
671             dispatch_group_enter(fileLoadingGroup.get());
672             dispatch_group_enter(synchronousFileLoadingGroup.get());
673             [loadResult.itemProvider loadFileRepresentationForTypeIdentifier:typeToLoad completionHandler:[protectedLoadResult = retainPtr(loadResult), synchronousFileLoadingGroup, setFileURLsLock, protectedTypeToLoad = retainPtr(typeToLoad), fileLoadingGroup] (NSURL *url, NSError *) {
674                 // 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.
675                 // 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
676                 // WKFileUploadPanel.mm. The temporary files are cleaned up by the system at a later time.
677                 if (NSURL *destination = linkTemporaryItemProviderFilesToDropStagingDirectory(url, [protectedLoadResult itemProvider].suggestedName, protectedTypeToLoad.get())) {
678                     [setFileURLsLock lock];
679                     [protectedLoadResult setFileURL:destination forType:protectedTypeToLoad.get()];
680                     [setFileURLsLock unlock];
681                 }
682                 dispatch_group_leave(fileLoadingGroup.get());
683                 dispatch_group_leave(synchronousFileLoadingGroup.get());
684             }];
685         }
686     }
687
688     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
689     auto itemLoadCompletion = [retainedSelf, synchronousFileLoadingGroup, fileLoadingGroup, loadResults, completionBlock = makeBlockPtr(action), changeCountBeforeLoading] {
690         if (changeCountBeforeLoading == retainedSelf->_changeCount) {
691             for (WebItemProviderLoadResult *loadResult in loadResults.get())
692                 retainedSelf->_loadResults.append(loadResult);
693         }
694
695         completionBlock([retainedSelf allDroppedFileURLs]);
696     };
697
698     if (synchronousTimeout > 0 && !dispatch_group_wait(synchronousFileLoadingGroup.get(), dispatch_time(DISPATCH_TIME_NOW, synchronousTimeout * NSEC_PER_SEC))) {
699         itemLoadCompletion();
700         return;
701     }
702
703     dispatch_group_notify(fileLoadingGroup.get(), dispatch_get_main_queue(), itemLoadCompletion);
704 }
705
706 - (UIItemProvider *)itemProviderAtIndex:(NSUInteger)index
707 {
708     return index < [_itemProviders count] ? [_itemProviders objectAtIndex:index] : nil;
709 }
710
711 - (BOOL)hasPendingOperation
712 {
713     return _pendingOperationCount;
714 }
715
716 - (void)incrementPendingOperationCount
717 {
718     _pendingOperationCount++;
719 }
720
721 - (void)decrementPendingOperationCount
722 {
723     _pendingOperationCount--;
724 }
725
726 - (void)enumerateItemProvidersWithBlock:(void (^)(UIItemProvider *itemProvider, NSUInteger index, BOOL *stop))block
727 {
728     [_itemProviders enumerateObjectsUsingBlock:block];
729 }
730
731 - (void)stageRegistrationList:(nullable WebItemProviderRegistrationInfoList *)info
732 {
733     _stagedRegistrationInfoList = info.numberOfItems ? info : nil;
734 }
735
736 - (WebItemProviderRegistrationInfoList *)takeRegistrationList
737 {
738     auto stagedRegistrationInfoList = _stagedRegistrationInfoList;
739     _stagedRegistrationInfoList = nil;
740     return stagedRegistrationInfoList.autorelease();
741 }
742
743 @end
744
745 #endif // ENABLE(DATA_INTERACTION)