/* * Copyright (C) 2017-2018 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "WebItemProviderPasteboard.h" #if PLATFORM(IOS_FAMILY) && ENABLE(DRAG_SUPPORT) #import #import #import #import #import #import #import #import #import #import #import #import #import typedef void(^ItemProviderDataLoadCompletionHandler)(NSData *, NSError *); typedef void(^ItemProviderFileLoadCompletionHandler)(NSURL *, BOOL, NSError *); typedef NSMutableDictionary TypeToFileURLMap; using WebCore::Pasteboard; using WebCore::PasteboardCustomData; static BOOL typeConformsToTypes(NSString *type, NSArray *conformsToTypes) { for (NSString *conformsToType in conformsToTypes) { ALLOW_DEPRECATED_DECLARATIONS_BEGIN if (UTTypeConformsTo((__bridge CFStringRef)type, (__bridge CFStringRef)conformsToType)) return YES; ALLOW_DEPRECATED_DECLARATIONS_END } return NO; } @implementation NSItemProvider (WebCoreExtras) - (BOOL)web_containsFileURLAndFileUploadContent { for (NSString *identifier in self.registeredTypeIdentifiers) { ALLOW_DEPRECATED_DECLARATIONS_BEGIN if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeFileURL)) return self.web_fileUploadContentTypes.count; ALLOW_DEPRECATED_DECLARATIONS_END } return NO; } - (NSArray *)web_fileUploadContentTypes { auto types = adoptNS([NSMutableArray new]); for (NSString *identifier in self.registeredTypeIdentifiers) { ALLOW_DEPRECATED_DECLARATIONS_BEGIN if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeURL)) continue; ALLOW_DEPRECATED_DECLARATIONS_END if ([identifier isEqualToString:@"com.apple.mapkit.map-item"]) { // This type conforms to "public.content", yet the corresponding data is only a serialization of MKMapItem and isn't suitable for file uploads. // Ignore over this type representation for the purposes of file uploads, in favor of "public.vcard" data. continue; } if (typeConformsToTypes(identifier, Pasteboard::supportedFileUploadPasteboardTypes())) [types addObject:identifier]; } return types.autorelease(); } @end @interface WebItemProviderDataRegistrar : NSObject - (instancetype)initWithData:(NSData *)data type:(NSString *)utiType; @property (nonatomic, readonly) NSString *typeIdentifier; @property (nonatomic, readonly) NSData *data; @end @implementation WebItemProviderDataRegistrar { RetainPtr _typeIdentifier; RetainPtr _data; } - (instancetype)initWithData:(NSData *)data type:(NSString *)utiType { if (!(self = [super init])) return nil; _data = data; _typeIdentifier = utiType; return self; } - (NSString *)typeIdentifier { return _typeIdentifier.get(); } - (NSData *)data { return _data.get(); } - (NSString *)typeIdentifierForClient { return self.typeIdentifier; } - (NSData *)dataForClient { return self.data; } - (void)registerItemProvider:(NSItemProvider *)itemProvider { [itemProvider registerDataRepresentationForTypeIdentifier:self.typeIdentifier visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[itemData = _data] (ItemProviderDataLoadCompletionHandler completionHandler) -> NSProgress * { completionHandler(itemData.get(), nil); return nil; }]; } - (NSString *)description { return [NSString stringWithFormat:@"(%@ => %tu bytes)", _typeIdentifier.get(), [_data length]]; } @end @interface WebItemProviderWritableObjectRegistrar : NSObject - (instancetype)initWithObject:(id )representingObject; @property (nonatomic, readonly) id representingObject; @end @implementation WebItemProviderWritableObjectRegistrar { RetainPtr> _representingObject; } - (instancetype)initWithObject:(id )representingObject { if (!(self = [super init])) return nil; _representingObject = representingObject; return self; } - (id )representingObject { return _representingObject.get(); } - (id )representingObjectForClient { return self.representingObject; } - (void)registerItemProvider:(NSItemProvider *)itemProvider { [itemProvider registerObject:self.representingObject visibility:NSItemProviderRepresentationVisibilityAll]; } - (NSString *)description { return [NSString stringWithFormat:@"(%@)", [_representingObject class]]; } @end @interface WebItemProviderPromisedFileRegistrar : NSObject - (instancetype)initWithType:(NSString *)utiType callback:(void(^)(WebItemProviderFileCallback))callback; @property (nonatomic, readonly) NSString *typeIdentifier; @end @implementation WebItemProviderPromisedFileRegistrar { RetainPtr _typeIdentifier; BlockPtr _callback; } - (instancetype)initWithType:(NSString *)utiType callback:(void(^)(WebItemProviderFileCallback))callback { if (!(self = [super init])) return nil; _typeIdentifier = utiType; _callback = callback; return self; } - (void)registerItemProvider:(NSItemProvider *)itemProvider { [itemProvider registerFileRepresentationForTypeIdentifier:_typeIdentifier.get() fileOptions:0 visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[callback = _callback] (ItemProviderFileLoadCompletionHandler completionHandler) -> NSProgress * { callback([protectedCompletionHandler = makeBlockPtr(completionHandler)] (NSURL *fileURL, NSError *error) { protectedCompletionHandler(fileURL, NO, error); }); return nil; }]; } - (NSString *)typeIdentifier { return _typeIdentifier.get(); } - (NSString *)description { return [NSString stringWithFormat:@"(file promise: %@)", _typeIdentifier.get()]; } @end @interface WebItemProviderRegistrationInfoList () { RetainPtr _representations; } @end @implementation WebItemProviderRegistrationInfoList - (instancetype)init { if (self = [super init]) { _representations = adoptNS([[NSMutableArray alloc] init]); _preferredPresentationSize = CGSizeZero; _preferredPresentationStyle = WebPreferredPresentationStyleUnspecified; } return self; } - (void)dealloc { [_suggestedName release]; [_teamData release]; [super dealloc]; } - (void)addData:(NSData *)data forType:(NSString *)typeIdentifier { auto representation = adoptNS([[WebItemProviderDataRegistrar alloc] initWithData:data type:typeIdentifier]); [_representations addObject:representation.get()]; } - (void)addRepresentingObject:(id )object { ASSERT([object conformsToProtocol:@protocol(NSItemProviderWriting)]); auto representation = adoptNS([[WebItemProviderWritableObjectRegistrar alloc] initWithObject:object]); [_representations addObject:representation.get()]; } - (void)addPromisedType:(NSString *)typeIdentifier fileCallback:(void(^)(WebItemProviderFileCallback))callback { auto representation = adoptNS([[WebItemProviderPromisedFileRegistrar alloc] initWithType:typeIdentifier callback:callback]); [_representations addObject:representation.get()]; } - (NSUInteger)numberOfItems { return [_representations count]; } - (id )itemAtIndex:(NSUInteger)index { if (index >= self.numberOfItems) return nil; return [_representations objectAtIndex:index]; } - (void)enumerateItems:(void (^)(id , NSUInteger))block { for (NSUInteger index = 0; index < self.numberOfItems; ++index) block([self itemAtIndex:index], index); } #if !PLATFORM(MACCATALYST) static UIPreferredPresentationStyle uiPreferredPresentationStyle(WebPreferredPresentationStyle style) { switch (style) { case WebPreferredPresentationStyleUnspecified: return UIPreferredPresentationStyleUnspecified; case WebPreferredPresentationStyleInline: return UIPreferredPresentationStyleInline; case WebPreferredPresentationStyleAttachment: return UIPreferredPresentationStyleAttachment; default: ASSERT_NOT_REACHED(); return UIPreferredPresentationStyleUnspecified; } } #endif - (NSItemProvider *)itemProvider { if (!self.numberOfItems) return nil; auto itemProvider = adoptNS([NSItemProvider new]); for (id representation in _representations.get()) [representation registerItemProvider:itemProvider.get()]; [itemProvider setSuggestedName:self.suggestedName]; #if !PLATFORM(MACCATALYST) [itemProvider setPreferredPresentationSize:self.preferredPresentationSize]; [itemProvider setPreferredPresentationStyle:uiPreferredPresentationStyle(self.preferredPresentationStyle)]; [itemProvider setTeamData:self.teamData]; #endif return itemProvider.autorelease(); } - (NSString *)description { __block NSMutableString *description = [NSMutableString string]; [description appendFormat:@"<%@: %p", [self class], self]; [self enumerateItems:^(id item, NSUInteger index) { if (index) [description appendString:@", "]; [description appendString:[item description]]; }]; [description appendString:@">"]; return description; } @end @interface WebItemProviderLoadResult : NSObject - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray *)typesToLoad; - (NSURL *)fileURLForType:(NSString *)type; - (void)setFileURL:(NSURL *)url forType:(NSString *)type; @property (nonatomic, readonly) NSArray *loadedFileURLs; @property (nonatomic, readonly) NSArray *loadedTypeIdentifiers; @property (nonatomic, readonly) BOOL canBeRepresentedAsFileUpload; @property (nonatomic, readonly) NSItemProvider *itemProvider; @property (nonatomic, readonly) NSArray *typesToLoad; @end @implementation WebItemProviderLoadResult { RetainPtr _fileURLs; RetainPtr _itemProvider; RetainPtr> _typesToLoad; } + (instancetype)loadResultWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray *)typesToLoad { return adoptNS([[self alloc] initWithItemProvider:itemProvider typesToLoad:typesToLoad]).autorelease(); } - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray *)typesToLoad { if (!(self = [super init])) return nil; _fileURLs = adoptNS([[NSMutableDictionary alloc] init]); _itemProvider = itemProvider; _typesToLoad = typesToLoad; return self; } - (BOOL)canBeRepresentedAsFileUpload { if ([_itemProvider web_containsFileURLAndFileUploadContent]) return YES; if ([_itemProvider preferredPresentationStyle] == UIPreferredPresentationStyleInline) return NO; #if PLATFORM(MACCATALYST) // On macCatalyst, -preferredPresentationStyle always returns UIPreferredPresentationStyleUnspecified, // even when the preferredPresentationStyle is set to something other than unspecified using the setter. // This means we're unable to avoid exposing item providers that have been marked as inline data as files // to the page -- see for more details. In the meantime, only expose item providers as // files if they have explicitly been given a suggested file name. return [_itemProvider suggestedName].length; #else return YES; #endif } - (NSArray *)typesToLoad { return _typesToLoad.get(); } - (NSURL *)fileURLForType:(NSString *)type { return [_fileURLs objectForKey:type]; } - (void)setFileURL:(NSURL *)url forType:(NSString *)type { [_fileURLs setObject:url forKey:type]; } - (NSArray *)loadedFileURLs { return [_fileURLs allValues]; } - (NSArray *)loadedTypeIdentifiers { return [_fileURLs allKeys]; } - (NSItemProvider *)itemProvider { return _itemProvider.get(); } - (NSString *)description { __block NSMutableString *description = [NSMutableString string]; [description appendFormat:@"<%@: %p typesToLoad: [ ", [self class], self]; [_typesToLoad enumerateObjectsUsingBlock:^(NSString *type, NSUInteger index, BOOL *) { [description appendString:type]; if (index + 1 < [_typesToLoad count]) [description appendString:@", "]; }]; [description appendFormat:@" ] fileURLs: { "]; __block NSUInteger index = 0; [_fileURLs enumerateKeysAndObjectsUsingBlock:^(NSString *type, NSURL *url, BOOL *) { [description appendFormat:@"%@ => \"%@\"", type, url.path]; if (++index < [_fileURLs count]) [description appendString:@", "]; }]; [description appendFormat:@" }>"]; return description; } @end @interface WebItemProviderPasteboard () @property (nonatomic) NSInteger numberOfItems; @property (nonatomic) NSInteger changeCount; @property (nonatomic) NSInteger pendingOperationCount; @end @implementation WebItemProviderPasteboard { // FIXME: These ivars should be refactored to be Vector> instead of generic NSArrays. RetainPtr _itemProviders; RetainPtr _supportedTypeIdentifiers; RetainPtr> _stagedRegistrationInfoLists; Vector> _loadResults; } + (instancetype)sharedInstance { static NeverDestroyed> sharedPasteboard; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^() { sharedPasteboard.get() = adoptNS([[WebItemProviderPasteboard alloc] init]); }); return sharedPasteboard.get().get(); } - (instancetype)init { if (self = [super init]) { _itemProviders = adoptNS([[NSArray alloc] init]); _changeCount = 0; _pendingOperationCount = 0; _supportedTypeIdentifiers = nil; _stagedRegistrationInfoLists = nil; _loadResults = { }; } return self; } - (void)updateSupportedTypeIdentifiers:(NSArray *)types { _supportedTypeIdentifiers = types; } - (NSArray *)pasteboardTypes { NSMutableSet *allTypes = [NSMutableSet set]; NSMutableArray *allTypesInOrder = [NSMutableArray array]; for (NSItemProvider *provider in _itemProviders.get()) { for (NSString *typeIdentifier in provider.registeredTypeIdentifiers) { if ([allTypes containsObject:typeIdentifier]) continue; [allTypes addObject:typeIdentifier]; [allTypesInOrder addObject:typeIdentifier]; } } return allTypesInOrder; } - (NSArray<__kindof NSItemProvider *> *)itemProviders { return _itemProviders.get(); } - (void)setItemProviders:(NSArray<__kindof NSItemProvider *> *)itemProviders { itemProviders = itemProviders ?: @[ ]; if (_itemProviders == itemProviders || [_itemProviders isEqualToArray:itemProviders]) return; _itemProviders = itemProviders; _changeCount++; if (!itemProviders.count) _loadResults = { }; } - (NSInteger)numberOfItems { return [_itemProviders count]; } - (NSData *)_preLoadedDataConformingToType:(NSString *)typeIdentifier forItemProviderAtIndex:(NSUInteger)index { if (_loadResults.size() != [_itemProviders count]) { ASSERT_NOT_REACHED(); return nil; } WebItemProviderLoadResult *loadResult = _loadResults[index].get(); for (NSString *loadedType in loadResult.loadedTypeIdentifiers) { ALLOW_DEPRECATED_DECLARATIONS_BEGIN if (!UTTypeConformsTo((CFStringRef)loadedType, (CFStringRef)typeIdentifier)) continue; ALLOW_DEPRECATED_DECLARATIONS_END // 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. if (NSData *result = [NSData dataWithContentsOfURL:[loadResult fileURLForType:loadedType] options:NSDataReadingMappedIfSafe error:nil]) return result; } return nil; } - (NSData *)dataForPasteboardType:(NSString *)pasteboardType { return [self dataForPasteboardType:pasteboardType inItemSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.numberOfItems)]].firstObject; } - (NSArray *)dataForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet { if (_loadResults.isEmpty()) return @[ ]; auto values = adoptNS([[NSMutableArray alloc] init]); RetainPtr retainedSelf = self; [itemSet enumerateIndexesUsingBlock:[retainedSelf, pasteboardType, values] (NSUInteger index, BOOL *) { NSItemProvider *provider = [retainedSelf itemProviderAtIndex:index]; if (!provider) return; if (NSData *loadedData = [retainedSelf _preLoadedDataConformingToType:pasteboardType forItemProviderAtIndex:index]) [values addObject:loadedData]; }]; return values.autorelease(); } static NSArray> *allLoadableClasses() { return @[ [PAL::getUIColorClass() class], [PAL::getUIImageClass() class], [NSURL class], [NSString class], [NSAttributedString class] ]; } static Class classForTypeIdentifier(NSString *typeIdentifier, NSString *&outTypeIdentifierToLoad) { outTypeIdentifierToLoad = typeIdentifier; // First, try to load a platform NSItemProviderReading-conformant object as-is. for (Class loadableClass in allLoadableClasses()) { if ([[loadableClass readableTypeIdentifiersForItemProvider] containsObject:(NSString *)typeIdentifier]) return loadableClass; } // If we were unable to load any object, check if the given type identifier is still something // WebKit knows how to handle. ALLOW_DEPRECATED_DECLARATIONS_BEGIN if ([typeIdentifier isEqualToString:(NSString *)kUTTypeHTML]) { // Load kUTTypeHTML as a plain text HTML string. outTypeIdentifierToLoad = (NSString *)kUTTypePlainText; return [NSString class]; } ALLOW_DEPRECATED_DECLARATIONS_END return nil; } - (NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet { if (_loadResults.isEmpty()) return @[ ]; auto values = adoptNS([[NSMutableArray alloc] init]); RetainPtr retainedSelf = self; [itemSet enumerateIndexesUsingBlock:[retainedSelf, pasteboardType, values] (NSUInteger index, BOOL *) { NSItemProvider *provider = [retainedSelf itemProviderAtIndex:index]; if (!provider) return; NSString *typeIdentifierToLoad; Class readableClass = classForTypeIdentifier(pasteboardType, typeIdentifierToLoad); if (!readableClass) return; NSData *preloadedData = [retainedSelf _preLoadedDataConformingToType:pasteboardType forItemProviderAtIndex:index]; if (!preloadedData) return; if (id readObject = [readableClass objectWithItemProviderData:preloadedData typeIdentifier:(NSString *)typeIdentifierToLoad error:nil]) [values addObject:readObject]; }]; return values.autorelease(); } - (NSInteger)changeCount { return _changeCount; } - (NSArray *)fileUploadURLsAtIndex:(NSUInteger)index fileTypes:(NSArray **)outFileTypes { auto fileTypes = adoptNS([NSMutableArray new]); auto fileURLs = adoptNS([NSMutableArray new]); if (index >= _loadResults.size()) return @[ ]; auto result = _loadResults[index]; if (![result canBeRepresentedAsFileUpload]) return @[ ]; for (NSString *contentType in [result itemProvider].web_fileUploadContentTypes) { if (NSURL *url = [result fileURLForType:contentType]) { [fileTypes addObject:contentType]; [fileURLs addObject:url]; } } *outFileTypes = fileTypes.autorelease(); return fileURLs.autorelease(); } - (NSArray *)allDroppedFileURLs { NSMutableArray *fileURLs = [NSMutableArray array]; for (const auto& loadResult : _loadResults) { if ([loadResult canBeRepresentedAsFileUpload]) [fileURLs addObjectsFromArray:[loadResult loadedFileURLs]]; } return fileURLs; } - (NSInteger)numberOfFiles { NSInteger numberOfFiles = 0; for (NSItemProvider *itemProvider in _itemProviders.get()) { #if !PLATFORM(MACCATALYST) // First, check if the source has explicitly indicated that this item should or should not be treated as an attachment. if (itemProvider.preferredPresentationStyle == UIPreferredPresentationStyleInline) continue; if (itemProvider.preferredPresentationStyle == UIPreferredPresentationStyleAttachment) { ++numberOfFiles; continue; } #endif // Otherwise, fall back to examining the item's registered type identifiers. if (itemProvider.web_fileUploadContentTypes.count) ++numberOfFiles; } return numberOfFiles; } static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, NSString *suggestedName, NSString *typeIdentifier) { static NSString *defaultDropFolderName = @"folder"; static NSString *defaultDropFileName = @"file"; static NSString *droppedDataDirectoryPrefix = @"dropped-data"; if (!url) return nil; NSString *temporaryDropDataDirectory = FileSystem::createTemporaryDirectory(droppedDataDirectoryPrefix); if (!temporaryDropDataDirectory) return nil; NSURL *destination = nil; ALLOW_DEPRECATED_DECLARATIONS_BEGIN BOOL isFolder = UTTypeConformsTo((CFStringRef)typeIdentifier, kUTTypeFolder); ALLOW_DEPRECATED_DECLARATIONS_END NSFileManager *fileManager = [NSFileManager defaultManager]; if (!suggestedName) suggestedName = url.lastPathComponent ?: (isFolder ? defaultDropFolderName : defaultDropFileName); if ([suggestedName.pathExtension caseInsensitiveCompare:url.pathExtension] != NSOrderedSame && !isFolder) suggestedName = [suggestedName stringByAppendingPathExtension:url.pathExtension]; destination = [NSURL fileURLWithPath:[temporaryDropDataDirectory stringByAppendingPathComponent:suggestedName]]; return [fileManager linkItemAtURL:url toURL:destination error:nil] ? destination : nil; } - (NSArray *)typeIdentifiersToLoad:(NSItemProvider *)itemProvider { ALLOW_DEPRECATED_DECLARATIONS_BEGIN auto typesToLoad = adoptNS([[NSMutableOrderedSet alloc] init]); NSString *highestFidelitySupportedType = nil; NSString *highestFidelityContentType = nil; NSArray *registeredTypeIdentifiers = itemProvider.registeredTypeIdentifiers; BOOL containsFile = itemProvider.web_containsFileURLAndFileUploadContent; BOOL containsFlatRTFD = [registeredTypeIdentifiers containsObject:(__bridge NSString *)kUTTypeFlatRTFD]; // First, search for the highest fidelity supported type or the highest fidelity generic content type. for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) { if (containsFlatRTFD && [registeredTypeIdentifier isEqualToString:(__bridge NSString *)kUTTypeRTFD]) { // In the case where attachments are enabled and we're accepting all types of content using attachment // elements as a fallback representation, if the source writes attributed strings to the pasteboard with // com.apple.rtfd at a higher fidelity than com.apple.flat-rtfd, we'll end up loading only com.apple.rtfd // and dropping the text as an attachment element because we cannot convert the dropped content to markup. // Instead, if flat RTFD is present in the item provider, always prefer that over RTFD so that dropping as // regular web content isn't overridden by enabling attachment elements. continue; } if (containsFile && UTTypeConformsTo((__bridge CFStringRef)registeredTypeIdentifier, kUTTypeURL)) continue; if (!highestFidelitySupportedType && typeConformsToTypes(registeredTypeIdentifier, _supportedTypeIdentifiers.get())) highestFidelitySupportedType = registeredTypeIdentifier; if (!highestFidelityContentType && UTTypeConformsTo((CFStringRef)registeredTypeIdentifier, kUTTypeContent)) highestFidelityContentType = registeredTypeIdentifier; } // For compatibility with DataTransfer APIs, additionally load web-exposed types. Since this is the only chance to // fault in any data at all, we need to load up front any information that the page may ask for later down the line. NSString *customPasteboardDataUTI = @(PasteboardCustomData::cocoaType().characters()); for (NSString *registeredTypeIdentifier in registeredTypeIdentifiers) { if ([registeredTypeIdentifier isEqualToString:highestFidelityContentType] || [registeredTypeIdentifier isEqualToString:highestFidelitySupportedType] || [registeredTypeIdentifier isEqualToString:customPasteboardDataUTI] || (!containsFile && UTTypeConformsTo((__bridge CFStringRef)registeredTypeIdentifier, kUTTypeURL)) || UTTypeConformsTo((__bridge CFStringRef)registeredTypeIdentifier, kUTTypeHTML) || UTTypeConformsTo((__bridge CFStringRef)registeredTypeIdentifier, kUTTypePlainText)) [typesToLoad addObject:registeredTypeIdentifier]; } ALLOW_DEPRECATED_DECLARATIONS_END return [typesToLoad array]; } - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action { [self doAfterLoadingProvidedContentIntoFileURLs:action synchronousTimeout:0]; } - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action synchronousTimeout:(NSTimeInterval)synchronousTimeout { _loadResults.clear(); auto changeCountBeforeLoading = _changeCount; auto loadResults = adoptNS([[NSMutableArray alloc] initWithCapacity:[_itemProviders count]]); // First, figure out which item providers we want to try and load files from. BOOL foundAnyDataToLoad = NO; RetainPtr protectedSelf = self; for (NSItemProvider *itemProvider in _itemProviders.get()) { NSArray *typeIdentifiersToLoad = [protectedSelf typeIdentifiersToLoad:itemProvider]; foundAnyDataToLoad |= typeIdentifiersToLoad.count; [loadResults addObject:[WebItemProviderLoadResult loadResultWithItemProvider:itemProvider typesToLoad:typeIdentifiersToLoad]]; } if (!foundAnyDataToLoad) { action(@[ ]); return; } auto setFileURLsLock = adoptNS([[NSLock alloc] init]); auto synchronousFileLoadingGroup = adoptOSObject(dispatch_group_create()); auto fileLoadingGroup = adoptOSObject(dispatch_group_create()); for (WebItemProviderLoadResult *loadResult in loadResults.get()) { for (NSString *typeToLoad in loadResult.typesToLoad) { dispatch_group_enter(fileLoadingGroup.get()); dispatch_group_enter(synchronousFileLoadingGroup.get()); [loadResult.itemProvider loadFileRepresentationForTypeIdentifier:typeToLoad completionHandler:[protectedLoadResult = retainPtr(loadResult), synchronousFileLoadingGroup, setFileURLsLock, protectedTypeToLoad = retainPtr(typeToLoad), fileLoadingGroup] (NSURL *url, NSError *) { // 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. // 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 // WKFileUploadPanel.mm. The temporary files are cleaned up by the system at a later time. if (NSURL *destination = linkTemporaryItemProviderFilesToDropStagingDirectory(url, [protectedLoadResult itemProvider].suggestedName, protectedTypeToLoad.get())) { [setFileURLsLock lock]; [protectedLoadResult setFileURL:destination forType:protectedTypeToLoad.get()]; [setFileURLsLock unlock]; } dispatch_group_leave(fileLoadingGroup.get()); dispatch_group_leave(synchronousFileLoadingGroup.get()); }]; } } RetainPtr retainedSelf = self; auto itemLoadCompletion = [retainedSelf, synchronousFileLoadingGroup, fileLoadingGroup, loadResults, completionBlock = makeBlockPtr(action), changeCountBeforeLoading] { if (changeCountBeforeLoading == retainedSelf->_changeCount) { for (WebItemProviderLoadResult *loadResult in loadResults.get()) retainedSelf->_loadResults.append(loadResult); } completionBlock([retainedSelf allDroppedFileURLs]); }; if (synchronousTimeout > 0 && !dispatch_group_wait(synchronousFileLoadingGroup.get(), dispatch_time(DISPATCH_TIME_NOW, synchronousTimeout * NSEC_PER_SEC))) { itemLoadCompletion(); return; } dispatch_group_notify(fileLoadingGroup.get(), dispatch_get_main_queue(), itemLoadCompletion); } - (NSItemProvider *)itemProviderAtIndex:(NSUInteger)index { return index < [_itemProviders count] ? [_itemProviders objectAtIndex:index] : nil; } - (BOOL)hasPendingOperation { return _pendingOperationCount; } - (void)incrementPendingOperationCount { _pendingOperationCount++; } - (void)decrementPendingOperationCount { _pendingOperationCount--; } - (void)enumerateItemProvidersWithBlock:(void (^)(__kindof NSItemProvider *itemProvider, NSUInteger index, BOOL *stop))block { [_itemProviders enumerateObjectsUsingBlock:block]; } - (void)stageRegistrationLists:(NSArray *)lists { ASSERT(lists.count); _stagedRegistrationInfoLists = lists; } - (void)clearRegistrationLists { _stagedRegistrationInfoLists = nil; } - (NSArray *)takeRegistrationLists { return _stagedRegistrationInfoLists.autorelease(); } @end #endif // PLATFORM(IOS_FAMILY) && ENABLE(DRAG_SUPPORT)