Update web-platform-tests to revision 8a2ceb5f18911302b7a5c1cd2791f4ab50ad4326

This commit is contained in:
Josh Matthews 2017-10-12 09:25:50 -04:00
parent 462c272380
commit 1f531f66ea
5377 changed files with 174916 additions and 84369 deletions

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Request signals &amp; the cache API</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(async () => {
await caches.delete('test');
const controller = new AbortController();
const signal = controller.signal;
const request = new Request('../resources/data.json', { signal });
const cache = await caches.open('test');
await cache.put(request, new Response(''));
const requests = await cache.keys();
assert_equals(requests.length, 1, 'Ensuring cleanup worked');
const [cachedRequest] = requests;
controller.abort();
assert_false(cachedRequest.signal.aborted, "Request from cache shouldn't be aborted");
const data = await fetch(cachedRequest).then(r => r.json());
assert_equals(data.key, 'value', 'Fetch fully completes');
}, "Signals are not stored in the cache API");
promise_test(async () => {
await caches.delete('test');
const controller = new AbortController();
const signal = controller.signal;
const request = new Request('../resources/data.json', { signal });
controller.abort();
const cache = await caches.open('test');
await cache.put(request, new Response(''));
const requests = await cache.keys();
assert_equals(requests.length, 1, 'Ensuring cleanup worked');
const [cachedRequest] = requests;
assert_false(cachedRequest.signal.aborted, "Request from cache shouldn't be aborted");
const data = await fetch(cachedRequest).then(r => r.json());
assert_equals(data.key, 'value', 'Fetch fully completes');
}, "Signals are not stored in the cache API, even if they're already aborted");
</script>
</body>
</html>

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>General fetch abort tests in a service worker</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
(async function() {
const scope = 'does/not/exist';
let reg = await navigator.serviceWorker.getRegistration(scope);
if (reg) await reg.unregister();
reg = await navigator.serviceWorker.register('general.any.worker.js', {scope});
fetch_tests_from_worker(reg.installing);
})();
</script>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>General fetch abort tests - shared worker</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
fetch_tests_from_worker(new SharedWorker("general.any.worker.js"));
</script>
</body>
</html>

View file

