| /* |
| * 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. |
| */ |
| |
| #include "config.h" |
| #import "WebItemProviderPasteboard.h" |
| |
| #if ENABLE(DATA_INTERACTION) |
| |
| #import <Foundation/NSItemProvider.h> |
| #import <Foundation/NSProgress.h> |
| #import <MobileCoreServices/MobileCoreServices.h> |
| #import <UIKit/NSItemProvider+UIKitAdditions.h> |
| #import <UIKit/UIColor.h> |
| #import <UIKit/UIImage.h> |
| #import <WebCore/Pasteboard.h> |
| #import <pal/ios/UIKitSoftLink.h> |
| #import <pal/spi/ios/UIKitSPI.h> |
| #import <wtf/BlockPtr.h> |
| #import <wtf/FileSystem.h> |
| #import <wtf/OSObjectPtr.h> |
| #import <wtf/RetainPtr.h> |
| |
| typedef void(^ItemProviderDataLoadCompletionHandler)(NSData *, NSError *); |
| typedef void(^ItemProviderFileLoadCompletionHandler)(NSURL *, BOOL, NSError *); |
| typedef NSMutableDictionary<NSString *, NSURL *> TypeToFileURLMap; |
| |
| using WebCore::Pasteboard; |
| using WebCore::PasteboardCustomData; |
| |
| static BOOL typeConformsToTypes(NSString *type, NSArray *conformsToTypes) |
| { |
| for (NSString *conformsToType in conformsToTypes) { |
| if (UTTypeConformsTo((__bridge CFStringRef)type, (__bridge CFStringRef)conformsToType)) |
| return YES; |
| } |
| return NO; |
| } |
| |
| @implementation NSItemProvider (WebCoreExtras) |
| |
| - (BOOL)web_containsFileURLAndFileUploadContent |
| { |
| for (NSString *identifier in self.registeredTypeIdentifiers) { |
| if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeFileURL)) |
| return self.web_fileUploadContentTypes.count; |
| } |
| return NO; |
| } |
| |
| - (NSArray<NSString *> *)web_fileUploadContentTypes |
| { |
| auto types = adoptNS([NSMutableArray new]); |
| for (NSString *identifier in self.registeredTypeIdentifiers) { |
| if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeURL)) |
| continue; |
| |
| 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 <WebItemProviderRegistrar> |
| - (instancetype)initWithData:(NSData *)data type:(NSString *)utiType; |
| @property (nonatomic, readonly) NSString *typeIdentifier; |
| @property (nonatomic, readonly) NSData *data; |
| @end |
| |
| @implementation WebItemProviderDataRegistrar { |
| RetainPtr<NSString> _typeIdentifier; |
| RetainPtr<NSData> _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 <WebItemProviderRegistrar> |
| - (instancetype)initWithObject:(id <NSItemProviderWriting>)representingObject; |
| @property (nonatomic, readonly) id <NSItemProviderWriting> representingObject; |
| @end |
| |
| @implementation WebItemProviderWritableObjectRegistrar { |
| RetainPtr<id <NSItemProviderWriting>> _representingObject; |
| } |
| |
| - (instancetype)initWithObject:(id <NSItemProviderWriting>)representingObject |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _representingObject = representingObject; |
| return self; |
| } |
| |
| - (id <NSItemProviderWriting>)representingObject |
| { |
| return _representingObject.get(); |
| } |
| |
| - (id <NSItemProviderWriting>)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 <WebItemProviderRegistrar> |
| - (instancetype)initWithType:(NSString *)utiType callback:(void(^)(WebItemProviderFileCallback))callback; |
| @property (nonatomic, readonly) NSString *typeIdentifier; |
| @end |
| |
| @implementation WebItemProviderPromisedFileRegistrar { |
| RetainPtr<NSString> _typeIdentifier; |
| BlockPtr<void(WebItemProviderFileCallback)> _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<NSMutableArray> _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 <NSItemProviderWriting>)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 <WebItemProviderRegistrar>)itemAtIndex:(NSUInteger)index |
| { |
| if (index >= self.numberOfItems) |
| return nil; |
| |
| return [_representations objectAtIndex:index]; |
| } |
| |
| - (void)enumerateItems:(void (^)(id <WebItemProviderRegistrar>, 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 <WebItemProviderRegistrar> 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 <WebItemProviderRegistrar> 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<NSString *> *)typesToLoad; |
| - (NSURL *)fileURLForType:(NSString *)type; |
| - (void)setFileURL:(NSURL *)url forType:(NSString *)type; |
| @property (nonatomic, readonly) NSArray<NSURL *> *loadedFileURLs; |
| @property (nonatomic, readonly) NSArray<NSString *> *loadedTypeIdentifiers; |
| @property (nonatomic, readonly) BOOL canBeRepresentedAsFileUpload; |
| @property (nonatomic, readonly) NSItemProvider *itemProvider; |
| @property (nonatomic, readonly) NSArray<NSString *> *typesToLoad; |
| |
| @end |
| |
| @implementation WebItemProviderLoadResult { |
| RetainPtr<TypeToFileURLMap> _fileURLs; |
| RetainPtr<NSItemProvider> _itemProvider; |
| RetainPtr<NSArray<NSString *>> _typesToLoad; |
| } |
| |
| + (instancetype)loadResultWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)typesToLoad |
| { |
| return [[[self alloc] initWithItemProvider:itemProvider typesToLoad:typesToLoad] autorelease]; |
| } |
| |
| - (instancetype)initWithItemProvider:(NSItemProvider *)itemProvider typesToLoad:(NSArray<NSString *> *)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 <rdar://55002929> 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<NSString *> *)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<NSURL *> *)loadedFileURLs |
| { |
| return [_fileURLs allValues]; |
| } |
| |
| - (NSArray<NSString *> *)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<RetainPtr<Type>> instead of generic NSArrays. |
| RetainPtr<NSArray> _itemProviders; |
| RetainPtr<NSArray> _supportedTypeIdentifiers; |
| RetainPtr<WebItemProviderRegistrationInfoList> _stagedRegistrationInfoList; |
| |
| Vector<RetainPtr<WebItemProviderLoadResult>> _loadResults; |
| } |
| |
| + (instancetype)sharedInstance |
| { |
| static WebItemProviderPasteboard *sharedPasteboard = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^() { |
| sharedPasteboard = [[WebItemProviderPasteboard alloc] init]; |
| }); |
| return sharedPasteboard; |
| } |
| |
| - (instancetype)init |
| { |
| if (self = [super init]) { |
| _itemProviders = adoptNS([[NSArray alloc] init]); |
| _changeCount = 0; |
| _pendingOperationCount = 0; |
| _supportedTypeIdentifiers = nil; |
| _stagedRegistrationInfoList = nil; |
| _loadResults = { }; |
| } |
| return self; |
| } |
| |
| - (void)updateSupportedTypeIdentifiers:(NSArray<NSString *> *)types |
| { |
| _supportedTypeIdentifiers = types; |
| } |
| |
| - (NSArray<NSString *> *)pasteboardTypes |
| { |
| NSMutableSet<NSString *> *allTypes = [NSMutableSet set]; |
| NSMutableArray<NSString *> *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 ?: [NSArray array]; |
| 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) { |
| if (!UTTypeConformsTo((CFStringRef)loadedType, (CFStringRef)typeIdentifier)) |
| continue; |
| |
| // 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<WebItemProviderPasteboard> 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<Class<NSItemProviderReading>> *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<NSItemProviderReading> 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. |
| if ([typeIdentifier isEqualToString:(NSString *)kUTTypeHTML]) { |
| // Load kUTTypeHTML as a plain text HTML string. |
| outTypeIdentifierToLoad = (NSString *)kUTTypePlainText; |
| return [NSString class]; |
| } |
| |
| return nil; |
| } |
| |
| - (NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(NSIndexSet *)itemSet |
| { |
| if (_loadResults.isEmpty()) |
| return @[ ]; |
| |
| auto values = adoptNS([[NSMutableArray alloc] init]); |
| RetainPtr<WebItemProviderPasteboard> 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 <NSItemProviderReading> readObject = [readableClass objectWithItemProviderData:preloadedData typeIdentifier:(NSString *)typeIdentifierToLoad error:nil]) |
| [values addObject:readObject]; |
| }]; |
| |
| return values.autorelease(); |
| } |
| |
| - (NSInteger)changeCount |
| { |
| return _changeCount; |
| } |
| |
| - (NSArray<NSURL *> *)fileUploadURLsAtIndex:(NSUInteger)index fileTypes:(NSArray<NSString *> **)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<NSURL *> *)allDroppedFileURLs |
| { |
| NSMutableArray<NSURL *> *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; |
| BOOL isFolder = UTTypeConformsTo((CFStringRef)typeIdentifier, kUTTypeFolder); |
| NSFileManager *fileManager = [NSFileManager defaultManager]; |
| |
| if (!suggestedName) |
| suggestedName = url.lastPathComponent ?: (isFolder ? defaultDropFolderName : defaultDropFileName); |
| |
| if (![suggestedName containsString:@"."] && !isFolder) |
| suggestedName = [suggestedName stringByAppendingPathExtension:url.pathExtension]; |
| |
| destination = [NSURL fileURLWithPath:[temporaryDropDataDirectory stringByAppendingPathComponent:suggestedName]]; |
| return [fileManager linkItemAtURL:url toURL:destination error:nil] ? destination : nil; |
| } |
| |
| - (NSArray<NSString *> *)typeIdentifiersToLoad:(NSItemProvider *)itemProvider |
| { |
| auto typesToLoad = adoptNS([[NSMutableOrderedSet alloc] init]); |
| NSString *highestFidelitySupportedType = nil; |
| NSString *highestFidelityContentType = nil; |
| |
| NSArray<NSString *> *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()); |
| 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]; |
| } |
| |
| 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<WebItemProviderPasteboard> protectedSelf = self; |
| for (NSItemProvider *itemProvider in _itemProviders.get()) { |
| NSArray<NSString *> *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<WebItemProviderPasteboard> 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)stageRegistrationList:(nullable WebItemProviderRegistrationInfoList *)info |
| { |
| _stagedRegistrationInfoList = info.numberOfItems ? info : nil; |
| } |
| |
| - (WebItemProviderRegistrationInfoList *)takeRegistrationList |
| { |
| auto stagedRegistrationInfoList = _stagedRegistrationInfoList; |
| _stagedRegistrationInfoList = nil; |
| return stagedRegistrationInfoList.autorelease(); |
| } |
| |
| @end |
| |
| #endif // ENABLE(DATA_INTERACTION) |