Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 102 additions & 32 deletions sw-stream-cancellation/index.html
Original file line number Diff line number Diff line change
@@ -1,42 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ServiceWorker - Response.body cancellation test</title>
<meta charset="utf-8" />
<title>ServiceWorker - Response.body cancellation test</title>
</head>
<body>
<h3>Test progress is outputted to console</h3>
Related chromium issue:
<a
href="https://bugs.chromium.org/p/chromium/issues/detail?id=638494"
target="_blank"
>638494</a
>
- Response.body cancellation should be notified to the service worker.
<hr />
<a
href="https://github.com/norstbox/trial/tree/master/sw-stream-cancellation"
target="_blank"
>View source</a
>
on GitHub
<br /><br />
<button id="start-download">Start download</button
><button id="abort-download">Abort download</button>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js')
.then((reg) => {
console.log('[MAIN] Registration succeeded. SW Scope is ' + reg.scope);
}).catch((error) => {
console.log('[MAIN] Registration failed with ' + error);
});

if (navigator.serviceWorker.controller)
doTest()
else
navigator.serviceWorker.oncontrollerchange = doTest
let readable, resolve, promise;

const unregisterServiceWorkers = async (_) => {
const registrations = await navigator.serviceWorker.getRegistrations();
for (const registration of registrations) {
console.log(registration);
try {
await registration.unregister();
} catch (e) {
throw e;
}
}
return `ServiceWorker's unregistered`;
};
unregisterServiceWorkers().then(console.log, console.error);
const start = document.getElementById('start-download');

const abort = document.getElementById('abort-download');

abort.onclick = async (e) => {
readable.cancel('Download aborted');
};

start.onclick = async (e) => {
if (document.getElementById('download')) {
document.body.removeChild(document.getElementById('download'));
}


async function doTest() {
console.log(`[MAIN] ${document.title} is started`)
const key = '/get-file'
let r = await fetch(key)
console.log('[MAIN] Trying to cancel stream using ReadableStream.cancel()')
try {
await r.body.cancel(key)
console.log('[MAIN] Stream cancelled succesfully')
} catch (e) {
console.log('[MAIN] Stream cancelled with error:', e)
promise = new Promise((_) => (resolve = _));
const unregisterable = await unregisterServiceWorkers();

console.log(unregisterable);

const reg = await navigator.serviceWorker.register('sw.js', {
scope: './',
});

console.log(reg);

let controller;

readable = new ReadableStream({
start(_) {
return (controller = _);
},
cancel(reason) {
console.log(`cancel(reason): ${reason}`);
resolve(reason);
},
});
const encoder = new TextEncoder();
downloadable: for (let i = 0; i < 100; i++) {
console.log(i, readable, controller);
try {
await new Promise((r) => setTimeout(r, 100));
controller.enqueue(encoder.encode(i + '\n'));
} catch (err) {
console.warn(err.message);
break downloadable;
}
}
resolve('Download ready');

if ((await promise) === 'Download aborted') {
console.log(`${await promise}, ${await unregisterServiceWorkers()}`);
readable = promise = resolve = void 0;
} else {
console.log(reg);
controller.close();

navigator.serviceWorker.onmessage = async (event) => {
console.log(event);
if (event.data === 'ServiceWorker ready to serve download') {
const iframe = document.createElement('iframe');

document.body.appendChild(iframe);
iframe.width = iframe.height = 0;
iframe.id = 'download';
iframe.src = 'get-file';
}
};
navigator.serviceWorker.controller.postMessage(readable, [readable]);
}
};
</script>
</head>

<body>
<h3>Test progress is outputted to console</h3>
Related chromium issue: <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=638494" target="_blank">638494</a> - Response.body cancellation should be notified to the service worker.<hr />
<a href="https://github.com/norstbox/trial/tree/master/sw-stream-cancellation" target="_blank">View source</a> on GitHub
</body>
</html>
73 changes: 27 additions & 46 deletions sw-stream-cancellation/sw.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,33 @@
self.addEventListener('install', event => {
console.log('[SW] install event fired')
});
let readable;

self.addEventListener('activate', event => {
console.log('[SW] activate event fired')
event.waitUntil(clients.claim())
self.addEventListener('message', async (event) => {
readable = event.data;
console.log(event);
event.source.postMessage('ServiceWorker ready to serve download');
});

self.addEventListener('fetch', (event) => {
let url = new URL(event.request.url)
if (url.pathname === '/get-file') {
let interval
let counter = 0
let cancelNotification = false
let initialCancelReason = false
let rs = new ReadableStream({
start(c) {
interval = setInterval( () => {
if (counter > 10) {
console.log('[SW] FAIL: Stream must be already closed, but still not')
clearInterval(interval)
c.close()
return
}
console.log('[SW] Stream: enqueue chunk', counter)
try {
c.enqueue( new Uint8Array([counter++]) )
} catch(e) {
if (cancelNotification) {
console.log('[SW] OK: Stream has been closed with prior notification about cancellation')
if (!initialCancelReason)
console.log('[SW] WARN: Stream cancellation reason is not matched to initial')
}
else
console.log('[SW] FAIL: Stream has been closed without prior notification about cancellation')
clearInterval(interval)
}
}, 2000)
},
cancel(reason) {
console.log('[SW] Stream cancelled with reason:', reason)
if (reason === url.pathname)
initialCancelReason = true
cancelNotification = true
}
console.log(event);
let url = new URL(event.request.url);
if (url.pathname.includes('get-file')) {
console.log({ readable });
const headers = {
'content-disposition': 'attachment; filename="filename.txt"',
};
event.respondWith(
new Response(readable, {
headers,
})
let r = new Response(rs)
event.respondWith( Promise.resolve(r) )
} else
event.respondWith( fetch(event.request) )
);
}
});

self.addEventListener('install', (event) => {
console.log(event);
event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', (event) => {
console.log(event);
event.waitUntil(self.clients.claim());
});