FileSystemDirectoryReader / FileSystemEntry should not prevent entering the back/forward cache
https://bugs.webkit.org/show_bug.cgi?id=203090
<rdar://problem/56550805>

Reviewed by Geoffrey Garen.

Source/WebCore:

FileSystemDirectoryReader / FileSystemEntry no longer prevent entering the back/forward cache.
We now dispatch tasks to the window event loop whenever we need to call a JS callback since the
window event loop takes care of suspending tasks while in the back/forward cache.

Tests: editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html
       editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html

* Modules/entriesapi/FileSystemDirectoryEntry.cpp:
(WebCore::FileSystemDirectoryEntry::getEntry):
* Modules/entriesapi/FileSystemDirectoryReader.cpp:
(WebCore::FileSystemDirectoryReader::document const):
(WebCore::FileSystemDirectoryReader::readEntries):
* Modules/entriesapi/FileSystemDirectoryReader.h:
* Modules/entriesapi/FileSystemEntry.cpp:
(WebCore::FileSystemEntry::document const):
(WebCore::FileSystemEntry::getParent):
* Modules/entriesapi/FileSystemEntry.h:
* Modules/entriesapi/FileSystemFileEntry.cpp:
(WebCore::FileSystemFileEntry::file):

LayoutTests:

Add layout test coverage.

* editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache-expected.txt: Added.
* editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html: Added.
* editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache-expected.txt: Added.
* editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251509 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 1099e072..dd4ce27 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,18 @@
+2019-10-23  Chris Dumez  <cdumez@apple.com>
+
+        FileSystemDirectoryReader / FileSystemEntry should not prevent entering the back/forward cache
+        https://bugs.webkit.org/show_bug.cgi?id=203090
+        <rdar://problem/56550805>
+
+        Reviewed by Geoffrey Garen.
+
+        Add layout test coverage.
+
+        * editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache-expected.txt: Added.
+        * editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html: Added.
+        * editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache-expected.txt: Added.
+        * editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html: Added.
+
 2019-10-23  Truitt Savell  <tsavell@apple.com>
 
         update expectations for inspector/heap/getRemoteObject.html
