serviceworker-intercepted.https.html (7486B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Aborting fetch when intercepted by a service worker</title> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="../../../service-workers/service-worker/resources/test-helpers.sub.js"></script> 9 </head> 10 <body> 11 <script> 12 // Duplicating this resource to make service worker scoping simpler. 13 const SCOPE = '../resources/basic.html'; 14 const BODY_METHODS = ['arrayBuffer', 'blob', 'bytes', 'formData', 'json', 'text']; 15 16 const error1 = new Error('error1'); 17 error1.name = 'error1'; 18 19 async function setupRegistration(t, scope, service_worker) { 20 const reg = await navigator.serviceWorker.register(service_worker, { scope }); 21 await wait_for_state(t, reg.installing, 'activated'); 22 add_completion_callback(_ => reg.unregister()); 23 return reg; 24 } 25 26 promise_test(async t => { 27 const suffix = "?q=aborted-not-intercepted"; 28 const scope = SCOPE + suffix; 29 await setupRegistration(t, scope, '../resources/sw-intercept.js'); 30 const iframe = await with_iframe(scope); 31 add_completion_callback(_ => iframe.remove()); 32 const w = iframe.contentWindow; 33 34 const controller = new w.AbortController(); 35 const signal = controller.signal; 36 controller.abort(); 37 38 const nextData = new Promise(resolve => { 39 w.navigator.serviceWorker.addEventListener('message', function once(event) { 40 // The message triggered by the iframe's document's fetch 41 // request cannot get dispatched by the time we add the event 42 // listener, so we have to guard against it. 43 if (!event.data.endsWith(suffix)) { 44 w.navigator.serviceWorker.removeEventListener('message', once); 45 resolve(event.data); 46 } 47 }) 48 }); 49 50 const fetchPromise = w.fetch('data.json', { signal }); 51 52 await promise_rejects_dom(t, "AbortError", w.DOMException, fetchPromise); 53 54 await w.fetch('data.json?no-abort'); 55 56 assert_true((await nextData).endsWith('?no-abort'), "Aborted request does not go through service worker"); 57 }, "Already aborted request does not land in service worker"); 58 59 for (const bodyMethod of BODY_METHODS) { 60 promise_test(async t => { 61 const scope = SCOPE + "?q=aborted-" + bodyMethod + "-rejects"; 62 await setupRegistration(t, scope, '../resources/sw-intercept.js'); 63 const iframe = await with_iframe(scope); 64 add_completion_callback(_ => iframe.remove()); 65 const w = iframe.contentWindow; 66 67 const controller = new w.AbortController(); 68 const signal = controller.signal; 69 70 const log = []; 71 const response = await w.fetch('data.json', { signal }); 72 73 controller.abort(); 74 75 const bodyPromise = response[bodyMethod](); 76 77 await Promise.all([ 78 bodyPromise.catch(() => log.push(`${bodyMethod}-reject`)), 79 Promise.resolve().then(() => log.push('next-microtask')) 80 ]); 81 82 await promise_rejects_dom(t, "AbortError", w.DOMException, bodyPromise); 83 84 assert_array_equals(log, [`${bodyMethod}-reject`, 'next-microtask']); 85 }, `response.${bodyMethod}() rejects if already aborted`); 86 } 87 88 promise_test(async t => { 89 const scope = SCOPE + "?q=aborted-stream-errors"; 90 await setupRegistration(t, scope, '../resources/sw-intercept.js'); 91 const iframe = await with_iframe(scope); 92 add_completion_callback(_ => iframe.remove()); 93 const w = iframe.contentWindow; 94 95 const controller = new w.AbortController(); 96 const signal = controller.signal; 97 98 const response = await w.fetch('data.json', { signal }); 99 const reader = response.body.getReader(); 100 101 controller.abort(); 102 103 await promise_rejects_dom(t, "AbortError", w.DOMException, reader.read()); 104 await promise_rejects_dom(t, "AbortError", w.DOMException, reader.closed); 105 }, "Stream errors once aborted."); 106 107 promise_test(async t => { 108 const scope = SCOPE + "?q=aborted-with-abort-reason"; 109 await setupRegistration(t, scope, '../resources/sw-intercept.js'); 110 const iframe = await with_iframe(scope); 111 add_completion_callback(_ => iframe.remove()); 112 const w = iframe.contentWindow; 113 114 const controller = new w.AbortController(); 115 const signal = controller.signal; 116 117 const fetchPromise = w.fetch('data.json', { signal }); 118 119 controller.abort(error1); 120 121 await promise_rejects_exactly(t, error1, fetchPromise); 122 }, "fetch() rejects with abort reason"); 123 124 125 promise_test(async t => { 126 const scope = SCOPE + "?q=aborted-with-abort-reason-in-body"; 127 await setupRegistration(t, scope, '../resources/sw-intercept.js'); 128 const iframe = await with_iframe(scope); 129 add_completion_callback(_ => iframe.remove()); 130 const w = iframe.contentWindow; 131 132 const controller = new w.AbortController(); 133 const signal = controller.signal; 134 135 const fetchResponse = await w.fetch('data.json', { signal }); 136 const bodyPromise = fetchResponse.body.getReader().read(); 137 controller.abort(error1); 138 139 await promise_rejects_exactly(t, error1, bodyPromise); 140 }, "fetch() response body has abort reason"); 141 142 promise_test(async t => { 143 const scope = SCOPE + "?q=service-worker-observes-abort-reason"; 144 await setupRegistration(t, scope, '../resources/sw-intercept-abort.js'); 145 const iframe = await with_iframe(scope); 146 add_completion_callback(_ => iframe.remove()); 147 const w = iframe.contentWindow; 148 149 const controller = new w.AbortController(); 150 const signal = controller.signal; 151 152 const fetchPromise = w.fetch('data.json', { signal }); 153 154 await new Promise(resolve => { 155 w.navigator.serviceWorker.addEventListener('message', t.step_func(event => { 156 assert_equals(event.data, "fetch event has arrived"); 157 resolve(); 158 }), {once: true}); 159 }); 160 161 controller.abort(error1); 162 163 await new Promise(resolve => { 164 w.navigator.serviceWorker.addEventListener('message', t.step_func(event => { 165 assert_equals(event.data.message, error1.message); 166 resolve(); 167 }), {once: true}); 168 }); 169 170 await promise_rejects_exactly(t, error1, fetchPromise); 171 }, "Service Worker can observe the fetch abort and associated abort reason"); 172 173 promise_test(async t => { 174 let incrementing_error = new Error('error1'); 175 incrementing_error.name = 'error1'; 176 177 const scope = SCOPE + "?q=serialization-on-abort"; 178 await setupRegistration(t, scope, '../resources/sw-intercept-abort.js'); 179 const iframe = await with_iframe(scope); 180 add_completion_callback(_ => iframe.remove()); 181 const w = iframe.contentWindow; 182 183 const controller = new w.AbortController(); 184 const signal = controller.signal; 185 186 const fetchPromise = w.fetch('data.json', { signal }); 187 188 await new Promise(resolve => { 189 w.navigator.serviceWorker.addEventListener('message', t.step_func(event => { 190 assert_equals(event.data, "fetch event has arrived"); 191 resolve(); 192 }), {once: true}); 193 }); 194 195 controller.abort(incrementing_error); 196 197 const original_error_name = incrementing_error.name; 198 199 incrementing_error.name = 'error2'; 200 201 await new Promise(resolve => { 202 w.navigator.serviceWorker.addEventListener('message', t.step_func(event => { 203 assert_equals(event.data.name, original_error_name); 204 resolve(); 205 }), {once: true}); 206 }); 207 208 await promise_rejects_exactly(t, incrementing_error, fetchPromise); 209 }, "Abort reason serialization happens on abort"); 210 </script> 211 </body> 212 </html>