Changeset 221751 in webkit


Ignore:
Timestamp:
Sep 7, 2017 1:12:18 PM (7 years ago)
Author:
Wenson Hsieh
Message:

[Directory Upload] Extend drag and drop support to iOS
https://bugs.webkit.org/show_bug.cgi?id=176492
<rdar://problem/34291584>

Reviewed by Tim Horton.

Source/WebCore:

Adds support for accepting dropped folders on iOS.

Tests: DataInteractionTests.ExternalSourceDataTransferItemGetFolderAsEntry

DataInteractionTests.ExternalSourceDataTransferItemGetPlainTextFileAsEntry

  • platform/ios/PasteboardIOS.mm:

(WebCore::Pasteboard::supportedFileUploadPasteboardTypes):

Add "public.folder" as a compatible pasteboard type for drops on iOS. This means file inputs and custom drop
targets that preventDefault() will, by default, be able to accept incoming folders.

  • platform/ios/WebItemProviderPasteboard.mm:

(linkTemporaryItemProviderFilesToDropStagingDirectory):

Tweak temporaryFileURLForDataInteractionContent to also hard link UIKit's temporary files instead, and return
a non-null destination URL only if the necessary file operations succeeded. Also renames this helper to
linkTemporaryItemProviderFilesToDropStagingDirectory to better reflect its new purpose. This makes logic much
cleaner at the call site, which no longer checks against various conditions before proceeding to set the data
transfer URL.

(-[WebItemProviderPasteboard doAfterLoadingProvidedContentIntoFileURLs:synchronousTimeout:]):
(temporaryFileURLForDataInteractionContent): Deleted.

Tools:

Adds two new iOS drag and drop unit tests, which both exercise the DataTransferItem.webKitGetAsEntry codepath
upon drop. (...)GetFolderAsEntry creates a new folder in the temporary directory and uses it to generate an item
provider. This item provider is then dropped over a custom drop handling element, which writes information about
the exposed FileSystemEntries into a textarea. (...)ExternalSourceDataTransferItemGetPlainTextFileAsEntry does
something similar, except that it only drops a plain text file instead.

  • TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
  • TestWebKitAPI/Tests/WebKitCocoa/DataTransferItem-getAsEntry.html: Added.

Introduce a new test page that dumps information about DataTransferItems' file system entries upon drop.

  • TestWebKitAPI/Tests/ios/DataInteractionTests.mm:

(runTestWithTemporaryTextFile):
(runTestWithTemporaryFolder):

Introduce helpers to set up and tear down temporary files and folders over the duration of a test.

(TestWebKitAPI::setUpTestWebViewForDataTransferItems):
(TestWebKitAPI::TEST):

