summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test/download_canceled/sw_download_canceled.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/serviceworkers/test/download_canceled/sw_download_canceled.js')
-rw-r--r--dom/serviceworkers/test/download_canceled/sw_download_canceled.js150
1 files changed, 150 insertions, 0 deletions
diff --git a/dom/serviceworkers/test/download_canceled/sw_download_canceled.js b/dom/serviceworkers/test/download_canceled/sw_download_canceled.js
new file mode 100644
index 0000000000..5d9d5f9bfd
--- /dev/null
+++ b/dom/serviceworkers/test/download_canceled/sw_download_canceled.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file is derived from :bkelly's https://glitch.com/edit/#!/html-sw-stream
+
+addEventListener("install", evt => {
+ evt.waitUntil(self.skipWaiting());
+});
+
+// Create a BroadcastChannel to notify when we have closed our streams.
+const channel = new BroadcastChannel("stream-closed");
+
+const MAX_TICK_COUNT = 3000;
+const TICK_INTERVAL = 4;
+/**
+ * Generate a continuous stream of data at a sufficiently high frequency that a
+ * there"s a good chance of racing channel cancellation.
+ */
+function handleStream(evt, filename) {
+ // Create some payload to send.
+ const encoder = new TextEncoder();
+ let strChunk =
+ "Static routes are the future of ServiceWorkers! So say we all!\n";
+ while (strChunk.length < 1024) {
+ strChunk += strChunk;
+ }
+ const dataChunk = encoder.encode(strChunk);
+
+ evt.waitUntil(
+ new Promise(resolve => {
+ let body = new ReadableStream({
+ start: controller => {
+ const closeStream = why => {
+ console.log("closing stream: " + JSON.stringify(why) + "\n");
+ clearInterval(intervalId);
+ resolve();
+ // In event of error, the controller will automatically have closed.
+ if (why.why != "canceled") {
+ try {
+ controller.close();
+ } catch (ex) {
+ // If we thought we should cancel but experienced a problem,
+ // that's a different kind of failure and we need to report it.
+ // (If we didn't catch the exception here, we'd end up erroneously
+ // in the tick() method's canceled handler.)
+ channel.postMessage({
+ what: filename,
+ why: "close-failure",
+ message: ex.message,
+ ticks: why.ticks,
+ });
+ return;
+ }
+ }
+ // Post prior to performing any attempt to close...
+ channel.postMessage(why);
+ };
+
+ controller.enqueue(dataChunk);
+ let count = 0;
+ let intervalId;
+ function tick() {
+ try {
+ // bound worst-case behavior.
+ if (count++ > MAX_TICK_COUNT) {
+ closeStream({
+ what: filename,
+ why: "timeout",
+ message: "timeout",
+ ticks: count,
+ });
+ return;
+ }
+ controller.enqueue(dataChunk);
+ } catch (e) {
+ closeStream({
+ what: filename,
+ why: "canceled",
+ message: e.message,
+ ticks: count,
+ });
+ }
+ }
+ // Alternately, streams' pull mechanism could be used here, but this
+ // test doesn't so much want to saturate the stream as to make sure the
+ // data is at least flowing a little bit. (Also, the author had some
+ // concern about slowing down the test by overwhelming the event loop
+ // and concern that we might not have sufficent back-pressure plumbed
+ // through and an infinite pipe might make bad things happen.)
+ intervalId = setInterval(tick, TICK_INTERVAL);
+ tick();
+ },
+ });
+ evt.respondWith(
+ new Response(body, {
+ headers: {
+ "Content-Disposition": `attachment; filename="${filename}"`,
+ "Content-Type": "application/octet-stream",
+ },
+ })
+ );
+ })
+ );
+}
+
+/**
+ * Use an .sjs to generate a similar stream of data to the above, passing the
+ * response through directly. Because we're handing off the response but also
+ * want to be able to report when cancellation occurs, we create a second,
+ * overlapping long-poll style fetch that will not finish resolving until the
+ * .sjs experiences closure of its socket and terminates the payload stream.
+ */
+function handlePassThrough(evt, filename) {
+ evt.waitUntil(
+ (async () => {
+ console.log("issuing monitor fetch request");
+ const response = await fetch("server-stream-download.sjs?monitor");
+ console.log("monitor headers received, awaiting body");
+ const data = await response.json();
+ console.log("passthrough monitor fetch completed, notifying.");
+ channel.postMessage({
+ what: filename,
+ why: data.why,
+ message: data.message,
+ });
+ })()
+ );
+ evt.respondWith(
+ fetch("server-stream-download.sjs").then(response => {
+ console.log("server-stream-download.sjs Response received, propagating");
+ return response;
+ })
+ );
+}
+
+addEventListener("fetch", evt => {
+ console.log(`SW processing fetch of ${evt.request.url}`);
+ if (evt.request.url.includes("sw-stream-download")) {
+ return handleStream(evt, "sw-stream-download");
+ }
+ if (evt.request.url.includes("sw-passthrough-download")) {
+ return handlePassThrough(evt, "sw-passthrough-download");
+ }
+});
+
+addEventListener("message", evt => {
+ if (evt.data === "claim") {
+ evt.waitUntil(clients.claim());
+ }
+});