Implement FileSystemFileEntry.file()
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 3 Sep 2017 04:02:05 +0000 (04:02 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 3 Sep 2017 04:02:05 +0000 (04:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176166
<rdar://problem/34187756>

Reviewed by Sam Weinig.

Source/WebCore:

Implement FileSystemFileEntry.file():
- https://wicg.github.io/entries-api/#dom-filesystemfileentry-file

Test: editing/pasteboard/datatransfer-items-drop-fileEntry-file.html

* Modules/entriesapi/DOMFileSystem.cpp:
(WebCore::validatePathIsExpectedType):
(WebCore::DOMFileSystem::listDirectory):
(WebCore::DOMFileSystem::getParent):
(WebCore::DOMFileSystem::getFile):
* Modules/entriesapi/DOMFileSystem.h:
* Modules/entriesapi/FileSystemFileEntry.cpp:
(WebCore::FileSystemFileEntry::file):

LayoutTests:

Add layout test coverage. I have verified that this test passes in Chrome as well.

* editing/pasteboard/datatransfer-items-drop-fileEntry-file-expected.txt: Added.
* editing/pasteboard/datatransfer-items-drop-fileEntry-file.html: Added.
* platform/wk2/TestExpectations:

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

LayoutTests/ChangeLog
LayoutTests/editing/pasteboard/datatransfer-items-drop-fileEntry-file-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/datatransfer-items-drop-fileEntry-file.html [new file with mode: 0644]
LayoutTests/platform/wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/entriesapi/DOMFileSystem.cpp
Source/WebCore/Modules/entriesapi/DOMFileSystem.h
Source/WebCore/Modules/entriesapi/FileSystemFileEntry.cpp
Source/WebCore/Modules/entriesapi/FileSystemFileEntry.h
Source/WebCore/Modules/entriesapi/FileSystemFileEntry.idl

index 34cabb4..4de7f99 100644 (file)
@@ -1,5 +1,19 @@
 2017-09-02  Chris Dumez  <cdumez@apple.com>
 
+        Implement FileSystemFileEntry.file()
+        https://bugs.webkit.org/show_bug.cgi?id=176166
+        <rdar://problem/34187756>
+
+        Reviewed by Sam Weinig.
+
+        Add layout test coverage. I have verified that this test passes in Chrome as well.
+
+        * editing/pasteboard/datatransfer-items-drop-fileEntry-file-expected.txt: Added.
+        * editing/pasteboard/datatransfer-items-drop-fileEntry-file.html: Added.
+        * platform/wk2/TestExpectations:
+
+2017-09-02  Chris Dumez  <cdumez@apple.com>
+
         Implement FileSystemDirectoryEntry.getDirectory()
         https://bugs.webkit.org/show_bug.cgi?id=176168
         <rdar://problem/34187787>
diff --git a/LayoutTests/editing/pasteboard/datatransfer-items-drop-fileEntry-file-expected.txt b/LayoutTests/editing/pasteboard/datatransfer-items-drop-fileEntry-file-expected.txt
new file mode 100644 (file)
index 0000000..5c0d4ee
--- /dev/null
@@ -0,0 +1,31 @@
+Basic test coverage for fileSystemFileEntry.file()
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS dataTransfer.items.length is 2
+PASS firstEntry.name is "test.txt"
+PASS firstEntry.fullPath is "/test.txt"
+PASS firstEntry.isFile is true
+PASS fileResult.__proto__ is File.prototype
+PASS fileResult.name is "test.txt"
+PASS fileResult.webkitRelativePath is ""
+PASS fileResult.size is 5
+PASS fileResult.type is "text/plain"
+PASS contentResult is "Hello"
+PASS secondEntry.name is "testFiles"
+PASS secondEntry.fullPath is "/testFiles"
+PASS secondEntry.isDirectory is true
+PASS fileEntryResult.name is "file3.txt"
+PASS fileEntryResult.fullPath is "/testFiles/subfolder1/file3.txt"
+PASS fileEntryResult.isFile is true
+PASS file3Result.__proto__ is File.prototype
+PASS file3Result.name is "file3.txt"
+PASS file3Result.webkitRelativePath is ""
+PASS file3Result.size is 2
+PASS file3Result.type is "text/plain"
+PASS contentResult is "3\n"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/datatransfer-items-drop-fileEntry-file.html b/LayoutTests/editing/pasteboard/datatransfer-items-drop-fileEntry-file.html
new file mode 100644 (file)
index 0000000..49c4ed6
--- /dev/null
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test.js"></script>
+<script src="../editing.js"></script>
+</head>
+<body>
+<div id="dropzone" style="width: 200px; height: 200px; background-color: grey;"></div>
+<script>
+description("Basic test coverage for fileSystemFileEntry.file()");
+jsTestIsAsync = true;
+
+function fileAsPromise(fileEntry)
+{
+    return new Promise((resolve, reject) => {
+        fileEntry.file(resolve, reject);
+    });
+}
+
+function readTextFileAsPromise(file)
+{
+    return new Promise((resolve, reject) => {
+        let fileReader = new FileReader();
+        fileReader.onload = function(e) {
+            resolve(fileReader.result);
+        }
+        fileReader.readAsText(file);
+    });
+}
+
+function getFileAsPromise(directoryEntry, path)
+{
+    return new Promise((resolve, reject) => {
+        directoryEntry.getFile(path, {}, resolve, reject);
+    });
+}
+
+function test1()
+{
+    shouldBeEqualToString("firstEntry.name", "test.txt");
+    shouldBeEqualToString("firstEntry.fullPath", "/test.txt");
+    shouldBeTrue("firstEntry.isFile");
+    return fileAsPromise(firstEntry).then(file => {
+        fileResult = file;
+        shouldBe("fileResult.__proto__", "File.prototype");
+        shouldBeEqualToString("fileResult.name", "test.txt");
+        shouldBeEqualToString("fileResult.webkitRelativePath", "");
+        shouldBe("fileResult.size", "5");
+        shouldBeEqualToString("fileResult.type", "text/plain");
+    }, e => {
+        testFailed("fileEntry.file() call failed unexpectedly: " + e);
+    }).then(function() {
+       return readTextFileAsPromise(fileResult).then(content => {
+           contentResult = content;
+           shouldBeEqualToString("contentResult", "Hello");
+       });
+    });
+}
+
+function test2()
+{
+    shouldBeEqualToString("secondEntry.name", "testFiles");
+    shouldBeEqualToString("secondEntry.fullPath", "/testFiles");
+    shouldBeTrue("secondEntry.isDirectory");
+
+    return getFileAsPromise(secondEntry, "subfolder1/file3.txt").then(fileEntry => {
+        fileEntryResult = fileEntry;
+        shouldBeEqualToString("fileEntryResult.name", "file3.txt");
+        shouldBeEqualToString("fileEntryResult.fullPath", "/testFiles/subfolder1/file3.txt");
+        shouldBeTrue("fileEntryResult.isFile");
+    }, e => {
+        testFailed("directoryEntry.getFile() call failed unexpectedly: " + e);
+    }).then(function() {
+        return fileAsPromise(fileEntryResult).then(file => {
+            file3Result = file;
+            shouldBe("file3Result.__proto__", "File.prototype");
+            shouldBeEqualToString("file3Result.name", "file3.txt");
+            shouldBeEqualToString("file3Result.webkitRelativePath", "");
+            shouldBe("file3Result.size", "2");
+            shouldBeEqualToString("file3Result.type", "text/plain");
+        }, e => {
+            testFailed("fileEntry.file() call failed unexpectedly: " + e);
+        }).then(function() {
+            return readTextFileAsPromise(file3Result).then(content => {
+                contentResult = content;
+                shouldBeEqualToString("contentResult", "3\n");
+            });
+        });
+    });
+}
+
+var dropzone = document.getElementById('dropzone');
+dropzone.ondrop = function(e) {
+    e.preventDefault();
+    dataTransfer = e.dataTransfer;
+
+    shouldBe("dataTransfer.items.length", "2");
+
+    firstEntry = dataTransfer.items[0].webkitGetAsEntry();
+    secondEntry = dataTransfer.items[1].webkitGetAsEntry();
+
+    test1().then(test2).then(finishJSTest);
+};
+
+dropzone.ondragover = function(ev) {
+    ev.preventDefault();
+}
+
+onload = function() {
+    dragFilesOntoElement(dropzone, ['../../fast/forms/resources/test.txt', '../../fast/forms/file/resources/testFiles']);
+}
+</script>
+</body>
+</html>
index 67948b4..8346551 100644 (file)
@@ -566,6 +566,7 @@ platform/mac/fast/events/objc-event-api.html
 editing/pasteboard/datatransfer-items-drop-directoryReader.html
 editing/pasteboard/datatransfer-items-drop-directoryReader-error.html
 editing/pasteboard/datatransfer-items-drop-directoryReader-root.html
+editing/pasteboard/datatransfer-items-drop-fileEntry-file.html
 editing/pasteboard/datatransfer-items-drop-getAsEntry.html
 editing/pasteboard/datatransfer-items-drop-getDirectory.html
 editing/pasteboard/datatransfer-items-drop-getFile.html
index df45627..e865cb7 100644 (file)
@@ -1,3 +1,25 @@
+2017-09-02  Chris Dumez  <cdumez@apple.com>
+
+        Implement FileSystemFileEntry.file()
+        https://bugs.webkit.org/show_bug.cgi?id=176166
+        <rdar://problem/34187756>
+
+        Reviewed by Sam Weinig.
+
+        Implement FileSystemFileEntry.file():
+        - https://wicg.github.io/entries-api/#dom-filesystemfileentry-file
+
+        Test: editing/pasteboard/datatransfer-items-drop-fileEntry-file.html
+
+        * Modules/entriesapi/DOMFileSystem.cpp:
+        (WebCore::validatePathIsExpectedType):
+        (WebCore::DOMFileSystem::listDirectory):
+        (WebCore::DOMFileSystem::getParent):
+        (WebCore::DOMFileSystem::getFile):
+        * Modules/entriesapi/DOMFileSystem.h:
+        * Modules/entriesapi/FileSystemFileEntry.cpp:
+        (WebCore::FileSystemFileEntry::file):
+
 2017-09-02  Andy Estes  <aestes@apple.com>
 
         [CA] Upstream QuartzCore-related WebKitSystemInterface functions
index 48c417f..f8b4564 100644 (file)
@@ -159,6 +159,20 @@ Ref<FileSystemEntry> DOMFileSystem::fileAsEntry(ScriptExecutionContext& context)
     return FileSystemFileEntry::create(context, *this, "/" + m_file->name());
 }
 
+static ExceptionOr<String> validatePathIsExpectedType(const String& fullPath, String&& virtualPath, FileMetadata::Type expectedType)
+{
+    ASSERT(!isMainThread());
+
+    FileMetadata metadata;
+    if (!getFileMetadata(fullPath, metadata, ShouldFollowSymbolicLinks::No))
+        return Exception { NotFoundError, ASCIILiteral("Path does not exist") };
+
+    if (metadata.type != expectedType)
+        return Exception { TypeMismatchError, "Entry at path does not have expected type" };
+
+    return WTFMove(virtualPath);
+}
+
 // https://wicg.github.io/entries-api/#resolve-a-relative-path
 static String resolveRelativeVirtualPath(const String& baseVirtualPath, StringView relativeVirtualPath)
 {
@@ -217,8 +231,8 @@ void DOMFileSystem::listDirectory(ScriptExecutionContext& context, FileSystemDir
 {
     ASSERT(&directory.filesystem() == this);
 
-    String directoryVirtualPath = directory.virtualPath();
-    String fullPath = evaluatePath(directoryVirtualPath);
+    auto directoryVirtualPath = directory.virtualPath();
+    auto fullPath = evaluatePath(directoryVirtualPath);
     if (fullPath == m_rootPath) {
         Vector<Ref<FileSystemEntry>> children;
         children.append(fileAsEntry(context));
@@ -226,7 +240,7 @@ void DOMFileSystem::listDirectory(ScriptExecutionContext& context, FileSystemDir
         return;
     }
 
-    m_workQueue->dispatch([this, context = makeRef(context), completionHandler = WTFMove(completionHandler), fullPath = fullPath.isolatedCopy(), directoryVirtualPath = directoryVirtualPath.isolatedCopy()]() mutable {
+    m_workQueue->dispatch([this, context = makeRef(context), completionHandler = WTFMove(completionHandler), fullPath = crossThreadCopy(fullPath), directoryVirtualPath = crossThreadCopy(directoryVirtualPath)]() mutable {
         auto listedChildren = listDirectoryWithMetadata(fullPath);
         callOnMainThread([this, context = WTFMove(context), completionHandler = WTFMove(completionHandler), listedChildren = crossThreadCopy(listedChildren), directoryVirtualPath = directoryVirtualPath.isolatedCopy()]() mutable {
             completionHandler(toFileSystemEntries(context, *this, WTFMove(listedChildren), directoryVirtualPath));
@@ -234,24 +248,15 @@ void DOMFileSystem::listDirectory(ScriptExecutionContext& context, FileSystemDir
     });
 }
 
-static ExceptionOr<String> validatePathIsDirectory(const String& fullPath, String&& virtualPath)
-{
-    ASSERT(!isMainThread());
-
-    if (!fileIsDirectory(fullPath, ShouldFollowSymbolicLinks::No))
-        return Exception { NotFoundError, "Path no longer exists or is no longer a directory" };
-    return WTFMove(virtualPath);
-}
-
 void DOMFileSystem::getParent(ScriptExecutionContext& context, FileSystemEntry& entry, GetParentCallback&& completionCallback)
 {
     ASSERT(&entry.filesystem() == this);
 
-    String virtualPath = resolveRelativeVirtualPath(entry.virtualPath(), "..");
+    auto virtualPath = resolveRelativeVirtualPath(entry.virtualPath(), "..");
     ASSERT(virtualPath[0] == '/');
-    String fullPath = evaluatePath(virtualPath);
+    auto fullPath = evaluatePath(virtualPath);
     m_workQueue->dispatch([this, context = makeRef(context), fullPath = crossThreadCopy(fullPath), virtualPath = crossThreadCopy(virtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
-        auto validatedVirtualPath = validatePathIsDirectory(fullPath, WTFMove(virtualPath));
+        auto validatedVirtualPath = validatePathIsExpectedType(fullPath, WTFMove(virtualPath), FileMetadata::TypeDirectory);
         callOnMainThread([this, context = WTFMove(context), validatedVirtualPath = crossThreadCopy(validatedVirtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
             if (validatedVirtualPath.hasException())
                 completionCallback(validatedVirtualPath.releaseException());
@@ -281,9 +286,9 @@ void DOMFileSystem::getEntry(ScriptExecutionContext& context, FileSystemDirector
         return;
     }
 
-    String resolvedVirtualPath = resolveRelativeVirtualPath(directory.virtualPath(), virtualPath);
+    auto resolvedVirtualPath = resolveRelativeVirtualPath(directory.virtualPath(), virtualPath);
     ASSERT(resolvedVirtualPath[0] == '/');
-    String fullPath = evaluatePath(resolvedVirtualPath);
+    auto fullPath = evaluatePath(resolvedVirtualPath);
     if (fullPath == m_rootPath) {
         callOnMainThread([this, context = makeRef(context), completionCallback = WTFMove(completionCallback)]() mutable {
             completionCallback(Ref<FileSystemEntry> { root(context) });
@@ -310,4 +315,19 @@ void DOMFileSystem::getEntry(ScriptExecutionContext& context, FileSystemDirector
     });
 }
 
+void DOMFileSystem::getFile(ScriptExecutionContext& context, FileSystemFileEntry& fileEntry, GetFileCallback&& completionCallback)
+{
+    auto virtualPath = fileEntry.virtualPath();
+    auto fullPath = evaluatePath(virtualPath);
+    m_workQueue->dispatch([this, context = makeRef(context), fullPath = crossThreadCopy(fullPath), virtualPath = crossThreadCopy(virtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
+        auto validatedVirtualPath = validatePathIsExpectedType(fullPath, WTFMove(virtualPath), FileMetadata::TypeFile);
+        callOnMainThread([this, context = WTFMove(context), fullPath = crossThreadCopy(fullPath), validatedVirtualPath = crossThreadCopy(validatedVirtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
+            if (validatedVirtualPath.hasException())
+                completionCallback(validatedVirtualPath.releaseException());
+            else
+                completionCallback(File::create(fullPath));
+        });
+    });
+}
+
 } // namespace WebCore
index 324f157..047a578 100644 (file)
@@ -61,6 +61,9 @@ public:
     using GetEntryCallback = WTF::Function<void(ExceptionOr<Ref<FileSystemEntry>>&&)>;
     void getEntry(ScriptExecutionContext&, FileSystemDirectoryEntry&, const String& virtualPath, const FileSystemDirectoryEntry::Flags&, GetEntryCallback&&);
 
+    using GetFileCallback = WTF::Function<void(ExceptionOr<Ref<File>>&&)>;
+    void getFile(ScriptExecutionContext&, FileSystemFileEntry&, GetFileCallback&&);
+
 private:
     explicit DOMFileSystem(Ref<File>&&);
 
index b80b65b..8829043 100644 (file)
@@ -27,7 +27,9 @@
 #include "FileSystemFileEntry.h"
 
 #include "DOMException.h"
+#include "DOMFileSystem.h"
 #include "ErrorCallback.h"
+#include "FileCallback.h"
 
 namespace WebCore {
 
@@ -36,10 +38,16 @@ FileSystemFileEntry::FileSystemFileEntry(ScriptExecutionContext& context, DOMFil
 {
 }
 
-void FileSystemFileEntry::file(ScriptExecutionContext& context, RefPtr<FileCallback>&&, RefPtr<ErrorCallback>&& errorCallback)
+void FileSystemFileEntry::file(ScriptExecutionContext& context, Ref<FileCallback>&& successCallback, RefPtr<ErrorCallback>&& errorCallback)
 {
-    if (errorCallback)
-        errorCallback->scheduleCallback(context, DOMException::create(NotSupportedError));
+    filesystem().getFile(context, *this, [successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) {
+        if (result.hasException()) {
+            if (errorCallback)
+                errorCallback->handleEvent(DOMException::create(result.releaseException()));
+            return;
+        }
+        successCallback->handleEvent(result.releaseReturnValue());
+    });
 }
 
 } // namespace WebCore
index 89be8df..34f6323 100644 (file)
@@ -40,7 +40,7 @@ public:
         return adoptRef(*new FileSystemFileEntry(context, filesystem, virtualPath));
     }
 
-    void file(ScriptExecutionContext&, RefPtr<FileCallback>&&, RefPtr<ErrorCallback>&& = nullptr);
+    void file(ScriptExecutionContext&, Ref<FileCallback>&&, RefPtr<ErrorCallback>&& = nullptr);
 
 private:
     bool isFile() const final { return true; }
index 13253c7..dad97b9 100644 (file)
@@ -26,5 +26,5 @@
 [
     EnabledAtRuntime=DirectoryUpload,
 ] interface FileSystemFileEntry : FileSystemEntry {
-    [CallWith=ScriptExecutionContext] void file(FileCallback? successCallback, optional ErrorCallback? errorCallback);
+    [CallWith=ScriptExecutionContext] void file(FileCallback successCallback, optional ErrorCallback? errorCallback);
 };