summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/background-fetch
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/background-fetch
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--testing/web-platform/tests/background-fetch/META.yml4
-rw-r--r--testing/web-platform/tests/background-fetch/abort.https.window.js74
-rw-r--r--testing/web-platform/tests/background-fetch/content-security-policy.https.window.js23
-rw-r--r--testing/web-platform/tests/background-fetch/fetch-uploads.https.window.js64
-rw-r--r--testing/web-platform/tests/background-fetch/fetch.https.window.js280
-rw-r--r--testing/web-platform/tests/background-fetch/get-ids.https.window.js45
-rw-r--r--testing/web-platform/tests/background-fetch/get.https.window.js64
-rw-r--r--testing/web-platform/tests/background-fetch/idlharness.https.any.js25
-rw-r--r--testing/web-platform/tests/background-fetch/match.https.window.js113
-rw-r--r--testing/web-platform/tests/background-fetch/mixed-content-and-allowed-schemes.https.window.js58
-rw-r--r--testing/web-platform/tests/background-fetch/port-blocking.https.window.js39
-rw-r--r--testing/web-platform/tests/background-fetch/resources/feature-name.txt1
-rw-r--r--testing/web-platform/tests/background-fetch/resources/upload.py3
-rw-r--r--testing/web-platform/tests/background-fetch/resources/utils.js71
-rw-r--r--testing/web-platform/tests/background-fetch/service_workers/sw-abort.js23
-rw-r--r--testing/web-platform/tests/background-fetch/service_workers/sw-helpers.js31
-rw-r--r--testing/web-platform/tests/background-fetch/service_workers/sw-update-ui.js33
-rw-r--r--testing/web-platform/tests/background-fetch/service_workers/sw.js64
-rw-r--r--testing/web-platform/tests/background-fetch/update-ui.https.window.js43
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);