Implement Cache-control: immutable
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 Jan 2017 18:44:18 +0000 (18:44 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 Jan 2017 18:44:18 +0000 (18:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167497

Reviewed by Chris Dumez.

Source/WebCore:

Cache-control value 'immutable' indicates that a subresource does not change and so does not need to be
revalidated on a normal reload. This can significantly speed up reloads and reduce network traffic.

It is has been implemented in Firefox and is already used by Facebook.

https://tools.ietf.org/html/draft-mcmanus-immutable-00
https://hacks.mozilla.org/2017/01/using-immutable-caching-to-speed-up-the-web/

This patch implements Cache-control: immutable for memory cache only. A disk cache implementation
doesn't seem necessary as the resource is basically always expected to be in memory cache on reload.

Immutable is only supported for https as suggested by the draft specification (and Gecko implementation).

Test: http/tests/cache/cache-control-immutable-http.html
      http/tests/cache/cache-control-immutable-https.html

* loader/cache/CachedResource.cpp:
(WebCore::CachedResource::makeRevalidationDecision):

    On normal reloads (CachePolicyRevalidate) of https resources check for 'Cache-control: immutable'.
    If the resource is not expired don't revalidate it.

* platform/network/CacheValidation.cpp:
(WebCore::parseCacheControlDirectives):
* platform/network/CacheValidation.h:
* platform/network/ResourceResponseBase.cpp:
(WebCore::ResourceResponseBase::cacheControlContainsImmutable):
* platform/network/ResourceResponseBase.h:

LayoutTests:

* http/tests/cache/cache-control-immutable-http-expected.txt: Added.
* http/tests/cache/cache-control-immutable-http.html: Added.
* http/tests/cache/cache-control-immutable-https-expected.txt: Added.
* http/tests/cache/cache-control-immutable-https.html: Added.
* http/tests/cache/resources/cache-control-immutable.js: Added.
* http/tests/cache/resources/iframe-with-script.cgi: Added.

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

13 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/cache/cache-control-immutable-http-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/cache/cache-control-immutable-http.html [new file with mode: 0644]
LayoutTests/http/tests/cache/cache-control-immutable-https-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/cache/cache-control-immutable-https.html [new file with mode: 0644]
LayoutTests/http/tests/cache/resources/cache-control-immutable.js [new file with mode: 0644]
LayoutTests/http/tests/cache/resources/iframe-with-script.cgi [new file with mode: 0755]
Source/WebCore/ChangeLog
Source/WebCore/loader/cache/CachedResource.cpp
Source/WebCore/platform/network/CacheValidation.cpp
Source/WebCore/platform/network/CacheValidation.h
Source/WebCore/platform/network/ResourceResponseBase.cpp
Source/WebCore/platform/network/ResourceResponseBase.h

index c82b4da..b3dd207 100644 (file)
@@ -1,3 +1,17 @@
+2017-01-27  Antti Koivisto  <antti@apple.com>
+
+        Implement Cache-control: immutable
+        https://bugs.webkit.org/show_bug.cgi?id=167497
+
+        Reviewed by Chris Dumez.
+
+        * http/tests/cache/cache-control-immutable-http-expected.txt: Added.
+        * http/tests/cache/cache-control-immutable-http.html: Added.
+        * http/tests/cache/cache-control-immutable-https-expected.txt: Added.
+        * http/tests/cache/cache-control-immutable-https.html: Added.
+        * http/tests/cache/resources/cache-control-immutable.js: Added.
+        * http/tests/cache/resources/iframe-with-script.cgi: Added.
+
 2017-01-26  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking media/modern-media-controls/layout-node/addChild.html as flaky.
diff --git a/LayoutTests/http/tests/cache/cache-control-immutable-http-expected.txt b/LayoutTests/http/tests/cache/cache-control-immutable-http-expected.txt
new file mode 100644 (file)
index 0000000..acb6ee8
--- /dev/null
@@ -0,0 +1,3 @@
+HTTP: Test if non-expired subresource with Cache-control: immutable is revalidated: YES (expected YES)
+HTTP: Test if expired subresource with Cache-control: immutable is revalidated: YES (expected YES)
+
diff --git a/LayoutTests/http/tests/cache/cache-control-immutable-http.html b/LayoutTests/http/tests/cache/cache-control-immutable-http.html
new file mode 100644 (file)
index 0000000..d9abba4
--- /dev/null
@@ -0,0 +1,19 @@
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+</script>
+<script src="resources/cache-control-immutable.js"></script>
+<body>
+<div id=logdiv><div>
+<script>
+test(1000, (revalidated) => {
+    log("HTTP: Test if non-expired subresource with Cache-control: immutable is revalidated", true, revalidated);
+    test(0, (revalidated) => {
+        log("HTTP: Test if expired subresource with Cache-control: immutable is revalidated", true, revalidated);
+        if (window.testRunner)
+            testRunner.notifyDone();
+    });
+});
+</script>
diff --git a/LayoutTests/http/tests/cache/cache-control-immutable-https-expected.txt b/LayoutTests/http/tests/cache/cache-control-immutable-https-expected.txt
new file mode 100644 (file)
index 0000000..70b7de1
--- /dev/null
@@ -0,0 +1,3 @@
+HTTPS: Test if non-expired subresource with Cache-control: immutable is revalidated: NO (expected NO)
+HTTPS: Test if expired subresource with Cache-control: immutable is revalidated: YES (expected YES)
+
diff --git a/LayoutTests/http/tests/cache/cache-control-immutable-https.html b/LayoutTests/http/tests/cache/cache-control-immutable-https.html
new file mode 100644 (file)
index 0000000..030f0c3
--- /dev/null
@@ -0,0 +1,21 @@
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+if (window.location.protocol == "http:")
+    window.location = "https://127.0.0.1:8443/cache/cache-control-immutable-https.html";
+</script>
+<script src="resources/cache-control-immutable.js"></script>
+<body>
+<div id=logdiv><div>
+<script>
+test(1000, (revalidated) => {
+    log("HTTPS: Test if non-expired subresource with Cache-control: immutable is revalidated", false, revalidated);
+    test(0, (revalidated) => {
+        log("HTTPS: Test if expired subresource with Cache-control: immutable is revalidated", true, revalidated);
+        if (window.testRunner)
+            testRunner.notifyDone();
+    });
+});
+</script>
diff --git a/LayoutTests/http/tests/cache/resources/cache-control-immutable.js b/LayoutTests/http/tests/cache/resources/cache-control-immutable.js
new file mode 100644 (file)
index 0000000..20a3772
--- /dev/null
@@ -0,0 +1,19 @@
+function log(text, expected, result)
+{
+    logdiv.innerText += text + ": " + (result ? "YES" : "NO") + " (expected " + (expected ? "YES" : "NO") + ")\n";
+}
+
+function insertIframe(maxAge, loaded) {
+    const iframe = document.createElement('iframe');
+    document.body.appendChild(iframe);
+    iframe.src = "resources/iframe-with-script.cgi?script-cache-control=immutable,max-age=" + maxAge;
+    iframe.onload = () => loaded(iframe);
+}
+
+function test(maxAge, callback) {
+    insertIframe(maxAge, (iframe) => {
+        const firstNumber = iframe.contentWindow.randomNumber;
+        iframe.onload = () => callback(firstNumber != iframe.contentWindow.randomNumber);
+        iframe.contentWindow.location.reload();
+    });
+}
diff --git a/LayoutTests/http/tests/cache/resources/iframe-with-script.cgi b/LayoutTests/http/tests/cache/resources/iframe-with-script.cgi
new file mode 100755 (executable)
index 0000000..0b53e64
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -w
+
+use CGI;
+
+print "Content-type: text/html\n";
+print "Cache-control: no-cache\n";
+print "\n";
+
+my $query = new CGI;
+my $scriptCacheControl = $query->param("script-cache-control");
+
+print "<script src='cache-simulator.cgi?uniqueId=1&Cache-control=";
+print $scriptCacheControl;
+print "'></script>\n";
index 0c1d8b7..aecb69e 100644 (file)
@@ -1,3 +1,39 @@
+2017-01-27  Antti Koivisto  <antti@apple.com>
+
+        Implement Cache-control: immutable
+        https://bugs.webkit.org/show_bug.cgi?id=167497
+
+        Reviewed by Chris Dumez.
+
+        Cache-control value 'immutable' indicates that a subresource does not change and so does not need to be
+        revalidated on a normal reload. This can significantly speed up reloads and reduce network traffic.
+
+        It is has been implemented in Firefox and is already used by Facebook.
+
+        https://tools.ietf.org/html/draft-mcmanus-immutable-00
+        https://hacks.mozilla.org/2017/01/using-immutable-caching-to-speed-up-the-web/
+
+        This patch implements Cache-control: immutable for memory cache only. A disk cache implementation
+        doesn't seem necessary as the resource is basically always expected to be in memory cache on reload.
+
+        Immutable is only supported for https as suggested by the draft specification (and Gecko implementation).
+
+        Test: http/tests/cache/cache-control-immutable-http.html
+              http/tests/cache/cache-control-immutable-https.html
+
+        * loader/cache/CachedResource.cpp:
+        (WebCore::CachedResource::makeRevalidationDecision):
+
+            On normal reloads (CachePolicyRevalidate) of https resources check for 'Cache-control: immutable'.
+            If the resource is not expired don't revalidate it.
+
+        * platform/network/CacheValidation.cpp:
+        (WebCore::parseCacheControlDirectives):
+        * platform/network/CacheValidation.h:
+        * platform/network/ResourceResponseBase.cpp:
+        (WebCore::ResourceResponseBase::cacheControlContainsImmutable):
+        * platform/network/ResourceResponseBase.h:
+
 2017-01-27  Youenn Fablet  <youennf@gmail.com>
 
         [WebRTC] Use MediaEndPointPeerConnection if not using libwebrtc
index 4051945..b75cadb 100644 (file)
@@ -748,7 +748,14 @@ CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(Ca
         return RevalidationDecision::No;
 
     case CachePolicyReload:
+        return RevalidationDecision::YesDueToCachePolicy;
+
     case CachePolicyRevalidate:
+        if (m_response.cacheControlContainsImmutable() && m_response.url().protocolIs("https")) {
+            if (isExpired())
+                return RevalidationDecision::YesDueToExpired;
+            return RevalidationDecision::No;
+        }
         return RevalidationDecision::YesDueToCachePolicy;
 
     case CachePolicyVerify:
index d49d475..a321a53 100644 (file)
@@ -316,7 +316,8 @@ CacheControlDirectives parseCacheControlDirectives(const HTTPHeaderMap& headers)
                 double maxStale = directives[i].second.toDouble(&ok);
                 if (ok)
                     result.maxStale = duration_cast<microseconds>(duration<double>(maxStale));
-            }
+            } else if (equalLettersIgnoringASCIICase(directives[i].first, "immutable"))
+                result.immutable = true;
         }
     }
 
