summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test/download_canceled/server-stream-download.sjs
blob: e6ae8c4f9889a47cfc3be1f875f21eca301bb6b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

const { setInterval, clearInterval } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);

// 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;
}

/*
 * We want to let the sw_download_canceled.js service worker know when the
 * stream was canceled.  To this end, we let it issue a monitor request which we
 * fulfill when the stream has been canceled.  In order to coordinate between
 * multiple requests, we use the getObjectState/setObjectState mechanism that
 * httpd.js exposes to let data be shared and/or persist between requests.  We
 * handle both possible orderings of the requests because we currently don't
 * try and impose an ordering between the two requests as issued by the SW, and
 * file_blocked_script.sjs encourages us to do this, but we probably could order
 * them.
 */
const MONITOR_KEY = "stream-monitor";
function completeMonitorResponse(response, data) {
  response.write(JSON.stringify(data));
  response.finish();
}
function handleMonitorRequest(request, response) {
  response.setHeader("Content-Type", "application/json");
  response.setStatusLine(request.httpVersion, 200, "Found");

  response.processAsync();
  // Necessary to cause the headers to be flushed; that or touching the
  // bodyOutputStream getter.
  response.write("");
  dump("server-stream-download.js: monitor headers issued\n");

  const alreadyCompleted = getGlobalState(MONITOR_KEY);
  if (alreadyCompleted) {
    completeMonitorResponse(response, alreadyCompleted);
    setGlobalState(null, MONITOR_KEY);
  } else {
    setGlobalState(response, MONITOR_KEY);
  }
}

const MAX_TICK_COUNT = 3000;
const TICK_INTERVAL = 2;
function handleStreamRequest(request, response) {
  const name = "server-stream-download";

  // Create some payload to send.
  let strChunk =
    "Static routes are the future of ServiceWorkers! So say we all!\n";
  while (strChunk.length < 1024) {
    strChunk += strChunk;
  }

  response.setHeader("Content-Disposition", `attachment; filename="${name}"`);
  response.setHeader(
    "Content-Type",
    `application/octet-stream; name="${name}"`
  );
  response.setHeader("Content-Length", `${strChunk.length * MAX_TICK_COUNT}`);
  response.setStatusLine(request.httpVersion, 200, "Found");

  response.processAsync();
  response.write(strChunk);
  dump("server-stream-download.js: stream headers + first payload issued\n");

  let count = 0;
  let intervalId;
  function closeStream(why, message) {
    dump("server-stream-download.js: closing stream: " + why + "\n");
    clearInterval(intervalId);
    response.finish();

    const data = { why, message };
    const monitorResponse = getGlobalState(MONITOR_KEY);
    if (monitorResponse) {
      completeMonitorResponse(monitorResponse, data);
      setGlobalState(null, MONITOR_KEY);
    } else {
      setGlobalState(data, MONITOR_KEY);
    }
  }
  function tick() {
    try {
      // bound worst-case behavior.
      if (count++ > MAX_TICK_COUNT) {
        closeStream("timeout", "timeout");
        return;
      }
      response.write(strChunk);
    } catch (e) {
      closeStream("canceled", e.message);
    }
  }
  intervalId = setInterval(tick, TICK_INTERVAL);
}

function handleRequest(request, response) {
  dump(
    "server-stream-download.js: processing request for " +
      request.path +
      "?" +
      request.queryString +
      "\n"
  );
  const query = new URLSearchParams(request.queryString);
  if (query.has("monitor")) {
    handleMonitorRequest(request, response);
  } else {
    handleStreamRequest(request, response);
  }
}