[iOS DnD] Support DataTransfer.getData and DataTransfer.setData when dragging or...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 13 Sep 2017 01:05:28 +0000 (01:05 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 13 Sep 2017 01:05:28 +0000 (01:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176672
<rdar://problem/34353723>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Makes several tweaks to support DataTransfer.getData and DataTransfer.setData when dragging and dropping on iOS.
See per-method changes below for more details. This patch also renames some old variable and property names
along the way, so they no longer reference "data interaction", and instead refer to the feature by its post-WWDC
name.

New test: DataInteractionTests.ExternalSourceInlineTextToFileInput
Modified: DataInteractionTests.CanStartDragOnDivWithDraggableAttribute
          DataInteractionTests.SinglePlainTextURLTypeIdentifiers
          DataInteractionTests.SinglePlainTextWordTypeIdentifiers

* platform/ios/AbstractPasteboard.h:
* platform/ios/PasteboardIOS.mm:
(WebCore::cocoaTypeFromHTMLClipboardType):

In cocoaTypeFromHTMLClipboardType, map the "text/plain" MIME type to the "public.plain-text" UTI. Previously,
this corresponded to "public.text", which is incorrect, since "public.text" also includes non-plain-text types
such as "public.html", thereby confusing NSItemProviders. Importantly, this makes it so that plain text strings
written via DataTransfer.setData() can actually be read back as a cocoa value, since "public.plain-text" is one
of the UTIs in +[NSString readableTypeIdentifiersForItemProvider].

(WebCore::Pasteboard::writeString):

Instead of writing { type : data } to the pasteboard, write { cocoaType : data }. It appears that this was
changed unintentionally in r156588 when upstreaming the iOS pasteboard implementation. This is made apparent by
how Pasteboard::readString() requests the cocoa UTI from the platform pasteboard, but Pasteboard::writeString()
sends the MIME type.

* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::PlatformPasteboard::filenamesForDataInteraction):
(WebCore::PlatformPasteboard::write):

When writing plain text or a URL, specify that the item wants inline style representation. This prevents odd and
unexpected behaviors (for instance, being able to drag plain text into the Files app as a file), but it also
makes getData() not bail and return the null string on drop, due to forFileDrag() being true in
DataTransfer::getData().

* platform/ios/WebItemProviderPasteboard.h:
* platform/ios/WebItemProviderPasteboard.mm:
(-[WebItemProviderRegistrationInfoList init]):
(uiPreferredPresentationStyle):
(-[WebItemProviderRegistrationInfoList itemProvider]):

Set the preferred presentation style when generating an item provider from a registration list.

(+[WebItemProviderLoadResult emptyLoadResult]):
(+[WebItemProviderLoadResult loadResultWithFileURLMap:presentationStyle:]):
(-[WebItemProviderLoadResult initWithFileURLMap:presentationStyle:]):
(-[WebItemProviderLoadResult fileURLForType:]):
(-[WebItemProviderLoadResult loadedFileURLs]):
(-[WebItemProviderLoadResult loadedTypeIdentifiers]):

Introduce WebItemProviderLoadResult, an object that encapsulates information needed to represent the contents of
an NSItemProvider dropped in web content. Previously, WebItemProviderPasteboard maintained an array of
dictionaries of UTI => file URL, where each dictionary represents where the dropped data for a given item
provider lives. Now that we additionally need to remember (for each item provider) whether we should consider
its data as a file upload, it's more helpful to have a separate object representing the "load results" of a
dropped item provider.

(-[WebItemProviderPasteboard init]):
(-[WebItemProviderPasteboard pasteboardTypes]):
(-[WebItemProviderPasteboard setItemProviders:]):
(-[WebItemProviderPasteboard _preLoadedDataConformingToType:forItemProviderAtIndex:]):
(-[WebItemProviderPasteboard droppedFileURLs]):

Respect item provider load results that should not be exposed as a file to the page.

(-[WebItemProviderPasteboard numberOfFiles]):

Respect item providers with UIPreferredPresentationStyleInline by not counting them towards the number of files.

(-[WebItemProviderPasteboard doAfterLoadingProvidedContentIntoFileURLs:synchronousTimeout:]):

Adjust for the transition from an array of dictionaries representing loaded item providers to an array of
WebItemProviderLoadResults.

(-[WebItemProviderPasteboard fileURLsForDataInteraction]): Deleted.
* platform/mac/DragDataMac.mm:
(WebCore::DragData::containsFiles const):

DragData::containsFiles previously only considered whether or not particular UTIs appear in the pasteboard. In
the case of Mac, this is NSFilesPromisePboardType and NSFilenamesPboardType, but in the case of iOS, this is a
much broader category (anything conforming to "public.content"), since files are not exposed explicitly as
"promise" or "file" types in the list of registered UTIs. This caused us to always bail in
DataTransfer.getData() on drop on iOS, since we will always believe there's a file on the pasteboard if there's
anything conforming to "public.content" at all.

To fix this and simplify the code at the same time, we simply replace the currently implementation of
DragData::containsFiles to return true iff the number of files is nonzero. On Mac, DragData::numberOfFiles
checks the same UTIs as DragData::containsFiles (NSFilesPromisePboardType and NSFilenamesPboardType), but
additionally counts the number of file URLs corresponding to those UTIs.

On iOS, the implementation of numberOfFiles is new to iOS 11, and relevant only in the drag and drop flow.
Previously, we would consider an item provider to "contain" a file if it had a UTI conforming to one of the UTIs
acceptable for drag and drop (at the time of writing, these are ["public.content", "public.zip",
"public.folder"]). With this patch, anything conforming to these UTIs will continue to be represented as files,
but importantly, if an item provider indicates that it should be represented inline (i.e. a plain text
selection), then we don't consider that item provider as vending a file. This allows us to distinguish between
cases where we are dragging a plain text selection over a file input, and when we are dragging a plain text file.
In both cases, "public.plain-text" is offered as a registered UTI, but in the former, the item provider should
indicate that inline presentation style is preferred. Refer to <rdar://problem/32202542> for more details.

Tools:

Adds new tests and tweaks existing DataInteractionTests to cover the tweaks made in this patch.
SinglePlainTextURLTypeIdentifiers: Verify that inline presentation style is requested when dragging plaintext.
SinglePlainTextWordTypeIdentifiers: Verify that inline presentation style is requested when dragging a link.
ExternalSourceInlineTextToFileInput:
        Verify that an item provider marked as preferring inline presentation does not trigger file uploads by
        dragging a piece of inline text into a file input.
CanStartDragOnDivWithDraggableAttribute:
        Verify that DataTransfer.setData and DataTransfer.getData work as expected by moving a draggable div.
        The test harness writes the id of the draggable div via the DataTransfer, and upon drop, reads the id
        back to figure out which element to append to the drop destination.

* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(TestWebKitAPI::TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@221952 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/ios/AbstractPasteboard.h
Source/WebCore/platform/ios/PasteboardIOS.mm
Source/WebCore/platform/ios/PlatformPasteboardIOS.mm
Source/WebCore/platform/ios/WebItemProviderPasteboard.h
Source/WebCore/platform/ios/WebItemProviderPasteboard.mm
Source/WebCore/platform/mac/DragDataMac.mm
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm

index 32b5b37..5eee35d 100644 (file)
@@ -1,3 +1,112 @@
+2017-09-12  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Support DataTransfer.getData and DataTransfer.setData when dragging or dropping
+        https://bugs.webkit.org/show_bug.cgi?id=176672
+        <rdar://problem/34353723>
+
+        Reviewed by Ryosuke Niwa.
+
+        Makes several tweaks to support DataTransfer.getData and DataTransfer.setData when dragging and dropping on iOS.
+        See per-method changes below for more details. This patch also renames some old variable and property names
+        along the way, so they no longer reference "data interaction", and instead refer to the feature by its post-WWDC
+        name.
+
+        New test: DataInteractionTests.ExternalSourceInlineTextToFileInput
+        Modified: DataInteractionTests.CanStartDragOnDivWithDraggableAttribute
+                  DataInteractionTests.SinglePlainTextURLTypeIdentifiers
+                  DataInteractionTests.SinglePlainTextWordTypeIdentifiers
+
+        * platform/ios/AbstractPasteboard.h:
+        * platform/ios/PasteboardIOS.mm:
+        (WebCore::cocoaTypeFromHTMLClipboardType):
+
+        In cocoaTypeFromHTMLClipboardType, map the "text/plain" MIME type to the "public.plain-text" UTI. Previously,
+        this corresponded to "public.text", which is incorrect, since "public.text" also includes non-plain-text types
+        such as "public.html", thereby confusing NSItemProviders. Importantly, this makes it so that plain text strings
+        written via DataTransfer.setData() can actually be read back as a cocoa value, since "public.plain-text" is one
+        of the UTIs in +[NSString readableTypeIdentifiersForItemProvider].
+
+        (WebCore::Pasteboard::writeString):
+
+        Instead of writing { type : data } to the pasteboard, write { cocoaType : data }. It appears that this was
+        changed unintentionally in r156588 when upstreaming the iOS pasteboard implementation. This is made apparent by
+        how Pasteboard::readString() requests the cocoa UTI from the platform pasteboard, but Pasteboard::writeString()
+        sends the MIME type.
+
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::PlatformPasteboard::filenamesForDataInteraction):
+        (WebCore::PlatformPasteboard::write):
+
+        When writing plain text or a URL, specify that the item wants inline style representation. This prevents odd and
+        unexpected behaviors (for instance, being able to drag plain text into the Files app as a file), but it also
+        makes getData() not bail and return the null string on drop, due to forFileDrag() being true in
+        DataTransfer::getData().
+
+        * platform/ios/WebItemProviderPasteboard.h:
+        * platform/ios/WebItemProviderPasteboard.mm:
+        (-[WebItemProviderRegistrationInfoList init]):
+        (uiPreferredPresentationStyle):
+        (-[WebItemProviderRegistrationInfoList itemProvider]):
+
+        Set the preferred presentation style when generating an item provider from a registration list.
+
+        (+[WebItemProviderLoadResult emptyLoadResult]):
+        (+[WebItemProviderLoadResult loadResultWithFileURLMap:presentationStyle:]):
+        (-[WebItemProviderLoadResult initWithFileURLMap:presentationStyle:]):
+        (-[WebItemProviderLoadResult fileURLForType:]):
+        (-[WebItemProviderLoadResult loadedFileURLs]):
+        (-[WebItemProviderLoadResult loadedTypeIdentifiers]):
+
+        Introduce WebItemProviderLoadResult, an object that encapsulates information needed to represent the contents of
+        an NSItemProvider dropped in web content. Previously, WebItemProviderPasteboard maintained an array of
+        dictionaries of UTI => file URL, where each dictionary represents where the dropped data for a given item
+        provider lives. Now that we additionally need to remember (for each item provider) whether we should consider
+        its data as a file upload, it's more helpful to have a separate object representing the "load results" of a
+        dropped item provider.
+
+        (-[WebItemProviderPasteboard init]):
+        (-[WebItemProviderPasteboard pasteboardTypes]):
+        (-[WebItemProviderPasteboard setItemProviders:]):
+        (-[WebItemProviderPasteboard _preLoadedDataConformingToType:forItemProviderAtIndex:]):
+        (-[WebItemProviderPasteboard droppedFileURLs]):
+
+        Respect item provider load results that should not be exposed as a file to the page.
+
+        (-[WebItemProviderPasteboard numberOfFiles]):
+
+        Respect item providers with UIPreferredPresentationStyleInline by not counting them towards the number of files.
+
+        (-[WebItemProviderPasteboard doAfterLoadingProvidedContentIntoFileURLs:synchronousTimeout:]):
+
+        Adjust for the transition from an array of dictionaries representing loaded item providers to an array of
+        WebItemProviderLoadResults.
+
+        (-[WebItemProviderPasteboard fileURLsForDataInteraction]): Deleted.
+        * platform/mac/DragDataMac.mm:
+        (WebCore::DragData::containsFiles const):
+
+        DragData::containsFiles previously only considered whether or not particular UTIs appear in the pasteboard. In
+        the case of Mac, this is NSFilesPromisePboardType and NSFilenamesPboardType, but in the case of iOS, this is a
+        much broader category (anything conforming to "public.content"), since files are not exposed explicitly as
+        "promise" or "file" types in the list of registered UTIs. This caused us to always bail in
+        DataTransfer.getData() on drop on iOS, since we will always believe there's a file on the pasteboard if there's
+        anything conforming to "public.content" at all.
+
+        To fix this and simplify the code at the same time, we simply replace the currently implementation of
+        DragData::containsFiles to return true iff the number of files is nonzero. On Mac, DragData::numberOfFiles
+        checks the same UTIs as DragData::containsFiles (NSFilesPromisePboardType and NSFilenamesPboardType), but
+        additionally counts the number of file URLs corresponding to those UTIs.
+
+        On iOS, the implementation of numberOfFiles is new to iOS 11, and relevant only in the drag and drop flow.
+        Previously, we would consider an item provider to "contain" a file if it had a UTI conforming to one of the UTIs
+        acceptable for drag and drop (at the time of writing, these are ["public.content", "public.zip",
+        "public.folder"]). With this patch, anything conforming to these UTIs will continue to be represented as files,
+        but importantly, if an item provider indicates that it should be represented inline (i.e. a plain text
+        selection), then we don't consider that item provider as vending a file. This allows us to distinguish between
+        cases where we are dragging a plain text selection over a file input, and when we are dragging a plain text file.
+        In both cases, "public.plain-text" is offered as a registered UTI, but in the former, the item provider should
+        indicate that inline presentation style is preferred. Refer to <rdar://problem/32202542> for more details.
+
 2017-09-12  Joseph Pecoraro  <pecoraro@apple.com>
 
         QualifiedName::init should assume AtomicStrings::init was already called
index 9a88e78..acccfb0 100644 (file)
@@ -55,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)setItems:(NSArray<NSDictionary *> *)items;
 - (NSArray<NSString *> *)pasteboardTypesByFidelityForItemAtIndex:(NSUInteger)index;
 @property (readonly, nonatomic) NSInteger numberOfFiles;
