author | Perry Jiang <perry@mozilla.com> |
Fri, 11 Oct 2019 23:23:20 +0000 | |
changeset 497334 | 0464faa83b959a5f00f2e6e7951d2818f419c6ac |
parent 497333 | f62280bf339a179ad3a90348e1f6ff43eb8c6540 |
child 497335 | 1c6ab26ee4cf9b1cc437efaa672b43966e15a187 |
push id | 36682 |
push user | ncsoregi@mozilla.com |
push date | Sat, 12 Oct 2019 09:52:03 +0000 |
treeherder | mozilla-central@06ea2371f897 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | asuth |
bugs | 1578919 |
milestone | 71.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html @@ -4,63 +4,125 @@ <script src="/resources/testharness.js"></script> <script src="resources/testharness-helpers.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/common/get-host-info.sub.js"></script> <script src="resources/test-helpers.sub.js"></script> <body> <script> -var worker = 'resources/fetch-waits-for-activate-worker.js'; -var expected_url = normalizeURL(worker); -var scope = 'resources/fetch-waits-for-activate/'; +const worker_url = 'resources/fetch-waits-for-activate-worker.js'; +const normalized_worker_url = normalizeURL(worker_url); +const worker_scope = 'resources/fetch-waits-for-activate/'; + +// Resolves with the Service Worker's registration once it's reached the +// "activating" state. (The Service Worker should remain "activating" until +// explicitly told advance to the "activated" state). +async function registerAndWaitForActivating(t) { + const registration = await service_worker_unregister_and_register( + t, worker_url, worker_scope); + t.add_cleanup(() => service_worker_unregister(t, worker_scope)); + + await wait_for_state(t, registration.installing, 'activating'); + + return registration; +} -promise_test(function(t) { - var registration; - var frameLoadPromise; - var frame; - return service_worker_unregister_and_register(t, worker, scope).then(function(reg) { - t.add_cleanup(function() { - return service_worker_unregister(t, scope); - }); +// Attempts to ensure that the "Handle Fetch" algorithm has reached the step +// +// "If activeWorker’s state is "activating", wait for activeWorker’s state to +// become "activated"." +// +// by waiting for some time to pass. +// +// WARNING: whether the algorithm has reached that step isn't directly +// observable, so this is best effort and can race. Note that this can only +// result in false positives (where the algorithm hasn't reached that step yet +// and any functional events haven't actually been handled by the Service +// Worker). +async function ensureFunctionalEventsAreWaiting(registration) { + await (new Promise(resolve => { setTimeout(resolve, 1000); })); - registration = reg; - return wait_for_state(t, reg.installing, 'activating'); - }).then(function() { - assert_equals(registration.active.scriptURL, expected_url, - 'active worker should be present'); - assert_equals(registration.active.state, 'activating', - 'active worker should be in activating state'); + assert_equals(registration.active.scriptURL, normalized_worker_url, + 'active worker should be present'); + assert_equals(registration.active.state, 'activating', + 'active worker should be in activating state'); +} + +promise_test(async t => { + const registration = await registerAndWaitForActivating(t); + + let frame = null; + t.add_cleanup(() => { + if (frame) { + frame.remove(); + } + }); + + // This should block until we message the worker to tell it to complete + // the activate event. + const frameLoadPromise = with_iframe(worker_scope).then(function(f) { + frame = f; + }); + + await ensureFunctionalEventsAreWaiting(registration); + assert_equals(frame, null, 'frame should not be loaded'); - // This should block until we message the worker to tell it to complete - // the activate event. - frameLoadPromise = with_iframe(scope).then(function(f) { - frame = f; - }); + registration.active.postMessage('ACTIVATE'); + + await frameLoadPromise; + assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalized_worker_url, + 'frame should now be loaded and controlled'); + assert_equals(registration.active.state, 'activated', + 'active worker should be in activated state'); +}, 'Navigation fetch events should wait for the activate event to complete.'); + +promise_test(async t => { + const frame = await with_iframe(worker_scope); + t.add_cleanup(() => { frame.remove(); }); + + const registration = await registerAndWaitForActivating(t); + + // Make the Service Worker control the frame so the frame can perform an + // intercepted fetch. + await (new Promise(resolve => { + navigator.serviceWorker.onmessage = e => { + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalized_worker_url, 'frame should be controlled'); + resolve(); + }; + + registration.active.postMessage('CLAIM'); + })); - // Wait some time to allow frame loading to proceed. It should not, - // however, if the fetch event is blocked on the activate. I don't - // see any way to force this race without a timeout, unfortunately. - return new Promise(function(resolve) { - setTimeout(resolve, 1000); - }); - }).then(function() { - assert_equals(frame, undefined, 'frame should not be loaded'); - assert_equals(registration.active.scriptURL, expected_url, - 'active worker should be present'); - assert_equals(registration.active.state, 'activating', - 'active worker should be in activating state'); + const fetch_url = `${worker_scope}non/existent/path`; + const expected_fetch_result = 'Hello world'; + let fetch_promise_settled = false; + + // This should block until we message the worker to tell it to complete + // the activate event. + const fetchPromise = frame.contentWindow.fetch(fetch_url, { + method: 'POST', + body: expected_fetch_result, + }).then(response => { + fetch_promise_settled = true; + return response; + }); - // This signals the activate event to complete. The frame should now - // load. - registration.active.postMessage('GO'); - return frameLoadPromise; - }).then(function() { - assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL, - expected_url, 'frame should now be loaded and controlled'); - assert_equals(registration.active.state, 'activated', - 'active worker should be in activated state'); - frame.remove(); - }); -}, 'Fetch events should wait for the activate event to complete.'); + await ensureFunctionalEventsAreWaiting(registration); + assert_false(fetch_promise_settled, + "fetch()-ing a Service Worker-controlled scope shouldn't have " + + "settled yet"); + + registration.active.postMessage('ACTIVATE'); + + const response = await fetchPromise; + assert_equals(await response.text(), expected_fetch_result, + "Service Worker should have responded to request to" + + fetch_url) + assert_equals(registration.active.state, 'activated', + 'active worker should be in activated state'); +}, 'Subresource fetch events should wait for the activate event to complete.'); </script> </body>
--- a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js @@ -1,17 +1,31 @@ var activatePromiseResolve; addEventListener('activate', function(evt) { evt.waitUntil(new Promise(function(resolve) { activatePromiseResolve = resolve; })); }); -addEventListener('message', function(evt) { - if (typeof activatePromiseResolve === 'function') { - activatePromiseResolve(); +addEventListener('message', async function(evt) { + switch (evt.data) { + case 'CLAIM': + evt.waitUntil(new Promise(async resolve => { + await clients.claim(); + evt.source.postMessage('CLAIMED'); + resolve(); + })); + break; + case 'ACTIVATE': + if (typeof activatePromiseResolve !== 'function') { + throw new Error('Not activating!'); + } + activatePromiseResolve(); + break; + default: + throw new Error('Unknown message!'); } }); addEventListener('fetch', function(evt) { evt.respondWith(new Response('Hello world')); });