From f6426f76aaf3c4eebc13f5053c1c237d8532929f Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Thu, 6 Jul 2017 17:12:04 +0100 Subject: [PATCH 01/13] Some initial tests --- fetch/api/abort/general.html | 11 + fetch/api/abort/general.js | 288 ++++++++++++++++++ fetch/api/resources/infinite-slow-response.py | 28 ++ fetch/api/resources/stash-take.py | 8 + tools/wptserve/wptserve/response.py | 6 +- 5 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 fetch/api/abort/general.html create mode 100644 fetch/api/abort/general.js create mode 100644 fetch/api/resources/infinite-slow-response.py create mode 100644 fetch/api/resources/stash-take.py diff --git a/fetch/api/abort/general.html b/fetch/api/abort/general.html new file mode 100644 index 00000000000000..21c7e461f76e64 --- /dev/null +++ b/fetch/api/abort/general.html @@ -0,0 +1,11 @@ + + + + + General fetch abort tests + + + + + + \ No newline at end of file diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js new file mode 100644 index 00000000000000..8cb49df4d6f557 --- /dev/null +++ b/fetch/api/abort/general.js @@ -0,0 +1,288 @@ +/* +// This is a really stupid set of hacks that I'm using to do some basic +// sanity-checking on the tests. +class AbortSignal { + constructor() { + this.aborted = false; + this._abortCallbacks = []; + } + _abort() { + this.aborted = true; + for (const func of this._abortCallbacks) func(); + this._abortCallbacks = null; + } + _addAbortCallback(callback) { + if (this.aborted) { + callback(); + return; + } + this._abortCallbacks.push(callback); + } +} + +class AbortController { + constructor() { + this.signal = new AbortSignal(); + } + abort() { + this.signal._abort(); + } +} + +function fetch(request, { + signal = request.signal +}={}) { + const url = request.url || request; + + if (signal && signal.aborted) return Promise.reject(new DOMException('Aborted', 'AbortError')); + + return new Promise((resolve, reject) => { + // Do the usual XHR stuff + const req = new XMLHttpRequest(); + req.open('GET', url); + + const read = new Promise((readResolve, readReject) => { + req.onload = () => { + readResolve(); + }; + + req.onreadystatechange = () => { + if (req.readyState == XMLHttpRequest.HEADERS_RECEIVED) { + resolve({ + async json() { + await read; + return JSON.parse(req.responseText); + }, + async text() { + await read; + return req.responseText; + } + }) + } + }; + + // Handle network errors + req.onerror = () => { + reject(Error("Network Error")); + readReject(Error("Network Error")); + }; + + req.onabort = () => { + reject(new DOMException('Aborted', 'AbortError')); + readReject(new DOMException('Aborted', 'AbortError')); + }; + + // Make the request + req.send(); + + if (signal) { + signal._addAbortCallback(() => req.abort()); + } + }); + }); +} +//*/ + +function assert_abort_error(err) { + assert_equals(err.constructor, DOMException); + assert_equals(err.name, 'AbortError'); +} + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + await fetch('../resources/data.json', { signal }).then(() => { + assert_unreached("Fetch must not resolve"); + }, err => { + // Using .catch rather than try/catch to ensure the promise + // is rejecting (rather than throwing). + assert_abort_error(err); + }); +}, "Aborting rejects with AbortError"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const log = []; + + await Promise.all([ + fetch('../resources/data.json', { signal }).then( + () => log.push('fetch-resolve'), + () => log.push('fetch-reject') + ), + Promise.resolve().then(() => log.push('next-microtask')) + ]); + + assert_array_equals(log, ['fetch-reject', 'next-microtask']); +}, "Already aborted signal rejects immediately"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json', { + signal, + method: 'POST', + body: 'foo', + headers: { 'Content-Type': 'text/plain' } + }); + + await fetch(request).catch(() => {}); + + assert_false(request.bodyUsed, "Body has not been used"); + assert_equals(await request.text(), 'foo', "Correct body"); +}, "Request is not 'used' if signal is aborted before fetching"); + +const bodyMethods = ['arrayBuffer', 'blob', 'formData', 'json', 'text']; + +for (const bodyMethod of bodyMethods) { + promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + + const log = []; + const response = await fetch('../resources/data.json', { signal }); + + controller.abort(); + + await Promise.all([ + response[bodyMethod]().then( + () => log.push(`${bodyMethod}-resolve`), + err => { + assert_abort_error(err); + log.push(`${bodyMethod}-reject`); + } + ), + Promise.resolve().then(() => log.push('next-microtask')) + ]); + + assert_array_equals(log, [`${bodyMethod}-reject`, 'next-microtask']); + }, `response.${bodyMethod}() rejects if already aborted`); +} + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + const key = token(); + controller.abort(); + + await fetch(`../resources/infinite-slow-response.py?key=${key}`, { signal }).catch(() => {}); + + // I'm hoping this will give the browser enough time to (incorrectly) make the request + // above, if it intends to. + await fetch('../resources/data.json').then(r => r.json()); + + const response = await fetch(`../resources/stash-take.py?key=${key}`); + const data = await response.json(); + + assert_equals(data, null, "Request hasn't been made to the server"); +}, "Already aborted signal does not make request"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const fetches = []; + + for (let i = 0; i < 3; i++) { + fetches.push( + fetch(`../resources/infinite-slow-response.py?${i}`, { signal }) + ); + } + + for (const fetchPromise of fetches) { + await fetchPromise.then(() => { + assert_unreached("Fetch must not resolve"); + }, err => { + assert_abort_error(err); + }); + } +}, "Already aborted signal can be used for many fetches"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + + await fetch('../resources/data.json', { signal }).then(r => r.json()); + + controller.abort(); + + const fetches = []; + + for (let i = 0; i < 3; i++) { + fetches.push( + fetch(`../resources/infinite-slow-response.py?${i}`, { signal }) + ); + } + + for (const fetchPromise of fetches) { + await fetchPromise.then(() => { + assert_unreached("Fetch must not resolve"); + }, err => { + assert_abort_error(err); + }); + } +}, "Signal can be used to abort other fetches, even if another fetch succeeded before aborting"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + const key = token(); + + await fetch(`../resources/infinite-slow-response.py?key=${key}`, { signal }); + + const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + assert_equals(beforeAbortResult, "open", "Connection is open"); + + controller.abort(); + + // The connection won't close immediately, but it should close at some point: + const start = Date.now(); + + while (true) { + // Stop spinning if 10 seconds have passed + if (Date.now() - start > 10000) throw Error('Timed out'); + + const afterAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + if (afterAbortResult == 'closed') break; + } +}, "Underlying connection is closed when aborting after receiving response"); + +for (const bodyMethod of bodyMethods) { + promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + const key = token(); + + const response = await fetch(`../resources/infinite-slow-response.py?key=${key}`, { signal }); + + const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + assert_equals(beforeAbortResult, "open", "Connection is open"); + + const bodyPromise = response[bodyMethod](); + + controller.abort(); + + await bodyPromise.then(() => { + assert_unreached("Body read must not resolve"); + }, err => { + assert_abort_error(err); + }); + + const start = Date.now(); + + while (true) { + // Stop spinning if 10 seconds have passed + if (Date.now() - start > 10000) throw Error('Timed out'); + + const afterAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + if (afterAbortResult == 'closed') break; + } + }, `Fetch aborted & connection closed when aborted after calling response.${bodyMethod}()`); +} \ No newline at end of file diff --git a/fetch/api/resources/infinite-slow-response.py b/fetch/api/resources/infinite-slow-response.py new file mode 100644 index 00000000000000..4f04f69ab60dba --- /dev/null +++ b/fetch/api/resources/infinite-slow-response.py @@ -0,0 +1,28 @@ +import time + + +def stash_write(request, key, value): + """Write to the stash, overwriting any previous value""" + dir = '/'.join(request.url_parts.path.split('/')[:-1]) + '/' + request.server.stash.take(key, dir) + request.server.stash.put(key, value, dir) + + +def main(request, response): + key = request.GET.first("key", "") + + if key: + stash_write(request, key, 'open') + + response.headers.set("Content-type", "text/plain") + response.write_status_headers() + + # Writing an initial 2k so browsers realise it's there. *shrug* + response.writer.write("." * 2048) + + while response.writer.flush(): + response.writer.write(".") + time.sleep(0.01) + + if key: + stash_write(request, key, 'closed') diff --git a/fetch/api/resources/stash-take.py b/fetch/api/resources/stash-take.py new file mode 100644 index 00000000000000..2b1871a6971545 --- /dev/null +++ b/fetch/api/resources/stash-take.py @@ -0,0 +1,8 @@ +from wptserve.handlers import json_handler + + +@json_handler +def main(request, response): + dir = '/'.join(request.url_parts.path.split('/')[:-1]) + '/' + key = request.GET.first("key") + return request.server.stash.take(key, dir) diff --git a/tools/wptserve/wptserve/response.py b/tools/wptserve/wptserve/response.py index 6c073feea9ee56..50ff00dde9440e 100644 --- a/tools/wptserve/wptserve/response.py +++ b/tools/wptserve/wptserve/response.py @@ -465,9 +465,11 @@ def encode(self, data): raise ValueError def flush(self): - """Flush the output.""" + """Flush the output. Returns False if the flush failed due to + the socket being closed by the remote end.""" try: self._wfile.flush() + return True except socket.error: # This can happen if the socket got closed by the remote end - pass + return False From dafbd681f3beb3c461b6204eb05b49ab491e6e50 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 7 Jul 2017 12:27:27 +0100 Subject: [PATCH 02/13] More tests --- fetch/api/abort/cache.https.html | 60 +++++ .../abort/general-serviceworker.https.html | 23 ++ fetch/api/abort/general.html | 8 + fetch/api/abort/general.js | 249 +++++++++++------- fetch/api/resources/infinite-slow-response.py | 26 +- fetch/api/resources/stash-put.py | 6 + 6 files changed, 262 insertions(+), 110 deletions(-) create mode 100644 fetch/api/abort/cache.https.html create mode 100644 fetch/api/abort/general-serviceworker.https.html create mode 100644 fetch/api/resources/stash-put.py diff --git a/fetch/api/abort/cache.https.html b/fetch/api/abort/cache.https.html new file mode 100644 index 00000000000000..b2ed933eabea51 --- /dev/null +++ b/fetch/api/abort/cache.https.html @@ -0,0 +1,60 @@ + + + + + Request signals & the cache API + + + + + + + \ No newline at end of file diff --git a/fetch/api/abort/general-serviceworker.https.html b/fetch/api/abort/general-serviceworker.https.html new file mode 100644 index 00000000000000..03eb7b7e77a43f --- /dev/null +++ b/fetch/api/abort/general-serviceworker.https.html @@ -0,0 +1,23 @@ + + + + + General fetch abort tests + + + + + + + \ No newline at end of file diff --git a/fetch/api/abort/general.html b/fetch/api/abort/general.html index 21c7e461f76e64..57f202adc9312a 100644 --- a/fetch/api/abort/general.html +++ b/fetch/api/abort/general.html @@ -8,4 +8,12 @@ + + + \ No newline at end of file diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js index 8cb49df4d6f557..a538f21d85c19c 100644 --- a/fetch/api/abort/general.js +++ b/fetch/api/abort/general.js @@ -1,87 +1,22 @@ -/* -// This is a really stupid set of hacks that I'm using to do some basic -// sanity-checking on the tests. -class AbortSignal { - constructor() { - this.aborted = false; - this._abortCallbacks = []; - } - _abort() { - this.aborted = true; - for (const func of this._abortCallbacks) func(); - this._abortCallbacks = null; - } - _addAbortCallback(callback) { - if (this.aborted) { - callback(); - return; - } - this._abortCallbacks.push(callback); - } -} - -class AbortController { - constructor() { - this.signal = new AbortSignal(); - } - abort() { - this.signal._abort(); - } +if (self.importScripts) { + // Load scripts if being run from a worker + importScripts( + '/resources/testharness.js', + '/common/utils.js' + ); } -function fetch(request, { - signal = request.signal -}={}) { - const url = request.url || request; - - if (signal && signal.aborted) return Promise.reject(new DOMException('Aborted', 'AbortError')); - - return new Promise((resolve, reject) => { - // Do the usual XHR stuff - const req = new XMLHttpRequest(); - req.open('GET', url); - - const read = new Promise((readResolve, readReject) => { - req.onload = () => { - readResolve(); - }; - - req.onreadystatechange = () => { - if (req.readyState == XMLHttpRequest.HEADERS_RECEIVED) { - resolve({ - async json() { - await read; - return JSON.parse(req.responseText); - }, - async text() { - await read; - return req.responseText; - } - }) - } - }; +// This is used to close connections that weren't correctly closed during the tests, +// otherwise you can end up running out of HTTP connections. +let requestKeys = []; - // Handle network errors - req.onerror = () => { - reject(Error("Network Error")); - readReject(Error("Network Error")); - }; - - req.onabort = () => { - reject(new DOMException('Aborted', 'AbortError')); - readReject(new DOMException('Aborted', 'AbortError')); - }; - - // Make the request - req.send(); - - if (signal) { - signal._addAbortCallback(() => req.abort()); - } - }); - }); +function abortRequests() { + const keys = requestKeys; + requestKeys = []; + return Promise.all( + keys.map(key => fetch(`../resources/stash-put.py?key=${key}&value=close`)) + ); } -//*/ function assert_abort_error(err) { assert_equals(err.constructor, DOMException); @@ -102,6 +37,97 @@ promise_test(async () => { }); }, "Aborting rejects with AbortError"); +test(() => { + // TODO: we may want to discuss this design idea + const request = new Request(''); + assert_true(Boolean(request.signal), "Signal member is present & truthy"); + assert_equals(request.signal.constructor, AbortSignal); +}, "Request objects have a signal property"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json', { signal }); + + // TODO: we may want to discuss this design idea + assert_true(Boolean(request.signal), "Signal member is present & truthy"); + assert_equals(request.signal.constructor, AbortSignal); + assert_not_equals(request.signal, signal, 'Request has a new signal, not a reference'); + + await fetch(request).then( + () => assert_unreached("Fetch must not resolve"), + err => assert_abort_error(err) + ); +}, "Signal on request object"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json', { signal }); + const requestFromRequest = new Request(request); + + await fetch(requestFromRequest).then( + () => assert_unreached("Fetch must not resolve"), + err => assert_abort_error(err) + ); +}, "Signal on request object created from request object"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json'); + const requestFromRequest = new Request(request, { signal }); + + await fetch(requestFromRequest).then( + () => assert_unreached("Fetch must not resolve"), + err => assert_abort_error(err) + ); +}, "Signal on request object created from request object, with signal on second request"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json', { signal: new AbortController().signal }); + const requestFromRequest = new Request(request, { signal }); + + await fetch(requestFromRequest).then( + () => assert_unreached("Fetch must not resolve"), + err => assert_abort_error(err) + ); +}, "Signal on request object created from request object, with signal on second request overriding another"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json', { signal }); + + await fetch(request, {method: 'POST'}).then( + () => assert_unreached("Fetch must not resolve"), + err => assert_abort_error(err) + ); +}, "Signal retained after unrelated properties are overridden by fetch"); + +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const request = new Request('../resources/data.json', { signal }); + + const data = await fetch(request, { signal: null }).then(r => r.json()); + assert_equals(data.key, 'value', 'Fetch fully completes'); +}, "Signal removed by setting to null"); + promise_test(async () => { const controller = new AbortController(); const signal = controller.signal; @@ -134,9 +160,8 @@ promise_test(async () => { await fetch(request).catch(() => {}); - assert_false(request.bodyUsed, "Body has not been used"); - assert_equals(await request.text(), 'foo', "Correct body"); -}, "Request is not 'used' if signal is aborted before fetching"); + assert_true(request.bodyUsed, "Body has been used"); +}, "Request is still 'used' if signal is aborted before fetching"); const bodyMethods = ['arrayBuffer', 'blob', 'formData', 'json', 'text']; @@ -166,24 +191,30 @@ for (const bodyMethod of bodyMethods) { } promise_test(async () => { + await abortRequests(); + const controller = new AbortController(); const signal = controller.signal; - const key = token(); + const stateKey = token(); + const abortKey = token(); + requestKeys.push(abortKey); controller.abort(); - await fetch(`../resources/infinite-slow-response.py?key=${key}`, { signal }).catch(() => {}); + await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }).catch(() => {}); // I'm hoping this will give the browser enough time to (incorrectly) make the request // above, if it intends to. await fetch('../resources/data.json').then(r => r.json()); - const response = await fetch(`../resources/stash-take.py?key=${key}`); + const response = await fetch(`../resources/stash-take.py?key=${stateKey}`); const data = await response.json(); assert_equals(data, null, "Request hasn't been made to the server"); }, "Already aborted signal does not make request"); promise_test(async () => { + await abortRequests(); + const controller = new AbortController(); const signal = controller.signal; controller.abort(); @@ -191,8 +222,11 @@ promise_test(async () => { const fetches = []; for (let i = 0; i < 3; i++) { + const abortKey = token(); + requestKeys.push(abortKey); + fetches.push( - fetch(`../resources/infinite-slow-response.py?${i}`, { signal }) + fetch(`../resources/infinite-slow-response.py?${i}&abortKey=${abortKey}`, { signal }) ); } @@ -206,18 +240,23 @@ promise_test(async () => { }, "Already aborted signal can be used for many fetches"); promise_test(async () => { + await abortRequests(); + const controller = new AbortController(); const signal = controller.signal; - + await fetch('../resources/data.json', { signal }).then(r => r.json()); - + controller.abort(); - + const fetches = []; - + for (let i = 0; i < 3; i++) { + const abortKey = token(); + requestKeys.push(abortKey); + fetches.push( - fetch(`../resources/infinite-slow-response.py?${i}`, { signal }) + fetch(`../resources/infinite-slow-response.py?${i}&abortKey=${abortKey}`, { signal }) ); } @@ -231,13 +270,17 @@ promise_test(async () => { }, "Signal can be used to abort other fetches, even if another fetch succeeded before aborting"); promise_test(async () => { + await abortRequests(); + const controller = new AbortController(); const signal = controller.signal; - const key = token(); + const stateKey = token(); + const abortKey = token(); + requestKeys.push(abortKey); - await fetch(`../resources/infinite-slow-response.py?key=${key}`, { signal }); + await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }); - const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); assert_equals(beforeAbortResult, "open", "Connection is open"); controller.abort(); @@ -249,20 +292,24 @@ promise_test(async () => { // Stop spinning if 10 seconds have passed if (Date.now() - start > 10000) throw Error('Timed out'); - const afterAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); if (afterAbortResult == 'closed') break; } }, "Underlying connection is closed when aborting after receiving response"); for (const bodyMethod of bodyMethods) { promise_test(async () => { + await abortRequests(); + const controller = new AbortController(); const signal = controller.signal; - const key = token(); + const stateKey = token(); + const abortKey = token(); + requestKeys.push(abortKey); - const response = await fetch(`../resources/infinite-slow-response.py?key=${key}`, { signal }); + const response = await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }); - const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); assert_equals(beforeAbortResult, "open", "Connection is open"); const bodyPromise = response[bodyMethod](); @@ -281,8 +328,8 @@ for (const bodyMethod of bodyMethods) { // Stop spinning if 10 seconds have passed if (Date.now() - start > 10000) throw Error('Timed out'); - const afterAbortResult = await fetch(`../resources/stash-take.py?key=${key}`).then(r => r.json()); + const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); if (afterAbortResult == 'closed') break; } }, `Fetch aborted & connection closed when aborted after calling response.${bodyMethod}()`); -} \ No newline at end of file +} diff --git a/fetch/api/resources/infinite-slow-response.py b/fetch/api/resources/infinite-slow-response.py index 4f04f69ab60dba..44f9f322b21b9d 100644 --- a/fetch/api/resources/infinite-slow-response.py +++ b/fetch/api/resources/infinite-slow-response.py @@ -1,18 +1,22 @@ import time +def url_dir(request): + return '/'.join(request.url_parts.path.split('/')[:-1]) + '/' + + def stash_write(request, key, value): """Write to the stash, overwriting any previous value""" - dir = '/'.join(request.url_parts.path.split('/')[:-1]) + '/' - request.server.stash.take(key, dir) - request.server.stash.put(key, value, dir) + request.server.stash.take(key, url_dir(request)) + request.server.stash.put(key, value, url_dir(request)) def main(request, response): - key = request.GET.first("key", "") + stateKey = request.GET.first("stateKey", "") + abortKey = request.GET.first("abortKey", "") - if key: - stash_write(request, key, 'open') + if stateKey: + stash_write(request, stateKey, 'open') response.headers.set("Content-type", "text/plain") response.write_status_headers() @@ -20,9 +24,13 @@ def main(request, response): # Writing an initial 2k so browsers realise it's there. *shrug* response.writer.write("." * 2048) - while response.writer.flush(): + while True: + if not response.writer.flush(): + break + if abortKey and request.server.stash.take(abortKey, url_dir(request)): + break response.writer.write(".") time.sleep(0.01) - if key: - stash_write(request, key, 'closed') + if stateKey: + stash_write(request, stateKey, 'closed') diff --git a/fetch/api/resources/stash-put.py b/fetch/api/resources/stash-put.py new file mode 100644 index 00000000000000..b6600dec968fa6 --- /dev/null +++ b/fetch/api/resources/stash-put.py @@ -0,0 +1,6 @@ +def main(request, response): + url_dir = '/'.join(request.url_parts.path.split('/')[:-1]) + '/' + key = request.GET.first("key") + value = request.GET.first("value") + request.server.stash.put(key, value, url_dir) + return "done" From b076f7780ca46ab9b6e8b3c37eb94d95a0060523 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 7 Jul 2017 14:48:39 +0100 Subject: [PATCH 03/13] Stream & no-cors tests --- fetch/api/abort/general.js | 171 ++++++++++++++++++++++++++---- fetch/api/resources/empty.txt | 0 fetch/api/resources/stash-put.py | 1 + fetch/api/resources/stash-take.py | 1 + 4 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 fetch/api/resources/empty.txt diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js index a538f21d85c19c..81304d013963bb 100644 --- a/fetch/api/abort/general.js +++ b/fetch/api/abort/general.js @@ -8,11 +8,11 @@ if (self.importScripts) { // This is used to close connections that weren't correctly closed during the tests, // otherwise you can end up running out of HTTP connections. -let requestKeys = []; +let requestAbortKeys = []; function abortRequests() { - const keys = requestKeys; - requestKeys = []; + const keys = requestAbortKeys; + requestAbortKeys = []; return Promise.all( keys.map(key => fetch(`../resources/stash-put.py?key=${key}&value=close`)) ); @@ -37,6 +37,26 @@ promise_test(async () => { }); }, "Aborting rejects with AbortError"); +promise_test(async () => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + const url = new URL('../resources/data.json', location); + url.hostname = 'www1.' + url.hostname; + + await fetch(url, { + signal, + mode: 'no-cors' + }).then(() => { + assert_unreached("Fetch must not resolve"); + }, err => { + // Using .catch rather than try/catch to ensure the promise + // is rejecting (rather than throwing). + assert_abort_error(err); + }); +}, "Aborting rejects with AbortError - no-cors"); + test(() => { // TODO: we may want to discuss this design idea const request = new Request(''); @@ -197,7 +217,7 @@ promise_test(async () => { const signal = controller.signal; const stateKey = token(); const abortKey = token(); - requestKeys.push(abortKey); + requestAbortKeys.push(abortKey); controller.abort(); await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }).catch(() => {}); @@ -223,7 +243,7 @@ promise_test(async () => { for (let i = 0; i < 3; i++) { const abortKey = token(); - requestKeys.push(abortKey); + requestAbortKeys.push(abortKey); fetches.push( fetch(`../resources/infinite-slow-response.py?${i}&abortKey=${abortKey}`, { signal }) @@ -231,11 +251,10 @@ promise_test(async () => { } for (const fetchPromise of fetches) { - await fetchPromise.then(() => { - assert_unreached("Fetch must not resolve"); - }, err => { - assert_abort_error(err); - }); + await fetchPromise.then( + () => assert_unreached("Fetch must not resolve"), + err => assert_abort_error(err) + ); } }, "Already aborted signal can be used for many fetches"); @@ -253,7 +272,7 @@ promise_test(async () => { for (let i = 0; i < 3; i++) { const abortKey = token(); - requestKeys.push(abortKey); + requestAbortKeys.push(abortKey); fetches.push( fetch(`../resources/infinite-slow-response.py?${i}&abortKey=${abortKey}`, { signal }) @@ -276,7 +295,7 @@ promise_test(async () => { const signal = controller.signal; const stateKey = token(); const abortKey = token(); - requestKeys.push(abortKey); + requestAbortKeys.push(abortKey); await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }); @@ -297,6 +316,43 @@ promise_test(async () => { } }, "Underlying connection is closed when aborting after receiving response"); +promise_test(async () => { + await abortRequests(); + + const controller = new AbortController(); + const signal = controller.signal; + const stateKey = token(); + const abortKey = token(); + requestAbortKeys.push(abortKey); + + const url = new URL(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, location); + url.hostname = 'www1.' + url.hostname; + + await fetch(url, { + signal, + mode: 'no-cors' + }); + + const stashTakeURL = new URL(`../resources/stash-take.py?key=${stateKey}`); + stashTakeURL.hostname = 'www1.' + stashTakeURL.hostname; + + const beforeAbortResult = await fetch(stashTakeURL).then(r => r.json()); + assert_equals(beforeAbortResult, "open", "Connection is open"); + + controller.abort(); + + // The connection won't close immediately, but it should close at some point: + const start = Date.now(); + + while (true) { + // Stop spinning if 10 seconds have passed + if (Date.now() - start > 10000) throw Error('Timed out'); + + const afterAbortResult = await fetch(stashTakeURL).then(r => r.json()); + if (afterAbortResult == 'closed') break; + } +}, "Underlying connection is closed when aborting after receiving response - no-cors"); + for (const bodyMethod of bodyMethods) { promise_test(async () => { await abortRequests(); @@ -305,7 +361,7 @@ for (const bodyMethod of bodyMethods) { const signal = controller.signal; const stateKey = token(); const abortKey = token(); - requestKeys.push(abortKey); + requestAbortKeys.push(abortKey); const response = await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }); @@ -316,11 +372,10 @@ for (const bodyMethod of bodyMethods) { controller.abort(); - await bodyPromise.then(() => { - assert_unreached("Body read must not resolve"); - }, err => { - assert_abort_error(err); - }); + await bodyPromise.then( + () => assert_unreached("Body read must not resolve"), + err => assert_abort_error(err) + ); const start = Date.now(); @@ -333,3 +388,83 @@ for (const bodyMethod of bodyMethods) { } }, `Fetch aborted & connection closed when aborted after calling response.${bodyMethod}()`); } + +promise_test(async () => { + await abortRequests(); + + const controller = new AbortController(); + const signal = controller.signal; + const stateKey = token(); + const abortKey = token(); + requestAbortKeys.push(abortKey); + + const response = await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }); + const reader = response.body.getReader(); + + controller.abort(); + + await reader.read().then( + () => assert_unreached("Stream read must not resolve"), + err => assert_abort_error(err) + ); + + // The connection won't close immediately, but it should close at some point: + const start = Date.now(); + + while (true) { + // Stop spinning if 10 seconds have passed + if (Date.now() - start > 10000) throw Error('Timed out'); + + const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); + if (afterAbortResult == 'closed') break; + } +}, "Stream errors once aborted. Underlying connection closed."); + +promise_test(async () => { + await abortRequests(); + + const controller = new AbortController(); + const signal = controller.signal; + const stateKey = token(); + const abortKey = token(); + requestAbortKeys.push(abortKey); + + const response = await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal }); + const reader = response.body.getReader(); + + await reader.read(); + + controller.abort(); + + await reader.read().then( + () => assert_unreached("Stream read must not resolve"), + err => assert_abort_error(err) + ); + + // The connection won't close immediately, but it should close at some point: + const start = Date.now(); + + while (true) { + // Stop spinning if 10 seconds have passed + if (Date.now() - start > 10000) throw Error('Timed out'); + + const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); + if (afterAbortResult == 'closed') break; + } +}, "Stream errors once aborted, after reading. Underlying connection closed."); + +promise_test(async () => { + await abortRequests(); + + const controller = new AbortController(); + const signal = controller.signal; + + const response = await fetch(`../resources/empty.txt`, { signal }); + const reader = response.body.getReader(); + + controller.abort(); + + await reader.read(); + + assert_true(reader.done, "Stream is done"); +}, "Stream will not error if body is empty. It's closed with an empty queue before it errors."); diff --git a/fetch/api/resources/empty.txt b/fetch/api/resources/empty.txt new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/fetch/api/resources/stash-put.py b/fetch/api/resources/stash-put.py index b6600dec968fa6..dd84ff1fc852cf 100644 --- a/fetch/api/resources/stash-put.py +++ b/fetch/api/resources/stash-put.py @@ -3,4 +3,5 @@ def main(request, response): key = request.GET.first("key") value = request.GET.first("value") request.server.stash.put(key, value, url_dir) + response.headers.set('Access-Control-Allow-Origin', '*') return "done" diff --git a/fetch/api/resources/stash-take.py b/fetch/api/resources/stash-take.py index 2b1871a6971545..fcaf96c96a6411 100644 --- a/fetch/api/resources/stash-take.py +++ b/fetch/api/resources/stash-take.py @@ -5,4 +5,5 @@ def main(request, response): dir = '/'.join(request.url_parts.path.split('/')[:-1]) + '/' key = request.GET.first("key") + response.headers.set('Access-Control-Allow-Origin', '*') return request.server.stash.take(key, dir) From fdf63fc625bb006d7ec3896fcaf562815e70a10a Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 7 Jul 2017 15:03:24 +0100 Subject: [PATCH 04/13] Don't need a promise here --- fetch/api/abort/cache.https.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/fetch/api/abort/cache.https.html b/fetch/api/abort/cache.https.html index b2ed933eabea51..8c6f3382c19ba0 100644 --- a/fetch/api/abort/cache.https.html +++ b/fetch/api/abort/cache.https.html @@ -23,10 +23,7 @@ const [cachedRequest] = requests; - await new Promise(resolve => { - signal.addEventListener('abort', resolve); - controller.abort(); - }); + controller.abort(); assert_false(cachedRequest.signal.aborted, "Request from cache shouldn't be aborted"); From 568ae5d0635062595624a452c99e951e2f768282 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 7 Jul 2017 16:10:55 +0100 Subject: [PATCH 05/13] Intercepted by service worker tests --- .../abort/general-serviceworker.https.html | 2 +- .../serviceworker-intercepted.https.html | 111 ++++++++++++++++++ fetch/api/resources/basic.html | 1 + fetch/api/resources/sw-intercept.js | 10 ++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 fetch/api/abort/serviceworker-intercepted.https.html create mode 100644 fetch/api/resources/basic.html create mode 100644 fetch/api/resources/sw-intercept.js diff --git a/fetch/api/abort/general-serviceworker.https.html b/fetch/api/abort/general-serviceworker.https.html index 03eb7b7e77a43f..6d3d969a70c04c 100644 --- a/fetch/api/abort/general-serviceworker.https.html +++ b/fetch/api/abort/general-serviceworker.https.html @@ -2,7 +2,7 @@ - General fetch abort tests + General fetch abort tests in a service worker diff --git a/fetch/api/abort/serviceworker-intercepted.https.html b/fetch/api/abort/serviceworker-intercepted.https.html new file mode 100644 index 00000000000000..4072941b89e946 --- /dev/null +++ b/fetch/api/abort/serviceworker-intercepted.https.html @@ -0,0 +1,111 @@ + + + + + Aborting fetch when intercepted by a service worker + + + + + + + + \ No newline at end of file diff --git a/fetch/api/resources/basic.html b/fetch/api/resources/basic.html new file mode 100644 index 00000000000000..763b0739be64c3 --- /dev/null +++ b/fetch/api/resources/basic.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fetch/api/resources/sw-intercept.js b/fetch/api/resources/sw-intercept.js new file mode 100644 index 00000000000000..1714f2dd1be993 --- /dev/null +++ b/fetch/api/resources/sw-intercept.js @@ -0,0 +1,10 @@ +async function broadcast(msg) { + for (const client of await clients.matchAll()) { + client.postMessage(msg); + } +} + +addEventListener('fetch', event => { + even.waitUntil(broadcast(event.request.url)); + event.respondWith(fetch(event.request)); +}); \ No newline at end of file From c1000fcdd735190c1c8c00072b3e844814d611ce Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Mon, 10 Jul 2017 13:15:31 +0100 Subject: [PATCH 06/13] Whitespace fixing --- .../abort/general-serviceworker.https.html | 2 +- fetch/api/abort/general.js | 30 +++++++++---------- .../serviceworker-intercepted.https.html | 10 +++---- fetch/api/resources/infinite-slow-response.py | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/fetch/api/abort/general-serviceworker.https.html b/fetch/api/abort/general-serviceworker.https.html index 6d3d969a70c04c..9aaed320527ff4 100644 --- a/fetch/api/abort/general-serviceworker.https.html +++ b/fetch/api/abort/general-serviceworker.https.html @@ -10,7 +10,7 @@ diff --git a/fetch/api/resources/sw-intercept.js b/fetch/api/resources/sw-intercept.js index 1714f2dd1be993..b8639dcfa89d76 100644 --- a/fetch/api/resources/sw-intercept.js +++ b/fetch/api/resources/sw-intercept.js @@ -5,6 +5,6 @@ async function broadcast(msg) { } addEventListener('fetch', event => { - even.waitUntil(broadcast(event.request.url)); + event.waitUntil(broadcast(event.request.url)); event.respondWith(fetch(event.request)); }); \ No newline at end of file From c2741a5c9f3b65c280046a154ce70e72669a192b Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 28 Jul 2017 13:34:45 +0100 Subject: [PATCH 09/13] Streaming body test --- fetch/api/abort/general.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js index c1ba352dba6431..c8e497d6167c79 100644 --- a/fetch/api/abort/general.js +++ b/fetch/api/abort/general.js @@ -432,3 +432,34 @@ promise_test(async t => { assert_true(item.done, "Stream is done"); }, "Stream will not error if body is empty. It's closed with an empty queue before it errors."); + + +test(t => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + + let cancelReason; + + const body = new ReadableStream({ + pull(controller) { + controller.enqueue(new Uint8Array([42])); + }, + cancel(reason) { + cancelReason = reason; + } + }); + + fetch('../resources/empty.txt', { + body, signal, + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + 'Content-Length': '2097152' + } + }); + + assert_true(!!cancelReason, 'Cancel called sync'); + assert_equals(err.constructor, DOMException); + assert_equals(err.name, 'AbortError'); +}, "Readable stream synchronously cancels with AbortError if aborted before reading"); From e9f100321272f03ba5446eff1f8cd74e384d6893 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 28 Jul 2017 14:28:28 +0100 Subject: [PATCH 10/13] Remove forbidden header --- fetch/api/abort/general.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js index c8e497d6167c79..fe6761e3446bfb 100644 --- a/fetch/api/abort/general.js +++ b/fetch/api/abort/general.js @@ -454,8 +454,7 @@ test(t => { body, signal, method: 'POST', headers: { - 'Content-Type': 'text/plain', - 'Content-Length': '2097152' + 'Content-Type': 'text/plain' } }); From 3e36e5e8db034839300b4d4a1eea7cb535003827 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Fri, 28 Jul 2017 16:22:03 +0100 Subject: [PATCH 11/13] Making test names unique --- fetch/api/abort/general.js | 49 +++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js index fe6761e3446bfb..662a8b1b04907b 100644 --- a/fetch/api/abort/general.js +++ b/fetch/api/abort/general.js @@ -20,6 +20,11 @@ function abortRequests() { ); } +// Add the global name to the test name +function contextualTestName(name) { + return `${self.constructor.name}: ${name}`; +} + promise_test(async t => { const controller = new AbortController(); const signal = controller.signal; @@ -28,7 +33,7 @@ promise_test(async t => { const fetchPromise = fetch('../resources/data.json', { signal }); await promise_rejects(t, "AbortError", fetchPromise); -}, "Aborting rejects with AbortError"); +}, contextualTestName("Aborting rejects with AbortError")); promise_test(async t => { const controller = new AbortController(); @@ -44,13 +49,13 @@ promise_test(async t => { }); await promise_rejects(t, "AbortError", fetchPromise); -}, "Aborting rejects with AbortError - no-cors"); +}, contextualTestName("Aborting rejects with AbortError - no-cors")); test(() => { const request = new Request(''); assert_true(Boolean(request.signal), "Signal member is present & truthy"); assert_equals(request.signal.constructor, AbortSignal); -}, "Request objects have a signal property"); +}, contextualTestName("Request objects have a signal property")); promise_test(async t => { const controller = new AbortController(); @@ -66,7 +71,7 @@ promise_test(async t => { const fetchPromise = fetch(request); await promise_rejects(t, "AbortError", fetchPromise); -}, "Signal on request object"); +}, contextualTestName("Signal on request object")); promise_test(async t => { const controller = new AbortController(); @@ -79,7 +84,7 @@ promise_test(async t => { const fetchPromise = fetch(requestFromRequest); await promise_rejects(t, "AbortError", fetchPromise); -}, "Signal on request object created from request object"); +}, contextualTestName("Signal on request object created from request object")); promise_test(async t => { const controller = new AbortController(); @@ -92,7 +97,7 @@ promise_test(async t => { const fetchPromise = fetch(requestFromRequest); await promise_rejects(t, "AbortError", fetchPromise); -}, "Signal on request object created from request object, with signal on second request"); +}, contextualTestName("Signal on request object created from request object, with signal on second request")); promise_test(async t => { const controller = new AbortController(); @@ -105,7 +110,7 @@ promise_test(async t => { const fetchPromise = fetch(requestFromRequest); await promise_rejects(t, "AbortError", fetchPromise); -}, "Signal on request object created from request object, with signal on second request overriding another"); +}, contextualTestName("Signal on request object created from request object, with signal on second request overriding another")); promise_test(async t => { const controller = new AbortController(); @@ -117,7 +122,7 @@ promise_test(async t => { const fetchPromise = fetch(request, {method: 'POST'}); await promise_rejects(t, "AbortError", fetchPromise); -}, "Signal retained after unrelated properties are overridden by fetch"); +}, contextualTestName("Signal retained after unrelated properties are overridden by fetch")); promise_test(async t => { const controller = new AbortController(); @@ -128,7 +133,7 @@ promise_test(async t => { const data = await fetch(request, { signal: null }).then(r => r.json()); assert_equals(data.key, 'value', 'Fetch fully completes'); -}, "Signal removed by setting to null"); +}, contextualTestName("Signal removed by setting to null")); promise_test(async t => { const controller = new AbortController(); @@ -146,7 +151,7 @@ promise_test(async t => { ]); assert_array_equals(log, ['fetch-reject', 'next-microtask']); -}, "Already aborted signal rejects immediately"); +}, contextualTestName("Already aborted signal rejects immediately")); promise_test(async t => { const controller = new AbortController(); @@ -163,7 +168,7 @@ promise_test(async t => { await fetch(request).catch(() => {}); assert_true(request.bodyUsed, "Body has been used"); -}, "Request is still 'used' if signal is aborted before fetching"); +}, contextualTestName("Request is still 'used' if signal is aborted before fetching")); for (const bodyMethod of BODY_METHODS) { promise_test(async t => { @@ -185,7 +190,7 @@ for (const bodyMethod of BODY_METHODS) { await promise_rejects(t, "AbortError", bodyPromise); assert_array_equals(log, [`${bodyMethod}-reject`, 'next-microtask']); - }, `response.${bodyMethod}() rejects if already aborted`); + }, contextualTestName(`response.${bodyMethod}() rejects if already aborted`)); } promise_test(async t => { @@ -208,7 +213,7 @@ promise_test(async t => { const data = await response.json(); assert_equals(data, null, "Request hasn't been made to the server"); -}, "Already aborted signal does not make request"); +}, contextualTestName("Already aborted signal does not make request")); promise_test(async t => { await abortRequests(); @@ -231,7 +236,7 @@ promise_test(async t => { for (const fetchPromise of fetches) { await promise_rejects(t, "AbortError", fetchPromise); } -}, "Already aborted signal can be used for many fetches"); +}, contextualTestName("Already aborted signal can be used for many fetches")); promise_test(async t => { await abortRequests(); @@ -257,7 +262,7 @@ promise_test(async t => { for (const fetchPromise of fetches) { await promise_rejects(t, "AbortError", fetchPromise); } -}, "Signal can be used to abort other fetches, even if another fetch succeeded before aborting"); +}, contextualTestName("Signal can be used to abort other fetches, even if another fetch succeeded before aborting")); promise_test(async t => { await abortRequests(); @@ -285,7 +290,7 @@ promise_test(async t => { const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); if (afterAbortResult == 'closed') break; } -}, "Underlying connection is closed when aborting after receiving response"); +}, contextualTestName("Underlying connection is closed when aborting after receiving response")); promise_test(async t => { await abortRequests(); @@ -322,7 +327,7 @@ promise_test(async t => { const afterAbortResult = await fetch(stashTakeURL).then(r => r.json()); if (afterAbortResult == 'closed') break; } -}, "Underlying connection is closed when aborting after receiving response - no-cors"); +}, contextualTestName("Underlying connection is closed when aborting after receiving response - no-cors")); for (const bodyMethod of BODY_METHODS) { promise_test(async t => { @@ -354,7 +359,7 @@ for (const bodyMethod of BODY_METHODS) { const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); if (afterAbortResult == 'closed') break; } - }, `Fetch aborted & connection closed when aborted after calling response.${bodyMethod}()`); + }, contextualTestName(`Fetch aborted & connection closed when aborted after calling response.${bodyMethod}()`)); } promise_test(async t => { @@ -384,7 +389,7 @@ promise_test(async t => { const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); if (afterAbortResult == 'closed') break; } -}, "Stream errors once aborted. Underlying connection closed."); +}, contextualTestName("Stream errors once aborted. Underlying connection closed.")); promise_test(async t => { await abortRequests(); @@ -415,7 +420,7 @@ promise_test(async t => { const afterAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json()); if (afterAbortResult == 'closed') break; } -}, "Stream errors once aborted, after reading. Underlying connection closed."); +}, contextualTestName("Stream errors once aborted, after reading. Underlying connection closed.")); promise_test(async t => { await abortRequests(); @@ -431,7 +436,7 @@ promise_test(async t => { const item = await reader.read(); assert_true(item.done, "Stream is done"); -}, "Stream will not error if body is empty. It's closed with an empty queue before it errors."); +}, contextualTestName("Stream will not error if body is empty. It's closed with an empty queue before it errors.")); test(t => { @@ -461,4 +466,4 @@ test(t => { assert_true(!!cancelReason, 'Cancel called sync'); assert_equals(err.constructor, DOMException); assert_equals(err.name, 'AbortError'); -}, "Readable stream synchronously cancels with AbortError if aborted before reading"); +}, contextualTestName("Readable stream synchronously cancels with AbortError if aborted before reading")); From 2951a230bc82151ebb88aae222c67de2d20f5dcf Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Mon, 31 Jul 2017 10:15:46 +0100 Subject: [PATCH 12/13] Addressing nits --- fetch/api/abort/cache.https.html | 2 +- fetch/api/abort/general-serviceworker.https.html | 2 +- fetch/api/abort/general.html | 2 +- fetch/api/abort/general.js | 4 ++-- fetch/api/abort/serviceworker-intercepted.https.html | 2 +- fetch/api/resources/basic.html | 6 +++++- fetch/api/resources/sw-intercept.js | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/fetch/api/abort/cache.https.html b/fetch/api/abort/cache.https.html index 8c6f3382c19ba0..25ed0e1e9183b9 100644 --- a/fetch/api/abort/cache.https.html +++ b/fetch/api/abort/cache.https.html @@ -54,4 +54,4 @@ }, "Signals are not stored in the cache API, even if they're already aborted"); - \ No newline at end of file + diff --git a/fetch/api/abort/general-serviceworker.https.html b/fetch/api/abort/general-serviceworker.https.html index 9aaed320527ff4..c1334e035f51a4 100644 --- a/fetch/api/abort/general-serviceworker.https.html +++ b/fetch/api/abort/general-serviceworker.https.html @@ -20,4 +20,4 @@ })(); - \ No newline at end of file + diff --git a/fetch/api/abort/general.html b/fetch/api/abort/general.html index 57f202adc9312a..0d5baf2a8e82da 100644 --- a/fetch/api/abort/general.html +++ b/fetch/api/abort/general.html @@ -16,4 +16,4 @@ } - \ No newline at end of file + diff --git a/fetch/api/abort/general.js b/fetch/api/abort/general.js index 662a8b1b04907b..8416e3a43dac7e 100644 --- a/fetch/api/abort/general.js +++ b/fetch/api/abort/general.js @@ -464,6 +464,6 @@ test(t => { }); assert_true(!!cancelReason, 'Cancel called sync'); - assert_equals(err.constructor, DOMException); - assert_equals(err.name, 'AbortError'); + assert_equals(cancelReason.constructor, DOMException); + assert_equals(cancelReason.name, 'AbortError'); }, contextualTestName("Readable stream synchronously cancels with AbortError if aborted before reading")); diff --git a/fetch/api/abort/serviceworker-intercepted.https.html b/fetch/api/abort/serviceworker-intercepted.https.html index 9281fd54a8b980..6df6aef2cdaa1a 100644 --- a/fetch/api/abort/serviceworker-intercepted.https.html +++ b/fetch/api/abort/serviceworker-intercepted.https.html @@ -99,4 +99,4 @@ }, "Stream errors once aborted."); - \ No newline at end of file + diff --git a/fetch/api/resources/basic.html b/fetch/api/resources/basic.html index 763b0739be64c3..c47f36fed96e49 100644 --- a/fetch/api/resources/basic.html +++ b/fetch/api/resources/basic.html @@ -1 +1,5 @@ - \ No newline at end of file + + diff --git a/fetch/api/resources/sw-intercept.js b/fetch/api/resources/sw-intercept.js index b8639dcfa89d76..b8166b62a5c939 100644 --- a/fetch/api/resources/sw-intercept.js +++ b/fetch/api/resources/sw-intercept.js @@ -7,4 +7,4 @@ async function broadcast(msg) { addEventListener('fetch', event => { event.waitUntil(broadcast(event.request.url)); event.respondWith(fetch(event.request)); -}); \ No newline at end of file +}); From b7e0b6400dc59a37c2b19baaab86c71ed0e144c1 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Mon, 31 Jul 2017 10:21:54 +0100 Subject: [PATCH 13/13] Better comment --- fetch/api/resources/basic.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fetch/api/resources/basic.html b/fetch/api/resources/basic.html index c47f36fed96e49..e23afd4bf6a7ec 100644 --- a/fetch/api/resources/basic.html +++ b/fetch/api/resources/basic.html @@ -1,5 +1,5 @@