Location:
trunk
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r221743 r221751  
     12017-09-07  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [Directory Upload] Extend drag and drop support to iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=176492
     5        <rdar://problem/34291584>
     6
     7        Reviewed by Tim Horton.
     8
     9        Adds support for accepting dropped folders on iOS.
     10
     11        Tests: DataInteractionTests.ExternalSourceDataTransferItemGetFolderAsEntry
     12               DataInteractionTests.ExternalSourceDataTransferItemGetPlainTextFileAsEntry
     13
     14        * platform/ios/PasteboardIOS.mm:
     15        (WebCore::Pasteboard::supportedFileUploadPasteboardTypes):
     16
     17        Add "public.folder" as a compatible pasteboard type for drops on iOS. This means file inputs and custom drop
     18        targets that preventDefault() will, by default, be able to accept incoming folders.
     19
     20        * platform/ios/WebItemProviderPasteboard.mm:
     21        (linkTemporaryItemProviderFilesToDropStagingDirectory):
     22
     23        Tweak temporaryFileURLForDataInteractionContent to also hard link UIKit's temporary files instead, and return
     24        a non-null destination URL only if the necessary file operations succeeded. Also renames this helper to
     25        linkTemporaryItemProviderFilesToDropStagingDirectory to better reflect its new purpose. This makes logic much
     26        cleaner at the call site, which no longer checks against various conditions before proceeding to set the data
     27        transfer URL.
     28
     29        (-[WebItemProviderPasteboard doAfterLoadingProvidedContentIntoFileURLs:synchronousTimeout:]):
     30        (temporaryFileURLForDataInteractionContent): Deleted.
     31
    1322017-09-07  Alex Christensen  <achristensen@webkit.org>
    233
  • trunk/Source/WebCore/platform/ios/PasteboardIOS.mm

    r221063 r221751  
    307307NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
    308308{
    309     return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive ];
     309    return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive, (NSString *)kUTTypeFolder ];
    310310}
    311311
  • trunk/Source/WebCore/platform/ios/WebItemProviderPasteboard.mm

    r221617 r221751  
    414414}
    415415
    416 static NSURL *temporaryFileURLForDataInteractionContent(NSURL *url, NSString *suggestedName)
    417 {
    418     static NSString *defaultDataInteractionFileName = @"file";
     416static NSURL *linkTemporaryItemProviderFilesToDropStagingDirectory(NSURL *url, NSString *suggestedName, NSString *typeIdentifier)
     417{
     418    static NSString *defaultDropFolderName = @"folder";
     419    static NSString *defaultDropFileName = @"file";
    419420    static NSString *dataInteractionDirectoryPrefix = @"data-interaction";
    420421    if (!url)
     
    425426        return nil;
    426427
    427     suggestedName = suggestedName ?: defaultDataInteractionFileName;
    428     if (![suggestedName containsString:@"."])
     428    NSURL *destination = nil;
     429    BOOL isFolder = UTTypeConformsTo((CFStringRef)typeIdentifier, kUTTypeFolder);
     430    NSFileManager *fileManager = [NSFileManager defaultManager];
     431
     432    if (!suggestedName)
     433        suggestedName = url.lastPathComponent ?: (isFolder ? defaultDropFolderName : defaultDropFileName);
     434
     435    if (![suggestedName containsString:@"."] && !isFolder)
    429436        suggestedName = [suggestedName stringByAppendingPathExtension:url.pathExtension];
    430437
    431     return [NSURL fileURLWithPath:[temporaryDataInteractionDirectory stringByAppendingPathComponent:suggestedName ?: url.lastPathComponent]];
     438    destination = [NSURL fileURLWithPath:[temporaryDataInteractionDirectory stringByAppendingPathComponent:suggestedName]];
     439    return [fileManager linkItemAtURL:url toURL:destination error:nil] ? destination : nil;
    432440}
    433441
     
    485493        dispatch_group_enter(fileLoadingGroup.get());
    486494        dispatch_group_enter(synchronousFileLoadingGroup.get());
    487         [itemProvider loadFileRepresentationForTypeIdentifier:typeIdentifier.get() completionHandler:[synchronousFileLoadingGroup, setFileURLsLock, indexInItemProviderArray, suggestedName, typeIdentifier, typeToFileURLMaps, fileLoadingGroup] (NSURL *url, NSError *error) {
     495        [itemProvider loadFileRepresentationForTypeIdentifier:typeIdentifier.get() completionHandler:[synchronousFileLoadingGroup, setFileURLsLock, indexInItemProviderArray, suggestedName, typeIdentifier, typeToFileURLMaps, fileLoadingGroup] (NSURL *url, NSError *) {
    488496            // 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.
    489497            // 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
    490498            // WKFileUploadPanel.mm. The temporary files are cleaned up by the system at a later time.
    491             RetainPtr<NSURL> destinationURL = temporaryFileURLForDataInteractionContent(url, suggestedName.get());
    492             if (destinationURL && !error && [[NSFileManager defaultManager] linkItemAtURL:url toURL:destinationURL.get() error:nil]) {
     499            if (NSURL *destination = linkTemporaryItemProviderFilesToDropStagingDirectory(url, suggestedName.get(), typeIdentifier.get())) {
    493500                [setFileURLsLock lock];
    494                 [typeToFileURLMaps setObject:[NSDictionary dictionaryWithObject:destinationURL.get() forKey:typeIdentifier.get()] atIndexedSubscript:indexInItemProviderArray];
     501                [typeToFileURLMaps setObject:[NSDictionary dictionaryWithObject:destination forKey:typeIdentifier.get()] atIndexedSubscript:indexInItemProviderArray];
    495502                [setFileURLsLock unlock];
    496503            }
  • trunk/Tools/ChangeLog

    r221750 r221751  
     12017-09-07  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [Directory Upload] Extend drag and drop support to iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=176492
     5        <rdar://problem/34291584>
     6
     7        Reviewed by Tim Horton.
     8
     9        Adds two new iOS drag and drop unit tests, which both exercise the DataTransferItem.webKitGetAsEntry codepath
     10        upon drop. (...)GetFolderAsEntry creates a new folder in the temporary directory and uses it to generate an item
     11        provider. This item provider is then dropped over a custom drop handling element, which writes information about
     12        the exposed FileSystemEntries into a textarea. (...)ExternalSourceDataTransferItemGetPlainTextFileAsEntry does
     13        something similar, except that it only drops a plain text file instead.
     14
     15        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
     16        * TestWebKitAPI/Tests/WebKitCocoa/DataTransferItem-getAsEntry.html: Added.
     17
     18        Introduce a new test page that dumps information about DataTransferItems' file system entries upon drop.
     19
     20        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
     21        (runTestWithTemporaryTextFile):
     22        (runTestWithTemporaryFolder):
     23
     24        Introduce helpers to set up and tear down temporary files and folders over the duration of a test.
     25
     26        (TestWebKitAPI::setUpTestWebViewForDataTransferItems):
     27        (TestWebKitAPI::TEST):
     28
    1292017-09-07  Filip Pizlo  <fpizlo@apple.com>
    230
  • trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj

    r221660 r221751  
    677677                F44D06471F39627A001A0E29 /* EditorStateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44D06461F395C4D001A0E29 /* EditorStateTests.mm */; };
    678678                F44D064A1F3962F2001A0E29 /* EditingTestHarness.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */; };
     679                F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */; };
    679680                F4538EF71E8473E600B5C953 /* large-red-square.png in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4538EF01E846B4100B5C953 /* large-red-square.png */; };
    680681                F45B63FB1F197F4A009D38B9 /* image-map.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F45B63FA1F197F33009D38B9 /* image-map.html */; };
     
    816817                                7AEAD4811E20122700416EFE /* CrossPartitionFileSchemeAccess.html in Copy Resources */,
    817818                                290F4275172A221C00939FF0 /* custom-protocol-sync-xhr.html in Copy Resources */,
     819                                F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */,
    818820                                C07E6CB213FD73930038B22B /* devicePixelRatio.html in Copy Resources */,
    819821                                0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */,
     
    17071709                F44D06481F3962E3001A0E29 /* EditingTestHarness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditingTestHarness.h; sourceTree = "<group>"; };
    17081710                F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EditingTestHarness.mm; sourceTree = "<group>"; };
     1711                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "DataTransferItem-getAsEntry.html"; sourceTree = "<group>"; };
    17091712                F4538EF01E846B4100B5C953 /* large-red-square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "large-red-square.png"; sourceTree = "<group>"; };
    17101713                F45B63FA1F197F33009D38B9 /* image-map.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "image-map.html"; sourceTree = "<group>"; };
     
    21532156                                A16F66B91C40EA2000BD4D24 /* ContentFiltering.html */,
    21542157                                5C2936941D5BFD1900DEAB1E /* CookieMessage.html */,
     2158                                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */,
    21552159                                0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */,
    21562160                                F41AB99E1EF4692C0083FA08 /* div-and-large-image.html */,
  • trunk/Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm

    r221660 r221751  
    3535#import <UIKit/NSItemProvider+UIKitAdditions.h>
    3636#import <WebKit/WKPreferencesPrivate.h>
     37#import <WebKit/WKPreferencesRefPrivate.h>
    3738#import <WebKit/WKProcessPoolPrivate.h>
    3839#import <WebKit/WKWebViewConfigurationPrivate.h>
     
    165166}
    166167
     168static void runTestWithTemporaryTextFile(void(^runTest)(NSURL *fileURL))
     169{
     170    NSString *fileName = [NSString stringWithFormat:@"drag-drop-text-file-%@.txt", [NSUUID UUID].UUIDString];
     171    RetainPtr<NSURL> temporaryFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName] isDirectory:NO];
     172    [[NSFileManager defaultManager] removeItemAtURL:temporaryFile.get() error:nil];
     173
     174    NSError *error = nil;
     175    [@"This is a tiny blob of text." writeToURL:temporaryFile.get() atomically:YES encoding:NSUTF8StringEncoding error:&error];
     176
     177    if (error)
     178        NSLog(@"Error writing temporary file: %@", error);
     179
     180    @try {
     181        runTest(temporaryFile.get());
     182    } @finally {
     183        [[NSFileManager defaultManager] removeItemAtURL:temporaryFile.get() error:nil];
     184    }
     185}
     186
     187static void runTestWithTemporaryFolder(void(^runTest)(NSURL *folderURL))
     188{
     189    NSString *folderName = [NSString stringWithFormat:@"some.directory-%@", [NSUUID UUID].UUIDString];
     190    RetainPtr<NSURL> temporaryFolder = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:folderName] isDirectory:YES];
     191    [[NSFileManager defaultManager] removeItemAtURL:temporaryFolder.get() error:nil];
     192
     193    NSError *error = nil;
     194    NSFileManager *defaultManager = [NSFileManager defaultManager];
     195    [defaultManager createDirectoryAtURL:temporaryFolder.get() withIntermediateDirectories:NO attributes:nil error:&error];
     196    [UIImagePNGRepresentation(testIconImage()) writeToURL:[temporaryFolder.get() URLByAppendingPathComponent:@"icon.png" isDirectory:NO] atomically:YES];
     197    [testZIPArchive() writeToURL:[temporaryFolder.get() URLByAppendingPathComponent:@"archive.zip" isDirectory:NO] atomically:YES];
     198
     199    NSURL *firstSubdirectory = [temporaryFolder.get() URLByAppendingPathComponent:@"subdirectory1" isDirectory:YES];
     200    [defaultManager createDirectoryAtURL:firstSubdirectory withIntermediateDirectories:NO attributes:nil error:&error];
     201    [@"I am a text file in the first subdirectory." writeToURL:[firstSubdirectory URLByAppendingPathComponent:@"text-file-1.txt" isDirectory:NO] atomically:YES encoding:NSUTF8StringEncoding error:&error];
     202
     203    NSURL *secondSubdirectory = [temporaryFolder.get() URLByAppendingPathComponent:@"subdirectory2" isDirectory:YES];
     204    [defaultManager createDirectoryAtURL:secondSubdirectory withIntermediateDirectories:NO attributes:nil error:&error];
     205    [@"I am a text file in the second subdirectory." writeToURL:[secondSubdirectory URLByAppendingPathComponent:@"text-file-2.txt" isDirectory:NO] atomically:YES encoding:NSUTF8StringEncoding error:&error];
     206
     207    if (error)
     208        NSLog(@"Error writing temporary file: %@", error);
     209
     210    @try {
     211        runTest(temporaryFolder.get());
     212    } @finally {
     213        [[NSFileManager defaultManager] removeItemAtURL:temporaryFolder.get() error:nil];
     214    }
     215}
     216
    167217namespace TestWebKitAPI {
    168218
     
    876926    NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
    877927    EXPECT_WK_STREQ("text/html", outputValue.UTF8String);
     928}
     929
     930static RetainPtr<TestWKWebView> setUpTestWebViewForDataTransferItems()
     931{
     932    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
     933    [webView synchronouslyLoadTestPageNamed:@"DataTransferItem-getAsEntry"];
     934
     935    auto preferences = (WKPreferencesRef)[[webView configuration] preferences];
     936    WKPreferencesSetDataTransferItemsEnabled(preferences, true);
     937    WKPreferencesSetDirectoryUploadEnabled(preferences, true);
     938
     939    return webView;
     940}
     941
     942TEST(DataInteractionTests, ExternalSourceDataTransferItemGetFolderAsEntry)
     943{
     944    NSArray<NSString *> *expectedOutput = @[
     945        @"Found data transfer item (kind: 'string', type: 'text/plain')",
     946        @"Found data transfer item (kind: 'file', type: '')",
     947        @"DIR: /somedirectory",
     948        @"FILE: /somedirectory/icon.png ('image/png', 42130 bytes)",
     949        @"DIR: /somedirectory/subdirectory1",
     950        @"FILE: /somedirectory/subdirectory1/text-file-1.txt ('text/plain', 43 bytes)",
     951        @"FILE: /somedirectory/archive.zip ('application/zip', 988 bytes)",
     952        @"DIR: /somedirectory/subdirectory2",
     953        @"FILE: /somedirectory/subdirectory2/text-file-2.txt ('text/plain', 44 bytes)",
     954        @""
     955    ];
     956
     957    auto webView = setUpTestWebViewForDataTransferItems();
     958    __block bool done = false;
     959    [webView performAfterReceivingMessage:@"dropped" action:^() {
     960        done = true;
     961    }];
     962
     963    runTestWithTemporaryFolder(^(NSURL *folderURL) {
     964        auto itemProvider = adoptNS([[NSItemProvider alloc] init]);
     965        [itemProvider setSuggestedName:@"somedirectory"];
     966        [itemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeFolder fileOptions:0 visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[capturedFolderURL = retainPtr(folderURL)] (FileLoadCompletionBlock completionHandler) -> NSProgress * {
     967            completionHandler(capturedFolderURL.get(), NO, nil);
     968            return nil;
     969        }];
     970
     971        auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     972        [dataInteractionSimulator setExternalItemProviders:@[ itemProvider.get() ]];
     973        [dataInteractionSimulator runFrom:CGPointMake(50, 50) to:CGPointMake(150, 50)];
     974    });
     975
     976    TestWebKitAPI::Util::run(&done);
     977    EXPECT_WK_STREQ([expectedOutput componentsJoinedByString:@"\n"], [webView stringByEvaluatingJavaScript:@"output.value"]);
     978}
     979
     980TEST(DataInteractionTests, ExternalSourceDataTransferItemGetPlainTextFileAsEntry)
     981{
     982    NSArray<NSString *> *expectedOutput = @[
     983        @"Found data transfer item (kind: 'string', type: 'text/plain')",
     984        @"Found data transfer item (kind: 'file', type: 'text/plain')",
     985        @"FILE: /foo.txt ('text/plain', 28 bytes)",
     986        @""
     987    ];
     988
     989    auto webView = setUpTestWebViewForDataTransferItems();
     990    __block bool done = false;
     991    [webView performAfterReceivingMessage:@"dropped" action:^() {
     992        done = true;
     993    }];
     994
     995    runTestWithTemporaryTextFile(^(NSURL *fileURL) {
     996        auto itemProvider = adoptNS([[NSItemProvider alloc] init]);
     997        [itemProvider setSuggestedName:@"foo"];
     998        [itemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeUTF8PlainText fileOptions:0 visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[capturedFileURL = retainPtr(fileURL)](FileLoadCompletionBlock completionHandler) -> NSProgress * {
     999            completionHandler(capturedFileURL.get(), NO, nil);
     1000            return nil;
     1001        }];
     1002
     1003        auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     1004        [dataInteractionSimulator setExternalItemProviders:@[ itemProvider.get() ]];
     1005        [dataInteractionSimulator runFrom:CGPointMake(50, 50) to:CGPointMake(150, 50)];
     1006    });
     1007
     1008    TestWebKitAPI::Util::run(&done);
     1009    EXPECT_WK_STREQ([expectedOutput componentsJoinedByString:@"\n"], [webView stringByEvaluatingJavaScript:@"output.value"]);
    8781010}
    8791011
Note: See TracChangeset for help on using the changeset viewer.