// 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');