Support stale-while-revalidate cache strategy
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 13 Nov 2019 10:22:04 +0000 (10:22 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 13 Nov 2019 10:22:04 +0000 (10:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=201461

Patch by Rob Buis <rbuis@igalia.com> on 2019-11-13
Reviewed by Youenn Fablet.

LayoutTests/imported/w3c:

Import stale-while-revalidate WPT tests.

* resources/import-expectations.json:
* web-platform-tests/fetch/stale-while-revalidate/fetch-expected.txt: Added.
* web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https-expected.txt: Added.
* web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html: Added.
* web-platform-tests/fetch/stale-while-revalidate/fetch.html: Added.
* web-platform-tests/fetch/stale-while-revalidate/resources/stale-css.py: Added.
(main):
* web-platform-tests/fetch/stale-while-revalidate/resources/stale-image.py: Added.
(main):
* web-platform-tests/fetch/stale-while-revalidate/resources/stale-script.py: Added.
(id_token):
(main):
* web-platform-tests/fetch/stale-while-revalidate/resources/w3c-import.log: Added.
* web-platform-tests/fetch/stale-while-revalidate/stale-css-expected.txt: Added.
* web-platform-tests/fetch/stale-while-revalidate/stale-css.html: Added.
* web-platform-tests/fetch/stale-while-revalidate/stale-image-expected.txt: Added.
* web-platform-tests/fetch/stale-while-revalidate/stale-image.html: Added.
* web-platform-tests/fetch/stale-while-revalidate/stale-script-expected.txt: Added.
* web-platform-tests/fetch/stale-while-revalidate/stale-script.html: Added.
* web-platform-tests/fetch/stale-while-revalidate/sw-intercept.js: Added.
(async.broadcast):
* web-platform-tests/fetch/stale-while-revalidate/w3c-import.log: Added.

Source/WebCore:

Start parsing the stale-while-revalidate Cache-Control directive
and expose it on ResourceResponse.

Tests: imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html
       imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch.html
       imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css.html
       imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html
       imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script.html

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

Source/WebKit:

Add a new UseDecision value AsyncRevalidate for async revalidation. This is used
when the retrieved cache entry is a stale-while-revalidate response [1].
In case of AsyncRevalidate, a check is made to see if there is a
current async revalidation ongoing for the entry, if not one is
started. Regardless, the stale entry is returned, until either the
async revalidation ends successfully or at the moment when the
response expires for real.

[1] https://fetch.spec.whatwg.org/#concept-stale-while-revalidate-response

* NetworkProcess/NetworkSession.cpp:
(WebKit::NetworkSession::NetworkSession):
* NetworkProcess/NetworkSession.h:
(WebKit::NetworkSession::isStaleWhileRevalidateEnabled const):
* NetworkProcess/NetworkSessionCreationParameters.cpp:
(WebKit::NetworkSessionCreationParameters::encode const):
(WebKit::NetworkSessionCreationParameters::decode):
* NetworkProcess/NetworkSessionCreationParameters.h:
* NetworkProcess/cache/AsyncRevalidation.cpp: Added.
(WebKit::NetworkCache::constructRevalidationRequest):
(WebKit::NetworkCache::AsyncRevalidation::staleWhileRevalidateEnding):
(WebKit::NetworkCache::AsyncRevalidation::AsyncRevalidation):
* NetworkProcess/cache/AsyncRevalidation.h: Added.
(WebKit::NetworkCache::AsyncRevalidation::load const):
* NetworkProcess/cache/NetworkCache.cpp:
(WebKit::NetworkCache::responseNeedsRevalidation):
(WebKit::NetworkCache::makeUseDecision):
(WebKit::NetworkCache::makeStoreDecision):
(WebKit::NetworkCache::Cache::startAsyncRevalidationIfNeeded):
(WebKit::NetworkCache::Cache::retrieve):
(WebKit::NetworkCache::responseHasExpired): Deleted.
* NetworkProcess/cache/NetworkCache.h:
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp:
(WebKit::NetworkCache::dumpHTTPHeadersDiff):
(WebKit::NetworkCache::requestsHeadersMatch):
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.h:
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp:
(WebKit::NetworkCache::dumpHTTPHeadersDiff): Deleted.
(WebKit::NetworkCache::requestsHeadersMatch): Deleted.
* Sources.txt:
* UIProcess/API/C/WKWebsiteDataStoreConfigurationRef.cpp:
(WKWebsiteDataStoreConfigurationGetStaleWhileRevalidateEnabled):
(WKWebsiteDataStoreConfigurationSetStaleWhileRevalidateEnabled):
* UIProcess/API/C/WKWebsiteDataStoreConfigurationRef.h:
* UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
(WebKit::WebsiteDataStore::parameters):
* UIProcess/WebsiteData/WebsiteDataStoreConfiguration.cpp:
(WebKit::WebsiteDataStoreConfiguration::copy const):
* UIProcess/WebsiteData/WebsiteDataStoreConfiguration.h:
(WebKit::WebsiteDataStoreConfiguration::staleWhileRevalidateEnabled const):
(WebKit::WebsiteDataStoreConfiguration::setStaleWhileRevalidateEnabled):
* WebKit.xcodeproj/project.pbxproj:

Tools:

Enable stale-while-revalidate for the test runner.

* WebKitTestRunner/TestController.cpp:
(WTR::TestController::websiteDataStore):

LayoutTests:

Skip newly imported tests for WK1.

* platform/ios-wk1/TestExpectations:
* platform/mac-wk1/TestExpectations:

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

47 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/resources/import-expectations.json
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-css.py [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-image.py [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-script.py [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/w3c-import.log [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/sw-intercept.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/w3c-import.log [new file with mode: 0644]
LayoutTests/platform/ios-wk1/TestExpectations
LayoutTests/platform/mac-wk1/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/platform/network/CacheValidation.cpp
Source/WebCore/platform/network/CacheValidation.h
Source/WebCore/platform/network/ResourceResponseBase.cpp
Source/WebCore/platform/network/ResourceResponseBase.h
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/NetworkSession.cpp
Source/WebKit/NetworkProcess/NetworkSession.h
Source/WebKit/NetworkProcess/NetworkSessionCreationParameters.cpp
Source/WebKit/NetworkProcess/NetworkSessionCreationParameters.h
Source/WebKit/NetworkProcess/cache/AsyncRevalidation.cpp [new file with mode: 0644]
Source/WebKit/NetworkProcess/cache/AsyncRevalidation.h [new file with mode: 0644]
Source/WebKit/NetworkProcess/cache/NetworkCache.cpp
Source/WebKit/NetworkProcess/cache/NetworkCache.h
Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp
Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoad.h
Source/WebKit/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp
Source/WebKit/Sources.txt
Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreConfigurationRef.cpp
Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreConfigurationRef.h
Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm
Source/WebKit/UIProcess/WebsiteData/WebsiteDataStoreConfiguration.cpp
Source/WebKit/UIProcess/WebsiteData/WebsiteDataStoreConfiguration.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/WebKitTestRunner/TestController.cpp

index d1d18c7..641cf3f 100644 (file)
@@ -1,3 +1,15 @@
+2019-11-13  Rob Buis  <rbuis@igalia.com>
+
+        Support stale-while-revalidate cache strategy
+        https://bugs.webkit.org/show_bug.cgi?id=201461
+
+        Reviewed by Youenn Fablet.
+
+        Skip newly imported tests for WK1.
+
+        * platform/ios-wk1/TestExpectations:
+        * platform/mac-wk1/TestExpectations:
+
 2019-11-12  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         Unreviewed test gardening for WinCairo
index 64bdd87..6ee5eaa 100644 (file)
@@ -1,3 +1,35 @@
+2019-11-13  Rob Buis  <rbuis@igalia.com>
+
+        Support stale-while-revalidate cache strategy
+        https://bugs.webkit.org/show_bug.cgi?id=201461
+
+        Reviewed by Youenn Fablet.
+
+        Import stale-while-revalidate WPT tests.
+
+        * resources/import-expectations.json:
+        * web-platform-tests/fetch/stale-while-revalidate/fetch-expected.txt: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https-expected.txt: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/fetch.html: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/resources/stale-css.py: Added.
+        (main):
+        * web-platform-tests/fetch/stale-while-revalidate/resources/stale-image.py: Added.
+        (main):
+        * web-platform-tests/fetch/stale-while-revalidate/resources/stale-script.py: Added.
+        (id_token):
+        (main):
+        * web-platform-tests/fetch/stale-while-revalidate/resources/w3c-import.log: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/stale-css-expected.txt: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/stale-css.html: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/stale-image-expected.txt: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/stale-image.html: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/stale-script-expected.txt: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/stale-script.html: Added.
+        * web-platform-tests/fetch/stale-while-revalidate/sw-intercept.js: Added.
+        (async.broadcast):
+        * web-platform-tests/fetch/stale-while-revalidate/w3c-import.log: Added.
+
 2019-11-12  Carlos Alberto Lopez Perez  <clopez@igalia.com>
 
         [GTK][WPE] Support Pointer Events
index 3503373..5c5dcf1 100644 (file)
     "web-platform-tests/fetch/api": "import", 
     "web-platform-tests/fetch/api/cors": "import", 
     "web-platform-tests/fetch/range": "import", 
+    "web-platform-tests/fetch/stale-while-revalidate": "import", 
     "web-platform-tests/fullscreen": "skip", 
     "web-platform-tests/gamepad": "skip", 
     "web-platform-tests/generic-sensor": "skip", 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-expected.txt
new file mode 100644 (file)
index 0000000..46bf26f
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Second fetch returns same response 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https-expected.txt
new file mode 100644 (file)
index 0000000..46bf26f
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Second fetch returns same response 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html
new file mode 100644 (file)
index 0000000..efcebc2
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Stale Revalidation Requests don't get sent to service worker</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="../../service-workers/service-worker/resources/test-helpers.sub.js"></script>
+  <script src="/common/utils.js"></script>
+</head>
+<body>
+<script>
+
+  // Duplicating this resource to make service worker scoping simpler.
+  async function setupRegistrationAndWaitToBeControlled(t, scope) {
+    const controlled = new Promise((resolve) => {
+      navigator.serviceWorker.oncontrollerchange = () => { resolve(); };
+    });
+    const reg = await navigator.serviceWorker.register('sw-intercept.js');
+    await wait_for_state(t, reg.installing, 'activated');
+    await controlled;
+    add_completion_callback(_ => reg.unregister());
+    return reg;
+  }
+
+  // Using 250ms polling interval to provide enough 'network calmness' to give
+  // the background low priority revalidation request a chance to kick in.
+  function wait250ms(test) {
+    return new Promise(resolve => {
+      test.step_timeout(() => {
+        resolve();
+      }, 250);
+    });
+  }
+
+  promise_test(async (test) => {
+    var request_token = token();
+    const uri = 'resources/stale-script.py?token=' + request_token;
+
+    await setupRegistrationAndWaitToBeControlled(test, 'resources/stale-script.py');
+
+    var service_worker_count = 0;
+    navigator.serviceWorker.addEventListener('message', function once(event) {
+      if (event.data.endsWith(uri)) {
+        service_worker_count++;
+      }
+    });
+
+    const response = await fetch(uri);
+    const response2 = await fetch(uri);
+    assert_equals(response.headers.get('Unique-Id'), response2.headers.get('Unique-Id'));
+    while(true) {
+      const revalidation_check = await fetch(`resources/stale-script.py?query&token=` + request_token);
+      if (revalidation_check.headers.get('Count') == '2') {
+        // The service worker should not see the revalidation request.
+        assert_equals(service_worker_count, 2);
+        break;
+      }
+      await wait250ms(test);
+    }
+  }, 'Second fetch returns same response');
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch.html b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch.html
new file mode 100644 (file)
index 0000000..73390c7
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests Stale While Revalidate is not executed for fetch API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script>
+function wait25ms(test) {
+  return new Promise(resolve => {
+    test.step_timeout(() => {
+      resolve();
+    }, 25);
+  });
+}
+
+promise_test(async (test) => {
+  var request_token = token();
+
+  const response = await fetch(`resources/stale-script.py?token=` + request_token);
+  const response2 = await fetch(`resources/stale-script.py?token=` + request_token);
+
+  assert_equals(response.headers.get('Unique-Id'), response2.headers.get('Unique-Id'));
+
+  while(true) {
+    const revalidation_check = await fetch(`resources/stale-script.py?query&token=` + request_token);
+    if (revalidation_check.headers.get('Count') == '2') {
+      break;
+    }
+    await wait25ms(test);
+  }
+}, 'Second fetch returns same response');
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-css.py b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-css.py
new file mode 100644 (file)
index 0000000..a6ae546
--- /dev/null
@@ -0,0 +1,28 @@
+def main(request, response):
+
+    token = request.GET.first("token", None)
+    is_query = request.GET.first("query", None) != None
+    with request.server.stash.lock:
+      value = request.server.stash.take(token)
+      count = 0
+      if value != None:
+        count = int(value)
+      if is_query:
+        if count < 2:
+          request.server.stash.put(token, count)
+      else:
+        count = count + 1
+        request.server.stash.put(token, count)
+    if is_query:
+      headers = [("Count", count)]
+      content = ""
+      return 200, headers, content
+    else:
+      content = "body { background: rgb(0, 128, 0); }"
+      if count > 1:
+        content = "body { background: rgb(255, 0, 0); }"
+
+      headers = [("Content-Type", "text/css"),
+               ("Cache-Control", "private, max-age=0, stale-while-revalidate=60")]
+
+      return 200, headers, content
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-image.py b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-image.py
new file mode 100644 (file)
index 0000000..839eb84
--- /dev/null
@@ -0,0 +1,38 @@
+import os.path
+
+def main(request, response):
+
+    token = request.GET.first("token", None)
+    is_query = request.GET.first("query", None) != None
+    with request.server.stash.lock:
+      value = request.server.stash.take(token)
+      count = 0
+      if value != None:
+        count = int(value)
+      if is_query:
+        if count < 2:
+          request.server.stash.put(token, count)
+      else:
+        count = count + 1
+        request.server.stash.put(token, count)
+
+    if is_query:
+      headers = [("Count", count)]
+      content = ""
+      return 200, headers, content
+    else:
+      filename = "green-16x16.png"
+      if count > 1:
+        filename = "green-256x256.png"
+
+      path = os.path.join(os.path.dirname(__file__), "../../../images", filename)
+      body = open(path, "rb").read()
+
+      response.add_required_headers = False
+      response.writer.write_status(200)
+      response.writer.write_header("content-length", len(body))
+      response.writer.write_header("Cache-Control", "private, max-age=0, stale-while-revalidate=60")
+      response.writer.write_header("content-type", "image/png")
+      response.writer.end_headers()
+
+      response.writer.write(body)
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-script.py b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-script.py
new file mode 100644 (file)
index 0000000..8ad5467
--- /dev/null
@@ -0,0 +1,32 @@
+import random, string, datetime
+
+def id_token():
+   letters = string.ascii_lowercase
+   return ''.join(random.choice(letters) for i in range(20))
+
+def main(request, response):
+    token = request.GET.first("token", None)
+    is_query = request.GET.first("query", None) != None
+    with request.server.stash.lock:
+      value = request.server.stash.take(token)
+      count = 0
+      if value != None:
+        count = int(value)
+      if is_query:
+        if count < 2:
+          request.server.stash.put(token, count)
+      else:
+        count = count + 1
+        request.server.stash.put(token, count)
+
+    if is_query:
+      headers = [("Count", count)]
+      content = ""
+      return 200, headers, content
+    else:
+      unique_id = id_token()
+      headers = [("Content-Type", "text/javascript"),
+                 ("Cache-Control", "private, max-age=0, stale-while-revalidate=60"),
+                 ("Unique-Id", unique_id)]
+      content = "report('{}')".format(unique_id)
+      return 200, headers, content
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/w3c-import.log
new file mode 100644 (file)
index 0000000..75a7732
--- /dev/null
@@ -0,0 +1,19 @@
+The tests in this directory were imported from the W3C repository.
+Do NOT modify these tests directly in WebKit.
+Instead, create a pull request on the WPT github:
+       https://github.com/web-platform-tests/wpt
+
+Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
+
+Do NOT modify or remove this file.
+
+------------------------------------------------------------------------
+Properties requiring vendor prefixes:
+None
+Property values requiring vendor prefixes:
+None
+------------------------------------------------------------------------
+List of files:
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-css.py
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-image.py
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/resources/stale-script.py
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css-expected.txt
new file mode 100644 (file)
index 0000000..8ac9940
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Cache returns stale resource 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css.html b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css.html
new file mode 100644 (file)
index 0000000..f56260f
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests Stale While Revalidate works for css</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<body>
+<script>
+
+var request_token = token();
+async_test(t => {
+  window.onload = t.step_func(() => {
+    t.step_timeout(() => {
+      assert_equals(window.getComputedStyle(document.body).getPropertyValue('background-color'), "rgb(0, 128, 0)");
+      var link2 = document.createElement("link");
+      link2.onload = t.step_func(() => {
+        assert_equals(window.getComputedStyle(document.body).getPropertyValue('background-color'), "rgb(0, 128, 0)");
+        var checkResult = () => {
+          // We poll because we don't know when the revalidation will occur.
+          fetch("resources/stale-css.py?query&token=" + request_token).then(t.step_func((response) => {
+            var count = response.headers.get("Count");
+            if (count == '2') {
+              t.done();
+            } else {
+              t.step_timeout(checkResult, 25);
+            }
+          }));
+        };
+        t.step_timeout(checkResult, 25);
+      });
+      link2.rel = "stylesheet";
+      link2.type = "text/css";
+      link2.href = "resources/stale-css.py?token=" + request_token;
+      document.body.appendChild(link2);
+    }, 0);
+  });
+}, 'Cache returns stale resource');
+
+var link = document.createElement("link");
+link.rel = "stylesheet";
+link.type = "text/css";
+link.href = "resources/stale-css.py?token=" + request_token;
+document.body.appendChild(link);
+</script>
+</body>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image-expected.txt
new file mode 100644 (file)
index 0000000..1e42014
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS Cache returns stale resource 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html
new file mode 100644 (file)
index 0000000..78a5a09
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests Stale While Revalidate works for images</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<body>
+<!--
+Use a child document to load the second stale image into because
+an image loaded into the same document will skip cache-control headers.
+See: https://html.spec.whatwg.org/#the-list-of-available-images
+-->
+<iframe id="child" srcdoc=""></iframe>
+<script>
+var request_token = token();
+async_test(t => {
+  window.onload = t.step_func(() => {
+    t.step_timeout(() => {
+      assert_equals(document.getElementById("firstimage").width, 16, "Width is 16");
+      var childDocument = document.getElementById('child').contentDocument;
+      var img2 = childDocument.createElement("img");
+      img2.onload = t.step_func(() => {
+        assert_equals(img2.width, 16, "image dimension");
+        var checkResult = () => {
+          // We poll because we don't know when the revalidation will occur.
+          fetch("resources/stale-image.py?query&token=" + request_token).then(t.step_func((response) => {
+            var count = response.headers.get("Count");
+            if (count == '2') {
+              t.done();
+            } else {
+              t.step_timeout(checkResult, 25);
+            }
+          }));
+        };
+        t.step_timeout(checkResult, 25);
+      });
+      img2.src = "resources/stale-image.py?token=" + request_token;
+      childDocument.body.appendChild(img2);
+    }, 0);
+  });
+}, 'Cache returns stale resource');
+
+var img = document.createElement("img");
+img.src = "resources/stale-image.py?token=" + request_token;
+img.id = "firstimage";
+document.body.appendChild(img);
+</script>
+</body>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script-expected.txt
new file mode 100644 (file)
index 0000000..8ac9940
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Cache returns stale resource 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script.html b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script.html
new file mode 100644 (file)
index 0000000..68793e5
--- /dev/null
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests Stale While Revalidate works for scripts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<body>
+<script>
+var last_modified;
+var last_modified_count = 0;
+var request_token = token();
+
+// The script will call report via a uniquely generated ID on the subresource.
+// If it is a cache hit the ID will be the same and the test will pass.
+function report(mod) {
+  if (!last_modified) {
+    last_modified = mod;
+    last_modified_count = 1;
+  } else if (last_modified == mod) {
+    last_modified_count++;
+  }
+}
+
+async_test(t => {
+  window.onload = t.step_func(() => {
+    step_timeout(() => {
+      var script = document.createElement("script");
+      script.src = "resources/stale-script.py?token=" + request_token;
+      document.body.appendChild(script);
+      script.onload = t.step_func(() => {
+          assert_equals(last_modified_count, 2, "last modified");
+          var checkResult = () => {
+            // We poll because we don't know when the revalidation will occur.
+            fetch("resources/stale-script.py?query&token=" + request_token).then(t.step_func((response) => {
+              var count = response.headers.get("Count");
+              if (count == '2') {
+                  t.done();
+              } else {
+                t.step_timeout(checkResult, 25);
+              }
+            }));
+          };
+          t.step_timeout(checkResult, 25);
+      });
+    }, 0);
+  });
+}, 'Cache returns stale resource');
+
+var script = document.createElement("script");
+script.src = "resources/stale-script.py?token=" + request_token;
+document.body.appendChild(script);
+</script>
+</body>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/sw-intercept.js b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/sw-intercept.js
new file mode 100644 (file)
index 0000000..dca7de5
--- /dev/null
@@ -0,0 +1,14 @@
+async function broadcast(msg) {
+  for (const client of await clients.matchAll()) {
+    client.postMessage(msg);
+  }
+}
+
+self.addEventListener('fetch', event => {
+  event.waitUntil(broadcast(event.request.url));
+  event.respondWith(fetch(event.request));
+});
+
+self.addEventListener('activate', event => {
+  self.clients.claim();
+});
diff --git a/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/w3c-import.log
new file mode 100644 (file)
index 0000000..ab9dd06
--- /dev/null
@@ -0,0 +1,22 @@
+The tests in this directory were imported from the W3C repository.
+Do NOT modify these tests directly in WebKit.
+Instead, create a pull request on the WPT github:
+       https://github.com/web-platform-tests/wpt
+
+Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
+
+Do NOT modify or remove this file.
+
+------------------------------------------------------------------------
+Properties requiring vendor prefixes:
+None
+Property values requiring vendor prefixes:
+None
+------------------------------------------------------------------------
+List of files:
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch.html
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css.html
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script.html
+/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/sw-intercept.js
index c309734..f1fd7a0 100644 (file)
@@ -2005,4 +2005,7 @@ inspector/canvas/recording-html-2d.html
 webkit.org/b/159724 [ Debug ] imported/w3c/web-platform-tests/xhr/send-redirect-post-upload.htm [ Skip ]
 
 # Skip IsLoggedIn
-http/tests/is-logged-in/ [ Skip ]
\ No newline at end of file
+http/tests/is-logged-in/ [ Skip ]
+
+# Stale-while-revalidate is not supported on WK1
+imported/w3c/web-platform-tests/fetch/stale-while-revalidate [ Skip ]
index 43668e4..a280050 100644 (file)
@@ -822,3 +822,6 @@ webkit.org/b/203501 imported/w3c/web-platform-tests/css/css-animations/animation
 webkit.org/b/203501 imported/w3c/web-platform-tests/css/css-animations/animation-transform-pause-and-set-time.html [ ImageOnlyFailure ]
 
 webkit.org/b/203517 imported/w3c/web-platform-tests/css/css-sizing/dynamic-available-size-iframe.html [ Pass ImageOnlyFailure ]
+
+# Stale-while-revalidate is not supported on WK1
+imported/w3c/web-platform-tests/fetch/stale-while-revalidate [ Skip ]
index 350571c..5d21511 100644 (file)
@@ -1,3 +1,26 @@
+2019-11-13  Rob Buis  <rbuis@igalia.com>
+
+        Support stale-while-revalidate cache strategy
+        https://bugs.webkit.org/show_bug.cgi?id=201461
+
+        Reviewed by Youenn Fablet.
+
+        Start parsing the stale-while-revalidate Cache-Control directive
+        and expose it on ResourceResponse.
+
+        Tests: imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch-sw.https.html
+               imported/w3c/web-platform-tests/fetch/stale-while-revalidate/fetch.html
+               imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-css.html
+               imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html
+               imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-script.html
+
+        * platform/network/CacheValidation.cpp:
+        (WebCore::parseCacheControlDirectives):
+        * platform/network/CacheValidation.h:
+        * platform/network/ResourceResponseBase.cpp:
+        (WebCore::ResourceResponseBase::cacheControlStaleWhileRevalidate const):
+        * platform/network/ResourceResponseBase.h:
+
 2019-11-12  Simon Fraser  <simon.fraser@apple.com>
 
         Move CSSUnitType enum to its own file
index 4959050..2d3507d 100644 (file)
@@ -313,8 +313,18 @@ CacheControlDirectives parseCacheControlDirectives(const HTTPHeaderMap& headers)
                 double maxStale = directives[i].second.toDouble(&ok);
                 if (ok)
                     result.maxStale = Seconds { maxStale };
-            } else if (equalLettersIgnoringASCIICase(directives[i].first, "immutable"))
+            } else if (equalLettersIgnoringASCIICase(directives[i].first, "immutable")) {
                 result.immutable = true;
+            } else if (equalLettersIgnoringASCIICase(directives[i].first, "stale-while-revalidate")) {
+                if (result.staleWhileRevalidate) {
+                    // First stale-while-revalidate directive wins if there are multiple ones.
+                    continue;
+                }
+                bool ok;
+                double staleWhileRevalidate = directives[i].second.toDouble(&ok);
+                if (ok)
+                    result.staleWhileRevalidate = Seconds { staleWhileRevalidate };
+            }
         }
     }
 
index 56efea6..040a969 100644 (file)
@@ -69,6 +69,7 @@ struct CacheControlDirectives {
 
     Markable<Seconds, Seconds::MarkableTraits> maxAge;
     Markable<Seconds, Seconds::MarkableTraits> maxStale;
+    Markable<Seconds, Seconds::MarkableTraits> staleWhileRevalidate;
     bool noCache : 1;
     bool noStore : 1;
     bool mustRevalidate : 1;
index 6ed27f3..7549e51 100644 (file)
@@ -657,6 +657,13 @@ Optional<Seconds> ResourceResponseBase::cacheControlMaxAge() const
     return m_cacheControlDirectives.maxAge;
 }
 
+Optional<Seconds> ResourceResponseBase::cacheControlStaleWhileRevalidate() const
+{
+    if (!m_haveParsedCacheControlHeader)
+        parseCacheControlDirectives();
+    return m_cacheControlDirectives.staleWhileRevalidate;
+}
+
 static Optional<WallTime> parseDateValueInHeader(const HTTPHeaderMap& headers, HTTPHeaderName headerName)
 {
     String headerValue = headers.get(headerName);
index e7a8f3b..c0caed0 100644 (file)
@@ -137,6 +137,7 @@ public:
     WEBCORE_EXPORT bool cacheControlContainsImmutable() const;
     WEBCORE_EXPORT bool hasCacheValidatorFields() const;
     WEBCORE_EXPORT Optional<Seconds> cacheControlMaxAge() const;
+    WEBCORE_EXPORT Optional<Seconds> cacheControlStaleWhileRevalidate() const;
     WEBCORE_EXPORT Optional<WallTime> date() const;
     WEBCORE_EXPORT Optional<Seconds> age() const;
     WEBCORE_EXPORT Optional<WallTime> expires() const;
index 8b5927c..5a8ab2a 100644 (file)
@@ -1,3 +1,63 @@
+2019-11-13  Rob Buis  <rbuis@igalia.com>
+
+        Support stale-while-revalidate cache strategy
+        https://bugs.webkit.org/show_bug.cgi?id=201461
+
+        Reviewed by Youenn Fablet.
+
+        Add a new UseDecision value AsyncRevalidate for async revalidation. This is used
+        when the retrieved cache entry is a stale-while-revalidate response [1].
+        In case of AsyncRevalidate, a check is made to see if there is a
+        current async revalidation ongoing for the entry, if not one is
+        started. Regardless, the stale entry is returned, until either the
+        async revalidation ends successfully or at the moment when the
+        response expires for real.
+
+        [1] https://fetch.spec.whatwg.org/#concept-stale-while-revalidate-response
+
+        * NetworkProcess/NetworkSession.cpp:
+        (WebKit::NetworkSession::NetworkSession):
+        * NetworkProcess/NetworkSession.h:
+        (WebKit::NetworkSession::isStaleWhileRevalidateEnabled const):
+        * NetworkProcess/NetworkSessionCreationParameters.cpp:
+        (WebKit::NetworkSessionCreationParameters::encode const):
+        (WebKit::NetworkSessionCreationParameters::decode):
+        * NetworkProcess/NetworkSessionCreationParameters.h:
+        * NetworkProcess/cache/AsyncRevalidation.cpp: Added.
+        (WebKit::NetworkCache::constructRevalidationRequest):
+        (WebKit::NetworkCache::AsyncRevalidation::staleWhileRevalidateEnding):
+        (WebKit::NetworkCache::AsyncRevalidation::AsyncRevalidation):
+        * NetworkProcess/cache/AsyncRevalidation.h: Added.
+        (WebKit::NetworkCache::AsyncRevalidation::load const):
+        * NetworkProcess/cache/NetworkCache.cpp:
+        (WebKit::NetworkCache::responseNeedsRevalidation):
+        (WebKit::NetworkCache::makeUseDecision):
+        (WebKit::NetworkCache::makeStoreDecision):
+        (WebKit::NetworkCache::Cache::startAsyncRevalidationIfNeeded):
+        (WebKit::NetworkCache::Cache::retrieve):
+        (WebKit::NetworkCache::responseHasExpired): Deleted.
+        * NetworkProcess/cache/NetworkCache.h:
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp:
+        (WebKit::NetworkCache::dumpHTTPHeadersDiff):
+        (WebKit::NetworkCache::requestsHeadersMatch):
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoad.h:
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp:
+        (WebKit::NetworkCache::dumpHTTPHeadersDiff): Deleted.
+        (WebKit::NetworkCache::requestsHeadersMatch): Deleted.
+        * Sources.txt:
+        * UIProcess/API/C/WKWebsiteDataStoreConfigurationRef.cpp:
+        (WKWebsiteDataStoreConfigurationGetStaleWhileRevalidateEnabled):
+        (WKWebsiteDataStoreConfigurationSetStaleWhileRevalidateEnabled):
+        * UIProcess/API/C/WKWebsiteDataStoreConfigurationRef.h:
+        * UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
+        (WebKit::WebsiteDataStore::parameters):
+        * UIProcess/WebsiteData/WebsiteDataStoreConfiguration.cpp:
+        (WebKit::WebsiteDataStoreConfiguration::copy const):
+        * UIProcess/WebsiteData/WebsiteDataStoreConfiguration.h:
+        (WebKit::WebsiteDataStoreConfiguration::staleWhileRevalidateEnabled const):
+        (WebKit::WebsiteDataStoreConfiguration::setStaleWhileRevalidateEnabled):
+        * WebKit.xcodeproj/project.pbxproj:
+
 2019-11-12  Simon Fraser  <simon.fraser@apple.com>
 
         Convert CSSPrimitiveValue::UnitType to an enum class, and cleanup
index c5b25fa..b5c5950 100644 (file)
@@ -108,6 +108,8 @@ NetworkSession::NetworkSession(NetworkProcess& networkProcess, const NetworkSess
             SandboxExtension::consumePermanently(parameters.resourceLoadStatisticsDirectoryExtensionHandle);
     }
 
+    m_isStaleWhileRevalidateEnabled = parameters.staleWhileRevalidateEnabled;
+
     m_adClickAttribution->setPingLoadFunction([this, weakThis = makeWeakPtr(this)](NetworkResourceLoadParameters&& loadParameters, CompletionHandler<void(const WebCore::ResourceError&, const WebCore::ResourceResponse&)>&& completionHandler) {
         if (!weakThis)
             return;
index e6ba7d8..f25dd65 100644 (file)
@@ -124,6 +124,8 @@ public:
 
     unsigned testSpeedMultiplier() const { return m_testSpeedMultiplier; }
 
+    bool isStaleWhileRevalidateEnabled() const { return m_isStaleWhileRevalidateEnabled; }
+
 protected:
     NetworkSession(NetworkProcess&, const NetworkSessionCreationParameters&);
 
@@ -144,6 +146,7 @@ protected:
     bool m_downgradeReferrer { true };
     bool m_thirdPartyCookieBlockingEnabled { false };
 #endif
+    bool m_isStaleWhileRevalidateEnabled { false };
     UniqueRef<AdClickAttributionManager> m_adClickAttribution;
 
     HashSet<Ref<NetworkResourceLoader>> m_keptAliveLoads;
index 038c250..3e85bea 100644 (file)
@@ -78,6 +78,7 @@ void NetworkSessionCreationParameters::encode(IPC::Encoder& encoder) const
     encoder << fastServerTrustEvaluationEnabled;
     encoder << networkCacheSpeculativeValidationEnabled;
     encoder << shouldUseTestingNetworkSession;
+    encoder << staleWhileRevalidateEnabled;
     encoder << testSpeedMultiplier;
     encoder << suppressesConnectionTerminationOnSystemChange;
 }
@@ -243,7 +244,12 @@ Optional<NetworkSessionCreationParameters> NetworkSessionCreationParameters::dec
     decoder >> shouldUseTestingNetworkSession;
     if (!shouldUseTestingNetworkSession)
         return WTF::nullopt;
-    
+
+    Optional<bool> staleWhileRevalidateEnabled;
+    decoder >> staleWhileRevalidateEnabled;
+    if (!staleWhileRevalidateEnabled)
+        return WTF::nullopt;
+
     Optional<unsigned> testSpeedMultiplier;
     decoder >> testSpeedMultiplier;
     if (!testSpeedMultiplier)
@@ -292,6 +298,7 @@ Optional<NetworkSessionCreationParameters> NetworkSessionCreationParameters::dec
         , WTFMove(*fastServerTrustEvaluationEnabled)
         , WTFMove(*networkCacheSpeculativeValidationEnabled)
         , WTFMove(*shouldUseTestingNetworkSession)
+        , WTFMove(*staleWhileRevalidateEnabled)
         , WTFMove(*testSpeedMultiplier)
         , WTFMove(*suppressesConnectionTerminationOnSystemChange)
     }};
index 501252c..ac436a3 100644 (file)
@@ -96,6 +96,7 @@ struct NetworkSessionCreationParameters {
     bool fastServerTrustEvaluationEnabled { false };
     bool networkCacheSpeculativeValidationEnabled { false };
     bool shouldUseTestingNetworkSession { false };
+    bool staleWhileRevalidateEnabled { false };
     unsigned testSpeedMultiplier { 1 };
     bool suppressesConnectionTerminationOnSystemChange { false };
 };
diff --git a/Source/WebKit/NetworkProcess/cache/AsyncRevalidation.cpp b/Source/WebKit/NetworkProcess/cache/AsyncRevalidation.cpp
new file mode 100644 (file)
index 0000000..21035b5
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "AsyncRevalidation.h"
+
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+#include <WebCore/CacheValidation.h>
+#include <WebCore/ResourceRequest.h>
+
+namespace WebKit {
+namespace NetworkCache {
+
+static inline WebCore::ResourceRequest constructRevalidationRequest(const Key& key, const WebCore::ResourceRequest& request, const Entry& entry)
+{
+    WebCore::ResourceRequest revalidationRequest = request;
+    if (!key.partition().isEmpty())
+        revalidationRequest.setCachePartition(key.partition());
+    ASSERT_WITH_MESSAGE(key.range().isEmpty(), "range is not supported");
+
+    revalidationRequest.makeUnconditional();
+    auto eTag = entry.response().httpHeaderField(WebCore::HTTPHeaderName::ETag);
+    if (!eTag.isEmpty())
+        revalidationRequest.setHTTPHeaderField(WebCore::HTTPHeaderName::IfNoneMatch, eTag);
+
+    auto lastModified = entry.response().httpHeaderField(WebCore::HTTPHeaderName::LastModified);
+    if (!lastModified.isEmpty())
+        revalidationRequest.setHTTPHeaderField(WebCore::HTTPHeaderName::IfModifiedSince, lastModified);
+
+    revalidationRequest.setPriority(WebCore::ResourceLoadPriority::Low);
+
+    return revalidationRequest;
+}
+
+void AsyncRevalidation::staleWhileRevalidateEnding()
+{
+    if (m_completionHandler)
+        m_completionHandler(Result::Timeout);
+}
+
+AsyncRevalidation::AsyncRevalidation(Cache& cache, const GlobalFrameID& frameID, const WebCore::ResourceRequest& request, std::unique_ptr<NetworkCache::Entry>&& entry, CompletionHandler<void(Result)>&& handler)
+    : m_timer(*this, &AsyncRevalidation::staleWhileRevalidateEnding)
+    , m_completionHandler(WTFMove(handler))
+{
+    auto key = entry->key();
+    auto revalidationRequest = constructRevalidationRequest(key, request, *entry.get());
+    auto age = WebCore::computeCurrentAge(entry->response(), entry->timeStamp());
+    auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(entry->response(), entry->timeStamp());
+    auto responseMaxStaleness = entry->response().cacheControlStaleWhileRevalidate();
+    ASSERT(responseMaxStaleness);
+    m_timer.startOneShot(*responseMaxStaleness + (lifetime - age));
+    m_load = makeUnique<SpeculativeLoad>(cache, frameID, WTFMove(revalidationRequest), WTFMove(entry), [this, key, revalidationRequest](auto&& revalidatedEntry) {
+        ASSERT(!revalidatedEntry || !revalidatedEntry->needsValidation());
+        ASSERT(!revalidatedEntry || revalidatedEntry->key() == key);
+        if (m_completionHandler)
+            m_completionHandler(revalidatedEntry ? Result::Success : Result::Failure);
+    });
+}
+
+} // namespace NetworkCache
+} // namespace WebKit
+
+#endif // ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
diff --git a/Source/WebKit/NetworkProcess/cache/AsyncRevalidation.h b/Source/WebKit/NetworkProcess/cache/AsyncRevalidation.h
new file mode 100644 (file)
index 0000000..3dd608d
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+
+#include "NetworkCache.h"
+#include "NetworkCacheEntry.h"
+#include "NetworkCacheSpeculativeLoad.h"
+#include <wtf/CompletionHandler.h>
+
+namespace WebCore {
+class ResourceRequest;
+};
+
+namespace WebKit {
+
+class SpeculativeLoad;
+
+namespace NetworkCache {
+
+class AsyncRevalidation {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    enum class Result {
+        Failure,
+        Timeout,
+        Success,
+    };
+    AsyncRevalidation(Cache&, const GlobalFrameID&, const WebCore::ResourceRequest&, std::unique_ptr<NetworkCache::Entry>&&, CompletionHandler<void(Result)>&&);
+
+    const SpeculativeLoad& load() const { return *m_load; }
+
+private:
+    void staleWhileRevalidateEnding();
+
+    std::unique_ptr<SpeculativeLoad> m_load;
+    WebCore::Timer m_timer;
+    CompletionHandler<void(Result)> m_completionHandler;
+};
+
+} // namespace NetworkCache
+} // namespace WebKit
+
+#endif // ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
index f344d1a..275e00e 100644 (file)
 #include "config.h"
 #include "NetworkCache.h"
 
+#include "AsyncRevalidation.h"
 #include "Logging.h"
+#include "NetworkCacheSpeculativeLoad.h"
 #include "NetworkCacheSpeculativeLoadManager.h"
 #include "NetworkCacheStorage.h"
 #include "NetworkProcess.h"
+#include "NetworkSession.h"
 #include <WebCore/CacheValidation.h>
 #include <WebCore/HTTPHeaderNames.h>
 #include <WebCore/LowPowerModeNotifier.h>
@@ -152,35 +155,46 @@ static bool cachePolicyAllowsExpired(WebCore::ResourceRequestCachePolicy policy)
     return false;
 }
 
-static bool responseHasExpired(const WebCore::ResourceResponse& response, WallTime timestamp, Optional<Seconds> maxStale)
+static UseDecision responseNeedsRevalidation(NetworkSession& networkSession, const WebCore::ResourceResponse& response, WallTime timestamp, Optional<Seconds> maxStale)
 {
     if (response.cacheControlContainsNoCache())
-        return true;
+        return UseDecision::Validate;
 
     auto age = WebCore::computeCurrentAge(response, timestamp);
     auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);
 
     auto maximumStaleness = maxStale ? maxStale.value() : 0_ms;
     bool hasExpired = age - lifetime > maximumStaleness;
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+    if (hasExpired && !maxStale && networkSession.isStaleWhileRevalidateEnabled()) {
+        auto responseMaxStaleness = response.cacheControlStaleWhileRevalidate();
+        maximumStaleness += responseMaxStaleness ? responseMaxStaleness.value() : 0_ms;
+        bool inResponseStaleness = age - lifetime < maximumStaleness;
+        if (inResponseStaleness)
+            return UseDecision::AsyncRevalidate;
+    }
+#endif
 
+    if (hasExpired) {
 #ifndef LOG_DISABLED
-    if (hasExpired)
-        LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-stale=%g", age, lifetime, maxStale);
+        LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-staleness=%f", age, lifetime, maximumStaleness);
 #endif
+        return UseDecision::Validate;
+    }
 
-    return hasExpired;
+    return UseDecision::Use;
 }
 
