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