diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/background-fetch | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/background-fetch')
19 files changed, 1058 insertions, 0 deletions
diff --git a/testing/web-platform/tests/background-fetch/META.yml b/testing/web-platform/tests/background-fetch/META.yml new file mode 100644 index 0000000000..8ce9f8faa2 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/META.yml @@ -0,0 +1,4 @@ +spec: https://wicg.github.io/background-fetch/ +suggested_reviewers: + - beverloo + - jakearchibald diff --git a/testing/web-platform/tests/background-fetch/abort.https.window.js b/testing/web-platform/tests/background-fetch/abort.https.window.js new file mode 100644 index 0000000000..9cc8c25f7a --- /dev/null +++ b/testing/web-platform/tests/background-fetch/abort.https.window.js @@ -0,0 +1,74 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Covers basic functionality provided by BackgroundFetchManager.abort(). +// https://wicg.github.io/background-fetch/#background-fetch-registration-abort + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + uniqueId(), + ['resources/feature-name.txt', '/common/slow.py']); + + assert_true(await registration.abort()); + assert_false(await registration.abort()); + +}, 'Aborting the same registration twice fails'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + uniqueId(), + ['resources/feature-name.txt', '/common/slow.py']); + + await new Promise(resolve => { + let aborted = false; + const expectedResultText = 'Background Fetch'; + + registration.onprogress = async event => { + if (event.target.downloaded < expectedResultText.length) + return; + + if (aborted) + return; + + // Abort after the first file has been downloaded and check the results. + + aborted = true; + assert_true(await registration.abort()); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + + assert_equals(eventRegistration.result, 'failure'); + assert_equals(eventRegistration.failureReason, 'aborted'); + assert_equals(registration.result, 'failure'); + assert_equals(registration.failureReason, 'aborted'); + + assert_equals(type, 'backgroundfetchabort'); + + assert_equals(results.length, 2); + + const completedResult = results[0] || results[1]; + // The abort might have gone through before the first result was persisted. + if (completedResult) { + assert_true(completedResult.url.includes('resources/feature-name.txt')); + assert_equals(completedResult.status, 200); + assert_equals(completedResult.text, expectedResultText); + } + + resolve(); + }; + }); + +}, 'Calling BackgroundFetchRegistration.abort sets the correct fields and responses are still available'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + uniqueId(), '/common/slow.py'); + assert_true(await registration.abort()); + + const {results} = await getMessageFromServiceWorker(); + assert_equals(results.length, 1); + assert_false(results[0].response); + assert_equals(results[0].name, 'AbortError'); + +}, 'An aborted fetch throws a DOM exception when accessing an incomplete record', 'sw-abort.js');
\ No newline at end of file diff --git a/testing/web-platform/tests/background-fetch/content-security-policy.https.window.js b/testing/web-platform/tests/background-fetch/content-security-policy.https.window.js new file mode 100644 index 0000000000..0b12185c5b --- /dev/null +++ b/testing/web-platform/tests/background-fetch/content-security-policy.https.window.js @@ -0,0 +1,23 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Tests that requests blocked by Content Security Policy are rejected. +// https://w3c.github.io/webappsec-csp/#should-block-request + +// This is not a comprehensive test of Content Security Policy - it is just +// intended to check that CSP checks are enabled. + +var meta = document.createElement('meta'); +meta.setAttribute('http-equiv', 'Content-Security-Policy'); +meta.setAttribute('content', "connect-src 'none'"); +document.head.appendChild(meta); + +backgroundFetchTest(async (t, bgFetch) => { + const fetch = await bgFetch.fetch(uniqueId(), '/'); + + const record = await fetch.match('/'); + return promise_rejects_js( + t, TypeError, + record.responseReady); +}, 'fetch blocked by CSP should reject'); diff --git a/testing/web-platform/tests/background-fetch/fetch-uploads.https.window.js b/testing/web-platform/tests/background-fetch/fetch-uploads.https.window.js new file mode 100644 index 0000000000..f93f88a6ba --- /dev/null +++ b/testing/web-platform/tests/background-fetch/fetch-uploads.https.window.js @@ -0,0 +1,64 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Covers basic functionality provided by BackgroundFetchManager.fetch(). +// Specifically, when `fetch` contains request uploads. +// https://wicg.github.io/background-fetch/#background-fetch-manager-fetch + +backgroundFetchTest(async (test, backgroundFetch) => { + const uploadData = 'Background Fetch!'; + const request = + new Request('resources/upload.py', {method: 'POST', body: uploadData}); + + const registration = await backgroundFetch.fetch(uniqueId(), request); + assert_equals(registration.uploadTotal, uploadData.length); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals(type, 'backgroundfetchsuccess'); + assert_equals(results.length, 1); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + assert_equals(eventRegistration.uploaded, uploadData.length); + assert_equals(results[0].text, uploadData); +}, 'Fetch with an upload should work'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const uploadData = 'Background Fetch!'; + const uploadRequest = + new Request('resources/upload.py', {method: 'POST', body: uploadData}); + + const registration = await backgroundFetch.fetch( + uniqueId(), + [uploadRequest, '/common/slow.py']); + + const uploaded = await new Promise(resolve => { + registration.onprogress = event => { + if (event.target.downloaded === 0) + return; + // If a progress event with downloaded bytes was received, then + // everything was uploaded. + resolve(event.target.uploaded); + }; + }); + + assert_equals(uploaded, uploadData.length); +}, 'Progress event includes uploaded bytes'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const uploadRequest1 = + new Request('resources/upload.py', {method: 'POST', body: 'upload1'}); + const uploadRequest2 = + new Request('resources/upload.py', {method: 'POST', body: 'upload2'}); + + await backgroundFetch.fetch(uniqueId(), [uploadRequest1, uploadRequest2]); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals(type, 'backgroundfetchsuccess'); + assert_equals(results.length, 2); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + + assert_array_equals([results[0].text, results[1].text].sort(), + ['upload1', 'upload2']); +}, 'Duplicate upload requests work and can be distinguished.'); diff --git a/testing/web-platform/tests/background-fetch/fetch.https.window.js b/testing/web-platform/tests/background-fetch/fetch.https.window.js new file mode 100644 index 0000000000..1756a0e6e3 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/fetch.https.window.js @@ -0,0 +1,280 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js + +'use strict'; + +// Covers basic functionality provided by BackgroundFetchManager.fetch(). +// https://wicg.github.io/background-fetch/#background-fetch-manager-fetch + +const wait = milliseconds => + new Promise(resolve => step_timeout(resolve, milliseconds)); + +promise_test(async test => { + // 6.3.1.9.2: If |registration|’s active worker is null, then reject promise + // with a TypeError and abort these steps. + const script = 'service_workers/sw.js'; + const scope = 'service_workers/' + location.pathname; + + const serviceWorkerRegistration = + await service_worker_unregister_and_register(test, script, scope); + + assert_equals( + serviceWorkerRegistration.active, null, + 'There must not be an activated worker'); + + await promise_rejects_js( + test, TypeError, + serviceWorkerRegistration.backgroundFetch.fetch( + uniqueId(), ['resources/feature-name.txt']), + 'fetch() must reject on pending and installing workers'); + +}, 'Background Fetch requires an activated Service Worker'); + +backgroundFetchTest(async (test, backgroundFetch) => { + // 6.3.1.6: If |requests| is empty, then return a promise rejected with a + // TypeError. + await promise_rejects_js( + test, TypeError, backgroundFetch.fetch(uniqueId(), []), + 'Empty sequences are treated as NULL'); + + // 6.3.1.7.1: Let |internalRequest| be the request of the result of invoking + // the Request constructor with |request|. If this throws an + // exception, return a promise rejected with the exception. + await promise_rejects_js( + test, TypeError, + backgroundFetch.fetch(uniqueId(), 'https://user:pass@domain/secret.txt'), + 'Exceptions thrown in the Request constructor are rethrown'); + + // 6.3.1.7.2: If |internalRequest|’s mode is "no-cors", then return a + // promise rejected with a TypeError. + { + const request = + new Request('resources/feature-name.txt', {mode: 'no-cors'}); + + await promise_rejects_js( + test, TypeError, backgroundFetch.fetch(uniqueId(), request), + 'Requests must not be in no-cors mode'); + } + +}, 'Argument verification is done for BackgroundFetchManager.fetch()'); + +backgroundFetchTest(async (test, backgroundFetch) => { + // 6.3.1.9.2: If |bgFetchMap[id]| exists, reject |promise| with a TypeError + // and abort these steps. + return promise_rejects_js(test, TypeError, Promise.all([ + backgroundFetch.fetch('my-id', 'resources/feature-name.txt?1'), + backgroundFetch.fetch('my-id', 'resources/feature-name.txt?2') + ])); + +}, 'IDs must be unique among active Background Fetch registrations'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, ''); + + assert_equals(registration.id, registrationId); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + + assert_equals('backgroundfetchsuccess', type); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + +}, 'Empty URL is OK.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, + new Request('https://example/com', { + method: 'PUT', + })); + + assert_equals(registration.id, registrationId); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + + assert_equals(type, 'backgroundfetchsuccess'); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); +}, 'Requests with PUT method require CORS Preflight and succeed.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, + new Request('https://example/com', { + method: 'POST', + headers: {'Content-Type': 'text/json'} + })); + + assert_equals(registration.id, registrationId); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + + assert_equals(type, 'backgroundfetchsuccess'); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); +}, 'Requests with text/json content type require CORS Preflight and succeed.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + + assert_equals(registration.id, registrationId); + assert_equals(registration.uploadTotal, 0); + assert_equals(registration.uploaded, 0); + assert_equals(registration.downloadTotal, 0); + assert_equals(registration.result, ''); + assert_equals(registration.failureReason, ''); + assert_true(registration.recordsAvailable); + // Skip `downloaded`, as the transfer may have started already. + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + assert_equals(results.length, 1); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + + assert_true(results[0].url.includes('resources/feature-name.txt')); + assert_equals(results[0].status, 200); + assert_equals(results[0].text, 'Background Fetch'); + +}, 'Using Background Fetch to successfully fetch a single resource'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + + assert_equals(registration.result, ''); + assert_equals(registration.failureReason, ''); + + const {type, eventRegistration, results} = + await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(registration.result, 'success'); + assert_equals(registration.failureReason, ''); + +}, 'Registration object gets updated values when a background fetch completes.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + + // Very large download total that will definitely exceed the quota. + const options = {downloadTotal: Number.MAX_SAFE_INTEGER}; + await promise_rejects_dom( + test, 'QUOTA_EXCEEDED_ERR', + backgroundFetch.fetch(registrationId, 'resources/feature-name.txt', options), + 'This fetch should have thrown a quota exceeded error'); + +}, 'Background Fetch that exceeds the quota throws a QuotaExceededError'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + 'my-id', ['resources/feature-name.txt', 'resources/feature-name.txt']); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + assert_equals(results.length, 2); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + + for (const result of results) { + assert_true(result.url.includes('resources/feature-name.txt')); + assert_equals(result.status, 200); + assert_equals(result.text, 'Background Fetch'); + } + +}, 'Fetches can have requests with duplicate URLs'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + assert_true(registration.recordsAvailable); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + assert_equals(results.length, 1); + + // Wait for up to 5 seconds for the |eventRegistration|'s recordsAvailable + // flag to be set to false, which happens after the successful invocation + // of the ServiceWorker event has finished. + for (let i = 0; i < 50; ++i) { + if (!registration.recordsAvailable) + break; + await wait(100); + } + + assert_false(registration.recordsAvailable); +}, 'recordsAvailable is false after onbackgroundfetchsuccess finishes execution.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, 'resources/missing-cat.txt'); + + assert_equals(registration.id, registrationId); + assert_equals(registration.result, ''); + assert_equals(registration.failureReason, ''); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals(type, 'backgroundfetchfail'); + assert_equals(results.length, 1); + assert_true(results[0].url.includes('resources/missing-cat.txt')); + assert_equals(results[0].status, 404); + assert_equals(results[0].text, ''); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.result, 'failure'); + assert_equals(eventRegistration.failureReason, 'bad-status'); + + assert_equals(registration.result, 'failure'); + assert_equals(registration.failureReason, 'bad-status'); + +}, 'Using Background Fetch to fetch a non-existent resource should fail.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + 'my-id', + [location.origin, location.origin.replace('https', 'http')]); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + + assert_equals('backgroundfetchfail', type); + assert_equals(eventRegistration.failureReason, 'fetch-error'); + + assert_equals(results.length, 2); + + const validResponse = results[0] ? results[0] : results[1]; + const nullResponse = !results[0] ? results[0] : results[1]; + + assert_true(validResponse.url.includes(location.origin)); + assert_equals(nullResponse, null); + +}, 'Fetches with mixed content should fail.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const filePath = '/background-fetch/resources/feature-name.txt'; + const registration = await backgroundFetch.fetch( + uniqueId(), + `https://${get_host_info().REMOTE_HOST}${filePath}`); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals(type, 'backgroundfetchfail'); + assert_equals(results.length, 1); + + assert_equals(results[0], null); + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.downloaded, 0); +}, 'Responses failing CORS checks are not leaked'); diff --git a/testing/web-platform/tests/background-fetch/get-ids.https.window.js b/testing/web-platform/tests/background-fetch/get-ids.https.window.js new file mode 100644 index 0000000000..4c8bf26190 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/get-ids.https.window.js @@ -0,0 +1,45 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Covers functionality provided by BackgroundFetchManager.getIds(), which +// exposes the keys of active background fetches. +// +// https://wicg.github.io/background-fetch/#background-fetch-manager-getIds + +promise_test(async test => { + const script = 'service_workers/sw.js'; + const scope = 'service_workers/' + location.pathname; + + const serviceWorkerRegistration = + await service_worker_unregister_and_register(test, script, scope); + + assert_equals( + serviceWorkerRegistration.active, null, + 'There must not be an activated worker'); + + const ids = await serviceWorkerRegistration.backgroundFetch.getIds(); + assert_equals(ids.length, 0); + +}, 'BackgroundFetchManager.getIds() does not require an activated worker'); + +backgroundFetchTest(async (test, backgroundFetch) => { + // There should not be any active background fetches at this point. + { + const ids = await backgroundFetch.getIds(); + assert_equals(ids.length, 0); + } + + const registrationId = uniqueId(); + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + assert_equals(registration.id, registrationId); + + // The |registrationId| should be active, and thus be included in getIds(). + { + const ids = await backgroundFetch.getIds(); + assert_equals(ids.length, 1); + assert_equals(ids[0], registrationId); + } + +}, 'The BackgroundFetchManager exposes active fetches'); diff --git a/testing/web-platform/tests/background-fetch/get.https.window.js b/testing/web-platform/tests/background-fetch/get.https.window.js new file mode 100644 index 0000000000..d4448d184a --- /dev/null +++ b/testing/web-platform/tests/background-fetch/get.https.window.js @@ -0,0 +1,64 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Covers functionality provided by BackgroundFetchManager.get(), which +// exposes the keys of active background fetches. +// +// https://wicg.github.io/background-fetch/#background-fetch-manager-get + +promise_test(async test => { + const script = 'service_workers/sw.js'; + const scope = 'service_workers/' + location.pathname; + + const serviceWorkerRegistration = + await service_worker_unregister_and_register(test, script, scope); + + assert_equals( + serviceWorkerRegistration.active, null, + 'There must not be an activated worker'); + + const registration = await serviceWorkerRegistration.backgroundFetch.get('x'); + assert_equals(registration, undefined); + +}, 'BackgroundFetchManager.get() does not require an activated worker'); + +backgroundFetchTest(async (test, backgroundFetch) => { + // The |id| parameter to the BackgroundFetchManager.get() method is required. + await promise_rejects_js(test, TypeError, backgroundFetch.get()); + await promise_rejects_js(test, TypeError, backgroundFetch.get('')); + + const registration = await backgroundFetch.get('my-id'); + assert_equals(registration, undefined); + +}, 'Getting non-existing registrations yields `undefined`'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = uniqueId(); + const registration = await backgroundFetch.fetch( + registrationId, 'resources/feature-name.txt', {downloadTotal: 1234}); + + assert_equals(registration.id, registrationId); + assert_equals(registration.uploadTotal, 0); + assert_equals(registration.uploaded, 0); + assert_equals(registration.downloadTotal, 1234); + assert_equals(registration.result, ''); + assert_equals(registration.failureReason, ''); + assert_true(registration.recordsAvailable); + // Skip `downloaded`, as the transfer may have started already. + + const secondRegistration = await backgroundFetch.get(registrationId); + assert_not_equals(secondRegistration, null); + + assert_equals(secondRegistration.id, registration.id); + assert_equals(secondRegistration.uploadTotal, registration.uploadTotal); + assert_equals(secondRegistration.uploaded, registration.uploaded); + assert_equals(secondRegistration.downloadTotal, registration.downloadTotal); + assert_equals(secondRegistration.failureReason, registration.failureReason); + assert_equals(secondRegistration.recordsAvailable, registration.recordsAvailable); + + // While the transfer might have started, both BackgroundFetchRegistration + // objects should have the latest progress values. + assert_equals(secondRegistration.downloaded, registration.downloaded); + +}, 'Getting an existing registration has the expected values'); diff --git a/testing/web-platform/tests/background-fetch/idlharness.https.any.js b/testing/web-platform/tests/background-fetch/idlharness.https.any.js new file mode 100644 index 0000000000..61f517fdc7 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/idlharness.https.any.js @@ -0,0 +1,25 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +'use strict'; + +// https://wicg.github.io/background-fetch/ + +idl_test( + ['background-fetch'], + ['service-workers', 'html', 'dom'], + idl_array => { + const isServiceWorker = location.pathname.includes('.serviceworker.'); + if (isServiceWorker) { + idl_array.add_objects({ + ServiceWorkerGlobalScope: ['self'], + ServiceWorkerRegistration: ['registration'], + BackgroundFetchManager: ['registration.backgroundFetch'], + BackgroundFetchEvent: ['new BackgroundFetchEvent("type")'], + BackgroundFetchUpdateEvent: ['new BackgroundFetchUpdateEvent("type")'], + }); + } + } +); diff --git a/testing/web-platform/tests/background-fetch/match.https.window.js b/testing/web-platform/tests/background-fetch/match.https.window.js new file mode 100644 index 0000000000..a9be5e7061 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/match.https.window.js @@ -0,0 +1,113 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js + +'use strict'; + +// Covers basic functionality provided by BackgroundFetchRegistration.match(All)?. +// https://wicg.github.io/background-fetch/#dom-backgroundfetchregistration-match + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = 'matchexistingrequest'; + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + + assert_equals(registration.id, registrationId); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + assert_equals(results.length, 1); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + + assert_true(results[0].url.includes('resources/feature-name.txt')); + assert_equals(results[0].status, 200); + assert_equals(results[0].text, 'Background Fetch'); + +}, 'Matching to a single request should work'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = 'matchmissingrequest'; + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + + assert_equals(registration.id, registrationId); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + assert_equals(results.length, 0); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + +}, 'Matching to a non-existing request should work'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = 'matchexistingrequesttwice'; + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + + assert_equals(registration.id, registrationId); + + const {type, eventRegistration, results} = await getMessageFromServiceWorker(); + assert_equals('backgroundfetchsuccess', type); + assert_equals(results.length, 2); + + assert_equals(eventRegistration.id, registration.id); + assert_equals(eventRegistration.result, 'success'); + assert_equals(eventRegistration.failureReason, ''); + + assert_true(results[0].url.includes('resources/feature-name.txt')); + assert_equals(results[0].status, 200); + assert_equals(results[0].text, 'Background Fetch'); + + assert_true(results[1].url.includes('resources/feature-name.txt')); + assert_equals(results[1].status, 200); + assert_equals(results[1].text, 'Background Fetch'); + +}, 'Matching multiple times on the same request works as expected.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + uniqueId(), ['resources/feature-name.txt', '/common/slow.py']); + + const record = await registration.match('resources/feature-name.txt'); + const response = await record.responseReady; + assert_true(response.url.includes('resources/feature-name.txt')); + const completedResponseText = await response.text(); + assert_equals(completedResponseText, 'Background Fetch'); + +}, 'Access to active fetches is supported.'); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registration = await backgroundFetch.fetch( + uniqueId(), [ + 'resources/feature-name.txt', + 'resources/feature-name.txt', + 'resources/feature-name.txt?id=3', + new Request('resources/feature-name.txt', {method: 'PUT'}), + '/common/slow.py', + ]); + + let matchedRecords = null; + + // We should match all the duplicates. + matchedRecords = await registration.matchAll('resources/feature-name.txt'); + assert_equals(matchedRecords.length, 2); + + // We should match the request with the query param as well. + matchedRecords = await registration.matchAll('resources/feature-name.txt', {ignoreSearch: true}); + assert_equals(matchedRecords.length, 3); + + // We should match the PUT request as well. + matchedRecords = await registration.matchAll('resources/feature-name.txt', {ignoreMethod: true}); + assert_equals(matchedRecords.length, 3); + + // We should match all requests. + matchedRecords = await registration.matchAll('resources/feature-name.txt', {ignoreSearch: true, ignoreMethod: true}); + assert_equals(matchedRecords.length, 4); + +}, 'Match with query options.'); diff --git a/testing/web-platform/tests/background-fetch/mixed-content-and-allowed-schemes.https.window.js b/testing/web-platform/tests/background-fetch/mixed-content-and-allowed-schemes.https.window.js new file mode 100644 index 0000000000..87a84bf9fb --- /dev/null +++ b/testing/web-platform/tests/background-fetch/mixed-content-and-allowed-schemes.https.window.js @@ -0,0 +1,58 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Tests that Mixed Content requests are blocked. +// https://w3c.github.io/webappsec-mixed-content/#should-block-fetch +// https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url +// https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy + +// With an additional restriction that only https:// and loopback http:// +// requests are allowed. Hence the wss:, file:, data:, etc schemes are blocked. +// https://github.com/WICG/background-fetch/issues/44 + +// This is not a comprehensive test of mixed content blocking - it is just +// intended to check that blocking is enabled. + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'https://example.com'); +}, 'https: fetch should register ok'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'http://127.0.0.1'); +}, 'loopback IPv4 http: fetch should register ok'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'http://[::1]'); +}, 'loopback IPv6 http: fetch should register ok'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'http://localhost'); +}, 'localhost http: fetch should register ok'); + +function testBgFetch(bgFetch, url) +{ + return bgFetch.fetch(uniqueId(), url).then(fetch => { + return fetch.match(url); + }).then(match => match.responseReady); +} + +backgroundFetchTest((t, bgFetch) => { + return promise_rejects_js(t, TypeError, + testBgFetch(bgFetch, 'wss:127.0.0.1')); +}, 'wss: fetch should reject'); + +backgroundFetchTest((t, bgFetch) => { + return promise_rejects_js(t, TypeError, + testBgFetch(bgFetch, 'file:///')); +}, 'file: fetch should reject'); + +backgroundFetchTest((t, bgFetch) => { + return promise_rejects_js(t, TypeError, + testBgFetch(bgFetch, 'data:text/plain,foo')); +}, 'data: fetch should reject'); + +backgroundFetchTest((t, bgFetch) => { + return promise_rejects_js(t, TypeError, + testBgFetch(bgFetch, 'foobar:bazqux')); +}, 'unknown scheme fetch should reject'); diff --git a/testing/web-platform/tests/background-fetch/port-blocking.https.window.js b/testing/web-platform/tests/background-fetch/port-blocking.https.window.js new file mode 100644 index 0000000000..358f382b64 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/port-blocking.https.window.js @@ -0,0 +1,39 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Tests that requests to bad ports are blocked. +// https://fetch.spec.whatwg.org/#port-blocking + +// This is not a comprehensive test of blocked ports - it is just intended to +// check that blocking is enabled. + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'https://example.com'); +}, 'fetch to default https port should register ok'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'http://127.0.0.1'); +}, 'fetch to default http port should register ok'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'https://example.com:443'); +}, 'fetch to port 443 should register ok'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'https://example.com:80'); +}, 'fetch to port 80 should register ok, even over https'); + +backgroundFetchTest((t, bgFetch) => { + return bgFetch.fetch(uniqueId(), 'https://example.com:8080'); +}, 'fetch to non-default non-bad port (8080) should register ok'); + +backgroundFetchTest(async (t, bgFetch) => { + const promise = bgFetch.fetch(uniqueId(), 'https://example.com:587').then(fetch => { + return fetch.match('https://example.com:587'); + }).then(record => record.responseReady); + + return promise_rejects_js( + t, TypeError, + promise); +}, 'fetch to bad port (SMTP) should reject'); diff --git a/testing/web-platform/tests/background-fetch/resources/feature-name.txt b/testing/web-platform/tests/background-fetch/resources/feature-name.txt new file mode 100644 index 0000000000..4d54f5054c --- /dev/null +++ b/testing/web-platform/tests/background-fetch/resources/feature-name.txt @@ -0,0 +1 @@ +Background Fetch
\ No newline at end of file diff --git a/testing/web-platform/tests/background-fetch/resources/upload.py b/testing/web-platform/tests/background-fetch/resources/upload.py new file mode 100644 index 0000000000..4c421c7d37 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/resources/upload.py @@ -0,0 +1,3 @@ +# Simply returns the request body to check if the upload succeeded. +def main(request, response): + return 200, [(b"Content-Type", request.headers[b'content-type'])], request.body diff --git a/testing/web-platform/tests/background-fetch/resources/utils.js b/testing/web-platform/tests/background-fetch/resources/utils.js new file mode 100644 index 0000000000..10895c3ccf --- /dev/null +++ b/testing/web-platform/tests/background-fetch/resources/utils.js @@ -0,0 +1,71 @@ +'use strict'; + +let nextBackgroundFetchId = 0; + +function loadScript(path) { + let script = document.createElement('script'); + let promise = new Promise(resolve => script.onload = resolve); + script.src = path; + script.async = false; + document.head.appendChild(script); + return promise; +} + +// Waits for a single message received from a registered Service Worker. +async function getMessageFromServiceWorker() { + return new Promise(resolve => { + function listener(event) { + navigator.serviceWorker.removeEventListener('message', listener); + resolve(event.data); + } + + navigator.serviceWorker.addEventListener('message', listener); + }); +} + +// Registers the |name| instrumentation Service Worker located at "service_workers/" +// with a scope unique to the test page that's running, and waits for it to be +// activated. The Service Worker will be unregistered automatically. +// +// Depends on /service-workers/service-worker/resources/test-helpers.sub.js +async function registerAndActivateServiceWorker(test, name) { + const script = `service_workers/${name}`; + const scope = 'service_workers/scope' + location.pathname; + + let serviceWorkerRegistration = + await service_worker_unregister_and_register(test, script, scope); + + add_completion_callback(() => serviceWorkerRegistration.unregister()); + + await wait_for_state(test, serviceWorkerRegistration.installing, 'activated'); + return serviceWorkerRegistration; +} + +// Creates a Promise test for |func| given the |description|. The |func| will be +// executed with the `backgroundFetch` object of an activated Service Worker +// Registration. +// |workerName| is the name of the service worker file in the service_workers +// directory to register. +function backgroundFetchTest(func, description, workerName = 'sw.js') { + promise_test(async t => { + if (typeof test_driver === 'undefined') { + await loadScript('/resources/testdriver.js'); + await loadScript('/resources/testdriver-vendor.js'); + } + + await test_driver.set_permission({name: 'background-fetch'}, 'granted'); + + const serviceWorkerRegistration = + await registerAndActivateServiceWorker(t, workerName); + serviceWorkerRegistration.active.postMessage(null); + + assert_equals(await getMessageFromServiceWorker(), 'ready'); + + return func(t, serviceWorkerRegistration.backgroundFetch); + }, description); +} + +// Returns a Background Fetch ID that's unique for the current page. +function uniqueId() { + return 'id' + nextBackgroundFetchId++; +} diff --git a/testing/web-platform/tests/background-fetch/service_workers/sw-abort.js b/testing/web-platform/tests/background-fetch/service_workers/sw-abort.js new file mode 100644 index 0000000000..c61377758d --- /dev/null +++ b/testing/web-platform/tests/background-fetch/service_workers/sw-abort.js @@ -0,0 +1,23 @@ +importScripts('sw-helpers.js'); + +async function getFetchResult(record) { + try { + await record.responseReady; + } catch (e) { + return { + response: false, + name: e.name, + }; + } + + return { + response: true, + }; +} +self.addEventListener('backgroundfetchabort', event => { + event.waitUntil( + event.registration.matchAll() + .then(records => + Promise.all(records.map(record => getFetchResult(record)))) + .then(results => sendMessageToDocument({results}))); +}); diff --git a/testing/web-platform/tests/background-fetch/service_workers/sw-helpers.js b/testing/web-platform/tests/background-fetch/service_workers/sw-helpers.js new file mode 100644 index 0000000000..e4c772135d --- /dev/null +++ b/testing/web-platform/tests/background-fetch/service_workers/sw-helpers.js @@ -0,0 +1,31 @@ +// The source to post setup and completion results to. +let source = null; + +function sendMessageToDocument(msg) { + source.postMessage(msg); +} + +// This is needed to create a local javascript object identical to the +// one returned by a BackgroundFetchEvent, so that it can be serialized +// and transmitted from the service worker context to the document. +function cloneRegistration(registration) { + function deepCopy(src) { + if (typeof src !== 'object' || src === null) + return src; + var dst = Array.isArray(src) ? [] : {}; + for (var property in src) { + if (typeof src[property] === 'function') + continue; + dst[property] = deepCopy(src[property]); + } + return dst; + } + + return deepCopy(registration); +} + +// Notify the document that the SW is registered and ready. +self.addEventListener('message', event => { + source = event.source; + sendMessageToDocument('ready'); +}); diff --git a/testing/web-platform/tests/background-fetch/service_workers/sw-update-ui.js b/testing/web-platform/tests/background-fetch/service_workers/sw-update-ui.js new file mode 100644 index 0000000000..3848dc4402 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/service_workers/sw-update-ui.js @@ -0,0 +1,33 @@ +importScripts('/resources/testharness.js'); +importScripts('sw-helpers.js'); + +async function updateUI(event) { + let updateParams = []; + switch (event.registration.id) { + case 'update-once': + updateParams = [{title: 'Title1'}]; + break; + case 'update-twice': + updateParams = [{title: 'Title1'}, {title: 'Title2'}]; + break; + } + + return Promise.all(updateParams.map(param => event.updateUI(param))) + .then(() => 'update success') + .catch(e => e.name); +} + +self.addEventListener('backgroundfetchsuccess', event => { + if (event.registration.id === 'update-inactive') { + // Post an async task before calling updateUI from the inactive event. + // Any async behaviour outside `waitUntil` should mark the event as + // inactive, and subsequent calls to `updateUI` should fail. + new Promise(r => step_timeout(r, 0)) + .then(() => event.updateUI({ title: 'New title' })) + .catch(e => sendMessageToDocument({ update: e.name })); + return; + } + + event.waitUntil(updateUI(event) + .then(update => sendMessageToDocument({ update }))); +}); diff --git a/testing/web-platform/tests/background-fetch/service_workers/sw.js b/testing/web-platform/tests/background-fetch/service_workers/sw.js new file mode 100644 index 0000000000..be43629f03 --- /dev/null +++ b/testing/web-platform/tests/background-fetch/service_workers/sw.js @@ -0,0 +1,64 @@ + +importScripts('sw-helpers.js'); + +async function getFetchResult(record) { + const response = await record.responseReady.catch(() => null); + if (!response) return null; + + return { + url: response.url, + status: response.status, + text: await response.text().catch(() => 'error'), + }; +} + +function handleBackgroundFetchEvent(event) { + let matchFunction = null; + + switch (event.registration.id) { + case 'matchexistingrequest': + matchFunction = event.registration.match.bind( + event.registration, '/background-fetch/resources/feature-name.txt'); + break; + case 'matchexistingrequesttwice': + matchFunction = (async () => { + const match1 = await event.registration.match('/background-fetch/resources/feature-name.txt'); + const match2 = await event.registration.match('/background-fetch/resources/feature-name.txt'); + return [match1, match2]; + }).bind(event.registration); + break; + case 'matchmissingrequest': + matchFunction = event.registration.match.bind( + event.registration, '/background-fetch/resources/missing.txt'); + break; + default: + matchFunction = event.registration.matchAll.bind(event.registration); + break; + } + + event.waitUntil( + matchFunction() + // Format `match(All)?` function results. + .then(records => { + if (!records) return []; // Nothing was matched. + if (!records.map) return [records]; // One entry was returned. + return records; // Already in a list. + }) + // Extract responses. + .then(records => + Promise.all(records.map(record => getFetchResult(record)))) + // Clone registration and send message. + .then(results => { + const registrationCopy = cloneRegistration(event.registration); + sendMessageToDocument( + { type: event.type, eventRegistration: registrationCopy, results }) + }) + .catch(error => { + sendMessageToDocument( + { type: event.type, eventRegistration:{}, results:[], error:true }) + })); +} + +self.addEventListener('backgroundfetchsuccess', handleBackgroundFetchEvent); +self.addEventListener('backgroundfetchfail', handleBackgroundFetchEvent); +self.addEventListener('backgroundfetchabort', handleBackgroundFetchEvent); diff --git a/testing/web-platform/tests/background-fetch/update-ui.https.window.js b/testing/web-platform/tests/background-fetch/update-ui.https.window.js new file mode 100644 index 0000000000..90d3c2e7ed --- /dev/null +++ b/testing/web-platform/tests/background-fetch/update-ui.https.window.js @@ -0,0 +1,43 @@ +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: script=resources/utils.js +'use strict'; + +// Covers functionality provided by BackgroundFetchUpdateUIEvent.updateUI(). +// +// https://wicg.github.io/background-fetch/#backgroundfetchupdateuievent + +const swName = 'sw-update-ui.js'; + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = 'update-once'; + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + assert_equals(registration.id, registrationId); + + const message = await getMessageFromServiceWorker(); + assert_equals(message.update, 'update success'); + +}, 'Background Fetch updateUI resolves', swName); + + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = 'update-twice'; + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + assert_equals(registration.id, registrationId); + + const message = await getMessageFromServiceWorker(); + assert_equals(message.update, 'InvalidStateError'); + +}, 'Background Fetch updateUI called twice fails', swName); + +backgroundFetchTest(async (test, backgroundFetch) => { + const registrationId = 'update-inactive'; + const registration = + await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt'); + assert_equals(registration.id, registrationId); + + const message = await getMessageFromServiceWorker(); + assert_equals(message.update, 'InvalidStateError'); + +}, 'Background Fetch updateUI fails when event is not active', swName); |