summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test/isolated
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/serviceworkers/test/isolated/README.md19
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini7
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js147
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html40
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs100
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);
+ }
+}