diff --git a/LayoutTests/editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache-expected.txt b/LayoutTests/editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache-expected.txt
new file mode 100644
index 0000000..97af183
--- /dev/null
+++ b/LayoutTests/editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache-expected.txt
@@ -0,0 +1,15 @@
+Make sure that fileSystemDirectoryEntry.getFile() does not prevent a page from entering the back/forward cache.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+pageshow - not from cache
+pagehide - entering cache
+pageshow - from cache
+PASS Page did enter and was restored from the page cache
+PASS Success callback was called
+PASS restoredFromPageCache is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html b/LayoutTests/editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html
new file mode 100644
index 0000000..0d6aee8
--- /dev/null
+++ b/LayoutTests/editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html
@@ -0,0 +1,60 @@
+<!-- webkit-test-runner [ enableBackForwardCache=true ] -->
+<!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("Make sure that fileSystemDirectoryEntry.getFile() does not prevent a page from entering the back/forward cache.");
+jsTestIsAsync = true;
+restoredFromPageCache = false;
+
+window.addEventListener("pageshow", function(event) {
+    debug("pageshow - " + (event.persisted ? "" : "not ") + "from cache");
+    if (event.persisted) {
+        testPassed("Page did enter and was restored from the page cache");
+        restoredFromPageCache = true;
+    }
+}, false);
+
+window.addEventListener("pagehide", function(event) {
+    debug("pagehide - " + (event.persisted ? "" : "not ") + "entering cache");
+    if (!event.persisted) {
+        testFailed("Page did not enter the page cache.");
+        finishJSTest();
+    } else {
+        entry.getFile('/testFiles/subfolder1/file3.txt', {}, function(file) {
+            testPassed("Success callback was called");
+            shouldBeTrue("restoredFromPageCache");
+            finishJSTest();
+        }, function(e) {
+            testFailed("Error callback was called");
+            finishJSTest();
+        });
+    }
+}, false);
+
+var dropzone = document.getElementById('dropzone');
+dropzone.ondrop = function(e) {
+    e.preventDefault();
+    dataTransfer = e.dataTransfer;
+
+    entry = dataTransfer.items[0].webkitGetAsEntry();
+    window.location.href = "../../../fast/history/resources/page-cache-helper.html";
+};
+
+dropzone.ondragover = function(ev) {
+    ev.preventDefault();
+}
+
+onload = function() {
+    setTimeout(() => {
+        dragFilesOntoElement(dropzone, ['../../../fast/forms/file/entries-api/resources/testFiles']);
+    }, 0);
+}
+</script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache-expected.txt b/LayoutTests/editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache-expected.txt
new file mode 100644
index 0000000..4954288
--- /dev/null
+++ b/LayoutTests/editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache-expected.txt
@@ -0,0 +1,15 @@
+Basic test coverage for fileSystemDirectoryReader.readEntries()
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+pageshow - not from cache
+pagehide - entering cache
+pageshow - from cache
+PASS Page did enter and was restored from the page cache
+PASS Success callback was called
+PASS restoredFromPageCache is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html b/LayoutTests/editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html
new file mode 100644
index 0000000..1d60279
--- /dev/null
+++ b/LayoutTests/editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html
@@ -0,0 +1,84 @@
+<!-- webkit-test-runner [ enableBackForwardCache=true ] -->
+<!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 fileSystemDirectoryReader.readEntries()");
+jsTestIsAsync = true;
+restoredFromPageCache = false;
+
+function getEntriesAsPromise(dirEntry) {
+    return new Promise((resolve, reject) => {
+        let result = [];
+        reader = dirEntry.createReader();
+        let doBatch = () => {
+            reader.readEntries(entries => {
+            if (entries.length > 0) {
+                entries.forEach(e => result.push(e));
+                doBatch();
+            } else {
+                resolve(result.sort(function (a, b) {
+                    if (a.name > b.name)
+                        return 1;
+                    else if (a.name < b.name)
+                        return -1;
+                    return 0;
+                }));
+            }
+        }, reject);
+    };
+    doBatch();
+  });
+}
+
+window.addEventListener("pageshow", function(event) {
+    debug("pageshow - " + (event.persisted ? "" : "not ") + "from cache");
+    if (event.persisted) {
+        testPassed("Page did enter and was restored from the page cache");
+        restoredFromPageCache = true;
+    }
+}, false);
+
+window.addEventListener("pagehide", function(event) {
+    debug("pagehide - " + (event.persisted ? "" : "not ") + "entering cache");
+    if (!event.persisted) {
+        testFailed("Page did not enter the page cache.");
+        finishJSTest();
+    } else {
+        getEntriesAsPromise(entry).then(function(entries) {
+            testPassed("Success callback was called");
+            shouldBeTrue("restoredFromPageCache");
+            finishJSTest();
+        }, function(e) {
+            testFailed("Error callback was called");
+            finishJSTest();
+        });
+    }
+}, false);
+
+var dropzone = document.getElementById('dropzone');
+dropzone.ondrop = function(e) {
+    e.preventDefault();
+    dataTransfer = e.dataTransfer;
+
+    entry = dataTransfer.items[0].webkitGetAsEntry();
+    window.location.href = "../../../fast/history/resources/page-cache-helper.html";
+};
+
+dropzone.ondragover = function(ev) {
+    ev.preventDefault();
+}
+
+onload = function() {
+    setTimeout(() => {
+        dragFilesOntoElement(dropzone, ['../../../fast/forms/file/entries-api/resources/testFiles']);
+    }, 0);
+}
+</script>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 7b19857..1bd257c 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,31 @@
+2019-10-23  Chris Dumez  <cdumez@apple.com>
+
+        FileSystemDirectoryReader / FileSystemEntry should not prevent entering the back/forward cache
+        https://bugs.webkit.org/show_bug.cgi?id=203090
+        <rdar://problem/56550805>
+
+        Reviewed by Geoffrey Garen.
+
+        FileSystemDirectoryReader / FileSystemEntry no longer prevent entering the back/forward cache.
+        We now dispatch tasks to the window event loop whenever we need to call a JS callback since the
+        window event loop takes care of suspending tasks while in the back/forward cache.
+
+        Tests: editing/pasteboard/entries-api/DirectoryEntry-getFile-back-forward-cache.html
+               editing/pasteboard/entries-api/DirectoryReader-readEntries-back-forward-cache.html
+
+        * Modules/entriesapi/FileSystemDirectoryEntry.cpp:
+        (WebCore::FileSystemDirectoryEntry::getEntry):
+        * Modules/entriesapi/FileSystemDirectoryReader.cpp:
+        (WebCore::FileSystemDirectoryReader::document const):
+        (WebCore::FileSystemDirectoryReader::readEntries):
+        * Modules/entriesapi/FileSystemDirectoryReader.h:
+        * Modules/entriesapi/FileSystemEntry.cpp:
+        (WebCore::FileSystemEntry::document const):
+        (WebCore::FileSystemEntry::getParent):
+        * Modules/entriesapi/FileSystemEntry.h:
+        * Modules/entriesapi/FileSystemFileEntry.cpp:
+        (WebCore::FileSystemFileEntry::file):
+
 2019-10-23  Myles C. Maxfield  <mmaxfield@apple.com>
 
         [iOS] Remove Hiragino Sans site-specific quirk for Yahoo Japan
