Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

<!doctype html>
<meta charset=utf-8>
<title></title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script>
// Tests a _registered_ ServiceWorker whose script evaluation results in an
// "abrupt completion", e.g. threw an uncaught exception. Such a ServiceWorker's
// first script evaluation must result in a "normal completion", however, for
// the Update algorithm to not abort in its step 18 when registering:
//
// 18. If runResult is failure or an abrupt completion, then: [...]
const script = "./abrupt_completion_worker.js";
const scope = "./empty.html";
const expectedMessage = "handler-before-throw";
let registration = null;
// Should only be called once registration.active is non-null. Uses
// implementation details by zero-ing the "idle timeout"s and then sending an
// event to the ServiceWorker, which should immediately cause its termination.
// The idle timeouts are restored after the ServiceWorker is terminated.
async function startAndStopServiceWorker() {
SpecialPowers.registerObservers("service-worker-shutdown");
const spTopic = "specialpowers-service-worker-shutdown";
const origIdleTimeout =
SpecialPowers.getIntPref("dom.serviceWorkers.idle_timeout");
const origIdleExtendedTimeout =
SpecialPowers.getIntPref("dom.serviceWorkers.idle_extended_timeout");
await new Promise(resolve => {
const observer = {
async observe(subject, topic, data) {
if (topic !== spTopic) {
return;
}
SpecialPowers.removeObserver(observer, spTopic);
await SpecialPowers.pushPrefEnv({
set: [
["dom.serviceWorkers.idle_timeout", origIdleTimeout],
["dom.serviceWorkers.idle_extended_timeout", origIdleExtendedTimeout]
]
});
resolve();
},
};
// Speed things up.
SpecialPowers.pushPrefEnv({
set: [
["dom.serviceWorkers.idle_timeout", 0],
["dom.serviceWorkers.idle_extended_timeout", 0]
]
}).then(() => {
SpecialPowers.addObserver(observer, spTopic);
registration.active.postMessage("");
});
});
}
// eslint-disable-next-line mozilla/no-addtask-setup
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true]
]
});
registration = await navigator.serviceWorker.register(script, { scope });
SimpleTest.registerCleanupFunction(async function unregisterRegistration() {
await registration.unregister();
});
await new Promise(resolve => {
const serviceWorker = registration.installing;
serviceWorker.onstatechange = () => {
if (serviceWorker.state === "activated") {
resolve();
}
};
});
ok(registration.active instanceof ServiceWorker, "ServiceWorker is activated");
});
// We expect that the restarted SW that experiences an abrupt completion at
// startup after adding its message handler 1) will be active in order to
// respond to our postMessage and 2) will respond with the global value set
// prior to the importScripts call that throws (and not the global value that
// would have been assigned after the importScripts call if it didn't throw).
add_task(async function testMessageHandler() {
await startAndStopServiceWorker();
await new Promise(resolve => {
navigator.serviceWorker.onmessage = e => {
is(e.data, expectedMessage, "Correct message handler");
resolve();
};
registration.active.postMessage("");
});
});
// We expect that the restarted SW that experiences an abrupt completion at
// startup before adding its "fetch" listener will 1) successfully dispatch the
// event and 2) it will not be handled (respondWith() will not be called) so
// interception will be reset and the response will contain the contents of
// empty.html. Before the fix in bug 1603484 the SW would fail to properly start
// up and the fetch event would result in a NetworkError, breaking the
// controlled page.
add_task(async function testFetchHandler() {
await startAndStopServiceWorker();
const iframe = document.createElement("iframe");
SimpleTest.registerCleanupFunction(function removeIframe() {
iframe.remove();
});
await new Promise(resolve => {
iframe.src = scope;
iframe.onload = resolve;
document.body.appendChild(iframe);
});
const response = await iframe.contentWindow.fetch(scope);
// NetworkError will have a status of 0, which is not "ok", and this is
// a stronger guarantee that should be true instead of just checking if there
// isn't a NetworkError.
ok(response.ok, "Fetch succeeded and didn't result in a NetworkError");
const text = await response.text();
is(text, "", "Correct response text");
});
</script>