diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/service-workers/service-worker/navigation-preload | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/service-workers/service-worker/navigation-preload')
31 files changed, 1039 insertions, 0 deletions
diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html new file mode 100644 index 0000000000..ec74282ac3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload with chunked encoding</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script> +promise_test(t => { + var script = 'resources/broken-chunked-encoding-worker.js'; + var scope = 'resources/broken-chunked-encoding-scope.asis'; + return service_worker_unregister_and_register(t, script, scope) + .then(registration => { + add_completion_callback(_ => registration.unregister()); + var worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(_ => with_iframe(scope)) + .then(frame => { + assert_equals( + frame.contentDocument.body.textContent, + 'PASS: preloadResponse resolved'); + }); + }, 'FetchEvent#preloadResponse resolves even if the body is sent with broken chunked encoding.'); + +promise_test(t => { + var script = 'resources/broken-chunked-encoding-worker.js'; + var scope = 'resources/chunked-encoding-scope.py?use_broken_body'; + return service_worker_unregister_and_register(t, script, scope) + .then(registration => { + add_completion_callback(_ => registration.unregister()); + var worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(_ => with_iframe(scope)) + .then(frame => { + assert_equals( + frame.contentDocument.body.textContent, + 'PASS: preloadResponse resolved'); + }); + }, 'FetchEvent#preloadResponse resolves even if the body is sent with broken chunked encoding with some delays'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/chunked-encoding.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/chunked-encoding.https.html new file mode 100644 index 0000000000..830ce32cea --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/chunked-encoding.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload with chunked encoding</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script> +promise_test(t => { + var script = 'resources/chunked-encoding-worker.js'; + var scope = 'resources/chunked-encoding-scope.py'; + return service_worker_unregister_and_register(t, script, scope) + .then(registration => { + add_completion_callback(_ => registration.unregister()); + var worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(_ => with_iframe(scope)) + .then(frame => { + assert_equals( + frame.contentDocument.body.textContent, + '0123456789'); + }); + }, 'Navigation Preload must work with chunked encoding.'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/empty-preload-response-body.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/empty-preload-response-body.https.html new file mode 100644 index 0000000000..7e8aacdd36 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/empty-preload-response-body.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload empty response body</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script> +promise_test(t => { + var script = 'resources/empty-preload-response-body-worker.js'; + var scope = 'resources/empty-preload-response-body-scope.html'; + return service_worker_unregister_and_register(t, script, scope) + .then(registration => { + add_completion_callback(_ => registration.unregister()); + var worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(_ => with_iframe(scope)) + .then(frame => { + assert_equals( + frame.contentDocument.body.textContent, + '[]'); + }); + }, 'Navigation Preload empty response body.'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/get-state.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/get-state.https.html new file mode 100644 index 0000000000..08e2f4976c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/get-state.https.html @@ -0,0 +1,217 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>NavigationPreloadManager.getState</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script src="resources/helpers.js"></script> +<body> +<script> +function post_and_wait_for_reply(worker, message) { + return new Promise(resolve => { + navigator.serviceWorker.onmessage = e => { resolve(e.data); }; + worker.postMessage(message); + }); +} + +promise_test(t => { + const scope = '../resources/get-state'; + const script = '../resources/empty-worker.js'; + var np; + + return service_worker_unregister_and_register(t, script, scope) + .then(r => { + np = r.navigationPreload; + add_completion_callback(() => r.unregister()); + return wait_for_state(t, r.installing, 'activated'); + }) + .then(() => np.getState()) + .then(state => { + expect_navigation_preload_state(state, false, 'true', 'default state'); + return np.enable(); + }) + .then(result => { + assert_equals(result, undefined, + 'enable() should resolve to undefined'); + return np.getState(); + }) + .then(state => { + expect_navigation_preload_state(state, true, 'true', + 'state after enable()'); + return np.disable(); + }) + .then(result => { + assert_equals(result, undefined, + 'disable() should resolve to undefined'); + return np.getState(); + }) + .then(state => { + expect_navigation_preload_state(state, false, 'true', + 'state after disable()'); + return np.setHeaderValue('dreams that cannot be'); + }) + .then(result => { + assert_equals(result, undefined, + 'setHeaderValue() should resolve to undefined'); + return np.getState(); + }) + .then(state => { + expect_navigation_preload_state(state, false, 'dreams that cannot be', + 'state after setHeaderValue()'); + return np.setHeaderValue('').then(() => np.getState()); + }) + .then(state => { + expect_navigation_preload_state(state, false, '', + 'after setHeaderValue to empty string'); + return np.setHeaderValue(null).then(() => np.getState()); + }) + .then(state => { + expect_navigation_preload_state(state, false, 'null', + 'after setHeaderValue to null'); + return promise_rejects_js(t, + TypeError, + np.setHeaderValue('what\uDC00\uD800this'), + 'setHeaderValue() should throw if passed surrogates'); + }) + .then(() => { + return promise_rejects_js(t, + TypeError, + np.setHeaderValue('zer\0o'), + 'setHeaderValue() should throw if passed \\0'); + }) + .then(() => { + return promise_rejects_js(t, + TypeError, + np.setHeaderValue('\rcarriage'), + 'setHeaderValue() should throw if passed \\r'); + }) + .then(() => { + return promise_rejects_js(t, + TypeError, + np.setHeaderValue('newline\n'), + 'setHeaderValue() should throw if passed \\n'); + }) + .then(() => { + return promise_rejects_js(t, + TypeError, + np.setHeaderValue(), + 'setHeaderValue() should throw if passed undefined'); + }) + .then(() => np.enable().then(() => np.getState())) + .then(state => { + expect_navigation_preload_state(state, true, 'null', + 'enable() should not change header'); + }); + }, 'getState'); + +// This test sends commands to a worker to call enable()/disable()/getState(). +// It checks the results from the worker and verifies that they match the +// navigation preload state accessible from the page. +promise_test(t => { + const scope = 'resources/get-state-worker'; + const script = 'resources/get-state-worker.js'; + var worker; + var registration; + + return service_worker_unregister_and_register(t, script, scope) + .then(r => { + registration = r; + add_completion_callback(() => registration.unregister()); + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(() => { + // Call getState(). + return post_and_wait_for_reply(worker, 'getState'); + }) + .then(data => { + return Promise.all([data, registration.navigationPreload.getState()]); + }) + .then(states => { + expect_navigation_preload_state(states[0], false, 'true', + 'default state (from worker)'); + expect_navigation_preload_state(states[1], false, 'true', + 'default state (from page)'); + // Call enable() and then getState(). + return post_and_wait_for_reply(worker, 'enable'); + }) + .then(data => { + assert_equals(data, undefined, 'enable() should resolve to undefined'); + return Promise.all([ + post_and_wait_for_reply(worker, 'getState'), + registration.navigationPreload.getState() + ]); + }) + .then(states => { + expect_navigation_preload_state(states[0], true, 'true', + 'state after enable() (from worker)'); + expect_navigation_preload_state(states[1], true, 'true', + 'state after enable() (from page)'); + // Call disable() and then getState(). + return post_and_wait_for_reply(worker, 'disable'); + }) + .then(data => { + assert_equals(data, undefined, + '.disable() should resolve to undefined'); + return Promise.all([ + post_and_wait_for_reply(worker, 'getState'), + registration.navigationPreload.getState() + ]); + }) + .then(states => { + expect_navigation_preload_state(states[0], false, 'true', + 'state after disable() (from worker)'); + expect_navigation_preload_state(states[1], false, 'true', + 'state after disable() (from page)'); + return post_and_wait_for_reply(worker, 'setHeaderValue'); + }) + .then(data => { + assert_equals(data, undefined, + '.setHeaderValue() should resolve to undefined'); + return Promise.all([ + post_and_wait_for_reply(worker, 'getState'), + registration.navigationPreload.getState()]); + }) + .then(states => { + expect_navigation_preload_state( + states[0], false, 'insightful', + 'state after setHeaderValue() (from worker)'); + expect_navigation_preload_state( + states[1], false, 'insightful', + 'state after setHeaderValue() (from page)'); + }); + }, 'getState from a worker'); + +// This tests navigation preload API when there is no active worker. It calls +// the API from the main page and then from the worker itself. +promise_test(t => { + const scope = 'resources/wait-for-activate-worker'; + const script = 'resources/wait-for-activate-worker.js'; + var registration; + var np; + return service_worker_unregister_and_register(t, script, scope) + .then(r => { + registration = r; + np = registration.navigationPreload; + add_completion_callback(() => registration.unregister()); + return Promise.all([ + promise_rejects_dom( + t, 'InvalidStateError', np.enable(), + 'enable should reject if there is no active worker'), + promise_rejects_dom( + t, 'InvalidStateError', np.disable(), + 'disable should reject if there is no active worker'), + promise_rejects_dom( + t, 'InvalidStateError', np.setHeaderValue('umm'), + 'setHeaderValue should reject if there is no active worker')]); + }) + .then(() => np.getState()) + .then(state => { + expect_navigation_preload_state(state, false, 'true', + 'state before activation'); + return post_and_wait_for_reply(registration.installing, 'ping'); + }) + .then(result => assert_equals(result, 'PASS')); + }, 'no active worker'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/navigationPreload.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/navigationPreload.https.html new file mode 100644 index 0000000000..392e5c14dc --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/navigationPreload.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>ServiceWorker: navigator.serviceWorker.navigationPreload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script src="resources/helpers.js"></script> +<script> +promise_test(async t => { + const SCRIPT = '../resources/empty-worker.js'; + const SCOPE = '../resources/navigationpreload'; + const registration = + await service_worker_unregister_and_register(t, SCRIPT, SCOPE); + const navigationPreload = registration.navigationPreload; + assert_true(navigationPreload instanceof NavigationPreloadManager, + 'ServiceWorkerRegistration.navigationPreload'); + await registration.unregister(); +}, "The navigationPreload attribute must return service worker " + + "registration's NavigationPreloadManager object."); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/redirect.https.html new file mode 100644 index 0000000000..5970f053e3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/redirect.https.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload redirect response</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script> + +function check_opaqueredirect(response_info, scope) { + assert_equals(response_info.type, 'opaqueredirect'); + assert_equals(response_info.url, '' + new URL(scope, location)); + assert_equals(response_info.status, 0); + assert_equals(response_info.ok, false); + assert_equals(response_info.statusText, ''); + assert_equals(response_info.headers.length, 0); +} + +function redirect_response_test(t, scope, expected_body, expected_urls) { + var script = 'resources/redirect-worker.js'; + var registration; + var message_resolvers = []; + function wait_for_message(count) { + var promises = []; + message_resolvers = []; + for (var i = 0; i < count; ++i) { + promises.push(new Promise(resolve => message_resolvers.push(resolve))); + } + return promises; + } + function on_message(e) { + var resolve = message_resolvers.shift(); + if (resolve) + resolve(e.data); + } + return service_worker_unregister_and_register(t, script, scope) + .then(reg => { + registration = reg; + add_completion_callback(_ => registration.unregister()); + var worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(_ => with_iframe(scope + '&base')) + .then(frame => { + assert_equals(frame.contentDocument.body.textContent, 'OK'); + frame.contentWindow.navigator.serviceWorker.onmessage = on_message; + return Promise.all(wait_for_message(expected_urls.length) + .concat(with_iframe(scope))); + }) + .then(results => { + var frame = results[expected_urls.length]; + assert_equals(frame.contentDocument.body.textContent, expected_body); + for (var i = 0; i < expected_urls.length; ++i) { + check_opaqueredirect(results[i], expected_urls[i]); + } + frame.remove(); + return registration.unregister(); + }); +} + +promise_test(t => { + return redirect_response_test( + t, + 'resources/redirect-scope.py?type=normal', + 'redirected\n', + ['resources/redirect-scope.py?type=normal']); + }, 'Navigation Preload redirect response.'); + +promise_test(t => { + return redirect_response_test( + t, + 'resources/redirect-scope.py?type=no-location', + '', + ['resources/redirect-scope.py?type=no-location']); + }, 'Navigation Preload no-location redirect response.'); + +promise_test(t => { + return redirect_response_test( + t, + 'resources/redirect-scope.py?type=no-location-with-body', + 'BODY', + ['resources/redirect-scope.py?type=no-location-with-body']); + }, 'Navigation Preload no-location redirect response with body.'); + +promise_test(t => { + return redirect_response_test( + t, + 'resources/redirect-scope.py?type=redirect-to-scope', + 'redirected\n', + ['resources/redirect-scope.py?type=redirect-to-scope', + 'resources/redirect-scope.py?type=redirect-to-scope2', + 'resources/redirect-scope.py?type=redirect-to-scope3',]); + }, 'Navigation Preload redirect to the same scope.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/request-headers.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/request-headers.https.html new file mode 100644 index 0000000000..0964201021 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/request-headers.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload request headers</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script> +promise_test(t => { + var script = 'resources/request-headers-worker.js'; + var scope = 'resources/request-headers-scope.py'; + return service_worker_unregister_and_register(t, script, scope) + .then(registration => { + add_completion_callback(_ => registration.unregister()); + var worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(_ => with_iframe(scope)) + .then(frame => { + var headers = JSON.parse(frame.contentDocument.body.textContent); + assert_true( + 'SERVICE-WORKER-NAVIGATION-PRELOAD' in headers, + 'The Navigation Preload request must specify a ' + + '"Service-Worker-Navigation-Preload" header.'); + assert_array_equals( + headers['SERVICE-WORKER-NAVIGATION-PRELOAD'], + ['hello'], + 'The Navigation Preload request must specify the correct value ' + + 'for the "Service-Worker-Navigation-Preload" header.'); + assert_true( + 'UPGRADE-INSECURE-REQUESTS' in headers, + 'The Navigation Preload request must specify an ' + + '"Upgrade-Insecure-Requests" header.'); + assert_array_equals( + headers['UPGRADE-INSECURE-REQUESTS'], + ['1'], + 'The Navigation Preload request must specify the correct value ' + + 'for the "Upgrade-Insecure-Requests" header.'); + }); + }, 'Navigation Preload request headers.'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resource-timing.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resource-timing.https.html new file mode 100644 index 0000000000..468a1f51e8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resource-timing.https.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload Resource Timing</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<script> + +function check_timing_entry(entry, url, decodedBodySize, encodedBodySize) { + assert_equals(entry.name, url, 'The entry name of '+ url); + + assert_equals( + entry.entryType, 'resource', + 'The entryType of preload response timing entry must be "resource' + + '" :' + url); + assert_equals( + entry.initiatorType, 'navigation', + 'The initiatorType of preload response timing entry must be ' + + '"navigation":' + url); + + // If the server returns the redirect response, |decodedBodySize| is null and + // |entry.decodedBodySize| should be 0. Otherwise |entry.decodedBodySize| must + // same as |decodedBodySize| + assert_equals( + entry.decodedBodySize, Number(decodedBodySize), + 'decodedBodySize must same as the decoded size in the server:' + url); + + // If the server returns the redirect response, |encodedBodySize| is null and + // |entry.encodedBodySize| should be 0. Otherwise |entry.encodedBodySize| must + // same as |encodedBodySize| + assert_equals( + entry.encodedBodySize, Number(encodedBodySize), + 'encodedBodySize must same as the encoded size in the server:' + url); + + assert_greater_than( + entry.transferSize, entry.decodedBodySize, + 'transferSize must greater then encodedBodySize.'); + + assert_greater_than(entry.startTime, 0, 'startTime of ' + url); + assert_greater_than_equal(entry.fetchStart, entry.startTime, + 'fetchStart >= startTime of ' + url); + assert_greater_than_equal(entry.domainLookupStart, entry.fetchStart, + 'domainLookupStart >= fetchStart of ' + url); + assert_greater_than_equal(entry.domainLookupEnd, entry.domainLookupStart, + 'domainLookupEnd >= domainLookupStart of ' + url); + assert_greater_than_equal(entry.connectStart, entry.domainLookupEnd, + 'connectStart >= domainLookupEnd of ' + url); + assert_greater_than_equal(entry.connectEnd, entry.connectStart, + 'connectEnd >= connectStart of ' + url); + assert_greater_than_equal(entry.requestStart, entry.connectEnd, + 'requestStart >= connectEnd of ' + url); + assert_greater_than_equal(entry.responseStart, entry.requestStart, + 'domainLookupStart >= requestStart of ' + url); + assert_greater_than_equal(entry.responseEnd, entry.responseStart, + 'responseEnd >= responseStart of ' + url); + assert_greater_than(entry.duration, 0, 'duration of ' + url); +} + +promise_test(t => { + var script = 'resources/resource-timing-worker.js'; + var scope = 'resources/resource-timing-scope.py'; + var registration; + var frames = []; + return service_worker_unregister_and_register(t, script, scope) + .then(reg => { + registration = reg; + add_completion_callback(_ => registration.unregister()); + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(_ => with_iframe(scope + '?type=normal')) + .then(frame => { + frames.push(frame); + return with_iframe(scope + '?type=redirect'); + }) + .then(frame => { + frames.push(frame); + frames.forEach(frame => { + var result = JSON.parse(frame.contentDocument.body.textContent); + assert_equals( + result.timingEntries.length, 1, + 'performance.getEntriesByName() must returns one ' + + 'PerformanceResourceTiming entry for the navigation preload.'); + var entry = result.timingEntries[0]; + check_timing_entry(entry, frame.src, result.decodedBodySize, + result.encodedBodySize); + frame.remove(); + }); + return registration.unregister(); + }); + }, 'Navigation Preload Resource Timing.'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-scope.asis b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-scope.asis new file mode 100644 index 0000000000..2a719536fb --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-scope.asis @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-type: text/html; charset=UTF-8 +Transfer-encoding: chunked + +hello +world diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js new file mode 100644 index 0000000000..7a453e4055 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js @@ -0,0 +1,11 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse + .then( + _ => new Response('PASS: preloadResponse resolved'), + _ => new Response('FAIL: preloadResponse rejected'))); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py new file mode 100644 index 0000000000..659c4d8cdf --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py @@ -0,0 +1,19 @@ +import time + +def main(request, response): + use_broken_body = b'use_broken_body' in request.GET + + response.add_required_headers = False + response.writer.write_status(200) + response.writer.write_header(b"Content-type", b"text/html; charset=UTF-8") + response.writer.write_header(b"Transfer-encoding", b"chunked") + response.writer.end_headers() + + for idx in range(10): + if use_broken_body: + response.writer.write(u"%s\n%s\n" % (len(str(idx)), idx)) + else: + response.writer.write(u"%s\r\n%s\r\n" % (len(str(idx)), idx)) + time.sleep(0.001) + + response.writer.write(u"0\r\n\r\n") diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-worker.js new file mode 100644 index 0000000000..f30e5ed274 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-worker.js @@ -0,0 +1,8 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/cookie.py b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/cookie.py new file mode 100644 index 0000000000..30a1dd498a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/cookie.py @@ -0,0 +1,20 @@ +def main(request, response): + """ + Returns a response with a Set-Cookie header based on the query params. + The body will be "1" if the cookie is present in the request and `drop` parameter is "0", + otherwise the body will be "0". + """ + same_site = request.GET.first(b"same-site") + cookie_name = request.GET.first(b"cookie-name") + drop = request.GET.first(b"drop") + cookie_in_request = b"0" + cookie = b"%s=1; Secure; SameSite=%s" % (cookie_name, same_site) + + if drop == b"1": + cookie += b"; Max-Age=0" + + if request.cookies.get(cookie_name): + cookie_in_request = request.cookies[cookie_name].value + + headers = [(b'Content-Type', b'text/html'), (b'Set-Cookie', cookie)] + return (200, headers, cookie_in_request) diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-scope.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-scope.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-scope.html diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-worker.js new file mode 100644 index 0000000000..48c14b7916 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-worker.js @@ -0,0 +1,15 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith( + event.preloadResponse + .then(res => res.text()) + .then(text => { + return new Response( + '<body>[' + text + ']</body>', + {headers: [['content-type', 'text/html']]}); + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/get-state-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/get-state-worker.js new file mode 100644 index 0000000000..a14ffb4faa --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/get-state-worker.js @@ -0,0 +1,21 @@ +// This worker listens for commands from the page and messages back +// the result. + +function handle(message) { + const np = self.registration.navigationPreload; + switch (message) { + case 'getState': + return np.getState(); + case 'enable': + return np.enable(); + case 'disable': + return np.disable(); + case 'setHeaderValue': + return np.setHeaderValue('insightful'); + } + return Promise.reject('bad message'); +} + +self.addEventListener('message', e => { + e.waitUntil(handle(e.data).then(result => e.source.postMessage(result))); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/helpers.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/helpers.js new file mode 100644 index 0000000000..86f0c0916e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/helpers.js @@ -0,0 +1,5 @@ +function expect_navigation_preload_state(state, enabled, header, desc) { + assert_equals(Object.keys(state).length, 2, desc + ': # of keys'); + assert_equals(state.enabled, enabled, desc + ': enabled'); + assert_equals(state.headerValue, header, desc + ': header'); +} diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/navigation-preload-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/navigation-preload-worker.js new file mode 100644 index 0000000000..6e1ab23290 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/navigation-preload-worker.js @@ -0,0 +1,3 @@ +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); +}); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-redirected.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-redirected.html new file mode 100644 index 0000000000..f9bfce5e89 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-redirected.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<body>redirected</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-scope.py b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-scope.py new file mode 100644 index 0000000000..84a97e594b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-scope.py @@ -0,0 +1,38 @@ +def main(request, response): + if b"base" in request.GET: + return [(b"Content-Type", b"text/html")], b"OK" + type = request.GET.first(b"type") + + if type == b"normal": + response.status = 302 + response.headers.append(b"Location", b"redirect-redirected.html") + response.headers.append(b"Custom-Header", b"hello") + return b"" + + if type == b"no-location": + response.status = 302 + response.headers.append(b"Content-Type", b"text/html") + response.headers.append(b"Custom-Header", b"hello") + return b"" + + if type == b"no-location-with-body": + response.status = 302 + response.headers.append(b"Content-Type", b"text/html") + response.headers.append(b"Custom-Header", b"hello") + return b"<body>BODY</body>" + + if type == b"redirect-to-scope": + response.status = 302 + response.headers.append(b"Location", + b"redirect-scope.py?type=redirect-to-scope2") + return b"" + if type == b"redirect-to-scope2": + response.status = 302 + response.headers.append(b"Location", + b"redirect-scope.py?type=redirect-to-scope3") + return b"" + if type == b"redirect-to-scope3": + response.status = 302 + response.headers.append(b"Location", b"redirect-redirected.html") + response.headers.append(b"Custom-Header", b"hello") + return b"" diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-worker.js new file mode 100644 index 0000000000..1b55f2ef0d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/redirect-worker.js @@ -0,0 +1,35 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +function get_response_info(r) { + var info = { + type: r.type, + url: r.url, + status: r.status, + ok: r.ok, + statusText: r.statusText, + headers: [] + }; + r.headers.forEach((value, name) => { info.headers.push([value, name]); }); + return info; +} + +function post_to_page(data) { + return self.clients.matchAll() + .then(clients => clients.forEach(client => client.postMessage(data))); +} + +self.addEventListener('fetch', event => { + event.respondWith( + event.preloadResponse + .then( + res => { + if (res.url.includes("base")) { + return res; + } + return post_to_page(get_response_info(res)).then(_ => res); + }, + err => new Response(err.toString()))); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/request-headers-scope.py b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/request-headers-scope.py new file mode 100644 index 0000000000..5bab5b01f3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/request-headers-scope.py @@ -0,0 +1,14 @@ +import json + +from wptserve.utils import isomorphic_decode + +def main(request, response): + normalized = dict() + + for key, values in dict(request.headers).items(): + values = [isomorphic_decode(value) for value in values] + normalized[isomorphic_decode(key.upper())] = values + + response.headers.append(b"Content-Type", b"text/html") + + return json.dumps(normalized) diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/request-headers-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/request-headers-worker.js new file mode 100644 index 0000000000..1006cf2791 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/request-headers-worker.js @@ -0,0 +1,10 @@ +self.addEventListener('activate', event => { + event.waitUntil( + Promise.all[ + self.registration.navigationPreload.enable(), + self.registration.navigationPreload.setHeaderValue('hello')]); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-scope.py b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-scope.py new file mode 100644 index 0000000000..856f9dbc2a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-scope.py @@ -0,0 +1,19 @@ +import zlib + +def main(request, response): + type = request.GET.first(b"type") + + if type == "normal": + content = b"This is Navigation Preload Resource Timing test." + output = zlib.compress(content, 9) + headers = [(b"Content-type", b"text/plain"), + (b"Content-Encoding", b"deflate"), + (b"X-Decoded-Body-Size", len(content)), + (b"X-Encoded-Body-Size", len(output)), + (b"Content-Length", len(output))] + return headers, output + + if type == b"redirect": + response.status = 302 + response.headers.append(b"Location", b"redirect-redirected.html") + return b"" diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-worker.js new file mode 100644 index 0000000000..fac0d8de2a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-worker.js @@ -0,0 +1,37 @@ +async function wait_for_performance_entries(url) { + let entries = performance.getEntriesByName(url); + if (entries.length > 0) { + return entries; + } + return new Promise((resolve) => { + new PerformanceObserver((list) => { + const entries = list.getEntriesByName(url); + if (entries.length > 0) { + resolve(entries); + } + }).observe({ entryTypes: ['resource'] }); + }); +} + +self.addEventListener('activate', event => { + event.waitUntil(self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + let headers; + event.respondWith( + event.preloadResponse + .then(response => { + headers = response.headers; + return response.text() + }) + .then(_ => wait_for_performance_entries(event.request.url)) + .then(entries => + new Response( + JSON.stringify({ + decodedBodySize: headers.get('X-Decoded-Body-Size'), + encodedBodySize: headers.get('X-Encoded-Body-Size'), + timingEntries: entries + }), + {headers: {'Content-Type': 'text/html'}}))); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-iframe.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-iframe.html new file mode 100644 index 0000000000..a28b61261e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-iframe.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<body>samesite</body> +<script> +onmessage = (e) => { + if (e.data === "GetBody") { + parent.postMessage("samesite", '*'); + } +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-sw-helper.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-sw-helper.html new file mode 100644 index 0000000000..51fdc9ec74 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-sw-helper.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload Same Site SW registrator</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/test-helpers.sub.js"></script> +<script> + +/** + * This is a helper file to register/unregister service worker in a same-site + * iframe. + **/ + +async function messageToParent(msg) { + parent.postMessage(msg, '*'); +} + +onmessage = async (e) => { + // t is a , but the helper function needs a test object. + let t = { + step_func: (func) => func, + }; + if (e.data === "Register") { + let reg = await service_worker_unregister_and_register(t, "samesite-worker.js", "."); + let worker = reg.installing; + await wait_for_state(t, worker, 'activated'); + await messageToParent("SW Registered"); + } else if (e.data == "Unregister") { + await service_worker_unregister(t, "."); + await messageToParent("SW Unregistered"); + } +} + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-worker.js new file mode 100644 index 0000000000..f30e5ed274 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/samesite-worker.js @@ -0,0 +1,8 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/wait-for-activate-worker.js b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/wait-for-activate-worker.js new file mode 100644 index 0000000000..87791d2e48 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/resources/wait-for-activate-worker.js @@ -0,0 +1,40 @@ +// This worker remains in the installing phase so that the +// navigation preload API can be tested when there is no +// active worker. +importScripts('/resources/testharness.js'); +importScripts('helpers.js'); + +function expect_rejection(promise) { + return promise.then( + () => { return Promise.reject('unexpected fulfillment'); }, + err => { assert_equals('InvalidStateError', err.name); }); +} + +function test_before_activation() { + const np = self.registration.navigationPreload; + return expect_rejection(np.enable()) + .then(() => expect_rejection(np.disable())) + .then(() => expect_rejection(np.setHeaderValue('hi'))) + .then(() => np.getState()) + .then(state => expect_navigation_preload_state( + state, false, 'true', 'state should be the default')) + .then(() => 'PASS') + .catch(err => 'FAIL: ' + err); +} + +var resolve_done_promise; +var done_promise = new Promise(resolve => { resolve_done_promise = resolve; }); + +// Run the test once the page messages this worker. +self.addEventListener('message', e => { + e.waitUntil(test_before_activation() + .then(result => { + e.source.postMessage(result); + resolve_done_promise(); + })); + }); + +// Don't become the active worker until the test is done. +self.addEventListener('install', e => { + e.waitUntil(done_promise); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/samesite-cookies.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/samesite-cookies.https.html new file mode 100644 index 0000000000..a860d95456 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/samesite-cookies.https.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>Navigation Preload: SameSite cookies</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<body> +<script> +const scope = 'resources/cookie.py'; +const script = 'resources/navigation-preload-worker.js'; + +async function drop_cookie(t, same_site, cookie) { + const frame = await with_iframe(scope + '?same-site=' + same_site + '&cookie-name=' + cookie + '&drop=1'); + t.add_cleanup(() => frame.remove()); +} + +async function same_site_cookies_test(t, same_site, cookie) { + // Remove the cookie before the first visit. + await drop_cookie(t, same_site, cookie); + + { + const frame = await with_iframe(scope + '?same-site=' + same_site + '&cookie-name=' + cookie + '&drop=0'); + t.add_cleanup(() => frame.remove()); + // The body will be 0 because this is the first visit. + assert_equals(frame.contentDocument.body.textContent, '0', 'first visit'); + } + + { + const frame = await with_iframe(scope + '?same-site=' + same_site + '&cookie-name=' + cookie + '&drop=0'); + t.add_cleanup(() => frame.remove()); + // The body will be 1 because this is the second visit. + assert_equals(frame.contentDocument.body.textContent, '1', 'second visit'); + } + + // Remove the cookie after the test. + t.add_cleanup(() => drop_cookie(t, same_site, cookie)); +} + +promise_test(async t => { + const registration = + await service_worker_unregister_and_register(t, script, scope); + promise_test(t => registration.unregister(), 'Unregister a service worker.'); + + await wait_for_state(t, registration.installing, 'activated'); + await registration.navigationPreload.enable(); +}, 'Set up a service worker for navigation preload tests.'); + +promise_test(async t => { + await same_site_cookies_test(t, 'None', 'cookie-key-none'); +}, 'Navigation Preload for same site cookies (None).'); + +promise_test(async t => { + await same_site_cookies_test(t, 'Strict', 'cookie-key-strict'); +}, 'Navigation Preload for same site cookies (Strict).'); + +promise_test(async t => { + await same_site_cookies_test(t, 'Lax', 'cookie-key-lax'); +}, 'Navigation Preload for same site cookies (Lax).'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-preload/samesite-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/samesite-iframe.https.html new file mode 100644 index 0000000000..633da9926a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-preload/samesite-iframe.https.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigation Preload for same site iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="../resources/test-helpers.sub.js"></script> +<body></body> +<script> + +const SAME_SITE = get_host_info().HTTPS_REMOTE_ORIGIN; +const RESOURCES_DIR = "/service-workers/service-worker/navigation-preload/resources/"; + +/** + * This test is used for testing the NavigationPreload works in a same site iframe. + * The test scenario is + * 1. Create a same site iframe to register service worker and wait for it be activated + * 2. Create a same site iframe which be intercepted by the service worker. + * 3. Once the iframe is loaded, service worker should set the page through the preload response. + * And checking if the iframe's body content is expected. + * 4. Unregister the service worker. + * 5. remove created iframes. + */ + +promise_test(async (t) => { + let resolver; + let checkValue = false; + window.onmessage = (e) => { + if (checkValue) { + assert_equals(e.data, "samesite"); + checkValue = false; + } + resolver(); + }; + + let helperIframe = document.createElement("iframe"); + helperIframe.src = SAME_SITE + RESOURCES_DIR + "samesite-sw-helper.html"; + document.body.appendChild(helperIframe); + + await new Promise(resolve => { + resolver = resolve; + helperIframe.onload = async () => { + helperIframe.contentWindow.postMessage("Register", '*'); + } + }); + + let sameSiteIframe = document.createElement("iframe"); + sameSiteIframe.src = SAME_SITE + RESOURCES_DIR + "samesite-iframe.html"; + document.body.appendChild(sameSiteIframe); + await new Promise(resolve => { + resolver = resolve; + sameSiteIframe.onload = async() => { + checkValue = true; + sameSiteIframe.contentWindow.postMessage("GetBody", '*') + } + }); + + await new Promise(resolve => { + resolver = resolve; + helperIframe.contentWindow.postMessage("Unregister", '*') + }); + + helperIframe.remove(); + sameSiteIframe.remove(); + }); + +</script> |