diff --git a/Source/WebCore/Modules/entriesapi/FileSystemDirectoryEntry.cpp b/Source/WebCore/Modules/entriesapi/FileSystemDirectoryEntry.cpp
index 02130a4..0aa21da 100644
--- a/Source/WebCore/Modules/entriesapi/FileSystemDirectoryEntry.cpp
+++ b/Source/WebCore/Modules/entriesapi/FileSystemDirectoryEntry.cpp
@@ -33,6 +33,7 @@
 #include "FileSystemEntryCallback.h"
 #include "FileSystemFileEntry.h"
 #include "ScriptExecutionContext.h"
+#include "WindowEventLoop.h"
 
 namespace WebCore {
 
@@ -51,20 +52,30 @@
     if (!successCallback && !errorCallback)
         return;
 
-    filesystem().getEntry(context, *this, path, flags, [pendingActivity = makePendingActivity(*this), matches = WTFMove(matches), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) {
+    filesystem().getEntry(context, *this, path, flags, [this, pendingActivity = makePendingActivity(*this), matches = WTFMove(matches), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) mutable {
+        auto* document = this->document();
         if (result.hasException()) {
-            if (errorCallback)
-                errorCallback->handleEvent(DOMException::create(result.releaseException()));
+            if (errorCallback && document) {
+                document->eventLoop().queueTask(TaskSource::Networking, *document, [errorCallback = WTFMove(errorCallback), exception = result.releaseException(), pendingActivity = WTFMove(pendingActivity)]() mutable {
+                    errorCallback->handleEvent(DOMException::create(WTFMove(exception)));
+                });
+            }
             return;
         }
         auto entry = result.releaseReturnValue();
         if (!matches(entry)) {
-            if (errorCallback)
-                errorCallback->handleEvent(DOMException::create(Exception { TypeMismatchError, "Entry at given path does not match expected type"_s }));
+            if (errorCallback && document) {
+                document->eventLoop().queueTask(TaskSource::Networking, *document, [errorCallback = WTFMove(errorCallback), pendingActivity = WTFMove(pendingActivity)]() mutable {
+                    errorCallback->handleEvent(DOMException::create(Exception { TypeMismatchError, "Entry at given path does not match expected type"_s }));
+                });
+            }
             return;
         }
-        if (successCallback)
-            successCallback->handleEvent(WTFMove(entry));
+        if (successCallback && document) {
+            document->eventLoop().queueTask(TaskSource::Networking, *document, [successCallback = WTFMove(successCallback), entry = WTFMove(entry), pendingActivity = WTFMove(pendingActivity)]() mutable {
+                successCallback->handleEvent(WTFMove(entry));
+            });
+        }
     });
 }
 
diff --git a/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.cpp b/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.cpp
index 63aa0fe..5dddcf5 100644
--- a/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.cpp
+++ b/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.cpp
@@ -28,10 +28,12 @@
 
 #include "DOMException.h"
 #include "DOMFileSystem.h"
+#include "Document.h"
 #include "ErrorCallback.h"
 #include "FileSystemDirectoryEntry.h"
 #include "FileSystemEntriesCallback.h"
 #include "ScriptExecutionContext.h"
+#include "WindowEventLoop.h"
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/MainThread.h>
 
@@ -53,10 +55,9 @@
     return "FileSystemDirectoryReader";
 }
 
-// FIXME: This should never prevent entering the back/forward cache.
-bool FileSystemDirectoryReader::shouldPreventEnteringBackForwardCache_DEPRECATED() const
+Document* FileSystemDirectoryReader::document() const
 {
-    return hasPendingActivity();
+    return downcast<Document>(scriptExecutionContext());
 }
 
 // https://wicg.github.io/entries-api/#dom-filesystemdirectoryentry-readentries
@@ -83,15 +84,23 @@
     auto pendingActivity = makePendingActivity(*this);
     callOnMainThread([this, context = makeRef(context), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback), pendingActivity = WTFMove(pendingActivity)]() mutable {
         m_isReading = false;
-        m_directory->filesystem().listDirectory(context, m_directory, [this, successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback), pendingActivity = WTFMove(pendingActivity)](ExceptionOr<Vector<Ref<FileSystemEntry>>>&& result) {
+        m_directory->filesystem().listDirectory(context, m_directory, [this, successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback), pendingActivity = WTFMove(pendingActivity)](ExceptionOr<Vector<Ref<FileSystemEntry>>>&& result) mutable {
+            auto* document = this->document();
             if (result.hasException()) {
                 m_error = result.releaseException();
-                if (errorCallback)
-                    errorCallback->handleEvent(DOMException::create(*m_error));
+                if (errorCallback && document) {
+                    document->eventLoop().queueTask(TaskSource::Networking, *document, [this, errorCallback = WTFMove(errorCallback), pendingActivity = WTFMove(pendingActivity)]() mutable {
+                        errorCallback->handleEvent(DOMException::create(*m_error));
+                    });
+                }
                 return;
             }
             m_isDone = true;
-            successCallback->handleEvent(result.releaseReturnValue());
+            if (document) {
+                document->eventLoop().queueTask(TaskSource::Networking, *document, [successCallback = WTFMove(successCallback), pendingActivity = WTFMove(pendingActivity), result = result.releaseReturnValue()]() mutable {
+                    successCallback->handleEvent(WTFMove(result));
+                });
+            }
         });
     });
 }
