Crash: com.apple.WebKit.WebContent at com.apple.WebCore: WebCore::CachedFrameBase...
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 28 Jun 2015 18:53:57 +0000 (18:53 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 28 Jun 2015 18:53:57 +0000 (18:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=146388
<rdar://problem/21567343>

Reviewed by Darin Adler.

Source/WebCore:

Pages that are currently loading are not supposed to go into the
PageCache. However, PageCache::canCache() only checks if the
FrameLoader's documentLoader is loading. If the subframe is in
provisional load stage, we would fail to detect that the frame is
actually loading because the FrameLoader active documentLoader would
be the provisional documentLoader, not the regular documentLoader.
Therefore, the page would get added to the PageCache and the frame
would keep loading while in the PageCache.

On http://www.audiusa.com/models, this is what was happening. It was
crashing because the subframe would finish loading while in the
PageCache, in which case we would fire the 'load' event and the
content 'load' event handler would then proceed to remove the iframe.
Upon restoring the PageCache entry, we would run into trouble as we
would have a CachedFrame whose Frame has been removed.

The solution proposed is to prevent page-caching if a subframe is in
provisional load stage.

Test: http/tests/navigation/page-cache-iframe-provisional-load.html

* history/PageCache.cpp:
(WebCore::logCanCacheFrameDecision):
(WebCore::PageCache::canCachePageContainingThisFrame):
* page/DiagnosticLoggingKeys.cpp:
(WebCore::DiagnosticLoggingKeys::provisionalLoadKey):
* page/DiagnosticLoggingKeys.h:

LayoutTests:

Add layout test to cover the case where a subframe is currently in
provisional load stage when checking if the page if page-cacheable.

The test also removes the iframe once loaded in order to cause a crash
if the frame were to finish loading while in the page cache.

* http/tests/navigation/page-cache-iframe-provisional-load-expected.txt: Added.
* http/tests/navigation/page-cache-iframe-provisional-load.html: Added.
* http/tests/navigation/resources/page-cache-helper-slow.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/http/tests/navigation/page-cache-iframe-provisional-load-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/navigation/page-cache-iframe-provisional-load.html [new file with mode: 0644]
LayoutTests/http/tests/navigation/resources/page-cache-helper-slow.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/history/PageCache.cpp
Source/WebCore/page/DiagnosticLoggingKeys.cpp
Source/WebCore/page/DiagnosticLoggingKeys.h

index 3c06c94..f13d2c5 100644 (file)
@@ -1,3 +1,21 @@
+2015-06-28  Chris Dumez  <cdumez@apple.com>
+
+        Crash: com.apple.WebKit.WebContent at com.apple.WebCore: WebCore::CachedFrameBase::restore + 333
+        https://bugs.webkit.org/show_bug.cgi?id=146388
+        <rdar://problem/21567343>
+
+        Reviewed by Darin Adler.
+
+        Add layout test to cover the case where a subframe is currently in
+        provisional load stage when checking if the page if page-cacheable.
+
+        The test also removes the iframe once loaded in order to cause a crash
+        if the frame were to finish loading while in the page cache.
+
+        * http/tests/navigation/page-cache-iframe-provisional-load-expected.txt: Added.
+        * http/tests/navigation/page-cache-iframe-provisional-load.html: Added.
+        * http/tests/navigation/resources/page-cache-helper-slow.html: Added.
+
 2015-06-28  Skachkov Oleksandr  <gskachkov@gmail.com>
 
         [ES6] Implement ES6 arrow function syntax. No Line terminator between function parameters and =>
diff --git a/LayoutTests/http/tests/navigation/page-cache-iframe-provisional-load-expected.txt b/LayoutTests/http/tests/navigation/page-cache-iframe-provisional-load-expected.txt
new file mode 100644 (file)
index 0000000..b4cc38b
--- /dev/null
@@ -0,0 +1,11 @@
+A frame in provisional load stage should prevent page caching.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+pageshow - not from cache
+PASS Page was not restored from PageCache
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/navigation/page-cache-iframe-provisional-load.html b/LayoutTests/http/tests/navigation/page-cache-iframe-provisional-load.html
new file mode 100644 (file)
index 0000000..75a1c9e
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/js-test-pre.js"></script>
+<script>
+description("A frame in provisional load stage should prevent page caching.");
+window.jsTestIsAsync = true;
+
+if (window.testRunner)
+    testRunner.overridePreference("WebKitUsesPageCachePreferenceKey", 1);
+
+window.addEventListener("pageshow", function(event) {
+    debug("pageshow - " + (event.persisted ? "" : "not ") + "from cache");
+
+    if (!window.sessionStorage.page_cache_provisional_load_test_started)
+        return;
+
+    delete window.sessionStorage.page_cache_provisional_load_test_started;
+
+    if (event.persisted)
+        testFailed("Page was restored from PageCache");
+    else
+        testPassed("Page was not restored from PageCache");
+
+    finishJSTest();
+}, false);
+
+window.addEventListener("pagehide", function(event) {
+    debug("pagehide");
+}, false);
+
+function loadSubframeAndNavigateAway()
+{
+    // Force a back navigation back to this page.
+    window.sessionStorage.page_cache_provisional_load_test_started = true;
+    window.location.href = "resources/page-cache-helper-slow.html";
+
+    var testFrame = document.getElementById("testFrame");
+    testFrame.src = "http://127.0.0.1:8000/navigation/resources/slow-resource.pl?delay=100";
+
+    // If the page goes into the page cache and the frame keeps loading while in the cache,
+    // the following will cause crashes.
+    testFrame.onload = function() { document.getElementById("testFrame").remove(); };
+}
+
+window.addEventListener('load', function() {
+    setTimeout(function() {
+        loadSubframeAndNavigateAway();
+    }, 0);
+}, false);
+
+</script>
+<iframe id="testFrame" src="about:blank"></iframe>
+<script src="/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/navigation/resources/page-cache-helper-slow.html b/LayoutTests/http/tests/navigation/resources/page-cache-helper-slow.html
new file mode 100644 (file)
index 0000000..2fe61bd
--- /dev/null
@@ -0,0 +1,9 @@
+This page should go back. If a test outputs the contents of this
+page, then the test page failed to enter the page cache.
+<script>
+  window.addEventListener("load", function() {
+    setTimeout(function() {
+      history.back();
+    }, 500);
+  }, false);
+</script>
index 7ae9924..cb1fdf3 100644 (file)
@@ -1,3 +1,39 @@
+2015-06-28  Chris Dumez  <cdumez@apple.com>
+
+        Crash: com.apple.WebKit.WebContent at com.apple.WebCore: WebCore::CachedFrameBase::restore + 333
+        https://bugs.webkit.org/show_bug.cgi?id=146388
+        <rdar://problem/21567343>
+
+        Reviewed by Darin Adler.
+
+        Pages that are currently loading are not supposed to go into the
+        PageCache. However, PageCache::canCache() only checks if the
+        FrameLoader's documentLoader is loading. If the subframe is in
+        provisional load stage, we would fail to detect that the frame is
+        actually loading because the FrameLoader active documentLoader would
+        be the provisional documentLoader, not the regular documentLoader.
+        Therefore, the page would get added to the PageCache and the frame
+        would keep loading while in the PageCache.
+
+        On http://www.audiusa.com/models, this is what was happening. It was
+        crashing because the subframe would finish loading while in the
+        PageCache, in which case we would fire the 'load' event and the
+        content 'load' event handler would then proceed to remove the iframe.
+        Upon restoring the PageCache entry, we would run into trouble as we
+        would have a CachedFrame whose Frame has been removed.
+
+        The solution proposed is to prevent page-caching if a subframe is in
+        provisional load stage.
+
+        Test: http/tests/navigation/page-cache-iframe-provisional-load.html
+
+        * history/PageCache.cpp:
+        (WebCore::logCanCacheFrameDecision):
+        (WebCore::PageCache::canCachePageContainingThisFrame):
+        * page/DiagnosticLoggingKeys.cpp:
+        (WebCore::DiagnosticLoggingKeys::provisionalLoadKey):
+        * page/DiagnosticLoggingKeys.h:
+
 2015-06-28  Xabier Rodriguez Calvar  <calvaris@igalia.com> and Youenn Fablet  <youenn.fablet@crf.canon.fr>
 
         [Streams API] Add support for chunks with customized sizes
index 05a00e9..5790aad 100644 (file)
@@ -92,6 +92,7 @@ enum ReasonFrameCannotBeInPageCache {
     DocumentLoaderUsesApplicationCache,
     ClientDeniesCaching,
     NumberOfReasonsFramesCannotBeInPageCache,
+    IsInProvisionalLoadStage,
 };
 COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap);
 
@@ -111,6 +112,11 @@ static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String
 static unsigned logCanCacheFrameDecision(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, unsigned indentLevel)
 {
     PCLOG("+---");
+    if (!frame.isMainFrame() && frame.loader().state() == FrameStateProvisional) {
+        PCLOG("   -Frame is in provisional load stage");
+        logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::provisionalLoadKey());
+        return 1 << IsInProvisionalLoadStage;
+    }
     if (!frame.loader().documentLoader()) {
         PCLOG("   -There is no DocumentLoader object");
         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
@@ -297,6 +303,12 @@ bool PageCache::canCachePageContainingThisFrame(Frame& frame)
     }
     
     FrameLoader& frameLoader = frame.loader();
+
+    // Prevent page caching if a subframe is still in provisional load stage.
+    // We only do this check for subframes because the main frame is reused when navigating to a new page.
+    if (!frame.isMainFrame() && frameLoader.state() == FrameStateProvisional)
+        return false;
+
     DocumentLoader* documentLoader = frameLoader.documentLoader();
     Document* document = frame.document();
     
index e87da36..bfc93d4 100644 (file)
@@ -53,6 +53,11 @@ String DiagnosticLoggingKeys::pluginLoadingFailedKey()
     return ASCIILiteral("pluginFailedLoading");
 }
 
+String DiagnosticLoggingKeys::provisionalLoadKey()
+{
+    return ASCIILiteral("provisionalLoad");
+}
+
 String DiagnosticLoggingKeys::pageContainsPluginKey()
 {
     return ASCIILiteral("pageContainsPlugin");
index a98e7d2..1333912 100644 (file)
@@ -90,6 +90,7 @@ public:
     static String playedKey();
     static String pluginLoadedKey();
     static String pluginLoadingFailedKey();
+    static String provisionalLoadKey();
     static String prunedDueToMaxSizeReached();
     static String prunedDueToMemoryPressureKey();
     static String prunedDueToProcessSuspended();