tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

sw_download_canceled.js (5182B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 // This file is derived from :bkelly's https://glitch.com/edit/#!/html-sw-stream
      5 
      6 addEventListener("install", evt => {
      7  evt.waitUntil(self.skipWaiting());
      8 });
      9 
     10 // Create a BroadcastChannel to notify when we have closed our streams.
     11 const channel = new BroadcastChannel("stream-closed");
     12 
     13 const MAX_TICK_COUNT = 3000;
     14 const TICK_INTERVAL = 4;
     15 /**
     16 * Generate a continuous stream of data at a sufficiently high frequency that a
     17 * there"s a good chance of racing channel cancellation.
     18 */
     19 function handleStream(evt, filename) {
     20  // Create some payload to send.
     21  const encoder = new TextEncoder();
     22  let strChunk =
     23    "Static routes are the future of ServiceWorkers! So say we all!\n";
     24  while (strChunk.length < 1024) {
     25    strChunk += strChunk;
     26  }
     27  const dataChunk = encoder.encode(strChunk);
     28 
     29  evt.waitUntil(
     30    new Promise(resolve => {
     31      let body = new ReadableStream({
     32        start: controller => {
     33          const closeStream = why => {
     34            console.log("closing stream: " + JSON.stringify(why) + "\n");
     35            clearInterval(intervalId);
     36            resolve();
     37            // In event of error, the controller will automatically have closed.
     38            if (why.why != "canceled") {
     39              try {
     40                controller.close();
     41              } catch (ex) {
     42                // If we thought we should cancel but experienced a problem,
     43                // that's a different kind of failure and we need to report it.
     44                // (If we didn't catch the exception here, we'd end up erroneously
     45                // in the tick() method's canceled handler.)
     46                channel.postMessage({
     47                  what: filename,
     48                  why: "close-failure",
     49                  message: ex.message,
     50                  ticks: why.ticks,
     51                });
     52                return;
     53              }
     54            }
     55            // Post prior to performing any attempt to close...
     56            channel.postMessage(why);
     57          };
     58 
     59          controller.enqueue(dataChunk);
     60          let count = 0;
     61          let intervalId;
     62          function tick() {
     63            try {
     64              // bound worst-case behavior.
     65              if (count++ > MAX_TICK_COUNT) {
     66                closeStream({
     67                  what: filename,
     68                  why: "timeout",
     69                  message: "timeout",
     70                  ticks: count,
     71                });
     72                return;
     73              }
     74              controller.enqueue(dataChunk);
     75            } catch (e) {
     76              closeStream({
     77                what: filename,
     78                why: "canceled",
     79                message: e.message,
     80                ticks: count,
     81              });
     82            }
     83          }
     84          // Alternately, streams' pull mechanism could be used here, but this
     85          // test doesn't so much want to saturate the stream as to make sure the
     86          // data is at least flowing a little bit.  (Also, the author had some
     87          // concern about slowing down the test by overwhelming the event loop
     88          // and concern that we might not have sufficent back-pressure plumbed
     89          // through and an infinite pipe might make bad things happen.)
     90          intervalId = setInterval(tick, TICK_INTERVAL);
     91          tick();
     92        },
     93      });
     94      evt.respondWith(
     95        new Response(body, {
     96          headers: {
     97            "Content-Disposition": `attachment; filename="${filename}"`,
     98            "Content-Type": "application/octet-stream",
     99          },
    100        })
    101      );
    102    })
    103  );
    104 }
    105 
    106 /**
    107 * Use an .sjs to generate a similar stream of data to the above, passing the
    108 * response through directly.  Because we're handing off the response but also
    109 * want to be able to report when cancellation occurs, we create a second,
    110 * overlapping long-poll style fetch that will not finish resolving until the
    111 * .sjs experiences closure of its socket and terminates the payload stream.
    112 */
    113 function handlePassThrough(evt, filename) {
    114  evt.waitUntil(
    115    (async () => {
    116      console.log("issuing monitor fetch request");
    117      const response = await fetch("server-stream-download.sjs?monitor");
    118      console.log("monitor headers received, awaiting body");
    119      const data = await response.json();
    120      console.log("passthrough monitor fetch completed, notifying.");
    121      channel.postMessage({
    122        what: filename,
    123        why: data.why,
    124        message: data.message,
    125      });
    126    })()
    127  );
    128  evt.respondWith(
    129    fetch("server-stream-download.sjs").then(response => {
    130      console.log("server-stream-download.sjs Response received, propagating");
    131      return response;
    132    })
    133  );
    134 }
    135 
    136 addEventListener("fetch", evt => {
    137  console.log(`SW processing fetch of ${evt.request.url}`);
    138  if (evt.request.url.includes("sw-stream-download")) {
    139    handleStream(evt, "sw-stream-download");
    140    return;
    141  }
    142  if (evt.request.url.includes("sw-passthrough-download")) {
    143    handlePassThrough(evt, "sw-passthrough-download");
    144  }
    145 });
    146 
    147 addEventListener("message", evt => {
    148  if (evt.data === "claim") {
    149    evt.waitUntil(clients.claim());
    150  }
    151 });