@ -0,0 +1,524 @@
// META: script=/common/utils.js
// META: script=../request/request-error.js
const BODY_METHODS = ['arrayBuffer', 'blob', 'formData', 'json', 'text'];
// 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 requestAbortKeys = [];
function abortRequests() {
const keys = requestAbortKeys;
requestAbortKeys = [];
return Promise.all(
keys.map(key => fetch(`../resources/stash-put.py?key=${key}&value=close`))
);
}
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const fetchPromise = fetch('../resources/data.json', { signal });
await promise_rejects(t, "AbortError", fetchPromise);
}, "Aborting rejects with AbortError");
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const url = new URL('../resources/data.json', location);
url.hostname = 'www1.' + url.hostname;
const fetchPromise = fetch(url, {
signal,
mode: 'no-cors'
});
await promise_rejects(t, "AbortError", fetchPromise);
}, "Aborting rejects with AbortError - no-cors");
// Test that errors thrown from the request constructor take priority over abort errors.
// badRequestArgTests is from response-error.js
for (const { args, testName } of badRequestArgTests) {
promise_test(async t => {
try {
// If this doesn't throw, we'll effectively skip the test.
// It'll fail properly in ../request/request-error.html
new Request(...args);
}
catch (err) {
const controller = new AbortController();
controller.abort();
// Add signal to 2nd arg
args[1] = args[1] || {};
args[1].signal = controller.signal;
await promise_rejects(t, err, fetch(...args));
}
}, `TypeError from request constructor takes priority - ${testName}`);
}
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");
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const request = new Request('../resources/data.json', { signal });
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');
const fetchPromise = fetch(request);
await promise_rejects(t, "AbortError", fetchPromise);
}, "Signal on request object");
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const request = new Request('../resources/data.json', { signal });
const requestFromRequest = new Request(request);
const fetchPromise = fetch(requestFromRequest);
await promise_rejects(t, "AbortError", fetchPromise);
}, "Signal on request object created from request object");
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const request = new Request('../resources/data.json');
const requestFromRequest = new Request(request, { signal });
const fetchPromise = fetch(requestFromRequest);
await promise_rejects(t, "AbortError", fetchPromise);
}, "Signal on request object created from request object, with signal on second request");
promise_test(async t => {
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 });
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");
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const request = new Request('../resources/data.json', { signal });
const fetchPromise = fetch(request, {method: 'POST'});
await promise_rejects(t, "AbortError", fetchPromise);
}, "Signal retained after unrelated properties are overridden by fetch");
promise_test(async t => {
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 t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const log = [];
await Promise.all([
fetch('../resources/data.json', { signal }).then(
() => assert_unreached("Fetch must not 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 t => {
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_true(request.bodyUsed, "Body has been used");
}, "Request is still 'used' if signal is aborted before fetching");
for (const bodyMethod of BODY_METHODS) {
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
const log = [];
const response = await fetch('../resources/data.json', { signal });
controller.abort();
const bodyPromise = response[bodyMethod]();
await Promise.all([
bodyPromise.catch(() => log.push(`${bodyMethod}-reject`)),
Promise.resolve().then(() => log.push('next-microtask'))
]);
await promise_rejects(t, "AbortError", bodyPromise);
assert_array_equals(log, [`${bodyMethod}-reject`, 'next-microtask']);
}, `response.${bodyMethod}() rejects if already aborted`);
}
promise_test(async t => {
await abortRequests();
const controller = new AbortController();
const signal = controller.signal;
const stateKey = token();
const abortKey = token();
requestAbortKeys.push(abortKey);
controller.abort();
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=${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 t => {
await abortRequests();
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const fetches = [];
for (let i = 0; i < 3; i++) {
const abortKey = token();
requestAbortKeys.push(abortKey);
fetches.push(
fetch(`../resources/infinite-slow-response.py?${i}&abortKey=${abortKey}`, { signal })
);
}
for (const fetchPromise of fetches) {
await promise_rejects(t, "AbortError", fetchPromise);
}
}, "Already aborted signal can be used for many fetches");
promise_test(async t => {
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();
requestAbortKeys.push(abortKey);
fetches.push(
fetch(`../resources/infinite-slow-response.py?${i}&abortKey=${abortKey}`, { signal })
);
}
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");
promise_test(async t => {
await abortRequests();
const controller = new AbortController();
const signal = controller.signal;
const stateKey = token();
const abortKey = token();
requestAbortKeys.push(abortKey);
await fetch(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, { signal });
const beforeAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).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=${stateKey}`).then(r => r.json());
if (afterAbortResult == 'closed') break;
}
}, "Underlying connection is closed when aborting after receiving response");
promise_test(async t => {
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}`, location);
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 BODY_METHODS) {
promise_test(async t => {
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 beforeAbortResult = await fetch(`../resources/stash-take.py?key=${stateKey}`).then(r => r.json());
assert_equals(beforeAbortResult, "open", "Connection is open");
const bodyPromise = response[bodyMethod]();
controller.abort();
await promise_rejects(t, "AbortError", bodyPromise);
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;
}
}, `Fetch aborted & connection closed when aborted after calling response.${bodyMethod}()`);
}
promise_test(async t => {
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 promise_rejects(t, "AbortError", reader.read());
await promise_rejects(t, "AbortError", reader.closed);
// 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 t => {
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 promise_rejects(t, "AbortError", reader.read());
await promise_rejects(t, "AbortError", reader.closed);
// 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 t => {
await abortRequests();
const controller = new AbortController();
const signal = controller.signal;
const response = await fetch(`../resources/empty.txt`, { signal });
// Read whole response to ensure close signal has sent.
await response.clone().text();
const reader = response.body.getReader();
controller.abort();
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.");
promise_test(async 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;
}
});
const fetchPromise = fetch('../resources/empty.txt', {
body, signal,
method: 'POST',
headers: {
'Content-Type': 'text/plain'
}
});
assert_true(!!cancelReason, 'Cancel called sync');
assert_equals(cancelReason.constructor, DOMException);
assert_equals(cancelReason.name, 'AbortError');
await promise_rejects(t, "AbortError", fetchPromise);
const fetchErr = await fetchPromise.catch(e => e);
assert_equals(cancelReason, fetchErr, "Fetch rejects with same error instance");
}, "Readable stream synchronously cancels with AbortError if aborted before reading");
test(() => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
const request = new Request('.', { signal });
const requestSignal = request.signal;
const clonedRequest = request.clone();
assert_equals(requestSignal, request.signal, "Original request signal the same after cloning");
assert_true(request.signal.aborted, "Original request signal aborted");
assert_not_equals(clonedRequest.signal, request.signal, "Cloned request has different signal");
assert_true(clonedRequest.signal.aborted, "Cloned request signal aborted");
}, "Signal state is cloned");
test(() => {
const controller = new AbortController();
const signal = controller.signal;
const request = new Request('.', { signal });
const clonedRequest = request.clone();
const log = [];
request.signal.addEventListener('abort', () => log.push('original-aborted'));
clonedRequest.signal.addEventListener('abort', () => log.push('clone-aborted'));
controller.abort();
assert_array_equals(log, ['clone-aborted', 'original-aborted'], "Abort events fired in correct order");
assert_true(request.signal.aborted, 'Signal aborted');
assert_true(clonedRequest.signal.aborted, 'Signal aborted');
}, "Clone aborts with original controller");