-@property (readonly, nonatomic) NSArray<NSURL *> *fileURLsForDataInteraction;
+@property (readonly, nonatomic) NSArray<NSURL *> *droppedFileURLs;
 - (void)updateSupportedTypeIdentifiers:(NSArray<NSString *> *)types;
 
 @end
index 31b0482..b48d1a0 100644 (file)
@@ -331,7 +331,7 @@ static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
 {
     // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue.
     if (type == "text/plain")
-        return (NSString *)kUTTypeText;
+        return (NSString *)kUTTypePlainText;
 
     // Special case because UTI doesn't work with Cocoa's URL type.
     if (type == "text/uri-list")
@@ -423,7 +423,7 @@ void Pasteboard::writeString(const String& type, const String& data)
     if (!cocoaType)
         return;
 
-    platformStrategies()->pasteboardStrategy()->writeToPasteboard(type, data, m_pasteboardName);
+    platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName);
 }
 
 Vector<String> Pasteboard::types()
index 91f6c9a..ab9f1a0 100644 (file)
@@ -97,11 +97,11 @@ int PlatformPasteboard::numberOfFiles()
 
 Vector<String> PlatformPasteboard::filenamesForDataInteraction()
 {
-    if (![m_pasteboard respondsToSelector:@selector(fileURLsForDataInteraction)])
+    if (![m_pasteboard respondsToSelector:@selector(droppedFileURLs)])
         return { };
 
     Vector<String> filenames;
-    for (NSURL *fileURL in [m_pasteboard fileURLsForDataInteraction])
+    for (NSURL *fileURL in [m_pasteboard droppedFileURLs])
         filenames.append(fileURL.path);
 
     return filenames;
@@ -344,6 +344,7 @@ void PlatformPasteboard::write(const String& pasteboardType, const String& text)
 {
 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
+    [representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
 
     NSString *pasteboardTypeAsNSString = pasteboardType;
     if (!text.isEmpty() && pasteboardTypeAsNSString.length) {
@@ -381,6 +382,7 @@ void PlatformPasteboard::write(const PasteboardURL& url)
 {
 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
+    [representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
 
     if (NSURL *nsURL = url.url) {
         if (!url.title.isEmpty())
index b92a274..275ea45 100644 (file)
 
 struct CGSize;
 
+typedef NS_ENUM(NSInteger, WebPreferredPresentationStyle) {
+    WebPreferredPresentationStyleUnspecified,
+    WebPreferredPresentationStyleInline,
+    WebPreferredPresentationStyleAttachment
+};
+
 NS_ASSUME_NONNULL_BEGIN
 
 /*! A WebItemProviderRegistrationInfo represents a single call to register something to an item provider.
@@ -64,6 +70,8 @@ WEBCORE_EXPORT @interface WebItemProviderRegistrationInfoList : NSObject
 @property (nonatomic, copy) NSString *suggestedName;
 @property (nonatomic, readonly, nullable) UIItemProvider *itemProvider;
 
+@property (nonatomic) WebPreferredPresentationStyle preferredPresentationStyle;
+
 - (NSUInteger)numberOfItems;
 - (nullable WebItemProviderRegistrationInfo *)itemAtIndex:(NSUInteger)index;
 - (void)enumerateItems:(void(^)(WebItemProviderRegistrationInfo *item, NSUInteger index))block;
@@ -81,7 +89,7 @@ WEBCORE_EXPORT @interface WebItemProviderPasteboard : NSObject<AbstractPasteboar
 @property (readonly, nonatomic) NSInteger changeCount;
 
 // This will only be non-empty when an operation is being performed.
-@property (readonly, nonatomic) NSArray<NSURL *> *fileURLsForDataInteraction;
+@property (readonly, nonatomic) NSArray<NSURL *> *droppedFileURLs;
 
 @property (readonly, nonatomic) BOOL hasPendingOperation;
 - (void)incrementPendingOperationCount;
index 1af13d4..4b9c3a5 100644 (file)
@@ -107,6 +107,7 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
     if (self = [super init]) {
         _representations = adoptNS([[NSMutableArray alloc] init]);
         _preferredPresentationSize = CGSizeZero;
+        _preferredPresentationStyle = WebPreferredPresentationStyleUnspecified;
     }
 
     return self;
@@ -148,6 +149,21 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
         block([self itemAtIndex:index], index);
 }
 
+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;
+    }
+}
+
 - (UIItemProvider *)itemProvider
 {
     if (!self.numberOfItems)
@@ -171,6 +187,7 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
     }
     [itemProvider setPreferredPresentationSize:self.preferredPresentationSize];
     [itemProvider setSuggestedName:self.suggestedName];
+    [itemProvider setPreferredPresentationStyle:uiPreferredPresentationStyle(self.preferredPresentationStyle)];
     return itemProvider.autorelease();
 }
 
@@ -193,6 +210,60 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
 
 @end
 
+@interface WebItemProviderLoadResult : NSObject
+
++ (instancetype)loadResultWithFileURLMap:(TypeToFileURLMap *)fileURLs presentationStyle:(UIPreferredPresentationStyle)presentationStyle;
++ (instancetype)emptyLoadResult;
+
+- (NSURL *)fileURLForType:(NSString *)type;
+@property (nonatomic, readonly) NSArray<NSURL *> *loadedFileURLs;
+@property (nonatomic, readonly) NSArray<NSString *> *loadedTypeIdentifiers;
+@property (nonatomic, readonly) BOOL canBeRepresentedAsFileUpload;
+
+@end
+
+@implementation WebItemProviderLoadResult {
+    RetainPtr<TypeToFileURLMap> _fileURLs;
+}
+
++ (instancetype)emptyLoadResult
+{
+    return [[[self alloc] initWithFileURLMap:@{ } presentationStyle:UIPreferredPresentationStyleUnspecified] autorelease];
+}
+
++ (instancetype)loadResultWithFileURLMap:(TypeToFileURLMap *)fileURLs presentationStyle:(UIPreferredPresentationStyle)presentationStyle
+{
+    return [[[self alloc] initWithFileURLMap:fileURLs presentationStyle:presentationStyle] autorelease];
+}
+
+- (instancetype)initWithFileURLMap:(TypeToFileURLMap *)fileURLs presentationStyle:(UIPreferredPresentationStyle)presentationStyle
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _fileURLs = fileURLs;
+    _canBeRepresentedAsFileUpload = presentationStyle != UIPreferredPresentationStyleInline;
+
+    return self;
+}
+
+- (NSURL *)fileURLForType:(NSString *)type
+{
+    return [_fileURLs objectForKey:type];
+}
+
+- (NSArray<NSURL *> *)loadedFileURLs
+{
+    return [_fileURLs allValues];
+}
+
+- (NSArray<NSString *> *)loadedTypeIdentifiers
+{
+    return [_fileURLs allKeys];
+}
+
+@end
+
 @interface WebItemProviderPasteboard ()
 
 @property (nonatomic) NSInteger numberOfItems;
@@ -204,10 +275,10 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
 @implementation WebItemProviderPasteboard {
     // FIXME: These ivars should be refactored to be Vector<RetainPtr<Type>> instead of generic NSArrays.
     RetainPtr<NSArray> _itemProviders;
-    RetainPtr<NSArray> _cachedTypeIdentifiers;
-    RetainPtr<NSArray> _typeToFileURLMaps;
     RetainPtr<NSArray> _supportedTypeIdentifiers;
     RetainPtr<WebItemProviderRegistrationInfoList> _stagedRegistrationInfoList;
+
+    Vector<RetainPtr<WebItemProviderLoadResult>> _loadResults;
 }
 
 + (instancetype)sharedInstance
@@ -226,9 +297,9 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
         _itemProviders = adoptNS([[NSArray alloc] init]);
         _changeCount = 0;
         _pendingOperationCount = 0;
-        _typeToFileURLMaps = adoptNS([[NSArray alloc] init]);
         _supportedTypeIdentifiers = nil;
         _stagedRegistrationInfoList = nil;
+        _loadResults = { };
     }
     return self;
 }
@@ -245,9 +316,6 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
 
 - (NSArray<NSString *> *)pasteboardTypes
 {
-    if (_cachedTypeIdentifiers)
-        return _cachedTypeIdentifiers.get();
-
     NSMutableSet<NSString *> *allTypes = [NSMutableSet set];
     NSMutableArray<NSString *> *allTypesInOrder = [NSMutableArray array];
     for (UIItemProvider *provider in _itemProviders.get()) {
@@ -259,8 +327,7 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
             [allTypesInOrder addObject:typeIdentifier];
         }
     }
-    _cachedTypeIdentifiers = allTypesInOrder;
-    return _cachedTypeIdentifiers.get();
+    return allTypesInOrder;
 }
 
 - (NSArray<__kindof NSItemProvider *> *)itemProviders
@@ -276,12 +343,6 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
 
     _itemProviders = itemProviders;
     _changeCount++;
-    _cachedTypeIdentifiers = nil;
-
-    NSMutableArray *typeToFileURLMaps = [NSMutableArray arrayWithCapacity:itemProviders.count];
-    [itemProviders enumerateObjectsUsingBlock:[typeToFileURLMaps] (UIItemProvider *, NSUInteger, BOOL *) {
-        [typeToFileURLMaps addObject:@{ }];
-    }];
 }
 
 - (NSInteger)numberOfItems
@@ -291,18 +352,18 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
 
 - (NSData *)_preLoadedDataConformingToType:(NSString *)typeIdentifier forItemProviderAtIndex:(NSUInteger)index
 {
-    if ([_typeToFileURLMaps count] != [_itemProviders count]) {
+    if (_loadResults.size() != [_itemProviders count]) {
         ASSERT_NOT_REACHED();
         return nil;
     }
 
-    TypeToFileURLMap *typeToFileURLMap = [_typeToFileURLMaps objectAtIndex:index];
-    for (NSString *loadedType in typeToFileURLMap) {
+    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 UIItemProvider for the same data again.
-        if (NSData *result = [NSData dataWithContentsOfURL:typeToFileURLMap[loadedType] options:NSDataReadingMappedIfSafe error:nil])
+        if (NSData *result = [NSData dataWithContentsOfURL:[loadResult fileURLForType:loadedType] options:NSDataReadingMappedIfSafe error:nil])
             return result;
     }
     return nil;
@@ -379,11 +440,13 @@ static Class classForTypeIdentifier(NSString *typeIdentifier, NSString *&outType
     return _changeCount;
 }
 
-- (NSArray<NSURL *> *)fileURLsForDataInteraction
+- (NSArray<NSURL *> *)droppedFileURLs
 {
     NSMutableArray<NSURL *> *fileURLs = [NSMutableArray array];
-    for (TypeToFileURLMap *typeToFileURLMap in _typeToFileURLMaps.get())
-        [fileURLs addObjectsFromArray:[typeToFileURLMap allValues]];
+    for (auto loadResult : _loadResults) {
+        if ([loadResult canBeRepresentedAsFileUpload])
+            [fileURLs addObjectsFromArray:[loadResult loadedFileURLs]];
+    }
     return fileURLs;
 }
 
@@ -402,6 +465,9 @@ static BOOL typeConformsToTypes(NSString *type, NSArray *conformsToTypes)
     NSArray *supportedFileTypes = Pasteboard::supportedFileUploadPasteboardTypes();
     NSInteger numberOfFiles = 0;
     for (UIItemProvider *itemProvider in _itemProviders.get()) {
+        if (itemProvider.preferredPresentationStyle == UIPreferredPresentationStyleInline)
+            continue;
+
         for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
             if (!typeConformsToTypes(identifier, supportedFileTypes))
                 continue;
@@ -458,22 +524,24 @@ static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, N
 
 - (void)doAfterLoadingProvidedContentIntoFileURLs:(WebItemProviderFileLoadBlock)action synchronousTimeout:(NSTimeInterval)synchronousTimeout
 {
+    _loadResults.clear();
+
     auto changeCountBeforeLoading = _changeCount;
-    auto typeToFileURLMaps = adoptNS([[NSMutableArray alloc] initWithCapacity:[_itemProviders count]]);
+    auto loadResults = adoptNS([[NSMutableArray alloc] initWithCapacity:[_itemProviders count]]);
 
     // First, figure out which item providers we want to try and load files from.
     auto itemProvidersToLoad = adoptNS([[NSMutableArray alloc] init]);
     auto typeIdentifiersToLoad = adoptNS([[NSMutableArray alloc] init]);
     auto indicesOfitemProvidersToLoad = adoptNS([[NSMutableArray alloc] init]);
     RetainPtr<WebItemProviderPasteboard> protectedSelf = self;
-    [_itemProviders enumerateObjectsUsingBlock:[protectedSelf, itemProvidersToLoad, typeIdentifiersToLoad, indicesOfitemProvidersToLoad, typeToFileURLMaps] (UIItemProvider *itemProvider, NSUInteger index, BOOL *) {
+    [_itemProviders enumerateObjectsUsingBlock:[protectedSelf, itemProvidersToLoad, typeIdentifiersToLoad, indicesOfitemProvidersToLoad, loadResults] (UIItemProvider *itemProvider, NSUInteger index, BOOL *) {
         NSString *typeIdentifierToLoad = [protectedSelf typeIdentifierToLoadForRegisteredTypeIdentfiers:itemProvider.registeredTypeIdentifiers];
         if (typeIdentifierToLoad) {
             [itemProvidersToLoad addObject:itemProvider];
             [typeIdentifiersToLoad addObject:typeIdentifierToLoad];
             [indicesOfitemProvidersToLoad addObject:@(index)];
         }
-        [typeToFileURLMaps addObject:@{ }];
+        [loadResults addObject:[WebItemProviderLoadResult emptyLoadResult]];
     }];
 
     if (![itemProvidersToLoad count]) {
@@ -489,15 +557,17 @@ static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, N
         RetainPtr<NSString> typeIdentifier = [typeIdentifiersToLoad objectAtIndex:index];
         NSUInteger indexInItemProviderArray = [[indicesOfitemProvidersToLoad objectAtIndex:index] unsignedIntegerValue];
         RetainPtr<NSString> suggestedName = [itemProvider suggestedName];
+        auto presentationStyle = [itemProvider preferredPresentationStyle];
         dispatch_group_enter(fileLoadingGroup.get());
         dispatch_group_enter(synchronousFileLoadingGroup.get());
-        [itemProvider loadFileRepresentationForTypeIdentifier:typeIdentifier.get() completionHandler:[synchronousFileLoadingGroup, setFileURLsLock, indexInItemProviderArray, suggestedName, typeIdentifier, typeToFileURLMaps, fileLoadingGroup] (NSURL *url, NSError *) {
+        [itemProvider loadFileRepresentationForTypeIdentifier:typeIdentifier.get() completionHandler:[synchronousFileLoadingGroup, setFileURLsLock, indexInItemProviderArray, suggestedName, typeIdentifier, loadResults, fileLoadingGroup, presentationStyle] (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, suggestedName.get(), typeIdentifier.get())) {
+                WebItemProviderLoadResult *loadResult = [WebItemProviderLoadResult loadResultWithFileURLMap:@{ typeIdentifier.get() : destination } presentationStyle:presentationStyle];
                 [setFileURLsLock lock];
-                [typeToFileURLMaps setObject:[NSDictionary dictionaryWithObject:destination forKey:typeIdentifier.get()] atIndexedSubscript:indexInItemProviderArray];
+                [loadResults setObject:loadResult atIndexedSubscript:indexInItemProviderArray];
                 [setFileURLsLock unlock];
             }
             dispatch_group_leave(fileLoadingGroup.get());
@@ -506,11 +576,13 @@ static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, N
     }
 
     RetainPtr<WebItemProviderPasteboard> retainedSelf = self;
-    auto itemLoadCompletion = [retainedSelf, synchronousFileLoadingGroup, fileLoadingGroup, typeToFileURLMaps, completionBlock = makeBlockPtr(action), changeCountBeforeLoading] {
-        if (changeCountBeforeLoading == retainedSelf->_changeCount)
-            retainedSelf->_typeToFileURLMaps = typeToFileURLMaps;
+    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 fileURLsForDataInteraction]);
+        completionBlock([retainedSelf droppedFileURLs]);
     };
 
     if (synchronousTimeout > 0 && !dispatch_group_wait(synchronousFileLoadingGroup.get(), dispatch_time(DISPATCH_TIME_NOW, synchronousTimeout * NSEC_PER_SEC))) {
index 8e56b5f..8ff4132 100644 (file)
@@ -160,17 +160,7 @@ bool DragData::containsColor() const
 
 bool DragData::containsFiles() const
 {
-    NSArray *supportedFileTypes = Pasteboard::supportedFileUploadPasteboardTypes();
-    Vector<String> types;
-    platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
-    for (auto& type : types) {
-        auto cfType = type.createCFString();
-        for (NSString *fileType in supportedFileTypes) {
-            if (UTTypeConformsTo(cfType.get(), (CFStringRef)fileType))
-                return true;
-        }
-    }
-    return false;
+    return numberOfFiles();
 }
 
 unsigned DragData::numberOfFiles() const
index 283a1d6..ca1c48f 100644 (file)
@@ -1,3 +1,25 @@
+2017-09-12  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Support DataTransfer.getData and DataTransfer.setData when dragging or dropping
+        https://bugs.webkit.org/show_bug.cgi?id=176672
+        <rdar://problem/34353723>
+
+        Reviewed by Ryosuke Niwa.
+
+        Adds new tests and tweaks existing DataInteractionTests to cover the tweaks made in this patch.
+        SinglePlainTextURLTypeIdentifiers: Verify that inline presentation style is requested when dragging plaintext.
+        SinglePlainTextWordTypeIdentifiers: Verify that inline presentation style is requested when dragging a link.
+        ExternalSourceInlineTextToFileInput:
+                Verify that an item provider marked as preferring inline presentation does not trigger file uploads by
+                dragging a piece of inline text into a file input.
+        CanStartDragOnDivWithDraggableAttribute:
+                Verify that DataTransfer.setData and DataTransfer.getData work as expected by moving a draggable div.
+                The test harness writes the id of the draggable div via the DataTransfer, and upon drop, reads the id
+                back to figure out which element to append to the drop destination.
+
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (TestWebKitAPI::TEST):
+
 2017-09-12  Ryosuke Niwa  <rniwa@webkit.org>
 
         Dragging & dropping a file creates an attachment element even when it's disabled
index 574e3eb..6532579 100644 (file)
@@ -411,10 +411,12 @@ TEST(DataInteractionTests, SinglePlainTextWordTypeIdentifiers)
     [webView stringByEvaluatingJavaScript:@"source.selectionEnd = source.value.length"];
     [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
 
-    NSArray *registeredTypes = [[dataInteractionSimulator sourceItemProviders].firstObject registeredTypeIdentifiers];
+    NSItemProvider *itemProvider = [dataInteractionSimulator sourceItemProviders].firstObject;
+    NSArray *registeredTypes = [itemProvider registeredTypeIdentifiers];
     EXPECT_EQ(1UL, registeredTypes.count);
     EXPECT_WK_STREQ([(NSString *)kUTTypeUTF8PlainText UTF8String], [registeredTypes.firstObject UTF8String]);
     EXPECT_EQ([webView stringByEvaluatingJavaScript:@"source.value"].length, 0UL);
+    EXPECT_EQ(UIPreferredPresentationStyleInline, itemProvider.preferredPresentationStyle);
     EXPECT_WK_STREQ("pneumonoultramicroscopicsilicovolcanoconiosis", [webView editorValue].UTF8String);
 }
 
@@ -430,11 +432,13 @@ TEST(DataInteractionTests, SinglePlainTextURLTypeIdentifiers)
     [webView stringByEvaluatingJavaScript:@"source.selectionEnd = source.value.length"];
     [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
 
-    NSArray *registeredTypes = [[dataInteractionSimulator sourceItemProviders].firstObject registeredTypeIdentifiers];
+    NSItemProvider *itemProvider = [dataInteractionSimulator sourceItemProviders].firstObject;
+    NSArray *registeredTypes = [itemProvider registeredTypeIdentifiers];
     EXPECT_EQ(2UL, registeredTypes.count);
     EXPECT_WK_STREQ([(NSString *)kUTTypeURL UTF8String], [registeredTypes.firstObject UTF8String]);
     EXPECT_WK_STREQ([(NSString *)kUTTypeUTF8PlainText UTF8String], [registeredTypes.lastObject UTF8String]);
     EXPECT_EQ(0UL, [webView stringByEvaluatingJavaScript:@"source.value"].length);
+    EXPECT_EQ(UIPreferredPresentationStyleInline, itemProvider.preferredPresentationStyle);
     EXPECT_WK_STREQ("https://webkit.org/", [webView editorValue].UTF8String);
 }
 
@@ -540,9 +544,13 @@ TEST(DataInteractionTests, CanStartDragOnDivWithDraggableAttribute)
     [webView synchronouslyLoadTestPageNamed:@"custom-draggable-div"];
 
     auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
-    [dataInteractionSimulator runFrom:CGPointMake(100, 100) to:CGPointMake(250, 100)];
+    [dataInteractionSimulator runFrom:CGPointMake(100, 100) to:CGPointMake(100, 250)];
 
     EXPECT_GT([dataInteractionSimulator sourceItemProviders].count, 0UL);
+    NSItemProvider *itemProvider = [dataInteractionSimulator sourceItemProviders].firstObject;
+    EXPECT_EQ(UIPreferredPresentationStyleInline, itemProvider.preferredPresentationStyle);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"!!destination.querySelector('#item')"]);
+    EXPECT_WK_STREQ(@"PASS", [webView stringByEvaluatingJavaScript:@"item.textContent"]);
 }
 
 TEST(DataInteractionTests, ExternalSourcePlainTextToIFrame)
@@ -564,6 +572,22 @@ TEST(DataInteractionTests, ExternalSourcePlainTextToIFrame)
     checkDragCaretRectIsContainedInRect([simulator lastKnownDragCaretRect], CGRectMake(containerLeft, containerTop, containerWidth, containerHeight));
 }
 
+TEST(DataInteractionTests, ExternalSourceInlineTextToFileInput)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+    auto simulatedItemProvider = adoptNS([[UIItemProvider alloc] init]);
+    [simulatedItemProvider setPreferredPresentationStyle:UIPreferredPresentationStyleInline];
+    [simulatedItemProvider registerObject:@"This item provider requested inline presentation style." visibility:NSItemProviderRepresentationVisibilityAll];
+
+    auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    [dataInteractionSimulator setExternalItemProviders:@[ simulatedItemProvider.get() ]];
+    [dataInteractionSimulator runFrom:CGPointMake(200, 100) to:CGPointMake(100, 100)];
+
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"output.value"]);
+}
+
 TEST(DataInteractionTests, ExternalSourceJSONToFileInput)
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);