diff options
Diffstat (limited to '')
5 files changed, 313 insertions, 0 deletions
diff --git a/dom/serviceworkers/test/isolated/README.md b/dom/serviceworkers/test/isolated/README.md new file mode 100644 index 0000000000..2b462385af --- /dev/null +++ b/dom/serviceworkers/test/isolated/README.md @@ -0,0 +1,19 @@ +This directory contains tests that are flaky when run with other tests +but that we don't want to disable and where it's not trivial to make +the tests not flaky at this time, but we have a plan to fix them via +systemic fixes that are improving the codebase rather than hacking a +test until it works. + +This directory and ugly hack structure needs to exist because of +multi-e10s propagation races that will go away when we finish +implementing the multi-e10s overhaul for ServiceWorkers. Most +specifically, unregister() calls need to propagate across all +content processes. There are fixes on bug 1318142, but they're +ugly and complicate things. + +Specific test notes and rationalizations: +- multi-e10s-update: This test relies on there being no registrations + existing at its start. The preceding test that induces the breakage + (`browser_force_refresh.js`) was made to clean itself up, but the + unregister() race issue is not easily/cleanly hacked around and this + test will itself become moot when the multi-e10s changes land. diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini b/dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini new file mode 100644 index 0000000000..bb913e2583 --- /dev/null +++ b/dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + file_multie10s_update.html + server_multie10s_update.sjs + +[browser_multie10s_update.js] +skip-if = true # bug 1429794 is to re-enable diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js b/dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js new file mode 100644 index 0000000000..457d47863c --- /dev/null +++ b/dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js @@ -0,0 +1,147 @@ +"use strict"; + +// Testing if 2 child processes are correctly managed when they both try to do +// an SW update. + +const BASE_URI = + "http://mochi.test:8888/browser/dom/serviceworkers/test/isolated/multi-e10s-update/"; + +add_task(async function test_update() { + info("Setting the prefs to having multi-e10s enabled"); + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.ipc.processCount", 4], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + }); + + let url = BASE_URI + "file_multie10s_update.html"; + + info("Creating the first tab..."); + let tab1 = BrowserTestUtils.addTab(gBrowser, url); + let browser1 = gBrowser.getBrowserForTab(tab1); + await BrowserTestUtils.browserLoaded(browser1); + + info("Creating the second tab..."); + let tab2 = BrowserTestUtils.addTab(gBrowser, url); + let browser2 = gBrowser.getBrowserForTab(tab2); + await BrowserTestUtils.browserLoaded(browser2); + + let sw = BASE_URI + "server_multie10s_update.sjs"; + + info("Let's make sure there are no existing registrations..."); + let existingCount = await SpecialPowers.spawn( + browser1, + [], + async function () { + const regs = await content.navigator.serviceWorker.getRegistrations(); + return regs.length; + } + ); + is(existingCount, 0, "Previous tests should have cleaned up!"); + + info("Let's start the test..."); + /* eslint-disable no-shadow */ + let status = await SpecialPowers.spawn(browser1, [sw], function (url) { + // Let the SW be served immediately once by triggering a relase immediately. + // We don't need to await this. We do this from a frame script because + // it has fetch. + content.fetch(url + "?release"); + + // Registration of the SW + return ( + content.navigator.serviceWorker + .register(url) + + // Activation + .then(function (r) { + content.registration = r; + return new content.window.Promise(resolve => { + let worker = r.installing; + worker.addEventListener("statechange", () => { + if (worker.state === "installed") { + resolve(true); + } + }); + }); + }) + + // Waiting for the result. + .then(() => { + return new content.window.Promise(resolveResults => { + // Once both updates have been issued and a single update has failed, we + // can tell the .sjs to release a single copy of the SW script. + let updateCount = 0; + const uc = new content.window.BroadcastChannel("update"); + // This promise tracks the updates tally. + const updatesIssued = new Promise(resolveUpdatesIssued => { + uc.onmessage = function (e) { + updateCount++; + console.log("got update() number", updateCount); + if (updateCount === 2) { + resolveUpdatesIssued(); + } + }; + }); + + let results = []; + const rc = new content.window.BroadcastChannel("result"); + // This promise resolves when an update has failed. + const oneFailed = new Promise(resolveOneFailed => { + rc.onmessage = function (e) { + console.log("got result", e.data); + results.push(e.data); + if (e.data === 1) { + resolveOneFailed(); + } + if (results.length != 2) { + return; + } + + resolveResults(results[0] + results[1]); + }; + }); + + Promise.all([updatesIssued, oneFailed]).then(() => { + console.log("releasing update"); + content.fetch(url + "?release").catch(ex => { + console.error("problem releasing:", ex); + }); + }); + + // Let's inform the tabs. + const sc = new content.window.BroadcastChannel("start"); + sc.postMessage("go"); + }); + }) + ); + }); + /* eslint-enable no-shadow */ + + if (status == 0) { + ok(false, "both succeeded. This is wrong."); + } else if (status == 1) { + ok(true, "one succeded, one failed. This is good."); + } else { + ok(false, "both failed. This is definitely wrong."); + } + + // let's clean up the registration and get the fetch count. The count + // should be 1 for the initial fetch and 1 for the update. + /* eslint-disable no-shadow */ + const count = await SpecialPowers.spawn(browser1, [sw], async function (url) { + // We stored the registration on the frame script's wrapper, hence directly + // accesss content without using wrappedJSObject. + await content.registration.unregister(); + const { count } = await content + .fetch(url + "?get-and-clear-count") + .then(r => r.json()); + return count; + }); + /* eslint-enable no-shadow */ + is(count, 2, "SW should have been fetched only twice"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html b/dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html new file mode 100644 index 0000000000..d611b01b59 --- /dev/null +++ b/dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html @@ -0,0 +1,40 @@ +<html> +<body> +<script> + +var bc = new BroadcastChannel('start'); +bc.onmessage = function(e) { + // This message is not for us. + if (e.data != 'go') { + return; + } + + // It can happen that we don't have the registrations yet. Let's try with a + // timeout. + function proceed() { + return navigator.serviceWorker.getRegistrations().then(regs => { + if (!regs.length) { + setTimeout(proceed, 200); + return; + } + + bc = new BroadcastChannel('result'); + regs[0].update().then(() => { + bc.postMessage(0); + }, () => { + bc.postMessage(1); + }); + + // Tell the coordinating frame script that we've kicked off our update + // call so that the SW script can be released once both instances of us + // have triggered update() and 1 has failed. + const blockingChannel = new BroadcastChannel('update'); + blockingChannel.postMessage(true); + }); + } + + proceed(); +} +</script> +</body> +</html> diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs b/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs new file mode 100644 index 0000000000..5bf48be29e --- /dev/null +++ b/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs @@ -0,0 +1,100 @@ +// stolen from file_blocked_script.sjs +function setGlobalState(data, key) { + x = { + data, + QueryInterface(iid) { + return this; + }, + }; + x.wrappedJSObject = x; + setObjectState(key, x); +} + +function getGlobalState(key) { + var data; + getObjectState(key, function (x) { + data = x && x.wrappedJSObject.data; + }); + return data; +} + +function completeBlockingRequest(response) { + response.write("42"); + response.finish(); +} + +// This stores the response that's currently blocking, or true if the release +// got here before the blocking request. +const BLOCKING_KEY = "multie10s-update-release"; +// This tracks the number of blocking requests we received up to this point in +// time. This value will be cleared when fetched. It's on the caller to make +// sure that all the blocking requests that might occurr have already occurred. +const COUNT_KEY = "multie10s-update-count"; + +/** + * Serve a request that will only be completed when the ?release variant of this + * .sjs is fetched. This allows us to avoid using a timer, which slows down the + * tests and is brittle under slow hardware. + */ +function handleBlockingRequest(request, response) { + response.processAsync(); + response.setHeader("Content-Type", "application/javascript", false); + + const existingCount = getGlobalState(COUNT_KEY) || 0; + setGlobalState(existingCount + 1, COUNT_KEY); + + const alreadyReleased = getGlobalState(BLOCKING_KEY); + if (alreadyReleased === true) { + completeBlockingRequest(response); + setGlobalState(null, BLOCKING_KEY); + } else if (alreadyReleased) { + // If we've got another response stacked up, this means something is wrong + // with the test. The count mechanism will detect this, so just let this + // one through so we fail fast rather than hanging. + dump("we got multiple blocking requests stacked up!!\n"); + completeBlockingRequest(response); + } else { + setGlobalState(response, BLOCKING_KEY); + } +} + +function handleReleaseRequest(request, response) { + const blockingResponse = getGlobalState(BLOCKING_KEY); + if (blockingResponse) { + completeBlockingRequest(blockingResponse); + setGlobalState(null, BLOCKING_KEY); + } else { + setGlobalState(true, BLOCKING_KEY); + } + + response.setHeader("Content-Type", "application/json", false); + response.write(JSON.stringify({ released: true })); +} + +function handleCountRequest(request, response) { + const count = getGlobalState(COUNT_KEY) || 0; + // --verify requires that we clear this so the test can be re-run. + setGlobalState(0, COUNT_KEY); + + response.setHeader("Content-Type", "application/json", false); + response.write(JSON.stringify({ count })); +} + +Components.utils.importGlobalProperties(["URLSearchParams"]); +function handleRequest(request, response) { + dump( + "server_multie10s_update.sjs: processing request for " + + request.path + + "?" + + request.queryString + + "\n" + ); + const query = new URLSearchParams(request.queryString); + if (query.has("release")) { + handleReleaseRequest(request, response); + } else if (query.has("get-and-clear-count")) { + handleCountRequest(request, response); + } else { + handleBlockingRequest(request, response); + } +} |