View file

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Aborting fetch when intercepted by a service worker</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../../service-workers/service-worker/resources/test-helpers.sub.js"></script>
</head>
<body>
<script>
// Duplicating this resource to make service worker scoping simpler.
const SCOPE = '../resources/basic.html';
const BODY_METHODS = ['arrayBuffer', 'blob', 'formData', 'json', 'text'];
async function cleanup() {
for (const iframe of document.querySelectorAll('.test-iframe')) {
iframe.parentNode.removeChild(iframe);
}
const reg = await navigator.serviceWorker.getRegistration(SCOPE);
if (reg) await reg.unregister();
}
async function setupRegistration(t) {
await cleanup();
const reg = await navigator.serviceWorker.register('../resources/sw-intercept.js', { scope: SCOPE });
await wait_for_state(t, reg.installing, 'activated');
return reg;
}
promise_test(async t => {
await setupRegistration(t);
const iframe = await with_iframe(SCOPE);
const w = iframe.contentWindow;
const controller = new w.AbortController();
const signal = controller.signal;
controller.abort();
const nextData = new Promise(resolve => {
w.navigator.serviceWorker.addEventListener('message', function once(event) {
w.navigator.serviceWorker.removeEventListener('message', once);
resolve(event.data);
})
});
const fetchPromise = w.fetch('data.json', { signal });
await promise_rejects(t, "AbortError", fetchPromise);
await w.fetch('data.json?no-abort');
assert_true((await nextData).endsWith('?no-abort'), "Aborted request does not go through service worker");
}, "Already aborted request does not land in service worker");
for (const bodyMethod of BODY_METHODS) {
promise_test(async t => {
await setupRegistration(t);
const iframe = await with_iframe(SCOPE);
const w = iframe.contentWindow;
const controller = new w.AbortController();
const signal = controller.signal;
const log = [];
const response = await w.fetch('data.json', { signal });
controller.abort();
const bodyPromise = response[bodyMethod]();
await Promise.all([
bodyPromise.catch(() => log.push(`${bodyMethod}-reject`)),
Promise.resolve().then(() => log.push('next-microtask'))
]);
await promise_rejects(t, "AbortError", bodyPromise);
assert_array_equals(log, [`${bodyMethod}-reject`, 'next-microtask']);
}, `response.${bodyMethod}() rejects if already aborted`);
}
promise_test(async t => {
await setupRegistration(t);
const iframe = await with_iframe(SCOPE);
const w = iframe.contentWindow;
const controller = new w.AbortController();
const signal = controller.signal;
const response = await w.fetch('data.json', { signal });
const reader = response.body.getReader();
controller.abort();
await promise_rejects(t, "AbortError", reader.read());
await promise_rejects(t, "AbortError", reader.closed);
}, "Stream errors once aborted.");
</script>
</body>
</html>

View file