index 1bbbede..5c587c5 100644 (file)
@@ -66,6 +66,7 @@ struct CacheControlDirectives {
     bool noCache { false };
     bool noStore { false };
     bool mustRevalidate { false };
+    bool immutable { false };
 };
 WEBCORE_EXPORT CacheControlDirectives parseCacheControlDirectives(const HTTPHeaderMap&);
 
index 84b2c50..c02a5c5 100644 (file)
@@ -425,6 +425,13 @@ bool ResourceResponseBase::cacheControlContainsMustRevalidate() const
         parseCacheControlDirectives();
     return m_cacheControlDirectives.mustRevalidate;
 }
+    
+bool ResourceResponseBase::cacheControlContainsImmutable() const
+{
+    if (!m_haveParsedCacheControlHeader)
+        parseCacheControlDirectives();
+    return m_cacheControlDirectives.immutable;
+}
 
 bool ResourceResponseBase::hasCacheValidatorFields() const
 {
index e1fc466..226fd82 100644 (file)
@@ -119,10 +119,10 @@ public:
     const std::optional<CertificateInfo>& certificateInfo() const { return m_certificateInfo; };
     
     // These functions return parsed values of the corresponding response headers.
-    // NaN means that the header was not present or had invalid value.
     WEBCORE_EXPORT bool cacheControlContainsNoCache() const;
     WEBCORE_EXPORT bool cacheControlContainsNoStore() const;
     WEBCORE_EXPORT bool cacheControlContainsMustRevalidate() const;
+    WEBCORE_EXPORT bool cacheControlContainsImmutable() const;
     WEBCORE_EXPORT bool hasCacheValidatorFields() const;
     WEBCORE_EXPORT std::optional<std::chrono::microseconds> cacheControlMaxAge() const;
     WEBCORE_EXPORT std::optional<std::chrono::system_clock::time_point> date() const;