diff --git a/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.h b/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.h
index b900de7..1a0998a 100644
--- a/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.h
+++ b/Source/WebCore/Modules/entriesapi/FileSystemDirectoryReader.h
@@ -54,7 +54,7 @@
     FileSystemDirectoryReader(ScriptExecutionContext&, FileSystemDirectoryEntry&);
 
     const char* activeDOMObjectName() const final;
-    bool shouldPreventEnteringBackForwardCache_DEPRECATED() const final;
+    Document* document() const;
 
     Ref<FileSystemDirectoryEntry> m_directory;
     Optional<Exception> m_error;
diff --git a/Source/WebCore/Modules/entriesapi/FileSystemEntry.cpp b/Source/WebCore/Modules/entriesapi/FileSystemEntry.cpp
index d2516a3..a86db76 100644
--- a/Source/WebCore/Modules/entriesapi/FileSystemEntry.cpp
+++ b/Source/WebCore/Modules/entriesapi/FileSystemEntry.cpp
@@ -32,6 +32,7 @@
 #include "FileSystemDirectoryEntry.h"
 #include "FileSystemEntryCallback.h"
 #include "ScriptExecutionContext.h"
+#include "WindowEventLoop.h"
 #include <wtf/FileSystem.h>
 #include <wtf/IsoMallocInlines.h>
 
@@ -60,10 +61,9 @@
     return "FileSystemEntry";
 }
 
