mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
363 lines
12 KiB
HTML
363 lines
12 KiB
HTML
<!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>
|