diff options
Diffstat (limited to 'dom/push/test/test_serviceworker_lifetime.html')
-rw-r--r-- | dom/push/test/test_serviceworker_lifetime.html | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/dom/push/test/test_serviceworker_lifetime.html b/dom/push/test/test_serviceworker_lifetime.html new file mode 100644 index 000000000..03f66887a --- /dev/null +++ b/dom/push/test/test_serviceworker_lifetime.html @@ -0,0 +1,362 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test the lifetime management of service workers. We keep this test in + dom/push/tests to pass the external network check when connecting to + the mozilla push service. + + How this test works: + - the service worker maintains a state variable and a promise used for + extending its lifetime. Note that the terminating the worker will reset + these variables to their default values. + - we send 3 types of requests to the service worker: + |update|, |wait| and |release|. All three requests will cause the sw to update + its state to the new value and reply with a message containing + its previous state. Furthermore, |wait| will set a waitUntil or a respondWith + promise that's not resolved until the next |release| message. + - Each subtest will use a combination of values for the timeouts and check + if the service worker is in the correct state as we send it different + events. + - We also wait and assert for service worker termination using an event dispatched + through nsIObserverService. + --> +<head> + <title>Test for Bug 1188545</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> +</head> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1188545">Mozilla Bug 118845</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script class="testbody" type="text/javascript"> + + function start() { + return navigator.serviceWorker.register("lifetime_worker.js", {scope: "./"}) + .then((swr) => ({registration: swr})); + } + + function waitForActiveServiceWorker(ctx) { + return waitForActive(ctx.registration).then(function(result) { + ok(ctx.registration.active, "Service Worker is active"); + return ctx; + }); + } + + function unregister(ctx) { + return ctx.registration.unregister().then(function(result) { + ok(result, "Unregister should return true."); + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + } + + function registerPushNotification(ctx) { + var p = new Promise(function(res, rej) { + ctx.registration.pushManager.subscribe().then( + function(pushSubscription) { + ok(true, "successful registered for push notification"); + ctx.subscription = pushSubscription; + res(ctx); + }, function(error) { + ok(false, "could not register for push notification"); + res(ctx); + }); + }); + return p; + } + + var mockSocket = new MockWebSocket(); + var endpoint = "https://example.com/endpoint/1"; + var channelID = null; + mockSocket.onRegister = function(request) { + channelID = request.channelID; + this.serverSendMsg(JSON.stringify({ + messageType: "register", + uaid: "fa8f2e4b-5ddc-4408-b1e3-5f25a02abff0", + channelID, + status: 200, + pushEndpoint: endpoint + })); + }; + + function sendPushToPushServer(pushEndpoint) { + is(pushEndpoint, endpoint, "Got unexpected endpoint"); + mockSocket.serverSendMsg(JSON.stringify({ + messageType: "notification", + version: "vDummy", + channelID + })); + } + + function unregisterPushNotification(ctx) { + return ctx.subscription.unsubscribe().then(function(result) { + ok(result, "unsubscribe should succeed."); + ctx.subscription = null; + return ctx; + }); + } + + function createIframe(ctx) { + var p = new Promise(function(res, rej) { + var iframe = document.createElement('iframe'); + // This file doesn't exist, the service worker will give us an empty + // document. + iframe.src = "http://mochi.test:8888/tests/dom/push/test/lifetime_frame.html"; + + iframe.onload = function() { + ctx.iframe = iframe; + res(ctx); + } + document.body.appendChild(iframe); + }); + return p; + } + + function closeIframe(ctx) { + ctx.iframe.parentNode.removeChild(ctx.iframe); + return new Promise(function(res, rej) { + // XXXcatalinb: give the worker more time to "notice" it stopped + // controlling documents + ctx.iframe = null; + setTimeout(res, 0); + }).then(() => ctx); + } + + function waitAndCheckMessage(contentWindow, expected) { + function checkMessage(expected, resolve, event) { + ok(event.data.type == expected.type, "Received correct message type: " + expected.type); + ok(event.data.state == expected.state, "Service worker is in the correct state: " + expected.state); + this.navigator.serviceWorker.onmessage = null; + resolve(); + } + return new Promise(function(res, rej) { + contentWindow.navigator.serviceWorker.onmessage = + checkMessage.bind(contentWindow, expected, res); + }); + } + + function fetchEvent(ctx, expected_state, new_state) { + var expected = { type: "fetch", state: expected_state }; + var p = waitAndCheckMessage(ctx.iframe.contentWindow, expected); + ctx.iframe.contentWindow.fetch(new_state); + return p; + } + + function pushEvent(ctx, expected_state, new_state) { + var expected = {type: "push", state: expected_state}; + var p = waitAndCheckMessage(ctx.iframe.contentWindow, expected); + sendPushToPushServer(ctx.subscription.endpoint); + return p; + } + + function messageEventIframe(ctx, expected_state, new_state) { + var expected = {type: "message", state: expected_state}; + var p = waitAndCheckMessage(ctx.iframe.contentWindow, expected); + ctx.iframe.contentWindow.navigator.serviceWorker.controller.postMessage(new_state); + return p; + } + + function messageEvent(ctx, expected_state, new_state) { + var expected = {type: "message", state: expected_state}; + var p = waitAndCheckMessage(window, expected); + ctx.registration.active.postMessage(new_state); + return p; + } + + function checkStateAndUpdate(eventFunction, expected_state, new_state) { + return function(ctx) { + return eventFunction(ctx, expected_state, new_state) + .then(() => ctx); + } + } + + function setShutdownObserver(expectingEvent) { + info("Setting shutdown observer: expectingEvent=" + expectingEvent); + return function(ctx) { + cancelShutdownObserver(ctx); + + ctx.observer_promise = new Promise(function(res, rej) { + ctx.observer = { + observe: function(subject, topic, data) { + ok((topic == "service-worker-shutdown") && expectingEvent, "Service worker was terminated."); + this.remove(ctx); + }, + remove: function(ctx) { + SpecialPowers.removeObserver(this, "service-worker-shutdown"); + ctx.observer = null; + res(ctx); + } + } + SpecialPowers.addObserver(ctx.observer, "service-worker-shutdown", false); + }); + + return ctx; + } + } + + function waitOnShutdownObserver(ctx) { + info("Waiting on worker to shutdown."); + return ctx.observer_promise; + } + + function cancelShutdownObserver(ctx) { + if (ctx.observer) { + ctx.observer.remove(ctx); + } + return ctx.observer_promise; + } + + function subTest(test) { + return function(ctx) { + return new Promise(function(res, rej) { + function run() { + test.steps(ctx).catch(function(e) { + ok(false, "Some test failed with error: " + e); + }).then((ctx) => res(ctx)); + } + + SpecialPowers.pushPrefEnv({"set" : test.prefs}, run); + }); + } + } + + var test1 = { + prefs: [ + ["dom.serviceWorkers.idle_timeout", 0], + ["dom.serviceWorkers.idle_extended_timeout", 2999999] + ], + // Test that service workers are terminated after the grace period expires + // when there are no pending waitUntil or respondWith promises. + steps: function(ctx) { + // Test with fetch events and respondWith promises + return createIframe(ctx) + .then(setShutdownObserver(true)) + .then(checkStateAndUpdate(fetchEvent, "from_scope", "update")) + .then(waitOnShutdownObserver) + .then(setShutdownObserver(false)) + .then(checkStateAndUpdate(fetchEvent, "from_scope", "wait")) + .then(checkStateAndUpdate(fetchEvent, "wait", "update")) + .then(checkStateAndUpdate(fetchEvent, "update", "update")) + .then(setShutdownObserver(true)) + // The service worker should be terminated when the promise is resolved. + .then(checkStateAndUpdate(fetchEvent, "update", "release")) + .then(waitOnShutdownObserver) + .then(setShutdownObserver(false)) + .then(closeIframe) + .then(cancelShutdownObserver) + + // Test with push events and message events + .then(setShutdownObserver(true)) + .then(createIframe) + // Make sure we are shutdown before entering our "no shutdown" sequence + // to avoid races. + .then(waitOnShutdownObserver) + .then(setShutdownObserver(false)) + .then(checkStateAndUpdate(pushEvent, "from_scope", "wait")) + .then(checkStateAndUpdate(messageEventIframe, "wait", "update")) + .then(checkStateAndUpdate(messageEventIframe, "update", "update")) + .then(setShutdownObserver(true)) + .then(checkStateAndUpdate(messageEventIframe, "update", "release")) + .then(waitOnShutdownObserver) + .then(closeIframe) + } + } + + var test2 = { + prefs: [ + ["dom.serviceWorkers.idle_timeout", 0], + ["dom.serviceWorkers.idle_extended_timeout", 2999999] + ], + steps: function(ctx) { + // Older versions used to terminate workers when the last controlled + // window was closed. This should no longer happen, though. Verify + // the new behavior. + setShutdownObserver(true)(ctx); + return createIframe(ctx) + // Make sure we are shutdown before entering our "no shutdown" sequence + // to avoid races. + .then(waitOnShutdownObserver) + .then(setShutdownObserver(false)) + .then(checkStateAndUpdate(fetchEvent, "from_scope", "wait")) + .then(closeIframe) + .then(setShutdownObserver(true)) + .then(checkStateAndUpdate(messageEvent, "wait", "release")) + .then(waitOnShutdownObserver) + + // Push workers were exempt from the old rule and should continue to + // survive past the closing of the last controlled window. + .then(setShutdownObserver(true)) + .then(createIframe) + // Make sure we are shutdown before entering our "no shutdown" sequence + // to avoid races. + .then(waitOnShutdownObserver) + .then(setShutdownObserver(false)) + .then(checkStateAndUpdate(pushEvent, "from_scope", "wait")) + .then(closeIframe) + .then(setShutdownObserver(true)) + .then(checkStateAndUpdate(messageEvent, "wait", "release")) + .then(waitOnShutdownObserver) + } + }; + + var test3 = { + prefs: [ + ["dom.serviceWorkers.idle_timeout", 2999999], + ["dom.serviceWorkers.idle_extended_timeout", 0] + ], + steps: function(ctx) { + // set the grace period to 0 and dispatch a message which will reset + // the internal sw timer to the new value. + var test3_1 = { + prefs: [ + ["dom.serviceWorkers.idle_timeout", 0], + ["dom.serviceWorkers.idle_extended_timeout", 0] + ], + steps: function(ctx) { + return new Promise(function(res, rej) { + ctx.iframe.contentWindow.fetch("update"); + res(ctx); + }); + } + } + + // Test that service worker is closed when the extended timeout expired + return createIframe(ctx) + .then(setShutdownObserver(false)) + .then(checkStateAndUpdate(messageEvent, "from_scope", "update")) + .then(checkStateAndUpdate(messageEventIframe, "update", "update")) + .then(checkStateAndUpdate(fetchEvent, "update", "wait")) + .then(setShutdownObserver(true)) + .then(subTest(test3_1)) // This should cause the internal timer to expire. + .then(waitOnShutdownObserver) + .then(closeIframe) + } + } + + function runTest() { + start() + .then(waitForActiveServiceWorker) + .then(registerPushNotification) + .then(subTest(test1)) + .then(subTest(test2)) + .then(subTest(test3)) + .then(unregisterPushNotification) + .then(unregister) + .catch(function(e) { + ok(false, "Some test failed with error " + e) + }).then(SimpleTest.finish); + } + + setupPrefsAndMockSocket(mockSocket).then(_ => runTest()); + SpecialPowers.addPermission('desktop-notification', true, document); + SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> |