-// FIXME: This should never prevent entering the back/forward cache.
-bool FileSystemEntry::shouldPreventEnteringBackForwardCache_DEPRECATED() const
+Document* FileSystemEntry::document() const
 {
-    return hasPendingActivity();
+    return downcast<Document>(scriptExecutionContext());
 }
 
 void FileSystemEntry::getParent(ScriptExecutionContext& context, RefPtr<FileSystemEntryCallback>&& successCallback, RefPtr<ErrorCallback>&& errorCallback)
@@ -71,14 +71,20 @@
     if (!successCallback && !errorCallback)
         return;
 
-    filesystem().getParent(context, *this, [pendingActivity = makePendingActivity(*this), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) {
-        if (result.hasException()) {
-            if (errorCallback)
-                errorCallback->handleEvent(DOMException::create(result.releaseException()));
+    filesystem().getParent(context, *this, [this, pendingActivity = makePendingActivity(*this), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) mutable {
+        auto* document = this->document();
+        if (!document)
             return;
-        }
-        if (successCallback)
-            successCallback->handleEvent(result.releaseReturnValue());
+
+        document->eventLoop().queueTask(TaskSource::Networking, *document, [successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback), result = WTFMove(result), pendingActivity = WTFMove(pendingActivity)]() mutable {
+            if (result.hasException()) {
+                if (errorCallback)
+                    errorCallback->handleEvent(DOMException::create(result.releaseException()));
+                return;
+            }
+            if (successCallback)
+                successCallback->handleEvent(result.releaseReturnValue());
+        });
     });
 }
 
diff --git a/Source/WebCore/Modules/entriesapi/FileSystemEntry.h b/Source/WebCore/Modules/entriesapi/FileSystemEntry.h
index d9763ef..af28ce0 100644
--- a/Source/WebCore/Modules/entriesapi/FileSystemEntry.h
+++ b/Source/WebCore/Modules/entriesapi/FileSystemEntry.h
@@ -52,10 +52,10 @@
 
 protected:
     FileSystemEntry(ScriptExecutionContext&, DOMFileSystem&, const String& virtualPath);
+    Document* document() const;
 
 private:
     const char* activeDOMObjectName() const final;
-    bool shouldPreventEnteringBackForwardCache_DEPRECATED() const final;
 
     Ref<DOMFileSystem> m_filesystem;
     String m_name;
diff --git a/Source/WebCore/Modules/entriesapi/FileSystemFileEntry.cpp b/Source/WebCore/Modules/entriesapi/FileSystemFileEntry.cpp
index e8c9ef3..40f4724 100644
--- a/Source/WebCore/Modules/entriesapi/FileSystemFileEntry.cpp
+++ b/Source/WebCore/Modules/entriesapi/FileSystemFileEntry.cpp
@@ -28,8 +28,10 @@
 
 #include "DOMException.h"
 #include "DOMFileSystem.h"
+#include "Document.h"
 #include "ErrorCallback.h"
 #include "FileCallback.h"
+#include "WindowEventLoop.h"
 
 namespace WebCore {
 
@@ -40,13 +42,19 @@
 
 void FileSystemFileEntry::file(Ref<FileCallback>&& successCallback, RefPtr<ErrorCallback>&& errorCallback)
 {
-    filesystem().getFile(*this, [successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) {
-        if (result.hasException()) {
-            if (errorCallback)
-                errorCallback->handleEvent(DOMException::create(result.releaseException()));
+    filesystem().getFile(*this, [this, pendingActivity = makePendingActivity(*this), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) mutable {
+        auto* document = this->document();
+        if (!document)
             return;
-        }
-        successCallback->handleEvent(result.releaseReturnValue());
+
+        document->eventLoop().queueTask(TaskSource::Networking, *document, [successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback), result = WTFMove(result), pendingActivity = WTFMove(pendingActivity)]() mutable {
+            if (result.hasException()) {
+                if (errorCallback)
+                    errorCallback->handleEvent(DOMException::create(result.releaseException()));
+                return;
+            }
+            successCallback->handleEvent(result.releaseReturnValue());
+        });
     });
 }