-static bool responseNeedsRevalidation(const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, WallTime timestamp)
+static UseDecision responseNeedsRevalidation(NetworkSession& networkSession, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, WallTime timestamp)
 {
     auto requestDirectives = WebCore::parseCacheControlDirectives(request.httpHeaderFields());
     if (requestDirectives.noCache)
-        return true;
+        return UseDecision::Validate;
     // For requests we ignore max-age values other than zero.
     if (requestDirectives.maxAge && requestDirectives.maxAge.value() == 0_ms)
-        return true;
+        return UseDecision::Validate;
 
-    return responseHasExpired(response, timestamp, requestDirectives.maxStale);
+    return responseNeedsRevalidation(networkSession, response, timestamp, requestDirectives.maxStale);
 }
 
 static UseDecision makeUseDecision(NetworkProcess& networkProcess, const PAL::SessionID& sessionID, const Entry& entry, const WebCore::ResourceRequest& request)
@@ -197,8 +211,9 @@ static UseDecision makeUseDecision(NetworkProcess& networkProcess, const PAL::Se
     if (cachePolicyAllowsExpired(request.cachePolicy()))
         return UseDecision::Use;
 
-    if (!responseNeedsRevalidation(entry.response(), request, entry.timeStamp()))
-        return UseDecision::Use;
+    auto decision = responseNeedsRevalidation(*networkProcess.networkSession(sessionID), entry.response(), request, entry.timeStamp());
+    if (decision != UseDecision::Validate)
+        return decision;
 
     if (!entry.response().hasCacheValidatorFields())
         return UseDecision::NoDueToMissingValidatorFields;
@@ -251,8 +266,12 @@ static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalR
     bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority() == WebCore::ResourceLoadPriority::VeryHigh;
     if (!storeUnconditionallyForHistoryNavigation) {
         auto now = WallTime::now();
-        bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms;
-
+        Seconds allowedStale { 0_ms };
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+        if (auto value = response.cacheControlStaleWhileRevalidate())
+            allowedStale = value.value();
+#endif
+        bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && (WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms || allowedStale > 0_ms);
         bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;
         if (!possiblyReusable)
             return StoreDecision::NoDueToUnlikelyToReuse;
@@ -296,6 +315,18 @@ static bool inline canRequestUseSpeculativeRevalidation(const WebCore::ResourceR
 }
 #endif
 
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+void Cache::startAsyncRevalidationIfNeeded(const WebCore::ResourceRequest& request, const NetworkCache::Key& key, std::unique_ptr<Entry>&& entry, const GlobalFrameID& frameID)
+{
+    m_pendingAsyncRevalidations.ensure(key, [&] {
+        return makeUnique<AsyncRevalidation>(*this, frameID, request, WTFMove(entry), [this, key](AsyncRevalidation::Result result) {
+            m_pendingAsyncRevalidations.remove(key);
+            LOG(NetworkCache, "(NetworkProcess) Async revalidation completed for '%s' with result %d", key.identifier().utf8().data(), static_cast<int>(result));
+        });
+    });
+}
+#endif
+
 void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameID& frameID, RetrieveCompletionHandler&& completionHandler)
 {
     ASSERT(request.url().protocolIsInHTTPFamily());
@@ -334,12 +365,11 @@ void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameI
     }
 #endif
 
-    m_storage->retrieve(storageKey, priority, [request, completionHandler = WTFMove(completionHandler), info = WTFMove(info), storageKey, networkProcess = makeRef(networkProcess()), sessionID = m_sessionID](auto record, auto timings) mutable {
+    m_storage->retrieve(storageKey, priority, [this, protectedThis = makeRef(*this), request, completionHandler = WTFMove(completionHandler), info = WTFMove(info), storageKey, networkProcess = makeRef(networkProcess()), sessionID = m_sessionID, frameID](auto record, auto timings) mutable {
         info.storageTimings = timings;
 
         if (!record) {
             LOG(NetworkCache, "(NetworkProcess) not found in storage");
-
             completeRetrieve(WTFMove(completionHandler), nullptr, info);
             return false;
         }
@@ -350,6 +380,14 @@ void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameI
 
         auto useDecision = entry ? makeUseDecision(networkProcess, sessionID, *entry, request) : UseDecision::NoDueToDecodeFailure;
         switch (useDecision) {
+        case UseDecision::AsyncRevalidate: {
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+            auto entryCopy = makeUnique<Entry>(*entry);
+            entryCopy->setNeedsValidation(true);
+            startAsyncRevalidationIfNeeded(request, storageKey, WTFMove(entryCopy), frameID);
+#endif
+            FALLTHROUGH;
+        }
         case UseDecision::Use:
             break;
         case UseDecision::Validate:
index 0dee7f8..53540ae 100644 (file)
@@ -50,6 +50,7 @@ class NetworkProcess;
 
 namespace NetworkCache {
 
+class AsyncRevalidation;
 class Cache;
 class SpeculativeLoadManager;
 
@@ -82,6 +83,7 @@ enum class StoreDecision {
 enum class UseDecision {
     Use,
     Validate,
+    AsyncRevalidate,
     NoDueToVaryingHeaderMismatch,
     NoDueToMissingValidatorFields,
     NoDueToDecodeFailure,
@@ -167,6 +169,8 @@ public:
 
 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
     SpeculativeLoadManager* speculativeLoadManager() { return m_speculativeLoadManager.get(); }
+
+    void startAsyncRevalidationIfNeeded(const WebCore::ResourceRequest&, const NetworkCache::Key&, std::unique_ptr<Entry>&&, const GlobalFrameID&);
 #endif
 
     NetworkProcess& networkProcess() { return m_networkProcess.get(); }
@@ -193,6 +197,8 @@ private:
 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
     std::unique_ptr<WebCore::LowPowerModeNotifier> m_lowPowerModeNotifier;
     std::unique_ptr<SpeculativeLoadManager> m_speculativeLoadManager;
+
+    HashMap<Key, std::unique_ptr<AsyncRevalidation>> m_pendingAsyncRevalidations;
 #endif
 
     unsigned m_traverseCount { 0 };
index 6c72fab..01bc87f 100644 (file)
@@ -155,6 +155,43 @@ void SpeculativeLoad::didComplete()
     m_completionHandler(WTFMove(m_cacheEntry));
 }
 
+#if !LOG_DISABLED
+
+static void dumpHTTPHeadersDiff(const HTTPHeaderMap& headersA, const HTTPHeaderMap& headersB)
+{
+    auto aEnd = headersA.end();
+    for (auto it = headersA.begin(); it != aEnd; ++it) {
+        String valueB = headersB.get(it->key);
+        if (valueB.isNull())
+            LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in first request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
+        else if (it->value != valueB)
+            LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header differs in both requests: %s != %s", it->key.utf8().data(), it->value.utf8().data(), valueB.utf8().data());
+    }
+    auto bEnd = headersB.end();
+    for (auto it = headersB.begin(); it != bEnd; ++it) {
+        if (!headersA.contains(it->key))
+            LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in second request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
+    }
+}
+
+#endif
+
+bool requestsHeadersMatch(const ResourceRequest& speculativeValidationRequest, const ResourceRequest& actualRequest)
+{
+    ASSERT(!actualRequest.isConditional());
+    ResourceRequest speculativeRequest = speculativeValidationRequest;
+    speculativeRequest.makeUnconditional();
+
+    if (speculativeRequest.httpHeaderFields() != actualRequest.httpHeaderFields()) {
+        LOG(NetworkCacheSpeculativePreloading, "Cannot reuse speculatively validated entry because HTTP headers used for validation do not match");
+#if !LOG_DISABLED
+        dumpHTTPHeadersDiff(speculativeRequest.httpHeaderFields(), actualRequest.httpHeaderFields());
+#endif
+        return false;
+    }
+    return true;
+}
+
 } // namespace NetworkCache
 } // namespace WebKit
 
index 5406d9f..ef218f3 100644 (file)
@@ -78,6 +78,8 @@ private:
     bool m_didComplete { false };
 };
 
+bool requestsHeadersMatch(const WebCore::ResourceRequest& speculativeValidationRequest, const WebCore::ResourceRequest& actualRequest);
+
 } // namespace NetworkCache
 } // namespace WebKit
 
index 6794039..9806f62 100644 (file)
@@ -256,43 +256,6 @@ SpeculativeLoadManager::~SpeculativeLoadManager()
 {
 }
 
-#if !LOG_DISABLED
-
-static void dumpHTTPHeadersDiff(const HTTPHeaderMap& headersA, const HTTPHeaderMap& headersB)
-{
-    auto aEnd = headersA.end();
-    for (auto it = headersA.begin(); it != aEnd; ++it) {
-        String valueB = headersB.get(it->key);
-        if (valueB.isNull())
-            LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in first request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
-        else if (it->value != valueB)
-            LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header differs in both requests: %s != %s", it->key.utf8().data(), it->value.utf8().data(), valueB.utf8().data());
-    }
-    auto bEnd = headersB.end();
-    for (auto it = headersB.begin(); it != bEnd; ++it) {
-        if (!headersA.contains(it->key))
-            LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in second request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
-    }
-}
-
-#endif
-
-static bool requestsHeadersMatch(const ResourceRequest& speculativeValidationRequest, const ResourceRequest& actualRequest)
-{
-    ASSERT(!actualRequest.isConditional());
-    ResourceRequest speculativeRequest = speculativeValidationRequest;
-    speculativeRequest.makeUnconditional();
-
-    if (speculativeRequest.httpHeaderFields() != actualRequest.httpHeaderFields()) {
-        LOG(NetworkCacheSpeculativePreloading, "Cannot reuse speculatively validated entry because HTTP headers used for validation do not match");
-#if !LOG_DISABLED
-        dumpHTTPHeadersDiff(speculativeRequest.httpHeaderFields(), actualRequest.httpHeaderFields());
-#endif
-        return false;
-    }
-    return true;
-}
-
 bool SpeculativeLoadManager::canUsePreloadedEntry(const PreloadedEntry& entry, const ResourceRequest& actualRequest)
 {
     if (!entry.wasRevalidated())
index edbfb56..3f7d3fb 100644 (file)
@@ -78,6 +78,7 @@ NetworkProcess/cache/CacheStorageEngine.cpp
 NetworkProcess/cache/CacheStorageEngineCache.cpp
 NetworkProcess/cache/CacheStorageEngineCaches.cpp
 NetworkProcess/cache/CacheStorageEngineConnection.cpp
+NetworkProcess/cache/AsyncRevalidation.cpp
 NetworkProcess/cache/NetworkCache.cpp
 NetworkProcess/cache/NetworkCacheBlobStorage.cpp
 NetworkProcess/cache/NetworkCacheCoders.cpp
index d9a8cde..8187014 100644 (file)
@@ -139,3 +139,13 @@ void WKWebsiteDataStoreConfigurationSetTestingSessionEnabled(WKWebsiteDataStoreC
 {
     WebKit::toImpl(configuration)->setTestingSessionEnabled(enabled);
 }
+
+bool WKWebsiteDataStoreConfigurationGetStaleWhileRevalidateEnabled(WKWebsiteDataStoreConfigurationRef configuration)
+{
+    return WebKit::toImpl(configuration)->staleWhileRevalidateEnabled();
+}
+
+void WKWebsiteDataStoreConfigurationSetStaleWhileRevalidateEnabled(WKWebsiteDataStoreConfigurationRef configuration, bool enabled)
+{
+    WebKit::toImpl(configuration)->setStaleWhileRevalidateEnabled(enabled);
+}
index 64adeb8..2408488 100644 (file)
@@ -66,6 +66,9 @@ WK_EXPORT void WKWebsiteDataStoreConfigurationSetNetworkCacheSpeculativeValidati
 WK_EXPORT bool WKWebsiteDataStoreConfigurationGetTestingSessionEnabled(WKWebsiteDataStoreConfigurationRef configuration);
 WK_EXPORT void WKWebsiteDataStoreConfigurationSetTestingSessionEnabled(WKWebsiteDataStoreConfigurationRef configuration, bool enabled);
 
+WK_EXPORT bool WKWebsiteDataStoreConfigurationGetStaleWhileRevalidateEnabled(WKWebsiteDataStoreConfigurationRef configuration);
+WK_EXPORT void WKWebsiteDataStoreConfigurationSetStaleWhileRevalidateEnabled(WKWebsiteDataStoreConfigurationRef configuration, bool enabled);
+
 #ifdef __cplusplus
 }
 #endif
index 29e9003..0ff3ea0 100644 (file)
@@ -150,6 +150,7 @@ WebsiteDataStoreParameters WebsiteDataStore::parameters()
         m_configuration->fastServerTrustEvaluationEnabled(),
         m_configuration->networkCacheSpeculativeValidationEnabled(),
         m_configuration->testingSessionEnabled(),
+        m_configuration->staleWhileRevalidateEnabled(),
         m_configuration->testSpeedMultiplier(),
         m_configuration->suppressesConnectionTerminationOnSystemChange(),
     };
index 4ae5f3f..b503b42 100644 (file)
@@ -56,6 +56,7 @@ Ref<WebsiteDataStoreConfiguration> WebsiteDataStoreConfiguration::copy() const
     copy->m_serviceWorkerProcessTerminationDelayEnabled = this->m_serviceWorkerProcessTerminationDelayEnabled;
     copy->m_fastServerTrustEvaluationEnabled = this->m_fastServerTrustEvaluationEnabled;
     copy->m_networkCacheSpeculativeValidationEnabled = this->m_networkCacheSpeculativeValidationEnabled;
+    copy->m_staleWhileRevalidateEnabled = this->m_staleWhileRevalidateEnabled;
     copy->m_cacheStorageDirectory = this->m_cacheStorageDirectory;
     copy->m_perOriginStorageQuota = this->m_perOriginStorageQuota;
     copy->m_networkCacheDirectory = this->m_networkCacheDirectory;
index 9fe4678..6ad519d 100644 (file)
@@ -84,10 +84,13 @@ public:
 
     bool testingSessionEnabled() const { return m_testingSessionEnabled; }
     void setTestingSessionEnabled(bool enabled) { m_testingSessionEnabled = enabled; }
-    
+
+    bool staleWhileRevalidateEnabled() const { return m_staleWhileRevalidateEnabled; }
+    void setStaleWhileRevalidateEnabled(bool enabled) { m_staleWhileRevalidateEnabled = enabled; }
+
     unsigned testSpeedMultiplier() const { return m_testSpeedMultiplier; }
     void setTestSpeedMultiplier(unsigned multiplier) { m_testSpeedMultiplier = multiplier; }
-    
+
 #if PLATFORM(COCOA)
     CFDictionaryRef proxyConfiguration() const { return m_proxyConfiguration.get(); }
     void setProxyConfiguration(CFDictionaryRef configuration) { m_proxyConfiguration = configuration; }
@@ -159,6 +162,7 @@ private:
 #else
     bool m_networkCacheSpeculativeValidationEnabled { false };
 #endif
+    bool m_staleWhileRevalidateEnabled { false };
     String m_localStorageDirectory;
     String m_mediaKeysStorageDirectory;
     String m_deviceIdHashSaltsStorageDirectory;
index 5e3ea3b..887ced1 100644 (file)
                A78CCDDC193AC9FB005ECC25 /* com.apple.WebKit.WebContent.sb in CopyFiles */ = {isa = PBXBuildFile; fileRef = A78CCDD9193AC9E3005ECC25 /* com.apple.WebKit.WebContent.sb */; };
                A7D792D81767CCA300881CBE /* ActivityAssertion.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D792D41767CB0900881CBE /* ActivityAssertion.h */; };
                AAB145E6223F931200E489D8 /* PrefetchCache.h in Headers */ = {isa = PBXBuildFile; fileRef = AAB145E4223F931200E489D8 /* PrefetchCache.h */; };
+               AAFA634F234F7C6400FFA864 /* AsyncRevalidation.h in Headers */ = {isa = PBXBuildFile; fileRef = AAFA634E234F7C6300FFA864 /* AsyncRevalidation.h */; };
                B62E7312143047B00069EC35 /* WKHitTestResult.h in Headers */ = {isa = PBXBuildFile; fileRef = B62E7311143047B00069EC35 /* WKHitTestResult.h */; settings = {ATTRIBUTES = (Private, ); }; };
                B878B615133428DC006888E9 /* CorrectionPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = B878B613133428DC006888E9 /* CorrectionPanel.h */; };
                BC017D0716260FF4007054F5 /* WKDOMDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = BC017CFF16260FF4007054F5 /* WKDOMDocument.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A7E93CEB192531AA00A1DC48 /* AuxiliaryProcessIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AuxiliaryProcessIOS.mm; path = ios/AuxiliaryProcessIOS.mm; sourceTree = "<group>"; };
                AAB145E4223F931200E489D8 /* PrefetchCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrefetchCache.h; sourceTree = "<group>"; };
                AAB145E5223F931200E489D8 /* PrefetchCache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PrefetchCache.cpp; sourceTree = "<group>"; };
+               AAFA634E234F7C6300FFA864 /* AsyncRevalidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncRevalidation.h; sourceTree = "<group>"; };
+               AAFA6350234F7C7300FFA864 /* AsyncRevalidation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AsyncRevalidation.cpp; sourceTree = "<group>"; };
                B396EA5512E0ED2D00F4FEB7 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
                B62E730F143047A60069EC35 /* WKHitTestResult.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WKHitTestResult.cpp; sourceTree = "<group>"; };
                B62E7311143047B00069EC35 /* WKHitTestResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKHitTestResult.h; sourceTree = "<group>"; };
                E489D2821A0A2BE80078C06A /* cache */ = {
                        isa = PBXGroup;
                        children = (
+                               AAFA6350234F7C7300FFA864 /* AsyncRevalidation.cpp */,
+                               AAFA634E234F7C6300FFA864 /* AsyncRevalidation.h */,
                                41897ED61F415D860016FA42 /* CacheStorageEngine.cpp */,
                                41897ED21F415D850016FA42 /* CacheStorageEngine.h */,
                                41C858191F510DEE0065E085 /* CacheStorageEngineCache.cpp */,
                                A175C44A21AA3171000037D0 /* ArgumentCodersCocoa.h in Headers */,
                                CE1A0BD21A48E6C60054EF74 /* AssertionServicesSPI.h in Headers */,
                                515E7728183DD6F60007203F /* AsyncRequest.h in Headers */,
+                               AAFA634F234F7C6400FFA864 /* AsyncRevalidation.h in Headers */,
                                BCEE966D112FAF57006BCC24 /* Attachment.h in Headers */,
                                E1A31732134CEA6C007C9A4F /* AttributedString.h in Headers */,
                                512F589712A8838800629530 /* AuthenticationChallengeProxy.h in Headers */,
index cb9160a..6e88d33 100644 (file)
@@ -1,3 +1,15 @@
+2019-11-13  Rob Buis  <rbuis@igalia.com>
+
+        Support stale-while-revalidate cache strategy
+        https://bugs.webkit.org/show_bug.cgi?id=201461
+
+        Reviewed by Youenn Fablet.
+
+        Enable stale-while-revalidate for the test runner.
+
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::websiteDataStore):
+
 2019-11-12  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS] WKWebView does not respect system spellchecking preference
index 4e48f27..8a07325 100644 (file)
@@ -532,6 +532,7 @@ WKWebsiteDataStoreRef TestController::websiteDataStore()
             WKWebsiteDataStoreConfigurationSetResourceLoadStatisticsDirectory(configuration.get(), toWK(temporaryFolder + pathSeparator + "ResourceLoadStatistics").get());
             WKWebsiteDataStoreConfigurationSetPerOriginStorageQuota(configuration.get(), 400 * 1024);
             WKWebsiteDataStoreConfigurationSetNetworkCacheSpeculativeValidationEnabled(configuration.get(), true);
+            WKWebsiteDataStoreConfigurationSetStaleWhileRevalidateEnabled(configuration.get(), true);
             WKWebsiteDataStoreConfigurationSetTestingSessionEnabled(configuration.get(), true);
         }
         dataStore = WKWebsiteDataStoreCreateWithConfiguration(configuration.get());