Pages frequently fail to enter the back/forward cache due to frames with a quick redirect coming
https://bugs.webkit.org/show_bug.cgi?id=202768
<rdar://problem/56132022>

Reviewed by Alex Christensen.

Source/WebCore:

When a quick redirect is scheduled with the navigation scheduler, we set the m_quickRedirectComing flag.
This flag is supposed to get unset if the navigation gets cancelled and when the navigation actually
happens. However, for a navigation to a javascript: URL, we would return early after executing the JS
and fail to reset the m_quickRedirectComing flag. Later on, we would fail to enter the page cache because
we would think that the iframe still has a quick redirect scheduled.

Test: fast/history/page-cache-iframe-js-url.html

* loader/FrameLoader.cpp:
(WebCore::FrameLoader::urlSelected):
Reset the m_quickRedirectComing flag if we return early after executing
the javascript URL.

(WebCore::FrameLoader::stopForPageCache):
Stop policy checks & cancel scheduled navigations after stopping loads. Stopping loads may
run script which may in theory schedule new navigations. This is hardening.

LayoutTests:

Add lauout test coverage.

* fast/history/page-cache-iframe-js-url-expected.txt: Added.
* fast/history/page-cache-iframe-js-url.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251019 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index ba47bef..bb0f06b 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,16 @@
+2019-10-11  Chris Dumez  <cdumez@apple.com>
+
+        Pages frequently fail to enter the back/forward cache due to frames with a quick redirect coming
+        https://bugs.webkit.org/show_bug.cgi?id=202768
+        <rdar://problem/56132022>
+
+        Reviewed by Alex Christensen.
+
+        Add lauout test coverage.
+
+        * fast/history/page-cache-iframe-js-url-expected.txt: Added.
+        * fast/history/page-cache-iframe-js-url.html: Added.
+
 2019-10-11  Kate Cheney  <katherine_cheney@apple.com>
 
         Get StorageAccess API features working on SQLite database implementation (195422)
diff --git a/LayoutTests/fast/history/page-cache-iframe-js-url-expected.txt b/LayoutTests/fast/history/page-cache-iframe-js-url-expected.txt
new file mode 100644
index 0000000..b3d4b80
--- /dev/null
+++ b/LayoutTests/fast/history/page-cache-iframe-js-url-expected.txt
@@ -0,0 +1,13 @@
+Tests that a page with an iframe that ran a JavaScript URL is able to enter the page 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 successfullyParsed is true
+
+TEST COMPLETE
+  
diff --git a/LayoutTests/fast/history/page-cache-iframe-js-url.html b/LayoutTests/fast/history/page-cache-iframe-js-url.html
new file mode 100644
index 0000000..ccc6a53
--- /dev/null
+++ b/LayoutTests/fast/history/page-cache-iframe-js-url.html
@@ -0,0 +1,41 @@
+<!-- webkit-test-runner [ enablePageCache=true ] -->
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test.js"></script>
+</head>
+<body>
+<img src="../dom/resources/abe.png">
+<a id="testLink" style="display: none" href="resources/page-cache-helper.html">Link</a>
+<iframe id="testFrame" src="about:blank"></iframe>
+<script>
+description('Tests that a page with an iframe that ran a JavaScript URL is able to enter the page cache.');
+jsTestIsAsync = true;
+
+window.addEventListener("pageshow", function(event) {
+    debug("pageshow - " + (event.persisted ? "" : "not ") + "from cache");
+    if (event.persisted) {
+        debug("PASS - Page did enter and was restored from the page cache");
+        finishJSTest();
+    }
+});
+
+window.addEventListener("pagehide", function(event) {
+    debug("pagehide - " + (event.persisted ? "" : "not ") + "entering cache");
+    if (!event.persisted) {
+        debug("FAIL - Page did not enter the page cache.");
+        finishJSTest();
+    }
+});
+
+window.addEventListener('load', function() {
+    setTimeout(() => {
+        document.getElementById("testFrame").contentWindow.location.replace("javascript:'foo';");
+        setTimeout(() => {
+            testLink.click();
+        }, 0);
+    }, 0);
+});
+</script>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 20378d4..d8060e9 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,28 @@
+2019-10-11  Chris Dumez  <cdumez@apple.com>
+
+        Pages frequently fail to enter the back/forward cache due to frames with a quick redirect coming
+        https://bugs.webkit.org/show_bug.cgi?id=202768
+        <rdar://problem/56132022>
+
+        Reviewed by Alex Christensen.
+
+        When a quick redirect is scheduled with the navigation scheduler, we set the m_quickRedirectComing flag.
+        This flag is supposed to get unset if the navigation gets cancelled and when the navigation actually
+        happens. However, for a navigation to a javascript: URL, we would return early after executing the JS
+        and fail to reset the m_quickRedirectComing flag. Later on, we would fail to enter the page cache because
+        we would think that the iframe still has a quick redirect scheduled.
+
+        Test: fast/history/page-cache-iframe-js-url.html
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::urlSelected):
+        Reset the m_quickRedirectComing flag if we return early after executing
+        the javascript URL.
+
+        (WebCore::FrameLoader::stopForPageCache):
+        Stop policy checks & cancel scheduled navigations after stopping loads. Stopping loads may
+        run script which may in theory schedule new navigations. This is hardening.
+
 2019-10-11  Antti Koivisto  <antti@apple.com>
 
         Position::upstream/downstream should not need to call ensureLineBoxes
diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp
index 8b869f7..ed7409c 100644
--- a/Source/WebCore/loader/FrameLoader.cpp
+++ b/Source/WebCore/loader/FrameLoader.cpp
@@ -395,8 +395,10 @@
 
     Ref<Frame> protect(m_frame);
 
-    if (m_frame.script().executeIfJavaScriptURL(frameRequest.resourceRequest().url(), frameRequest.shouldReplaceDocumentIfJavaScriptURL()))
+    if (m_frame.script().executeIfJavaScriptURL(frameRequest.resourceRequest().url(), frameRequest.shouldReplaceDocumentIfJavaScriptURL())) {
+        m_quickRedirectComing = false;
         return;
+    }
 
     if (frameRequest.frameName().isEmpty())
         frameRequest.setFrameName(m_frame.document()->baseTarget());
@@ -1847,10 +1849,6 @@
 
 void FrameLoader::stopForPageCache()
 {
-    // Make sure there are no scheduled loads or policy checks.
-    policyChecker().stopCheck();
-    m_frame.navigationScheduler().cancel();
-
     // Stop provisional loads in subframes (The one in the main frame is about to be committed).
     if (!m_frame.isMainFrame()) {
         if (m_provisionalDocumentLoader)
@@ -1864,6 +1862,10 @@
 
     for (RefPtr<Frame> child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
         child->loader().stopForPageCache();
+
+    // Make sure there are no scheduled loads or policy checks.
+    policyChecker().stopCheck();
+    m_frame.navigationScheduler().cancel();
 }
 
 void FrameLoader::stopAllLoadersAndCheckCompleteness()