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
+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
+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
"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",
--- /dev/null
+
+PASS Second fetch returns same response
+
--- /dev/null
+
+PASS Second fetch returns same response
+
--- /dev/null
+<!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>
--- /dev/null
+<!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>
--- /dev/null
+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
--- /dev/null
+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)
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+
+PASS Cache returns stale resource
+
--- /dev/null
+<!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>
--- /dev/null
+
+
+PASS Cache returns stale resource
+
--- /dev/null
+<!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>
--- /dev/null
+
+PASS Cache returns stale resource
+
--- /dev/null
+<!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>
--- /dev/null
+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();
+});
--- /dev/null
+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
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 ]
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 ]
+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
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 };
+ }
}
}
Markable<Seconds, Seconds::MarkableTraits> maxAge;
Markable<Seconds, Seconds::MarkableTraits> maxStale;
+ Markable<Seconds, Seconds::MarkableTraits> staleWhileRevalidate;
bool noCache : 1;
bool noStore : 1;
bool mustRevalidate : 1;
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);
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;
+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
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;
unsigned testSpeedMultiplier() const { return m_testSpeedMultiplier; }
+ bool isStaleWhileRevalidateEnabled() const { return m_isStaleWhileRevalidateEnabled; }
+
protected:
NetworkSession(NetworkProcess&, const NetworkSessionCreationParameters&);
bool m_downgradeReferrer { true };
bool m_thirdPartyCookieBlockingEnabled { false };
#endif
+ bool m_isStaleWhileRevalidateEnabled { false };
UniqueRef<AdClickAttributionManager> m_adClickAttribution;
HashSet<Ref<NetworkResourceLoader>> m_keptAliveLoads;
encoder << fastServerTrustEvaluationEnabled;
encoder << networkCacheSpeculativeValidationEnabled;
encoder << shouldUseTestingNetworkSession;
+ encoder << staleWhileRevalidateEnabled;
encoder << testSpeedMultiplier;
encoder << suppressesConnectionTerminationOnSystemChange;
}
decoder >> shouldUseTestingNetworkSession;
if (!shouldUseTestingNetworkSession)
return WTF::nullopt;
-
+
+ Optional<bool> staleWhileRevalidateEnabled;
+ decoder >> staleWhileRevalidateEnabled;
+ if (!staleWhileRevalidateEnabled)
+ return WTF::nullopt;
+
Optional<unsigned> testSpeedMultiplier;
decoder >> testSpeedMultiplier;
if (!testSpeedMultiplier)
, WTFMove(*fastServerTrustEvaluationEnabled)
, WTFMove(*networkCacheSpeculativeValidationEnabled)
, WTFMove(*shouldUseTestingNetworkSession)
+ , WTFMove(*staleWhileRevalidateEnabled)
, WTFMove(*testSpeedMultiplier)
, WTFMove(*suppressesConnectionTerminationOnSystemChange)
}};
bool fastServerTrustEvaluationEnabled { false };
bool networkCacheSpeculativeValidationEnabled { false };
bool shouldUseTestingNetworkSession { false };
+ bool staleWhileRevalidateEnabled { false };
unsigned testSpeedMultiplier { 1 };
bool suppressesConnectionTerminationOnSystemChange { false };
};
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
#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>
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)
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;
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;
}
#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());
}
#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;
}
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:
namespace NetworkCache {
+class AsyncRevalidation;
class Cache;
class SpeculativeLoadManager;
enum class UseDecision {
Use,
Validate,
+ AsyncRevalidate,
NoDueToVaryingHeaderMismatch,
NoDueToMissingValidatorFields,
NoDueToDecodeFailure,
#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(); }
#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 };
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
bool m_didComplete { false };
};
+bool requestsHeadersMatch(const WebCore::ResourceRequest& speculativeValidationRequest, const WebCore::ResourceRequest& actualRequest);
+
} // namespace NetworkCache
} // namespace WebKit
{
}
-#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())
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
{
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);
+}
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
m_configuration->fastServerTrustEvaluationEnabled(),
m_configuration->networkCacheSpeculativeValidationEnabled(),
m_configuration->testingSessionEnabled(),
+ m_configuration->staleWhileRevalidateEnabled(),
m_configuration->testSpeedMultiplier(),
m_configuration->suppressesConnectionTerminationOnSystemChange(),
};
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;
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; }
#else
bool m_networkCacheSpeculativeValidationEnabled { false };
#endif
+ bool m_staleWhileRevalidateEnabled { false };
String m_localStorageDirectory;
String m_mediaKeysStorageDirectory;
String m_deviceIdHashSaltsStorageDirectory;
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 */,
+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
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());