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