@ -7,23 +7,27 @@
<script>
var noop = function() {};
["text/csv",
"audio/aiff",
"audio/midi",
"audio/whatever",
"video/avi",
"video/fli",
"video/whatever",
"image/jpeg",
"image/gif",
"image/whatever"].forEach(function(test_case) {
async_test(function(t) {
var script = document.createElement("script");
script.onerror = t.step_func_done(noop);
script.onload = t.unreached_func("Unexpected load event");
script.src = "../resources/script-with-header.py?mime=" + test_case;
document.body.appendChild(script);
}, "Should fail loading script with " + test_case + " MIME type");
["non-empty", "empty"].forEach(function(content) {
["text/csv",
"audio/aiff",
"audio/midi",
"audio/whatever",
"video/avi",
"video/fli",
"video/whatever",
"image/jpeg",
"image/gif",
"image/whatever"].forEach(function(test_case) {
async_test(function(t) {
var script = document.createElement("script");
script.onerror = t.step_func_done(noop);
script.onload = t.unreached_func("Unexpected load event");
script.src = "../resources/script-with-header.py?content=" + content +
"&mime=" + test_case;
document.body.appendChild(script);
}, "Should fail loading " + content + " script with " + test_case +
" MIME type");
});
});
["html", "plain"].forEach(function(test_case) {

View file

@ -3,16 +3,26 @@ if (this.document === undefined) {
importScripts("../resources/utils.js");
}
function integrity(desc, url, integrity, shouldPass) {
if (shouldPass) {
function integrity(desc, url, integrity, initRequestMode, shouldPass) {
var fetchRequestInit = {'integrity': integrity}
if (!!initRequestMode && initRequestMode !== "") {
fetchRequestInit.mode = initRequestMode;
}
if (shouldPass) {
promise_test(function(test) {
return fetch(url, {'integrity': integrity}).then(function(resp) {
assert_equals(resp.status, 200, "Response's status is 200");
return fetch(url, fetchRequestInit).then(function(resp) {
if (initRequestMode !== "no-cors") {
assert_equals(resp.status, 200, "Response's status is 200");
} else {
assert_equals(resp.status, 0, "Opaque response's status is 0");
assert_equals(resp.type, "opaque");
}
});
}, desc);
} else {
promise_test(function(test) {
return promise_rejects(test, new TypeError(), fetch(url, {'integrity': integrity}));
return promise_rejects(test, new TypeError(), fetch(url, fetchRequestInit));
}, desc);
}
}
@ -27,19 +37,43 @@ var url = "../resources/top.txt";
var corsUrl = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
/* Enable CORS*/
corsUrl += "?pipe=header(Access-Control-Allow-Origin,*)";
var corsUrl2 = "https://{{host}}:{{ports[https][0]}}/fetch/api/resource/top.txt";
integrity("Empty string integrity", url, "", true);
integrity("SHA-256 integrity", url, topSha256, true);
integrity("SHA-384 integrity", url, topSha384, true);
integrity("SHA-512 integrity", url, topSha512, true);
integrity("Invalid integrity", url, invalidSha256, false);
integrity("Multiple integrities: valid stronger than invalid", url, invalidSha256 + " " + topSha384, true);
integrity("Multiple integrities: invalid stronger than valid", url, invalidSha512 + " " + topSha384, false);
integrity("Multiple integrities: invalid as strong as valid", url, invalidSha512 + " " + topSha512, true);
integrity("Multiple integrities: both are valid", url, topSha384 + " " + topSha512, true);
integrity("Multiple integrities: both are invalid", url, invalidSha256 + " " + invalidSha512, false);
integrity("CORS empty integrity", corsUrl, "", true);
integrity("CORS SHA-512 integrity", corsUrl, topSha512, true);
integrity("CORS invalid integrity", corsUrl, invalidSha512, false);
integrity("Empty string integrity", url, "", /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("SHA-256 integrity", url, topSha256, /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("SHA-384 integrity", url, topSha384, /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("SHA-512 integrity", url, topSha512, /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("Invalid integrity", url, invalidSha256,
/* initRequestMode */ undefined, /* shouldPass */ false);
integrity("Multiple integrities: valid stronger than invalid", url,
invalidSha256 + " " + topSha384, /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("Multiple integrities: invalid stronger than valid",
url, invalidSha512 + " " + topSha384, /* initRequestMode */ undefined,
/* shouldPass */ false);
integrity("Multiple integrities: invalid as strong as valid", url,
invalidSha512 + " " + topSha512, /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("Multiple integrities: both are valid", url,
topSha384 + " " + topSha512, /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("Multiple integrities: both are invalid", url,
invalidSha256 + " " + invalidSha512, /* initRequestMode */ undefined,
/* shouldPass */ false);
integrity("CORS empty integrity", corsUrl, "", /* initRequestMode */ undefined,
/* shouldPass */ true);
integrity("CORS SHA-512 integrity", corsUrl, topSha512,
/* initRequestMode */ undefined, /* shouldPass */ true);
integrity("CORS invalid integrity", corsUrl, invalidSha512,
/* initRequestMode */ undefined, /* shouldPass */ false);
integrity("Empty string integrity for opaque response", corsUrl2, "",
/* initRequestMode */ "no-cors", /* shouldPass */ true);
integrity("SHA-* integrity for opaque response", corsUrl2, topSha512,
/* initRequestMode */ "no-cors", /* shouldPass */ false);
done();

View file

@ -4,7 +4,7 @@ if (this.document === undefined) {
}
const url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt",
sharedHeaders = "?pipe=header(Access-Control-Expose-Headers,*)|header(Test,X)|header(Set-Cookie,X)|"
sharedHeaders = "?pipe=header(Access-Control-Expose-Headers,*)|header(Test,X)|header(Set-Cookie,X)|header(*,whoa)|"
promise_test(() => {
const headers = "header(Access-Control-Allow-Origin,*)"
@ -13,6 +13,7 @@ promise_test(() => {
assert_equals(resp.type , "cors")
assert_equals(resp.headers.get("test"), "X")
assert_equals(resp.headers.get("set-cookie"), null)
assert_equals(resp.headers.get("*"), "whoa")
})
}, "Basic Access-Control-Expose-Headers: * support")
@ -25,7 +26,19 @@ promise_test(() => {
assert_equals(resp.headers.get("content-type"), "text/plain") // safelisted
assert_equals(resp.headers.get("test"), null)
assert_equals(resp.headers.get("set-cookie"), null)
assert_equals(resp.headers.get("*"), "whoa")
})
}, "Cannot use * for credentialed fetches")
}, "* for credentialed fetches only matches literally")
promise_test(() => {
const headers = "header(Access-Control-Allow-Origin,*)|header(Access-Control-Expose-Headers,set-cookie)"
return fetch(url + sharedHeaders + headers).then(resp => {
assert_equals(resp.status, 200)
assert_equals(resp.type , "cors")
assert_equals(resp.headers.get("test"), "X")
assert_equals(resp.headers.get("set-cookie"), null)
assert_equals(resp.headers.get("*"), "whoa")
})
}, "* can be one of several values")
done();

View file

@ -0,0 +1,46 @@
// META: script=/common/utils.js
// META: script=../resources/utils.js
// META: script=/common/get-host-info.sub.js
var cors_url = get_host_info().HTTP_REMOTE_ORIGIN +
dirname(location.pathname) +
RESOURCES_DIR +
"preflight.py";
promise_test((test) => {
var uuid_token = token();
var request_url =
cors_url + "?token=" + uuid_token + "&max_age=12000&allow_methods=POST" +
"&allow_headers=x-test-header";
return fetch(cors_url + "?token=" + uuid_token + "&clear-stash")
.then(() => {
return fetch(
new Request(request_url,
{
mode: "cors",
method: "POST",
headers: [["x-test-header", "test1"]]
}));
})
.then((resp) => {
assert_equals(resp.status, 200, "Response's status is 200");
assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
return fetch(cors_url + "?token=" + uuid_token + "&clear-stash");
})
.then((res) => res.text())
.then((txt) => {
assert_equals(txt, "1", "Server stash must be cleared.");
return fetch(
new Request(request_url,
{
mode: "cors",
method: "POST",
headers: [["x-test-header", "test2"]]
}));
})
.then((resp) => {
assert_equals(resp.status, 200, "Response's status is 200");
assert_equals(resp.headers.get("x-did-preflight"), "0", "Preflight request has not been made");
return fetch(cors_url + "?token=" + uuid_token + "&clear-stash");
});
});

View file

@ -16,7 +16,7 @@ function preflightTest(succeeds, withCredentials, allowMethod, allowHeader, useM
if (useMethod) {
requestInit.method = useMethod
}
if (useHeader) {
if (useHeader.length > 0) {
requestInit.headers = [useHeader]
}
testURL += "allow_methods=" + allowMethod + "&"
@ -36,7 +36,9 @@ preflightTest(true, false, "get", "x-test", "GET", ["X-Test", "1"])
preflightTest(true, false, "*", "x-test", "SUPER", ["X-Test", "1"])
preflightTest(true, false, "*", "*", "OK", ["X-Test", "1"])
preflightTest(false, true, "*", "*", "OK", ["X-Test", "1"])
preflightTest(false, true, "*", "", "PUT", undefined)
preflightTest(false, true, "put", "*", "PUT", undefined)
preflightTest(false, true, "*", "", "PUT", [])
preflightTest(true, true, "PUT", "*", "PUT", [])
preflightTest(false, true, "put", "*", "PUT", [])
preflightTest(false, true, "get", "*", "GET", ["X-Test", "1"])
preflightTest(false, true, "*", "*", "GET", ["X-Test", "1"])
preflightTest(true, true, "*", "*", "*", ["*", "1"])

View file

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>Headers nameshake</title>
<title>Headers have combined (and sorted) values</title>
<meta name="help" href="https://fetch.spec.whatwg.org/#headers">
<meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
<script src="/resources/testharness.js"></script>
@ -53,6 +53,24 @@
assert_equals(headers.get(name), (value + ", " + "newSingleValue"));
}
}, "Check append methods when called with already used name");
test(() => {
const headers = new Headers([["1", "a"],["1", "b"]]);
for(let header of headers) {
assert_array_equals(header, ["1", "a, b"]);
}
}, "Iterate combined values");
test(() => {
const headers = new Headers([["2", "a"], ["1", "b"], ["2", "b"]]),
expected = [["1", "b"], ["2", "a, b"]];
let i = 0;
for(let header of headers) {
assert_array_equals(header, expected[i]);
i++;
}
assert_equals(i, 2);
}, "Iterate combined values in sorted order")
</script>
</body>
</html>

View file

@ -251,59 +251,21 @@ test(function() {
ownKeys: function() {
return [ "a", "c", "a", "c" ];
},
getCalls: 0,
gotCOnce: false,
get: function(target, name, receiver) {
if (name == "c") {
this.gotCOnce = true;
}
if (typeof name == "string") {
return ++this.getCalls;
}
return Reflect.get(target, name, receiver);
},
getOwnPropertyDescriptor: function(target, name) {
var desc = Reflect.getOwnPropertyDescriptor(target, name);
if (name == "c" && this.gotCOnce) {
desc.enumerable = false;
}
return desc;
}
};
var lyingProxy = new Proxy(record, lyingHandler);
var proxy = new Proxy(lyingProxy, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 9);
// Returning duplicate keys from ownKeys() throws a TypeError.
assert_throws(new TypeError(),
function() { var h = new Headers(proxy); });
assert_equals(log.length, 2);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://heycam.github.io/webidl/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", lyingProxy]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", lyingProxy, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", lyingProxy, "a", proxy]);
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[4], ["getOwnPropertyDescriptor", lyingProxy, "c"]);
// Then the second [[Get]] from step 5.2.
assert_array_equals(log[5], ["get", lyingProxy, "c", proxy]);
// Then the third [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[6], ["getOwnPropertyDescriptor", lyingProxy, "a"]);
// Then the third [[Get]] from step 5.2.
assert_array_equals(log[7], ["get", lyingProxy, "a", proxy]);
// Then the fourth [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[8], ["getOwnPropertyDescriptor", lyingProxy, "c"]);
// No [[Get]] because not enumerable.
// Check the results.
assert_equals([...h].length, 2);
assert_array_equals([...h.keys()], ["a", "c"]);
assert_true(h.has("a"));
assert_equals(h.get("a"), "3");
assert_true(h.has("c"));
assert_equals(h.get("c"), "2");
}, "Correct operation ordering with repeated keys");
test(function() {
@ -395,5 +357,4 @@ test(function() {
assert_true(h.has("c"));
assert_equals(h.get("c"), "d");
}, "Operation with non-enumerable Symbol keys");
</script>

View file

@ -8,54 +8,20 @@
<meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="request-error.js"></script>
</head>
<body>
<script>
test(function() {
assert_throws(new TypeError() , function() { new Request("", {"window" : "http://test.url"}); },
"Expect TypeError exception");
},"RequestInit's window is not null");
test(function() {
assert_throws(new TypeError() , function() { new Request("http://:not a valid URL"); },
"Expect TypeError exception");
},"Input URL is not valid")
test(function() {
assert_throws(new TypeError() , function() { new Request("http://user:pass@test.url"); },
"Expect TypeError exception");
},"Input URL has credentials");
test(function() {
assert_throws(new TypeError() , function() { new Request("", {"mode" : "navigate"}); },
"Expect TypeError exception");
},"RequestInit's mode is navigate");
test(function() {
assert_throws(new TypeError() , function() { new Request("", {"referrer" : "http://:not a valid URL"}); },
"Expect TypeError exception");
},"RequestInit's referrer is invalid");
test(function() {
assert_throws(new TypeError() , function() { new Request("", {"method" : "IN VALID"}); },
"Expect TypeError exception");
}, "RequestInit's method is invalid");
test(function() {
assert_throws(new TypeError() , function() { new Request("", {"method" : "TRACE"}); },
"Expect TypeError exception");
}, "RequestInit's method is forbidden");
test(function() {
assert_throws(new TypeError() , function() { new Request("", {"mode" : "no-cors", "method" : "PUT"}); },
"Expect TypeError exception");
},"RequestInit's mode is no-cors and method is not simple");
test(function() {
assert_throws(new TypeError() ,
function() { new Request("", {"mode" : "cors", "cache" : "only-if-cached"}); },
"Expect TypeError exception");
},"RequestInit's cache mode is only-if-cached and mode is not same-origin");
// badRequestArgTests is from response-error.js
for (const { args, testName } of badRequestArgTests) {
test(() => {
assert_throws(
new TypeError(),
() => new Request(...args),
"Expect TypeError exception"
);
}, testName);
}
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
@ -86,28 +52,10 @@
assert_equals(request.headers.get("Content-Type"), "potato");
}, "Request should get its content-type from init headers if one is provided");
var parameters = ["referrerPolicy", "mode", "credentials", "cache", "redirect"];
parameters.forEach(function(parameter) {
test(function() {
var options = { };
options[parameter] = "BAD";
assert_throws(new TypeError(), function() { new Request("", options); });
},"Bad " + parameter +" init parameter value");
});
function testOnlyIfCachedMode(fetchMode, ok) {
test(function() {
var options = {"cache": "only-if-cached", "mode": fetchMode};
if (ok)
new Request("test", options);
else
assert_throws(new TypeError(), function() { new Request("test", options); });
}, "Request with cache mode: only-if-cached and fetch mode: " + fetchMode);
}
testOnlyIfCachedMode("same-origin", true);
testOnlyIfCachedMode("cors", false);
testOnlyIfCachedMode("no-cors", false);
test(function() {
var options = {"cache": "only-if-cached", "mode": "same-origin"};
new Request("test", options);
}, "Request with cache mode: only-if-cached and fetch mode: same-origin");
</script>
</body>
</html>

View file

@ -0,0 +1,57 @@
const badRequestArgTests = [
{
args: ["", { "window": "http://test.url" }],
testName: "RequestInit's window is not null"
},
{
args: ["http://:not a valid URL"],
testName: "Input URL is not valid"
},
{
args: ["http://user:pass@test.url"],
testName: "Input URL has credentials"
},
{
args: ["", { "mode": "navigate" }],
testName: "RequestInit's mode is navigate"
},
{
args: ["", { "referrer": "http://:not a valid URL" }],
testName: "RequestInit's referrer is invalid"
},
{
args: ["", { "method": "IN VALID" }],
testName: "RequestInit's method is invalid"
},
{
args: ["", { "method": "TRACE" }],
testName: "RequestInit's method is forbidden"
},
{
args: ["", { "mode": "no-cors", "method": "PUT" }],
testName: "RequestInit's mode is no-cors and method is not simple"
},
{
args: ["", { "mode": "cors", "cache": "only-if-cached" }],
testName: "RequestInit's cache mode is only-if-cached and mode is not same-origin"
},
{
args: ["test", { "cache": "only-if-cached", "mode": "cors" }],
testName: "Request with cache mode: only-if-cached and fetch mode cors"
},
{
args: ["test", { "cache": "only-if-cached", "mode": "no-cors" }],
testName: "Request with cache mode: only-if-cached and fetch mode no-cors"
}
];
badRequestArgTests.push(
...["referrerPolicy", "mode", "credentials", "cache", "redirect"].map(optionProp => {
const options = {};
options[optionProp] = "BAD";
return {
args: ["", options],
testName: `Bad ${optionProp} init parameter value`
};
})
);

View file

@ -168,6 +168,19 @@
});
}, "Testing empty Request Content-Type header");
test(function() {
const request1 = new Request("");
assert_equals(request1.headers, request1.headers);
const request2 = new Request("", {"headers": {"X-Foo": "bar"}});
assert_equals(request2.headers, request2.headers);
const headers = request2.headers;
request2.headers.set("X-Foo", "quux");
assert_equals(headers, request2.headers);
headers.set("X-Other-Header", "baz");
assert_equals(headers, request2.headers);
}, "Test that Request.headers has the [SameObject] extended attribute");
</script>
</body>
</html>

View file

@ -37,7 +37,6 @@
readonly attribute USVString url;
[SameObject] readonly attribute Headers headers;
readonly attribute RequestType type;
readonly attribute RequestDestination destination;
readonly attribute USVString referrer;
readonly attribute ReferrerPolicy referrerPolicy;

View file

@ -22,7 +22,6 @@
var attributes = ["method",
"url",
"headers",
"type",
"destination",
"referrer",
"referrerPolicy",
@ -56,11 +55,6 @@
return;
break;
case "type":
defaultValue = "";
newValue = "style";
break;
case "destination":
defaultValue = "";
newValue = "worker";

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<title>Request.type attribute should not exist</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id=log></div>
<script>
var request = new Request("https://domfarolino.com");
test(() => {
assert_equals(request.type, undefined, "request.type should be undefined");
}, "'type' getter should not exist on Request objects");
</script>

View file

@ -0,0 +1,25 @@
<!doctype html>
<meta charset=windows-1252>
<title>Fetch: URL encoding</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
const expectedURL = new URL("?%C3%9F", location.href).href;
const expectedURL2 = new URL("?%EF%BF%BD", location.href).href;
test(() => {
let r = new Request("?\u00DF");
assert_equals(r.url, expectedURL);
r = new Request("?\uD83D");
assert_equals(r.url, expectedURL2);
}, "URL encoding and Request");
promise_test(() => {
return fetch("?\u00DF").then(res => {
assert_equals(res.url, expectedURL);
return fetch("?\uD83D").then(res2 => {
assert_equals(res2.url, expectedURL2);
});
});
}, "URL encoding and fetch()");
</script>

View file

@ -0,0 +1,5 @@
<!DOCTYPE html>
<!--
Duplicating /common/blank.html to make service worker scoping simpler in
../abort/serviceworker-intercepted.https.html
-->

View file

@ -0,0 +1,36 @@
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"""
request.server.stash.take(key, url_dir(request))
request.server.stash.put(key, value, url_dir(request))
def main(request, response):
stateKey = request.GET.first("stateKey", "")
abortKey = request.GET.first("abortKey", "")
if stateKey:
stash_write(request, stateKey, '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 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 stateKey:
stash_write(request, stateKey, 'closed')

View file

@ -12,6 +12,12 @@ def main(request, response):
else:
headers.append(("Access-Control-Allow-Origin", "*"))
if "clear-stash" in request.GET:
if request.server.stash.take(token) is not None:
return headers, "1"
else:
return headers, "0"
if "credentials" in request.GET:
headers.append(("Access-Control-Allow-Credentials", "true"))

View file

@ -1,4 +1,7 @@
def main(request, response):
headers = [("Content-type", request.GET.first("mime"))]
content = "console.log('Script loaded')"
if "content" in request.GET and request.GET.first("content") == "empty":
content = ''
else:
content = "console.log('Script loaded')"
return 200, headers, content

View file

@ -0,0 +1,7 @@
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)
response.headers.set('Access-Control-Allow-Origin', '*')
return "done"

View file

@ -0,0 +1,9 @@
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")
response.headers.set('Access-Control-Allow-Origin', '*')
return request.server.stash.take(key, dir)

View file

@ -0,0 +1,10 @@
async function broadcast(msg) {
for (const client of await clients.matchAll()) {
client.postMessage(msg);
}
}
addEventListener('fetch', event => {
event.waitUntil(broadcast(event.request.url));
event.respondWith(fetch(event.request));
});

View file

@ -81,6 +81,26 @@ function validateStreamFromString(reader, expectedValue, retrievedArrayBuffer) {
});
}
function validateStreamFromPartialString(reader, expectedValue, retrievedArrayBuffer) {
return reader.read().then(function(data) {
if (!data.done) {
assert_true(data.value instanceof Uint8Array, "Fetch ReadableStream chunks should be Uint8Array");
var newBuffer;
if (retrievedArrayBuffer) {
newBuffer = new ArrayBuffer(data.value.length + retrievedArrayBuffer.length);
newBuffer.set(retrievedArrayBuffer, 0);
newBuffer.set(data.value, retrievedArrayBuffer.length);
} else {
newBuffer = data.value;
}
return validateStreamFromPartialString(reader, expectedValue, newBuffer);
}
var string = new TextDecoder("utf-8").decode(retrievedArrayBuffer);
return assert_true(string.search(expectedValue) != -1, "Retrieve and verify stream");
});
}
// From streams tests
function delay(milliseconds)
{

View file

@ -12,7 +12,6 @@
<body>
<script src="../resources/utils.js"></script>
<script>
promise_test(function(test) {
return new Response(new Blob([], { "type" : "text/plain" })).body.cancel();
}, "Cancelling a starting blob Response stream");
@ -51,16 +50,19 @@ promise_test(function() {
}, "Cancelling a loading Response stream");
promise_test(function() {
async function readAll(reader) {
while (true) {
const {value, done} = await reader.read();
if (done)
return;
}
}
return fetch(RESOURCES_DIR + "top.txt").then(function(response) {
var reader = response.body.getReader();
var closedPromise = reader.closed.then(function() {
return reader.cancel();
});
reader.read();
return closedPromise;
return readAll(reader).then(() => reader.cancel());
});
}, "Cancelling a closed Response stream");
</script>
</body>
</html>

View file

@ -57,15 +57,16 @@ promise_test(function(test) {
promise_test(function(test) {
var response = new Response(formData);
return validateStreamFromString(response.body.getReader(), "name=value");
return validateStreamFromPartialString(response.body.getReader(),
"Content-Disposition: form-data; name=\"name\"\r\n\r\nvalue");
}, "Read form data response's body as readableStream");
test(function() {
assert_equals(Response.error().body, null);
}, "Getting an error Response stream");
promise_test(function(test) {
assert_equals(Response.redirect(301).body, null);
test(function() {
assert_equals(Response.redirect("/").body, null);
}, "Getting a redirect Response stream");
</script>

View file

@ -58,6 +58,19 @@
"Expect response.ok is " + isOkStatus(response.status));
}
}, "Check " + attributeName + " init values and associated getter");
test(function() {
const response1 = new Response("");
assert_equals(response1.headers, response1.headers);
const response2 = new Response("", {"headers": {"X-Foo": "bar"}});
assert_equals(response2.headers, response2.headers);
const headers = response2.headers;
response2.headers.set("X-Foo", "quux");
assert_equals(headers, response2.headers);
headers.set("X-Other-Header", "baz");
assert_equals(headers, response2.headers);
}, "Test that Response.headers has the [SameObject] extended attribute");
</script>
</body>
</html>

View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>ReadableStream disturbed tests, via Response's bodyUsed property</title>
<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
<link rel="author" title="Takeshi Yoshino" href="mailto:tyoshino@chromium.org">
<link rel="help" href="https://streams.spec.whatwg.org/#is-readable-stream-disturbed">
<link rel="help" href="https://fetch.spec.whatwg.org/#dom-body-bodyused">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
"use strict";
test(() => {
const stream = new ReadableStream();
const response = new Response(stream);
assert_false(response.bodyUsed, "On construction");
const reader = stream.getReader();
assert_false(response.bodyUsed, "After getting a reader");
reader.read();
assert_true(response.bodyUsed, "After calling stream.read()");
}, "A non-closed stream on which read() has been called");
test(() => {
const stream = new ReadableStream();
const response = new Response(stream);
assert_false(response.bodyUsed, "On construction");
const reader = stream.getReader();
assert_false(response.bodyUsed, "After getting a reader");
reader.cancel();
assert_true(response.bodyUsed, "After calling stream.cancel()");
}, "A non-closed stream on which cancel() has been called");
test(() => {
const stream = new ReadableStream({
start(c) {
c.close();
}
});
const response = new Response(stream);
assert_false(response.bodyUsed, "On construction");
const reader = stream.getReader();
assert_false(response.bodyUsed, "After getting a reader");
reader.read();
assert_true(response.bodyUsed, "After calling stream.read()");
}, "A closed stream on which read() has been called");
test(() => {
const stream = new ReadableStream({
start(c) {
c.error(new Error("some error"));
}
});
const response = new Response(stream);
assert_false(response.bodyUsed, "On construction");
const reader = stream.getReader();
assert_false(response.bodyUsed, "After getting a reader");
reader.read();
assert_true(response.bodyUsed, "After calling stream.read()");
}, "An errored stream on which read() has been called");
test(() => {
const stream = new ReadableStream({
start(c) {
c.error(new Error("some error"));
}
});
const response = new Response(stream);
assert_false(response.bodyUsed, "On construction");
const reader = stream.getReader();
assert_false(response.bodyUsed, "After getting a reader");
reader.cancel();
assert_true(response.bodyUsed, "After calling stream.cancel()");
}, "An errored stream on which cancel() has been called");
</script>