summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/api/request
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/fetch/api/request
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/api/request')
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html51
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html51
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html124
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html46
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html60
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html435
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy0
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es0
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers1
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html0
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy.pngbin0 -> 18299 bytes
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttfbin0 -> 2528 bytes
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3bin0 -> 20498 bytes
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.ogabin0 -> 18541 bytes
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4bin0 -> 67369 bytes
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogvbin0 -> 94372 bytes
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html0
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js20
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js20
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js20
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js12
-rw-r--r--testing/web-platform/tests/fetch/api/request/destination/resources/importer.js1
-rw-r--r--testing/web-platform/tests/fetch/api/request/forbidden-method.any.js13
-rw-r--r--testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html3
-rw-r--r--testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html14
-rw-r--r--testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html27
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-bad-port.any.js92
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js170
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-default.any.js39
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js67
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js25
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js37
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js66
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js51
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-cache.js223
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-clone.sub.html63
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js101
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-consume.any.js145
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-disturbed.any.js109
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-error.any.js56
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-error.js57
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-headers.any.js176
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-init-001.sub.html112
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-init-002.any.js60
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-init-003.sub.html84
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js141
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-init-stream.any.js147
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html97
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-keepalive.any.js17
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html96
-rw-r--r--testing/web-platform/tests/fetch/api/request/request-structure.any.js133
-rw-r--r--testing/web-platform/tests/fetch/api/request/resources/cache.py67
-rw-r--r--testing/web-platform/tests/fetch/api/request/resources/hello.txt1
-rw-r--r--testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js19
-rw-r--r--testing/web-platform/tests/fetch/api/request/url-encoding.html25
55 files changed, 3374 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html
new file mode 100644
index 0000000000..f3f9f7856d
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-frame.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+const kScope = 'resources/dummy.html?dest=frame';
+
+// Set up the service worker and the frame.
+promise_test(t => {
+ const kScript = 'resources/fetch-destination-worker-frame.js';
+ return service_worker_unregister_and_register(t, kScript, kScope)
+ .then(registration => {
+ add_completion_callback(() => {
+ registration.unregister();
+ });
+
+ return wait_for_state(t, registration.installing, 'activated');
+ });
+ }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+ await new Promise((resolve, reject) => {
+ navigator.serviceWorker.onmessage = t.step_func(event => {
+ if (event.data == "PASS") {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+ }).catch(() => {;
+ assert_unreached("Wrong destination.");
+ });
+ t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+
+// Document destination
+///////////////////////
+promise_test(async t => {
+ var f = document.createElement('frame');
+ frame = f;
+ f.className = 'test-frame';
+ f.src = kScope;
+ document.body.appendChild(f);
+ await waitOnMessageFromSW(t);
+ add_completion_callback(() => { f.remove(); });
+}, 'frame fetches with a "frame" Request.destination');
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html
new file mode 100644
index 0000000000..1aa5a5613b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-iframe.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+const kScope = 'resources/dummy.html?dest=iframe';
+
+// Set up the service worker and the frame.
+promise_test(t => {
+ const kScript = 'resources/fetch-destination-worker-iframe.js';
+ return service_worker_unregister_and_register(t, kScript, kScope)
+ .then(registration => {
+ add_completion_callback(() => {
+ registration.unregister();
+ });
+
+ return wait_for_state(t, registration.installing, 'activated');
+ });
+ }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+ await new Promise((resolve, reject) => {
+ navigator.serviceWorker.onmessage = t.step_func(event => {
+ if (event.data == "PASS") {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+ }).catch(() => {;
+ assert_unreached("Wrong destination.");
+ });
+ t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+
+// Document destination
+///////////////////////
+promise_test(async t => {
+ var f = document.createElement('iframe');
+ frame = f;
+ f.className = 'test-iframe';
+ f.src = kScope;
+ document.body.appendChild(f);
+ await waitOnMessageFromSW(t);
+ add_completion_callback(() => { f.remove(); });
+}, 'iframe fetches with a "iframe" Request.destination');
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html
new file mode 100644
index 0000000000..1778bf2581
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+ const kScope = 'resources/';
+ const kFrame = 'resources/empty.https.html';
+ const kScript = 'resources/fetch-destination-worker-no-load-event.js';
+ return service_worker_unregister_and_register(t, kScript, kScope)
+ .then(registration => {
+ add_completion_callback(() => {
+ registration.unregister();
+ });
+
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(() => {
+ return with_iframe(kFrame);
+ })
+ .then(f => {
+ frame = f;
+ add_completion_callback(() => { f.remove(); });
+ });
+ }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+ await new Promise((resolve, reject) => {
+ frame.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => {
+ if (event.data == "PASS") {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+ }).catch(() => {;
+ assert_unreached("Wrong destination.");
+ });
+ t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+// Actual tests
+
+// Image destination
+////////////////////
+
+// CSS background image - image destination
+promise_test(async t => {
+ let node = frame.contentWindow.document.createElement("div");
+ node.style = "background-image: url(dummy.png?t=bg2&dest=image)";
+ frame.contentWindow.document.body.appendChild(node);
+
+ await waitOnMessageFromSW(t);
+}, 'Background image fetches with an "image" Request.destination');
+
+// Font destination
+///////////////////
+
+// Font loading API - font destination
+promise_test(async t => {
+ let font = new frame.contentWindow.FontFace("foo", "url(dummy.ttf?t=api&dest=font)");
+ font.load();
+
+ await waitOnMessageFromSW(t);
+}, 'Font loading API fetches with an "font" Request.destination');
+
+// CSS font - font destination
+promise_test(async t => {
+ let style = frame.contentWindow.document.createElement("style");
+ style.innerHTML = "@font-face { font-family: foo; src: url(dummy.ttf?t=css&dest=font); }";
+ style.innerHTML += "div {font-family: foo; }";
+ let div = frame.contentWindow.document.createElement("div");
+ div.innerHTML = "bar";
+ frame.contentWindow.document.body.appendChild(style);
+ frame.contentWindow.document.body.appendChild(div);
+
+ await waitOnMessageFromSW(t);
+}, 'CSS font fetches with an "font" Request.destination');
+
+// Empty string destination
+///////////////////////////
+
+// sendBeacon() - empty string destination
+promise_test(async t => {
+ frame.contentWindow.navigator.sendBeacon("dummy?t=beacon&dest=", "foobar");
+
+ await waitOnMessageFromSW(t);
+}, 'sendBeacon() fetches with an empty string Request.destination');
+
+// Cache.add() - empty string destination
+promise_test(async t => {
+ frame.contentWindow.caches.open("foo").then(cache => {
+ cache.add("dummy?t=cache&dest=");
+ });
+
+ await waitOnMessageFromSW(t);
+}, 'Cache.add() fetches with an empty string Request.destination');
+
+// script destination
+/////////////////////
+
+// importScripts() - script destination
+promise_test(async t => {
+ let worker = new frame.contentWindow.Worker("importer.js");
+
+ await waitOnMessageFromSW(t);
+}, 'importScripts() fetches with a "script" Request.destination');
+
+// style destination
+/////////////////////
+// @import - style destination
+promise_test(async t => {
+ let node = frame.contentWindow.document.createElement("style");
+ node.innerHTML = '@import url("dummy?t=import&dest=style")';
+ frame.contentWindow.document.body.appendChild(node);
+
+ await waitOnMessageFromSW(t);
+}, '@import fetches with a "style" Request.destination');
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html
new file mode 100644
index 0000000000..db99202df8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-prefetch.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Fetch destination test for prefetching</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/media.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+ const kScope = 'resources/empty.https.html';
+ const kScript = 'resources/fetch-destination-worker.js';
+ return service_worker_unregister_and_register(t, kScript, kScope)
+ .then(registration => {
+ add_completion_callback(() => {
+ registration.unregister();
+ });
+
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(() => {
+ return with_iframe(kScope);
+ })
+ .then(f => {
+ frame = f;
+ add_completion_callback(() => { f.remove(); });
+ });
+ }, 'Initialize global state');
+
+// HTMLLinkElement with rel=prefetch - empty string destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "prefetch";
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=prefetch fetches with an empty string Request.destination');
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html
new file mode 100644
index 0000000000..5935c1ff31
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination-worker.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+ const kScope = 'resources/dummy.html';
+ const kScript = 'resources/fetch-destination-worker-no-load-event.js';
+ return service_worker_unregister_and_register(t, kScript, kScope)
+ .then(registration => {
+ add_completion_callback(() => {
+ registration.unregister();
+ });
+
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(() => {
+ return with_iframe(kScope);
+ })
+ .then(f => {
+ frame = f;
+ add_completion_callback(() => { f.remove(); });
+ });
+ }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+ await new Promise((resolve, reject) => {
+ frame.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => {
+ if (event.data == "PASS") {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+ }).catch(() => {;
+ assert_unreached("Wrong destination.");
+ });
+ t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+
+// worker destination
+/////////////////////
+promise_test(async t => {
+ // We can use an html file as we don't really care about the dedicated worker successfully loading.
+ let worker = new frame.contentWindow.Worker("dummy.html?t=worker&dest=worker");
+ await waitOnMessageFromSW(t);
+}, 'DedicatedWorker fetches with a "worker" Request.destination');
+
+promise_test(async t => {
+ // We can use an html file as we don't really care about the shared worker successfully loading.
+ let worker = new frame.contentWindow.SharedWorker("dummy.html?t=sharedworker&dest=sharedworker");
+ await waitOnMessageFromSW(t);
+}, 'SharedWorker fetches with a "sharedworker" Request.destination');
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html
new file mode 100644
index 0000000000..0094b0b6fe
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/fetch-destination.https.html
@@ -0,0 +1,435 @@
+<!DOCTYPE html>
+<title>Fetch destination tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/media.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+ const kScope = 'resources/empty.https.html';
+ const kScript = 'resources/fetch-destination-worker.js';
+ return service_worker_unregister_and_register(t, kScript, kScope)
+ .then(registration => {
+ add_completion_callback(() => {
+ registration.unregister();
+ });
+
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(() => {
+ return with_iframe(kScope);
+ })
+ .then(f => {
+ frame = f;
+ add_completion_callback(() => { f.remove(); });
+ });
+ }, 'Initialize global state');
+
+// Actual tests
+
+// Image destination
+////////////////////
+
+// HTMLImageElement - image destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("img");
+ node.onload = resolve;
+ node.onerror = reject;
+ node.src = "dummy.png?dest=image";
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLImageElement fetches with an "image" Request.destination');
+
+// HTMLImageElement with srcset attribute - image destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("img");
+ node.onload = resolve;
+ node.onerror = reject;
+ node.srcset = "dummy.png?t=srcset&dest=image";
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLImageElement with srcset attribute fetches with an "image" Request.destination');
+
+// HTMLImageElement with srcset attribute - image destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let img = frame.contentWindow.document.createElement("img");
+ let picture = frame.contentWindow.document.createElement("picture");
+ let source = frame.contentWindow.document.createElement("source");
+ picture.appendChild(source);
+ picture.appendChild(img);
+ img.onload = resolve;
+ img.onerror = reject;
+ source.srcset = "dummy.png?t=picture&dest=image";
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLImageElement with a HTMLPictureElement parent attribute fetches with an "image" Request.destination');
+
+// SVGImageElement - image destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let svg = frame.contentWindow.document.createElementNS('http://www.w3.org/2000/svg','svg');
+ svg.setAttributeNS('http://www.w3.org/2000/svg','xlink','http://www.w3.org/1999/xlink');
+ let svgimg = frame.contentWindow.document.createElementNS('http://www.w3.org/2000/svg','image');
+ svgimg.onload = resolve;
+ svgimg.onerror = reject;
+ svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href','dummy.png?t=svg&dest=image');
+ svg.appendChild(svgimg);
+ frame.contentWindow.document.documentElement.appendChild(svg);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'SVGImageElement fetches with an "image" Request.destination');
+
+// Empty string destination
+///////////////////////////
+
+// fetch() - empty string destination
+promise_test(async t => {
+ let response = await frame.contentWindow.fetch("dummy?dest=");
+ assert_true(response.ok);
+}, 'fetch() fetches with an empty string Request.destination');
+
+// XMLHttpRequest - empty string destination
+promise_test(async t => {
+ let xhr;
+ await new Promise((resolve, reject) => {
+ xhr = new frame.contentWindow.XMLHttpRequest();
+ xhr.onload = resolve;
+ xhr.onerror = reject;
+ xhr.open("GET", "dummy?t=xhr&dest=");
+ xhr.send();
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+ assert_equals(xhr.status, 200);
+}, 'XMLHttpRequest() fetches with an empty string Request.destination');
+
+// EventSource - empty string destination
+promise_test(async t => {
+ let xhr;
+ await new Promise((resolve, reject) => {
+ eventSource = new frame.contentWindow.EventSource("dummy.es?t=eventsource&dest=");
+ eventSource.onopen = resolve;
+ eventSource.onerror = reject;
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'EventSource() fetches with an empty string Request.destination');
+
+// HTMLAudioElement - audio destination
+///////////////////////////////////////
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let audioURL = getAudioURI("dummy_audio");
+ let node = frame.contentWindow.document.createElement("audio");
+ node.onloadeddata = resolve;
+ node.onerror = reject;
+ node.src = audioURL + "?dest=audio";
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLAudioElement fetches with an "audio" Request.destination');
+
+// HTMLVideoElement - video destination
+///////////////////////////////////////
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let videoURL = getVideoURI("dummy_video");
+ let node = frame.contentWindow.document.createElement("video");
+ node.onloadeddata = resolve;
+ node.onerror = reject;
+ node.src = videoURL + "?dest=video";
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLVideoElement fetches with a "video" Request.destination');
+
+// script destinations
+//////////////////////
+
+// HTMLScriptElement - script destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("script");
+ node.onload = resolve;
+ node.onerror = reject;
+ node.src = "dummy?dest=script";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLScriptElement fetches with a "script" Request.destination');
+
+// audioworklet destination
+//////////////////////
+promise_test(async t => {
+ let audioContext = new frame.contentWindow.AudioContext();
+ await audioContext.audioWorklet.addModule("dummy?dest=audioworklet");
+}, 'AudioWorklet module fetches with a "audioworklet" Request.destination');
+
+// Style destination
+////////////////////
+
+// HTMLLinkElement with rel=stylesheet - style destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "stylesheet";
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=style";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=stylesheet fetches with a "style" Request.destination');
+
+// Preload tests
+////////////////
+// HTMLLinkElement with rel=preload and as=fetch - empty string destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "fetch";
+ if (node.as != "fetch") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?t=2&dest=";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=fetch fetches with an empty string Request.destination');
+
+// HTMLLinkElement with rel=preload and as=style - style destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "style";
+ if (node.as != "style") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?t=2&dest=style";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=style fetches with a "style" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=script - script destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "script";
+ if (node.as != "script") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?t=2&dest=script";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=script fetches with a "script" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=font - font destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "font";
+ if (node.as != "font") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?t=2&dest=font";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=font fetches with a "font" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=image - image destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "image";
+ if (node.as != "image") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy.png?t=2&dest=image";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=image fetches with a "image" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=audio - audio destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let audioURL = getAudioURI("dummy_audio");
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "audio";
+ if (node.as != "audio") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = audioURL + "?dest=audio";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=audio fetches with a "audio" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=video - video destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let videoURL = getVideoURI("dummy_video");
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "video";
+ if (node.as != "video") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = videoURL + "?dest=video";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=video fetches with a "video" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=track - track destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "track";
+ if (node.as != "track") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=track";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=track fetches with a "track" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=document - document destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "document";
+ if (node.as != "document") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=document";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=document fetches with a "document" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=worker - worker destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "worker";
+ if (node.as != "worker") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=worker";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=worker fetches with a "worker" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=sharedworker - sharedworker destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "sharedworker";
+ if (node.as != "sharedworker") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=sharedworker";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=sharedworker fetches with a "sharedworker" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=xslt - xslt destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "xslt";
+ if (node.as != "xslt") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=xslt";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=xslt fetches with a "xslt" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=manifest - manifest destination
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ let node = frame.contentWindow.document.createElement("link");
+ node.rel = "preload";
+ node.as = "manifest";
+ if (node.as != "manifest") {
+ resolve();
+ }
+ node.onload = resolve;
+ node.onerror = reject;
+ node.href = "dummy?dest=manifest";
+ frame.contentWindow.document.body.appendChild(node);
+ }).catch(() => {
+ assert_unreached("Fetch errored.");
+ });
+}, 'HTMLLinkElement with rel=preload and as=manifest fetches with a "manifest" Request.destination');
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers
new file mode 100644
index 0000000000..9bb8badcad
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.es.headers
@@ -0,0 +1 @@
+Content-Type: text/event-stream
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.html
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.png b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.png
new file mode 100644
index 0000000000..01c9666a8d
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.png
Binary files differ
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttf b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttf
new file mode 100644
index 0000000000..9023592ef5
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy.ttf
Binary files differ
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3 b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3
new file mode 100644
index 0000000000..0091330f1e
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.mp3
Binary files differ
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.oga b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.oga
new file mode 100644
index 0000000000..239ad2bd08
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_audio.oga
Binary files differ
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4 b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4
new file mode 100644
index 0000000000..7022e75c15
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.mp4
Binary files differ
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogv b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogv
new file mode 100644
index 0000000000..de99616ece
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/dummy_video.ogv
Binary files differ
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html b/testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/empty.https.html
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js
new file mode 100644
index 0000000000..b69de0b7df
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-frame.js
@@ -0,0 +1,20 @@
+self.addEventListener('fetch', function(event) {
+ if (event.request.url.includes('dummy')) {
+ event.waitUntil(async function() {
+ let destination = new URL(event.request.url).searchParams.get("dest");
+ let clients = await self.clients.matchAll({"includeUncontrolled": true});
+ clients.forEach(function(client) {
+ if (client.url.includes("fetch-destination-frame")) {
+ if (event.request.destination == destination) {
+ client.postMessage("PASS");
+ } else {
+ client.postMessage("FAIL");
+ }
+ }
+ })
+ }());
+ }
+ event.respondWith(fetch(event.request));
+});
+
+
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js
new file mode 100644
index 0000000000..76345839ea
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js
@@ -0,0 +1,20 @@
+self.addEventListener('fetch', function(event) {
+ if (event.request.url.includes('dummy')) {
+ event.waitUntil(async function() {
+ let destination = new URL(event.request.url).searchParams.get("dest");
+ let clients = await self.clients.matchAll({"includeUncontrolled": true});
+ clients.forEach(function(client) {
+ if (client.url.includes("fetch-destination-iframe")) {
+ if (event.request.destination == destination) {
+ client.postMessage("PASS");
+ } else {
+ client.postMessage("FAIL");
+ }
+ }
+ })
+ }());
+ }
+ event.respondWith(fetch(event.request));
+});
+
+
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js
new file mode 100644
index 0000000000..a583b1272a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js
@@ -0,0 +1,20 @@
+self.addEventListener('fetch', function(event) {
+ const url = event.request.url;
+ if (url.includes('dummy') && url.includes('?')) {
+ event.waitUntil(async function() {
+ let destination = new URL(url).searchParams.get("dest");
+ var result = "FAIL";
+ if (event.request.destination == destination ||
+ (event.request.destination == "empty" && destination == "")) {
+ result = "PASS";
+ }
+ let cl = await clients.matchAll({includeUncontrolled: true});
+ for (i = 0; i < cl.length; i++) {
+ cl[i].postMessage(result);
+ }
+ }())
+ }
+ event.respondWith(fetch(event.request));
+});
+
+
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js
new file mode 100644
index 0000000000..904009c172
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/fetch-destination-worker.js
@@ -0,0 +1,12 @@
+self.addEventListener('fetch', function(event) {
+ if (event.request.url.includes('dummy')) {
+ let destination = new URL(event.request.url).searchParams.get("dest");
+ if (event.request.destination == destination ||
+ (event.request.destination == "empty" && destination == "")) {
+ event.respondWith(fetch(event.request));
+ } else {
+ event.respondWith(Response.error());
+ }
+ }
+});
+
diff --git a/testing/web-platform/tests/fetch/api/request/destination/resources/importer.js b/testing/web-platform/tests/fetch/api/request/destination/resources/importer.js
new file mode 100644
index 0000000000..9568474d50
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/destination/resources/importer.js
@@ -0,0 +1 @@
+importScripts("dummy?t=importScripts&dest=script");
diff --git a/testing/web-platform/tests/fetch/api/request/forbidden-method.any.js b/testing/web-platform/tests/fetch/api/request/forbidden-method.any.js
new file mode 100644
index 0000000000..eb13f37f0b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/forbidden-method.any.js
@@ -0,0 +1,13 @@
+// META: global=window,worker
+
+// https://fetch.spec.whatwg.org/#forbidden-method
+for (const method of [
+ 'CONNECT', 'TRACE', 'TRACK',
+ 'connect', 'trace', 'track'
+ ]) {
+ test(function() {
+ assert_throws_js(TypeError,
+ function() { new Request('./', {method: method}); }
+ );
+ }, 'Request() with a forbidden method ' + method + ' must throw.');
+}
diff --git a/testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html b/testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html
new file mode 100644
index 0000000000..9bb6e0bbf3
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/multi-globals/current/current.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>Current page used as a test helper</title>
+<base href="success/">
diff --git a/testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html b/testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html
new file mode 100644
index 0000000000..a885b8a0a7
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/multi-globals/incumbent/incumbent.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Incumbent page used as a test helper</title>
+
+<iframe src="../current/current.html" id="c"></iframe>
+
+<script>
+'use strict';
+
+window.createRequest = (...args) => {
+ const current = document.querySelector('#c').contentWindow;
+ return new current.Request(...args);
+};
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html b/testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html
new file mode 100644
index 0000000000..df60e72507
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/multi-globals/url-parsing.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Request constructor URL parsing, with multiple globals in play</title>
+<link rel="help" href="https://fetch.spec.whatwg.org/#dom-request">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This is the entry global -->
+
+<iframe src="incumbent/incumbent.html"></iframe>
+
+<script>
+'use strict';
+
+const loadPromise = new Promise(resolve => {
+ window.addEventListener("load", () => resolve());
+});
+
+promise_test(() => {
+ return loadPromise.then(() => {
+ const req = document.querySelector('iframe').contentWindow.createRequest("url");
+
+ assert_equals(req.url, new URL("current/success/url", location.href).href);
+ });
+}, "should parse the URL relative to the current settings object");
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/request-bad-port.any.js b/testing/web-platform/tests/fetch/api/request/request-bad-port.any.js
new file mode 100644
index 0000000000..b0684d4be0
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-bad-port.any.js
@@ -0,0 +1,92 @@
+// META: global=window,worker
+
+// list of bad ports according to
+// https://fetch.spec.whatwg.org/#port-blocking
+var BLOCKED_PORTS_LIST = [
+ 1, // tcpmux
+ 7, // echo
+ 9, // discard
+ 11, // systat
+ 13, // daytime
+ 15, // netstat
+ 17, // qotd
+ 19, // chargen
+ 20, // ftp-data
+ 21, // ftp
+ 22, // ssh
+ 23, // telnet
+ 25, // smtp
+ 37, // time
+ 42, // name
+ 43, // nicname
+ 53, // domain
+ 69, // tftp
+ 77, // priv-rjs
+ 79, // finger
+ 87, // ttylink
+ 95, // supdup
+ 101, // hostriame
+ 102, // iso-tsap
+ 103, // gppitnp
+ 104, // acr-nema
+ 109, // pop2
+ 110, // pop3
+ 111, // sunrpc
+ 113, // auth
+ 115, // sftp
+ 117, // uucp-path
+ 119, // nntp
+ 123, // ntp
+ 135, // loc-srv / epmap
+ 137, // netbios-ns
+ 139, // netbios-ssn
+ 143, // imap2
+ 161, // snmp
+ 179, // bgp
+ 389, // ldap
+ 427, // afp (alternate)
+ 465, // smtp (alternate)
+ 512, // print / exec
+ 513, // login
+ 514, // shell
+ 515, // printer
+ 526, // tempo
+ 530, // courier
+ 531, // chat
+ 532, // netnews
+ 540, // uucp
+ 548, // afp
+ 554, // rtsp
+ 556, // remotefs
+ 563, // nntp+ssl
+ 587, // smtp (outgoing)
+ 601, // syslog-conn
+ 636, // ldap+ssl
+ 989, // ftps-data
+ 990, // ftps
+ 993, // ldap+ssl
+ 995, // pop3+ssl
+ 1719, // h323gatestat
+ 1720, // h323hostcall
+ 1723, // pptp
+ 2049, // nfs
+ 3659, // apple-sasl
+ 4045, // lockd
+ 5060, // sip
+ 5061, // sips
+ 6000, // x11
+ 6566, // sane-port
+ 6665, // irc (alternate)
+ 6666, // irc (alternate)
+ 6667, // irc (default)
+ 6668, // irc (alternate)
+ 6669, // irc (alternate)
+ 6697, // irc+tls
+ 10080, // amanda
+];
+
+BLOCKED_PORTS_LIST.map(function(a){
+ promise_test(function(t){
+ return promise_rejects_js(t, TypeError, fetch("http://example.com:" + a))
+ }, 'Request on bad port ' + a + ' should throw TypeError.');
+});
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js
new file mode 100644
index 0000000000..c5b2001cc8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-default-conditional.any.js
@@ -0,0 +1,170 @@
+// META: global=window,worker
+// META: title=Request cache - default with conditional requests
+// META: timeout=long
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+var tests = [
+ {
+ name: 'RequestCache "default" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Modified-Since": now.toGMTString()}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Modified-Since": now.toGMTString()}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Modified-Since": now.toGMTString()}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Modified-Since": now.toGMTString()}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-None-Match header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-None-Match": '"foo"'}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-None-Match header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-None-Match": '"foo"'}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-None-Match": '"foo"'}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-None-Match": '"foo"'}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Unmodified-Since": now.toGMTString()}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Unmodified-Since": now.toGMTString()}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Unmodified-Since": now.toGMTString()}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Unmodified-Since": now.toGMTString()}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Match header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Match": '"foo"'}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Match header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Match": '"foo"'}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Match": '"foo"'}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Match": '"foo"'}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Range header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Range": '"foo"'}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Range header (following a request without additional headers) is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{}, {"If-Range": '"foo"'}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"',
+ state: "stale",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Range": '"foo"'}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ request_headers: [{"If-Range": '"foo"'}, {}],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-default.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-default.any.js
new file mode 100644
index 0000000000..dfa8369c9a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-default.any.js
@@ -0,0 +1,39 @@
+// META: global=window,worker
+// META: title=Request cache - default
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+var tests = [
+ {
+ name: 'RequestCache "default" mode checks the cache for previously cached content and goes to the network for stale responses',
+ state: "stale",
+ request_cache: ["default", "default"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "default" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists',
+ state: "fresh",
+ request_cache: ["default", "default"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false],
+ },
+ {
+ name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
+ state: "stale",
+ cache_control: "no-store",
+ request_cache: ["default", "default"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
+ state: "fresh",
+ cache_control: "no-store",
+ request_cache: ["default", "default"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, false],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js
new file mode 100644
index 0000000000..00dce096c7
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-force-cache.any.js
@@ -0,0 +1,67 @@
+// META: global=window,worker
+// META: title=Request cache - force-cache
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+var tests = [
+ {
+ name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for stale responses',
+ state: "stale",
+ request_cache: ["default", "force-cache"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false],
+ },
+ {
+ name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for fresh responses',
+ state: "fresh",
+ request_cache: ["default", "force-cache"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false],
+ },
+ {
+ name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
+ state: "stale",
+ request_cache: ["force-cache"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false],
+ },
+ {
+ name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
+ state: "fresh",
+ request_cache: ["force-cache"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false],
+ },
+ {
+ name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
+ state: "stale",
+ vary: "*",
+ request_cache: ["default", "force-cache"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
+ state: "fresh",
+ vary: "*",
+ request_cache: ["default", "force-cache"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
+ state: "stale",
+ request_cache: ["force-cache", "default"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
+ state: "fresh",
+ request_cache: ["force-cache", "default"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js
new file mode 100644
index 0000000000..41fc22baf2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-no-cache.any.js
@@ -0,0 +1,25 @@
+// META: global=window,worker
+// META: title=Request cache : no-cache
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+var tests = [
+ {
+ name: 'RequestCache "no-cache" mode revalidates stale responses found in the cache',
+ state: "stale",
+ request_cache: ["default", "no-cache"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [false, false],
+ expected_max_age_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "no-cache" mode revalidates fresh responses found in the cache',
+ state: "fresh",
+ request_cache: ["default", "no-cache"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [false, false],
+ expected_max_age_headers: [false, true],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js
new file mode 100644
index 0000000000..9a28718bf2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-no-store.any.js
@@ -0,0 +1,37 @@
+// META: global=window,worker
+// META: title=Request cache - no store
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+var tests = [
+ {
+ name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
+ state: "stale",
+ request_cache: ["default", "no-store"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
+ state: "fresh",
+ request_cache: ["default", "no-store"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "no-store" mode does not store the response in the cache',
+ state: "stale",
+ request_cache: ["no-store", "default"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "no-store" mode does not store the response in the cache',
+ state: "fresh",
+ request_cache: ["no-store", "default"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [true, false],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js
new file mode 100644
index 0000000000..1305787c7c
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-only-if-cached.any.js
@@ -0,0 +1,66 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: title=Request cache - only-if-cached
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+// FIXME: avoid mixed content requests to enable service worker global
+var tests = [
+ {
+ name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for stale responses',
+ state: "stale",
+ request_cache: ["default", "only-if-cached"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false]
+ },
+ {
+ name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for fresh responses',
+ state: "fresh",
+ request_cache: ["default", "only-if-cached"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [false]
+ },
+ {
+ name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and does not go to the network if a cached response is not found',
+ state: "fresh",
+ request_cache: ["only-if-cached"],
+ response: ["error"],
+ expected_validation_headers: [],
+ expected_no_cache_headers: []
+ },
+ {
+ name: 'RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content',
+ state: "fresh",
+ request_cache: ["default", "only-if-cached"],
+ redirect: "same-origin",
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content',
+ state: "stale",
+ request_cache: ["default", "only-if-cached"],
+ redirect: "same-origin",
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects',
+ state: "fresh",
+ request_cache: ["default", "only-if-cached"],
+ redirect: "cross-origin",
+ response: [null, "error"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, false],
+ },
+ {
+ name: 'RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects',
+ state: "stale",
+ request_cache: ["default", "only-if-cached"],
+ redirect: "cross-origin",
+ response: [null, "error"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, false],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js b/testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js
new file mode 100644
index 0000000000..c7bfffb398
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache-reload.any.js
@@ -0,0 +1,51 @@
+// META: global=window,worker
+// META: title=Request cache - reload
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=request-cache.js
+
+var tests = [
+ {
+ name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
+ state: "stale",
+ request_cache: ["default", "reload"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
+ state: "fresh",
+ request_cache: ["default", "reload"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+ {
+ name: 'RequestCache "reload" mode does store the response in the cache',
+ state: "stale",
+ request_cache: ["reload", "default"],
+ expected_validation_headers: [false, true],
+ expected_no_cache_headers: [true, false],
+ },
+ {
+ name: 'RequestCache "reload" mode does store the response in the cache',
+ state: "fresh",
+ request_cache: ["reload", "default"],
+ expected_validation_headers: [false],
+ expected_no_cache_headers: [true],
+ },
+ {
+ name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
+ state: "stale",
+ request_cache: ["default", "reload", "default"],
+ expected_validation_headers: [false, false, true],
+ expected_no_cache_headers: [false, true, false],
+ },
+ {
+ name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
+ state: "fresh",
+ request_cache: ["default", "reload", "default"],
+ expected_validation_headers: [false, false],
+ expected_no_cache_headers: [false, true],
+ },
+];
+run_tests(tests);
diff --git a/testing/web-platform/tests/fetch/api/request/request-cache.js b/testing/web-platform/tests/fetch/api/request/request-cache.js
new file mode 100644
index 0000000000..f2fbecf496
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache.js
@@ -0,0 +1,223 @@
+/**
+ * Each test is run twice: once using etag/If-None-Match and once with
+ * date/If-Modified-Since. Each test run gets its own URL and randomized
+ * content and operates independently.
+ *
+ * The test steps are run with request_cache.length fetch requests issued
+ * and their immediate results sanity-checked. The cache.py server script
+ * stashes an entry containing any If-None-Match, If-Modified-Since, Pragma,
+ * and Cache-Control observed headers for each request it receives. When
+ * the test fetches have run, this state is retrieved from cache.py and the
+ * expected_* lists are checked, including their length.
+ *
+ * This means that if a request_* fetch is expected to hit the cache and not
+ * touch the network, then there will be no entry for it in the expect_*
+ * lists. AKA (request_cache.length - expected_validation_headers.length)
+ * should equal the number of cache hits that didn't touch the network.
+ *
+ * Test dictionary keys:
+ * - state: required string that determines whether the Expires response for
+ * the fetched document should be set in the future ("fresh") or past
+ * ("stale").
+ * - vary: optional string to be passed to the server for it to quote back
+ * in a Vary header on the response to us.
+ * - cache_control: optional string to be passed to the server for it to
+ * quote back in a Cache-Control header on the response to us.
+ * - redirect: optional string "same-origin" or "cross-origin". If
+ * provided, the server will issue an absolute redirect to the script on
+ * the same or a different origin, as appropriate. The redirected
+ * location is the script with the redirect parameter removed, so the
+ * content/state/etc. will be as if you hadn't specified a redirect.
+ * - request_cache: required array of cache modes to use (via `cache`).
+ * - request_headers: optional array of explicit fetch `headers` arguments.
+ * If provided, the server will log an empty dictionary for each request
+ * instead of the request headers it would normally log.
+ * - response: optional array of specialized response handling. Right now,
+ * "error" array entries indicate a network error response is expected
+ * which will reject with a TypeError.
+ * - expected_validation_headers: required boolean array indicating whether
+ * the server should have seen an If-None-Match/If-Modified-Since header
+ * in the request.
+ * - expected_no_cache_headers: required boolean array indicating whether
+ * the server should have seen Pragma/Cache-control:no-cache headers in
+ * the request.
+ * - expected_max_age_headers: optional boolean array indicating whether
+ * the server should have seen a Cache-Control:max-age=0 header in the
+ * request.
+ */
+
+var now = new Date();
+
+function base_path() {
+ return location.pathname.replace(/\/[^\/]*$/, '/');
+}
+function make_url(uuid, id, value, content, info) {
+ var dates = {
+ fresh: new Date(now.getFullYear() + 1, now.getMonth(), now.getDay()).toGMTString(),
+ stale: new Date(now.getFullYear() - 1, now.getMonth(), now.getDay()).toGMTString(),
+ };
+ var vary = "";
+ if ("vary" in info) {
+ vary = "&vary=" + info.vary;
+ }
+ var cache_control = "";
+ if ("cache_control" in info) {
+ cache_control = "&cache_control=" + info.cache_control;
+ }
+ var redirect = "";
+
+ var ignore_request_headers = "";
+ if ("request_headers" in info) {
+ // Ignore the request headers that we send since they may be synthesized by the test.
+ ignore_request_headers = "&ignore";
+ }
+ var url_sans_redirect = "resources/cache.py?token=" + uuid +
+ "&content=" + content +
+ "&" + id + "=" + value +
+ "&expires=" + dates[info.state] +
+ vary + cache_control + ignore_request_headers;
+ // If there's a redirect, the target is the script without any redirect at
+ // either the same domain or a different domain.
+ if ("redirect" in info) {
+ var host_info = get_host_info();
+ var origin;
+ switch (info.redirect) {
+ case "same-origin":
+ origin = host_info['HTTP_ORIGIN'];
+ break;
+ case "cross-origin":
+ origin = host_info['HTTP_REMOTE_ORIGIN'];
+ break;
+ }
+ var redirected_url = origin + base_path() + url_sans_redirect;
+ return url_sans_redirect + "&redirect=" + encodeURIComponent(redirected_url);
+ } else {
+ return url_sans_redirect;
+ }
+}
+function expected_status(type, identifier, init) {
+ if (type == "date" &&
+ init.headers &&
+ init.headers["If-Modified-Since"] == identifier) {
+ // The server will respond with a 304 in this case.
+ return [304, "Not Modified"];
+ }
+ return [200, "OK"];
+}
+function expected_response_text(type, identifier, init, content) {
+ if (type == "date" &&
+ init.headers &&
+ init.headers["If-Modified-Since"] == identifier) {
+ // The server will respond with a 304 in this case.
+ return "";
+ }
+ return content;
+}
+function server_state(uuid) {
+ return fetch("resources/cache.py?querystate&token=" + uuid)
+ .then(function(response) {
+ return response.text();
+ }).then(function(text) {
+ // null will be returned if the server never received any requests
+ // for the given uuid. Normalize that to an empty list consistent
+ // with our representation.
+ return JSON.parse(text) || [];
+ });
+}
+function make_test(type, info) {
+ return function(test) {
+ var uuid = token();
+ var identifier = (type == "tag" ? Math.random() : now.toGMTString());
+ var content = Math.random().toString();
+ var url = make_url(uuid, type, identifier, content, info);
+ var fetch_functions = [];
+ for (var i = 0; i < info.request_cache.length; ++i) {
+ fetch_functions.push(function(idx) {
+ var init = {cache: info.request_cache[idx]};
+ if ("request_headers" in info) {
+ init.headers = info.request_headers[idx];
+ }
+ if (init.cache === "only-if-cached") {
+ // only-if-cached requires we use same-origin mode.
+ init.mode = "same-origin";
+ }
+ return fetch(url, init)
+ .then(function(response) {
+ if ("response" in info && info.response[idx] === "error") {
+ assert_true(false, "fetch should have been an error");
+ return;
+ }
+ assert_array_equals([response.status, response.statusText],
+ expected_status(type, identifier, init));
+ return response.text();
+ }).then(function(text) {
+ assert_equals(text, expected_response_text(type, identifier, init, content));
+ }, function(reason) {
+ if ("response" in info && info.response[idx] === "error") {
+ assert_throws_js(TypeError, function() { throw reason; });
+ } else {
+ throw reason;
+ }
+ });
+ });
+ }
+ var i = 0;
+ function run_next_step() {
+ if (fetch_functions.length) {
+ return fetch_functions.shift()(i++)
+ .then(run_next_step);
+ } else {
+ return Promise.resolve();
+ }
+ }
+ return run_next_step()
+ .then(function() {
+ // Now, query the server state
+ return server_state(uuid);
+ }).then(function(state) {
+ var expectedState = [];
+ info.expected_validation_headers.forEach(function (validate) {
+ if (validate) {
+ if (type == "tag") {
+ expectedState.push({"If-None-Match": '"' + identifier + '"'});
+ } else {
+ expectedState.push({"If-Modified-Since": identifier});
+ }
+ } else {
+ expectedState.push({});
+ }
+ });
+ for (var i = 0; i < info.expected_no_cache_headers.length; ++i) {
+ if (info.expected_no_cache_headers[i]) {
+ expectedState[i]["Pragma"] = "no-cache";
+ expectedState[i]["Cache-Control"] = "no-cache";
+ }
+ }
+ if ("expected_max_age_headers" in info) {
+ for (var i = 0; i < info.expected_max_age_headers.length; ++i) {
+ if (info.expected_max_age_headers[i]) {
+ expectedState[i]["Cache-Control"] = "max-age=0";
+ }
+ }
+ }
+ assert_equals(state.length, expectedState.length);
+ for (var i = 0; i < state.length; ++i) {
+ for (var header in state[i]) {
+ assert_equals(state[i][header], expectedState[i][header]);
+ delete expectedState[i][header];
+ }
+ for (var header in expectedState[i]) {
+ assert_false(header in state[i]);
+ }
+ }
+ });
+ };
+}
+
+function run_tests(tests)
+{
+ tests.forEach(function(info) {
+ promise_test(make_test("tag", info), info.name + " with Etag and " + info.state + " response");
+ promise_test(make_test("date", info), info.name + " with Last-Modified and " + info.state + " response");
+ });
+}
diff --git a/testing/web-platform/tests/fetch/api/request/request-clone.sub.html b/testing/web-platform/tests/fetch/api/request/request-clone.sub.html
new file mode 100644
index 0000000000..c690bb3dc0
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-clone.sub.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Request clone</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#request">
+ <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="../resources/utils.js"></script>
+ </head>
+ <body>
+ <script>
+ var headers = new Headers({"name" : "value"});
+ var emptyHeaders = new Headers();
+
+ var initValuesDict = {"method" : "POST",
+ "referrer" : "http://{{host}}:{{ports[http][0]}}/",
+ "referrerPolicy" : "origin",
+ "mode" : "same-origin",
+ "credentials" : "include",
+ "cache" : "no-cache",
+ "redirect" : "error",
+ "integrity" : "Request's Integrity",
+ "headers" : headers,
+ "body" : "Request's body"
+ };
+
+ var expectedInitialized = {"method" : "POST",
+ "referrer" : "http://{{host}}:{{ports[http][0]}}/",
+ "referrerPolicy" : "origin",
+ "mode" : "same-origin",
+ "credentials" : "include",
+ "cache" : "no-cache",
+ "redirect" : "error",
+ "integrity" : "Request's Integrity",
+ "headers" : headers,
+ "body" : "Request's body"
+ };
+
+ test(function() {
+ var RequestInitialized = new Request("", initValuesDict);
+ var requestToCheck = RequestInitialized.clone();
+ checkRequest(requestToCheck, expectedInitialized);
+ }, "Check cloning a request");
+
+ test(function() {
+ var initialRequest = new Request("", {"headers" : new Headers({"a": "1", "b" : "2"})});
+ var request = initialRequest.clone();
+ assert_equals(request.headers.get("a"), "1", "cloned request should have header 'a'");
+ assert_equals(request.headers.get("b"), "2", "cloned request should have header 'b'");
+
+ initialRequest.headers.delete("a");
+ assert_equals(request.headers.get("a"), "1", "cloned request should still have header 'a'");
+
+ request.headers.delete("a");
+ assert_equals(initialRequest.headers.get("b"), "2", "initial request should have header 'b'");
+
+ }, "Check cloning a request copies the headers");
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js b/testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js
new file mode 100644
index 0000000000..034a86041a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-consume-empty.any.js
@@ -0,0 +1,101 @@
+// META: global=window,worker
+// META: title=Request consume empty bodies
+
+function checkBodyText(test, request) {
+ return request.text().then(function(bodyAsText) {
+ assert_equals(bodyAsText, "", "Resolved value should be empty");
+ assert_false(request.bodyUsed);
+ });
+}
+
+function checkBodyBlob(test, request) {
+ return request.blob().then(function(bodyAsBlob) {
+ var promise = new Promise(function(resolve, reject) {
+ var reader = new FileReader();
+ reader.onload = function(evt) {
+ resolve(reader.result)
+ };
+ reader.onerror = function() {
+ reject("Blob's reader failed");
+ };
+ reader.readAsText(bodyAsBlob);
+ });
+ return promise.then(function(body) {
+ assert_equals(body, "", "Resolved value should be empty");
+ assert_false(request.bodyUsed);
+ });
+ });
+}
+
+function checkBodyArrayBuffer(test, request) {
+ return request.arrayBuffer().then(function(bodyAsArrayBuffer) {
+ assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty");
+ assert_false(request.bodyUsed);
+ });
+}
+
+function checkBodyJSON(test, request) {
+ return request.json().then(
+ function(bodyAsJSON) {
+ assert_unreached("JSON parsing should fail");
+ },
+ function() {
+ assert_false(request.bodyUsed);
+ });
+}
+
+function checkBodyFormData(test, request) {
+ return request.formData().then(function(bodyAsFormData) {
+ assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData");
+ assert_false(request.bodyUsed);
+ });
+}
+
+function checkBodyFormDataError(test, request) {
+ return promise_rejects_js(test, TypeError, request.formData()).then(function() {
+ assert_false(request.bodyUsed);
+ });
+}
+
+function checkRequestWithNoBody(bodyType, checkFunction, headers = []) {
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "headers": headers});
+ assert_false(request.bodyUsed);
+ return checkFunction(test, request);
+ }, "Consume request's body as " + bodyType);
+}
+
+checkRequestWithNoBody("text", checkBodyText);
+checkRequestWithNoBody("blob", checkBodyBlob);
+checkRequestWithNoBody("arrayBuffer", checkBodyArrayBuffer);
+checkRequestWithNoBody("json (error case)", checkBodyJSON);
+checkRequestWithNoBody("formData with correct multipart type (error case)", checkBodyFormDataError, [["Content-Type", 'multipart/form-data; boundary="boundary"']]);
+checkRequestWithNoBody("formData with correct urlencoded type", checkBodyFormData, [["Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"]]);
+checkRequestWithNoBody("formData without correct type (error case)", checkBodyFormDataError);
+
+function checkRequestWithEmptyBody(bodyType, body, asText) {
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": body});
+ assert_false(request.bodyUsed, "bodyUsed is false at init");
+ if (asText) {
+ return request.text().then(function(bodyAsString) {
+ assert_equals(bodyAsString.length, 0, "Resolved value should be empty");
+ assert_true(request.bodyUsed, "bodyUsed is true after being consumed");
+ });
+ }
+ return request.arrayBuffer().then(function(bodyAsArrayBuffer) {
+ assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty");
+ assert_true(request.bodyUsed, "bodyUsed is true after being consumed");
+ });
+ }, "Consume empty " + bodyType + " request body as " + (asText ? "text" : "arrayBuffer"));
+}
+
+// FIXME: Add BufferSource, FormData and URLSearchParams.
+checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), false);
+checkRequestWithEmptyBody("text", "", false);
+checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), true);
+checkRequestWithEmptyBody("text", "", true);
+checkRequestWithEmptyBody("URLSearchParams", new URLSearchParams(""), true);
+// FIXME: This test assumes that the empty string be returned but it is not clear whether that is right. See https://github.com/web-platform-tests/wpt/pull/3950.
+checkRequestWithEmptyBody("FormData", new FormData(), true);
+checkRequestWithEmptyBody("ArrayBuffer", new ArrayBuffer(), true);
diff --git a/testing/web-platform/tests/fetch/api/request/request-consume.any.js b/testing/web-platform/tests/fetch/api/request/request-consume.any.js
new file mode 100644
index 0000000000..aff5d65244
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-consume.any.js
@@ -0,0 +1,145 @@
+// META: global=window,worker
+// META: title=Request consume
+// META: script=../resources/utils.js
+
+function checkBodyText(request, expectedBody) {
+ return request.text().then(function(bodyAsText) {
+ assert_equals(bodyAsText, expectedBody, "Retrieve and verify request's body");
+ assert_true(request.bodyUsed, "body as text: bodyUsed turned true");
+ });
+}
+
+function checkBodyBlob(request, expectedBody, checkContentType) {
+ return request.blob().then(function(bodyAsBlob) {
+ if (checkContentType)
+ assert_equals(bodyAsBlob.type, "text/plain", "Blob body type should be computed from the request Content-Type");
+
+ var promise = new Promise(function (resolve, reject) {
+ var reader = new FileReader();
+ reader.onload = function(evt) {
+ resolve(reader.result)
+ };
+ reader.onerror = function() {
+ reject("Blob's reader failed");
+ };
+ reader.readAsText(bodyAsBlob);
+ });
+ return promise.then(function(body) {
+ assert_equals(body, expectedBody, "Retrieve and verify request's body");
+ assert_true(request.bodyUsed, "body as blob: bodyUsed turned true");
+ });
+ });
+}
+
+function checkBodyArrayBuffer(request, expectedBody) {
+ return request.arrayBuffer().then(function(bodyAsArrayBuffer) {
+ validateBufferFromString(bodyAsArrayBuffer, expectedBody, "Retrieve and verify request's body");
+ assert_true(request.bodyUsed, "body as arrayBuffer: bodyUsed turned true");
+ });
+}
+
+function checkBodyJSON(request, expectedBody) {
+ return request.json().then(function(bodyAsJSON) {
+ var strBody = JSON.stringify(bodyAsJSON)
+ assert_equals(strBody, expectedBody, "Retrieve and verify request's body");
+ assert_true(request.bodyUsed, "body as json: bodyUsed turned true");
+ });
+}
+
+function checkBodyFormData(request, expectedBody) {
+ return request.formData().then(function(bodyAsFormData) {
+ assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData");
+ assert_true(request.bodyUsed, "body as formData: bodyUsed turned true");
+ });
+}
+
+function checkRequestBody(body, expected, bodyType) {
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": body, "headers": [["Content-Type", "text/PLAIN"]] });
+ assert_false(request.bodyUsed, "bodyUsed is false at init");
+ return checkBodyText(request, expected);
+ }, "Consume " + bodyType + " request's body as text");
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": body });
+ assert_false(request.bodyUsed, "bodyUsed is false at init");
+ return checkBodyBlob(request, expected);
+ }, "Consume " + bodyType + " request's body as blob");
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": body });
+ assert_false(request.bodyUsed, "bodyUsed is false at init");
+ return checkBodyArrayBuffer(request, expected);
+ }, "Consume " + bodyType + " request's body as arrayBuffer");
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": body });
+ assert_false(request.bodyUsed, "bodyUsed is false at init");
+ return checkBodyJSON(request, expected);
+ }, "Consume " + bodyType + " request's body as JSON");
+}
+
+var textData = JSON.stringify("This is response's body");
+var blob = new Blob([textData], { "type" : "text/plain" });
+
+checkRequestBody(textData, textData, "String");
+
+var string = "\"123456\"";
+function getArrayBuffer() {
+ var arrayBuffer = new ArrayBuffer(8);
+ var int8Array = new Int8Array(arrayBuffer);
+ for (var cptr = 0; cptr < 8; cptr++)
+ int8Array[cptr] = string.charCodeAt(cptr);
+ return arrayBuffer;
+}
+
+function getArrayBufferWithZeros() {
+ var arrayBuffer = new ArrayBuffer(10);
+ var int8Array = new Int8Array(arrayBuffer);
+ for (var cptr = 0; cptr < 8; cptr++)
+ int8Array[cptr + 1] = string.charCodeAt(cptr);
+ return arrayBuffer;
+}
+
+checkRequestBody(getArrayBuffer(), string, "ArrayBuffer");
+checkRequestBody(new Uint8Array(getArrayBuffer()), string, "Uint8Array");
+checkRequestBody(new Int8Array(getArrayBufferWithZeros(), 1, 8), string, "Int8Array");
+checkRequestBody(new Float32Array(getArrayBuffer()), string, "Float32Array");
+checkRequestBody(new DataView(getArrayBufferWithZeros(), 1, 8), string, "DataView");
+
+promise_test(function(test) {
+ var formData = new FormData();
+ formData.append("name", "value")
+ var request = new Request("", {"method": "POST", "body": formData });
+ assert_false(request.bodyUsed, "bodyUsed is false at init");
+ return checkBodyFormData(request, formData);
+}, "Consume FormData request's body as FormData");
+
+function checkBlobResponseBody(blobBody, blobData, bodyType, checkFunction) {
+ promise_test(function(test) {
+ var response = new Response(blobBody);
+ assert_false(response.bodyUsed, "bodyUsed is false at init");
+ return checkFunction(response, blobData);
+ }, "Consume blob response's body as " + bodyType);
+}
+
+checkBlobResponseBody(blob, textData, "blob", checkBodyBlob);
+checkBlobResponseBody(blob, textData, "text", checkBodyText);
+checkBlobResponseBody(blob, textData, "json", checkBodyJSON);
+checkBlobResponseBody(blob, textData, "arrayBuffer", checkBodyArrayBuffer);
+checkBlobResponseBody(new Blob([""]), "", "blob (empty blob as input)", checkBodyBlob);
+
+var goodJSONValues = ["null", "1", "true", "\"string\""];
+goodJSONValues.forEach(function(value) {
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": value});
+ return request.json().then(function(v) {
+ assert_equals(v, JSON.parse(value));
+ });
+ }, "Consume JSON from text: '" + JSON.stringify(value) + "'");
+});
+
+var badJSONValues = ["undefined", "{", "a", "["];
+badJSONValues.forEach(function(value) {
+ promise_test(function(test) {
+ var request = new Request("", {"method": "POST", "body": value});
+ return promise_rejects_js(test, SyntaxError, request.json());
+ }, "Trying to consume bad JSON text as JSON: '" + value + "'");
+});
diff --git a/testing/web-platform/tests/fetch/api/request/request-disturbed.any.js b/testing/web-platform/tests/fetch/api/request/request-disturbed.any.js
new file mode 100644
index 0000000000..8a11de78ff
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-disturbed.any.js
@@ -0,0 +1,109 @@
+// META: global=window,worker
+// META: title=Request disturbed
+// META: script=../resources/utils.js
+
+var initValuesDict = {"method" : "POST",
+ "body" : "Request's body"
+};
+
+var noBodyConsumed = new Request("");
+var bodyConsumed = new Request("", initValuesDict);
+
+test(() => {
+ assert_equals(noBodyConsumed.body, null, "body's default value is null");
+ assert_false(noBodyConsumed.bodyUsed , "bodyUsed is false when request is not disturbed");
+ assert_not_equals(bodyConsumed.body, null, "non-null body");
+ assert_true(bodyConsumed.body instanceof ReadableStream, "non-null body type");
+ assert_false(noBodyConsumed.bodyUsed, "bodyUsed is false when request is not disturbed");
+}, "Request's body: initial state");
+
+noBodyConsumed.blob();
+bodyConsumed.blob();
+
+test(function() {
+ assert_false(noBodyConsumed.bodyUsed , "bodyUsed is false when request is not disturbed");
+ try {
+ noBodyConsumed.clone();
+ } catch (e) {
+ assert_unreached("Can use request not disturbed for creating or cloning request");
+ }
+}, "Request without body cannot be disturbed");
+
+test(function() {
+ assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
+ assert_throws_js(TypeError, function() { bodyConsumed.clone(); });
+}, "Check cloning a disturbed request");
+
+test(function() {
+ assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
+ assert_throws_js(TypeError, function() { new Request(bodyConsumed); });
+}, "Check creating a new request from a disturbed request");
+
+promise_test(function() {
+ assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
+ const originalBody = bodyConsumed.body;
+ const bodyReplaced = new Request(bodyConsumed, { body: "Replaced body" });
+ assert_not_equals(bodyReplaced.body, originalBody, "new request's body is new");
+ assert_false(bodyReplaced.bodyUsed, "bodyUsed is false when request is not disturbed");
+ return bodyReplaced.text().then(text => {
+ assert_equals(text, "Replaced body");
+ });
+}, "Check creating a new request with a new body from a disturbed request");
+
+promise_test(function() {
+ var bodyRequest = new Request("", initValuesDict);
+ const originalBody = bodyRequest.body;
+ assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed");
+ var requestFromRequest = new Request(bodyRequest);
+ assert_true(bodyRequest.bodyUsed , "bodyUsed is true when request is disturbed");
+ assert_equals(bodyRequest.body, originalBody, "body should not change");
+ assert_not_equals(originalBody, undefined, "body should not be undefined");
+ assert_not_equals(originalBody, null, "body should not be null");
+ assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new");
+ return requestFromRequest.text().then(text => {
+ assert_equals(text, "Request's body");
+ });
+}, "Input request used for creating new request became disturbed");
+
+promise_test(() => {
+ const bodyRequest = new Request("", initValuesDict);
+ const originalBody = bodyRequest.body;
+ assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed");
+ const requestFromRequest = new Request(bodyRequest, { body : "init body" });
+ assert_true(bodyRequest.bodyUsed , "bodyUsed is true when request is disturbed");
+ assert_equals(bodyRequest.body, originalBody, "body should not change");
+ assert_not_equals(originalBody, undefined, "body should not be undefined");
+ assert_not_equals(originalBody, null, "body should not be null");
+ assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new");
+
+ return requestFromRequest.text().then(text => {
+ assert_equals(text, "init body");
+ });
+}, "Input request used for creating new request became disturbed even if body is not used");
+
+promise_test(function(test) {
+ assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
+ return promise_rejects_js(test, TypeError, bodyConsumed.blob());
+}, "Check consuming a disturbed request");
+
+test(function() {
+ var req = new Request(URL, {method: 'POST', body: 'hello'});
+ assert_false(req.bodyUsed,
+ 'Request should not be flagged as used if it has not been ' +
+ 'consumed.');
+ assert_throws_js(TypeError,
+ function() { new Request(req, {method: 'GET'}); },
+ 'A get request may not have body.');
+
+ assert_false(req.bodyUsed, 'After the GET case');
+
+ assert_throws_js(TypeError,
+ function() { new Request(req, {method: 'CONNECT'}); },
+ 'Request() with a forbidden method must throw.');
+
+ assert_false(req.bodyUsed, 'After the forbidden method case');
+
+ var req2 = new Request(req);
+ assert_true(req.bodyUsed,
+ 'Request should be flagged as used if it has been consumed.');
+}, 'Request construction failure should not set "bodyUsed"');
diff --git a/testing/web-platform/tests/fetch/api/request/request-error.any.js b/testing/web-platform/tests/fetch/api/request/request-error.any.js
new file mode 100644
index 0000000000..9ec8015198
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-error.any.js
@@ -0,0 +1,56 @@
+// META: global=window,worker
+// META: title=Request error
+// META: script=request-error.js
+
+// badRequestArgTests is from response-error.js
+for (const { args, testName } of badRequestArgTests) {
+ test(() => {
+ assert_throws_js(
+ TypeError,
+ () => new Request(...args),
+ "Expect TypeError exception"
+ );
+ }, testName);
+}
+
+test(function() {
+ assert_throws_js(
+ TypeError,
+ () => Request("about:blank"),
+ "Calling Request constructor without 'new' must throw"
+ );
+});
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders});
+ var request = new Request(initialRequest);
+ assert_equals(request.headers.get("Content-Type"), "potato");
+}, "Request should get its content-type from the init request");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders});
+ var headers = new Headers([]);
+ var request = new Request(initialRequest, {"headers" : headers});
+ assert_false(request.headers.has("Content-Type"));
+}, "Request should not get its content-type from the init request if init headers are provided");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type-Extra", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
+ var request = new Request(initialRequest);
+ assert_equals(request.headers.get("Content-Type"), "text/plain;charset=UTF-8");
+}, "Request should get its content-type from the body if none is provided");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
+ var request = new Request(initialRequest);
+ assert_equals(request.headers.get("Content-Type"), "potato");
+}, "Request should get its content-type from init headers if one is provided");
+
+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");
diff --git a/testing/web-platform/tests/fetch/api/request/request-error.js b/testing/web-platform/tests/fetch/api/request/request-error.js
new file mode 100644
index 0000000000..cf77313f5b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-error.js
@@ -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`
+ };
+ })
+);
diff --git a/testing/web-platform/tests/fetch/api/request/request-headers.any.js b/testing/web-platform/tests/fetch/api/request/request-headers.any.js
new file mode 100644
index 0000000000..b73b398013
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-headers.any.js
@@ -0,0 +1,176 @@
+// META: global=window,worker
+// META: title=Request Headers
+
+var validRequestHeaders = [
+ ["Content-Type", "OK"],
+ ["Potato", "OK"],
+ ["proxy", "OK"],
+ ["proxya", "OK"],
+ ["sec", "OK"],
+ ["secb", "OK"],
+ ["Set-Cookie2", "OK"],
+];
+var invalidRequestHeaders = [
+ ["Accept-Charset", "KO"],
+ ["accept-charset", "KO"],
+ ["ACCEPT-ENCODING", "KO"],
+ ["Accept-Encoding", "KO"],
+ ["Access-Control-Request-Headers", "KO"],
+ ["Access-Control-Request-Method", "KO"],
+ ["Access-Control-Request-Private-Network", "KO"],
+ ["Connection", "KO"],
+ ["Content-Length", "KO"],
+ ["Cookie", "KO"],
+ ["Cookie2", "KO"],
+ ["Date", "KO"],
+ ["DNT", "KO"],
+ ["Expect", "KO"],
+ ["Host", "KO"],
+ ["Keep-Alive", "KO"],
+ ["Origin", "KO"],
+ ["Referer", "KO"],
+ ["Set-Cookie", "KO"],
+ ["TE", "KO"],
+ ["Trailer", "KO"],
+ ["Transfer-Encoding", "KO"],
+ ["Upgrade", "KO"],
+ ["Via", "KO"],
+ ["Proxy-", "KO"],
+ ["proxy-a", "KO"],
+ ["Sec-", "KO"],
+ ["sec-b", "KO"],
+];
+
+var validRequestNoCorsHeaders = [
+ ["Accept", "OK"],
+ ["Accept-Language", "OK"],
+ ["content-language", "OK"],
+ ["content-type", "application/x-www-form-urlencoded"],
+ ["content-type", "application/x-www-form-urlencoded;charset=UTF-8"],
+ ["content-type", "multipart/form-data"],
+ ["content-type", "multipart/form-data;charset=UTF-8"],
+ ["content-TYPE", "text/plain"],
+ ["CONTENT-type", "text/plain;charset=UTF-8"],
+];
+var invalidRequestNoCorsHeaders = [
+ ["Content-Type", "KO"],
+ ["Potato", "KO"],
+ ["proxy", "KO"],
+ ["proxya", "KO"],
+ ["sec", "KO"],
+ ["secb", "KO"],
+];
+
+validRequestHeaders.forEach(function(header) {
+ test(function() {
+ var request = new Request("");
+ request.headers.set(header[0], header[1]);
+ assert_equals(request.headers.get(header[0]), header[1]);
+ }, "Adding valid request header \"" + header[0] + ": " + header[1] + "\"");
+});
+invalidRequestHeaders.forEach(function(header) {
+ test(function() {
+ var request = new Request("");
+ request.headers.set(header[0], header[1]);
+ assert_equals(request.headers.get(header[0]), null);
+ }, "Adding invalid request header \"" + header[0] + ": " + header[1] + "\"");
+});
+
+validRequestNoCorsHeaders.forEach(function(header) {
+ test(function() {
+ var requestNoCors = new Request("", {"mode": "no-cors"});
+ requestNoCors.headers.set(header[0], header[1]);
+ assert_equals(requestNoCors.headers.get(header[0]), header[1]);
+ }, "Adding valid no-cors request header \"" + header[0] + ": " + header[1] + "\"");
+});
+invalidRequestNoCorsHeaders.forEach(function(header) {
+ test(function() {
+ var requestNoCors = new Request("", {"mode": "no-cors"});
+ requestNoCors.headers.set(header[0], header[1]);
+ assert_equals(requestNoCors.headers.get(header[0]), null);
+ }, "Adding invalid no-cors request header \"" + header[0] + ": " + header[1] + "\"");
+});
+
+test(function() {
+ var headers = new Headers([["Cookie2", "potato"]]);
+ var request = new Request("", {"headers": headers});
+ assert_equals(request.headers.get("Cookie2"), null);
+}, "Check that request constructor is filtering headers provided as init parameter");
+
+test(function() {
+ var headers = new Headers([["Content-Type", "potato"]]);
+ var request = new Request("", {"headers": headers, "mode": "no-cors"});
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Check that no-cors request constructor is filtering headers provided as init parameter");
+
+test(function() {
+ var headers = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers": headers});
+ var request = new Request(initialRequest, {"mode": "no-cors"});
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Check that no-cors request constructor is filtering headers provided as part of request parameter");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders});
+ var request = new Request(initialRequest);
+ assert_equals(request.headers.get("Content-Type"), "potato");
+}, "Request should get its content-type from the init request");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders});
+ var headers = new Headers([]);
+ var request = new Request(initialRequest, {"headers" : headers});
+ assert_false(request.headers.has("Content-Type"));
+}, "Request should not get its content-type from the init request if init headers are provided");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type-Extra", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
+ var request = new Request(initialRequest);
+ assert_equals(request.headers.get("Content-Type"), "text/plain;charset=UTF-8");
+}, "Request should get its content-type from the body if none is provided");
+
+test(function() {
+ var initialHeaders = new Headers([["Content-Type", "potato"]]);
+ var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
+ var request = new Request(initialRequest);
+ assert_equals(request.headers.get("Content-Type"), "potato");
+}, "Request should get its content-type from init headers if one is provided");
+
+test(function() {
+ var array = [["hello", "worldAHH"]];
+ var object = {"hello": 'worldOOH'};
+ var headers = new Headers(array);
+
+ assert_equals(headers.get("hello"), "worldAHH");
+
+ var request1 = new Request("", {"headers": headers});
+ var request2 = new Request("", {"headers": array});
+ var request3 = new Request("", {"headers": object});
+
+ assert_equals(request1.headers.get("hello"), "worldAHH");
+ assert_equals(request2.headers.get("hello"), "worldAHH");
+ assert_equals(request3.headers.get("hello"), "worldOOH");
+}, "Testing request header creations with various objects");
+
+promise_test(function(test) {
+ var request = new Request("", {"headers" : [["Content-Type", ""]], "body" : "this is my plate", "method" : "POST"});
+ return request.blob().then(function(blob) {
+ assert_equals(blob.type, "", "Blob type should be the empty string");
+ });
+}, "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");
diff --git a/testing/web-platform/tests/fetch/api/request/request-init-001.sub.html b/testing/web-platform/tests/fetch/api/request/request-init-001.sub.html
new file mode 100644
index 0000000000..cc495a6652
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-init-001.sub.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Request init: simple cases</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#request">
+ <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>
+ </head>
+ <body>
+ <script>
+ // https://fetch.spec.whatwg.org/#concept-method-normalize
+ var methods = {
+ "givenValues" : [
+ "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS",
+ "get", "head", "post", "put", "delete", "options",
+ "Get", "hEad", "poSt", "Put", "deleTe", "optionS",
+ "PATCH", "patch", "patCh"
+ ],
+ "expectedValues" : [
+ "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS",
+ "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS",
+ "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS",
+ "PATCH", "patch", "patCh"
+ ]
+ };
+ var referrers = {"givenValues" : ["/relative/ressource",
+ "http://{{host}}:{{ports[http][0]}}/relative/ressource?query=true#fragment",
+ "http://{{host}}:{{ports[http][0]}}/",
+ "http://test.url",
+ "about:client",
+ ""
+ ],
+ "expectedValues" : ["http://{{host}}:{{ports[http][0]}}/relative/ressource",
+ "http://{{host}}:{{ports[http][0]}}/relative/ressource?query=true#fragment",
+ "http://{{host}}:{{ports[http][0]}}/",
+ "about:client",
+ "about:client",
+ ""
+ ]
+ };
+ var referrerPolicies = {"givenValues" : [ "",
+ "no-referrer",
+ "no-referrer-when-downgrade",
+ "origin",
+ "origin-when-cross-origin",
+ "unsafe-url",
+ "same-origin",
+ "strict-origin",
+ "strict-origin-when-cross-origin"
+ ],
+ "expectedValues" : ["",
+ "no-referrer",
+ "no-referrer-when-downgrade",
+ "origin",
+ "origin-when-cross-origin",
+ "unsafe-url",
+ "same-origin",
+ "strict-origin",
+ "strict-origin-when-cross-origin"
+ ]
+ };
+ var modes = {"givenValues" : ["same-origin", "no-cors", "cors"],
+ "expectedValues" : ["same-origin", "no-cors", "cors"]
+ };
+ var credentials = {"givenValues" : ["omit", "same-origin", "include"],
+ "expectedValues" : ["omit", "same-origin", "include"]
+ };
+ var caches = {"givenValues" : [ "default", "no-store", "reload", "no-cache", "force-cache"],
+ "expectedValues" : [ "default", "no-store", "reload", "no-cache", "force-cache"]
+ };
+ var redirects = {"givenValues" : ["follow", "error", "manual"],
+ "expectedValues" : ["follow", "error", "manual"]
+ };
+ var integrities = {"givenValues" : ["", "AZERTYUIOP1234567890" ],
+ "expectedValues" : ["", "AZERTYUIOP1234567890"]
+ };
+
+ //there is no getter for window, init's window might be null
+ var windows = {"givenValues" : [ null ],
+ "expectedValues" : [undefined]
+ };
+
+ var initValuesDict = { "method" : methods,
+ "referrer" : referrers,
+ "referrerPolicy" : referrerPolicies,
+ "mode" : modes,
+ "credentials" : credentials,
+ "cache" : caches,
+ "redirect" : redirects,
+ "integrity" : integrities,
+ "window" : windows
+ };
+
+ for (var attributeName in initValuesDict) {
+ var valuesToTest = initValuesDict[attributeName];
+ for (var valueIdx in valuesToTest["givenValues"]) {
+ var givenValue = valuesToTest["givenValues"][valueIdx];
+ var expectedValue = valuesToTest["expectedValues"][valueIdx];
+ test(function() {
+ var requestInit = {};
+ requestInit[attributeName] = givenValue
+ var request = new Request("", requestInit);
+ assert_equals(request[attributeName], expectedValue,
+ "Expect request's " + attributeName + " is " + expectedValue + " when initialized with " + givenValue);
+ }, "Check " + attributeName + " init value of " + givenValue + " and associated getter");
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fetch/api/request/request-init-002.any.js b/testing/web-platform/tests/fetch/api/request/request-init-002.any.js
new file mode 100644
index 0000000000..abb6689f1e
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-init-002.any.js
@@ -0,0 +1,60 @@
+// META: global=window,worker
+// META: title=Request init: headers and body
+
+test(function() {
+ var headerDict = {"name1": "value1",
+ "name2": "value2",
+ "name3": "value3"
+ };
+ var headers = new Headers(headerDict);
+ var request = new Request("", { "headers" : headers })
+ for (var name in headerDict) {
+ assert_equals(request.headers.get(name), headerDict[name],
+ "request's headers has " + name + " : " + headerDict[name]);
+ }
+}, "Initialize Request with headers values");
+
+function makeRequestInit(body, method) {
+ return {"method": method, "body": body};
+}
+
+function checkRequestInit(body, bodyType, expectedTextBody) {
+ promise_test(function(test) {
+ var request = new Request("", makeRequestInit(body, "POST"));
+ if (body) {
+ assert_throws_js(TypeError, function() { new Request("", makeRequestInit(body, "GET")); });
+ assert_throws_js(TypeError, function() { new Request("", makeRequestInit(body, "HEAD")); });
+ } else {
+ new Request("", makeRequestInit(body, "GET")); // should not throw
+ }
+ var reqHeaders = request.headers;
+ var mime = reqHeaders.get("Content-Type");
+ assert_true(!body || (mime && mime.search(bodyType) > -1), "Content-Type header should be \"" + bodyType + "\", not \"" + mime + "\"");
+ return request.text().then(function(bodyAsText) {
+ //not equals: cannot guess formData exact value
+ assert_true( bodyAsText.search(expectedTextBody) > -1, "Retrieve and verify request body");
+ });
+ }, `Initialize Request's body with "${body}", ${bodyType}`);
+}
+
+var blob = new Blob(["This is a blob"], {type: "application/octet-binary"});
+var formaData = new FormData();
+formaData.append("name", "value");
+var usvString = "This is a USVString"
+
+checkRequestInit(undefined, undefined, "");
+checkRequestInit(null, null, "");
+checkRequestInit(blob, "application/octet-binary", "This is a blob");
+checkRequestInit(formaData, "multipart/form-data", "name=\"name\"\r\n\r\nvalue");
+checkRequestInit(usvString, "text/plain;charset=UTF-8", "This is a USVString");
+checkRequestInit({toString: () => "hi!"}, "text/plain;charset=UTF-8", "hi!");
+
+// Ensure test does not time out in case of missing URLSearchParams support.
+if (self.URLSearchParams) {
+ var urlSearchParams = new URLSearchParams("name=value");
+ checkRequestInit(urlSearchParams, "application/x-www-form-urlencoded;charset=UTF-8", "name=value");
+} else {
+ promise_test(function(test) {
+ return Promise.reject("URLSearchParams not supported");
+ }, "Initialize Request's body with application/x-www-form-urlencoded;charset=UTF-8");
+}
diff --git a/testing/web-platform/tests/fetch/api/request/request-init-003.sub.html b/testing/web-platform/tests/fetch/api/request/request-init-003.sub.html
new file mode 100644
index 0000000000..79c91cdfe8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-init-003.sub.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Request: init with request or url</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#request">
+ <meta name="help" href="https://url.spec.whatwg.org/#concept-url-serializer">
+ <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>
+ </head>
+ <body>
+ <script src="../resources/utils.js"></script>
+ <script>
+ var headers = new Headers( {"name":"value"} );
+ var emptyHeaders = new Headers();
+
+ var initValuesDict = {"method" : "POST",
+ "referrer" : "http://{{host}}:{{ports[http][0]}}/",
+ "referrerPolicy" : "origin",
+ "mode" : "same-origin",
+ "credentials" : "include",
+ "cache" : "no-cache",
+ "redirect" : "error",
+ "integrity" : "Request's Integrity",
+ "headers" : headers,
+ "body" : "Request's body"
+ };
+
+ var expectedInitialized = {"method" : "POST",
+ "referrer" : "http://{{host}}:{{ports[http][0]}}/",
+ "referrerPolicy" : "origin",
+ "mode" : "same-origin",
+ "credentials" : "include",
+ "cache" : "no-cache",
+ "redirect" : "error",
+ "integrity" : "Request's Integrity",
+ "headers" : headers,
+ "body" : "Request's body"
+ };
+
+ var expectedDefault = {"method" : "GET",
+ "url" : location.href,
+ "referrer" : "about:client",
+ "referrerPolicy" : "",
+ "mode" : "cors",
+ "credentials" : "same-origin",
+ "cache" : "default",
+ "redirect" : "follow",
+ "integrity" : "",
+ "headers" : emptyHeaders
+ };
+
+ var requestDefault = new Request("");
+ var requestInitialized = new Request("", initValuesDict);
+
+ test(function() {
+ var requestToCheck = new Request(requestInitialized);
+ checkRequest(requestToCheck, expectedInitialized);
+ }, "Check request values when initialized from Request");
+
+ test(function() {
+ var requestToCheck = new Request(requestDefault, initValuesDict);
+ checkRequest(requestToCheck, expectedInitialized);
+ }, "Check request values when initialized from Request and init values");
+
+ test(function() {
+ var url = "http://url.test:1234/path/subpath?query=true";
+ url += "#fragment";
+ expectedDefault["url"] = url;
+ var requestToCheck = new Request(url);
+ checkRequest(requestToCheck, expectedDefault);
+ }, "Check request values when initialized from url string");
+
+ test(function() {
+ var url = "http://url.test:1234/path/subpath?query=true";
+ url += "#fragment";
+ expectedInitialized["url"] = url;
+ var requestToCheck = new Request(url , initValuesDict);
+ checkRequest(requestToCheck, expectedInitialized);
+ }, "Check request values when initialized from url and init values");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js b/testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js
new file mode 100644
index 0000000000..18a6969d4f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-init-contenttype.any.js
@@ -0,0 +1,141 @@
+function requestFromBody(body) {
+ return new Request(
+ "https://example.com",
+ {
+ method: "POST",
+ body,
+ duplex: "half",
+ },
+ );
+}
+
+test(() => {
+ const request = requestFromBody(undefined);
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Default Content-Type for Request with empty body");
+
+test(() => {
+ const blob = new Blob([]);
+ const request = requestFromBody(blob);
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Default Content-Type for Request with Blob body (no type set)");
+
+test(() => {
+ const blob = new Blob([], { type: "" });
+ const request = requestFromBody(blob);
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Default Content-Type for Request with Blob body (empty type)");
+
+test(() => {
+ const blob = new Blob([], { type: "a/b; c=d" });
+ const request = requestFromBody(blob);
+ assert_equals(request.headers.get("Content-Type"), "a/b; c=d");
+}, "Default Content-Type for Request with Blob body (set type)");
+
+test(() => {
+ const buffer = new Uint8Array();
+ const request = requestFromBody(buffer);
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Default Content-Type for Request with buffer source body");
+
+promise_test(async () => {
+ const formData = new FormData();
+ formData.append("a", "b");
+ const request = requestFromBody(formData);
+ const boundary = (await request.text()).split("\r\n")[0].slice(2);
+ assert_equals(
+ request.headers.get("Content-Type"),
+ `multipart/form-data; boundary=${boundary}`,
+ );
+}, "Default Content-Type for Request with FormData body");
+
+test(() => {
+ const usp = new URLSearchParams();
+ const request = requestFromBody(usp);
+ assert_equals(
+ request.headers.get("Content-Type"),
+ "application/x-www-form-urlencoded;charset=UTF-8",
+ );
+}, "Default Content-Type for Request with URLSearchParams body");
+
+test(() => {
+ const request = requestFromBody("");
+ assert_equals(
+ request.headers.get("Content-Type"),
+ "text/plain;charset=UTF-8",
+ );
+}, "Default Content-Type for Request with string body");
+
+test(() => {
+ const stream = new ReadableStream();
+ const request = requestFromBody(stream);
+ assert_equals(request.headers.get("Content-Type"), null);
+}, "Default Content-Type for Request with ReadableStream body");
+
+// -----------------------------------------------------------------------------
+
+const OVERRIDE_MIME = "test/only; mime=type";
+
+function requestFromBodyWithOverrideMime(body) {
+ return new Request(
+ "https://example.com",
+ {
+ method: "POST",
+ body,
+ headers: { "Content-Type": OVERRIDE_MIME },
+ duplex: "half",
+ },
+ );
+}
+
+test(() => {
+ const request = requestFromBodyWithOverrideMime(undefined);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with empty body");
+
+test(() => {
+ const blob = new Blob([]);
+ const request = requestFromBodyWithOverrideMime(blob);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with Blob body (no type set)");
+
+test(() => {
+ const blob = new Blob([], { type: "" });
+ const request = requestFromBodyWithOverrideMime(blob);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with Blob body (empty type)");
+
+test(() => {
+ const blob = new Blob([], { type: "a/b; c=d" });
+ const request = requestFromBodyWithOverrideMime(blob);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with Blob body (set type)");
+
+test(() => {
+ const buffer = new Uint8Array();
+ const request = requestFromBodyWithOverrideMime(buffer);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with buffer source body");
+
+test(() => {
+ const formData = new FormData();
+ const request = requestFromBodyWithOverrideMime(formData);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with FormData body");
+
+test(() => {
+ const usp = new URLSearchParams();
+ const request = requestFromBodyWithOverrideMime(usp);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with URLSearchParams body");
+
+test(() => {
+ const request = requestFromBodyWithOverrideMime("");
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with string body");
+
+test(() => {
+ const stream = new ReadableStream();
+ const request = requestFromBodyWithOverrideMime(stream);
+ assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
+}, "Can override Content-Type for Request with ReadableStream body");
diff --git a/testing/web-platform/tests/fetch/api/request/request-init-stream.any.js b/testing/web-platform/tests/fetch/api/request/request-init-stream.any.js
new file mode 100644
index 0000000000..f0ae441a00
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-init-stream.any.js
@@ -0,0 +1,147 @@
+// META: global=window,worker
+
+"use strict";
+
+const duplex = "half";
+const method = "POST";
+
+test(() => {
+ const body = new ReadableStream();
+ const request = new Request("...", { method, body, duplex });
+ assert_equals(request.body, body);
+}, "Constructing a Request with a stream holds the original object.");
+
+test((t) => {
+ const body = new ReadableStream();
+ body.getReader();
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "Constructing a Request with a stream on which getReader() is called");
+
+test((t) => {
+ const body = new ReadableStream();
+ body.getReader().read();
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "Constructing a Request with a stream on which read() is called");
+
+promise_test(async (t) => {
+ const body = new ReadableStream({ pull: c => c.enqueue(new Uint8Array()) });
+ const reader = body.getReader();
+ await reader.read();
+ reader.releaseLock();
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "Constructing a Request with a stream on which read() and releaseLock() are called");
+
+test((t) => {
+ const request = new Request("...", { method: "POST", body: "..." });
+ request.body.getReader();
+ assert_throws_js(TypeError, () => new Request(request));
+ // This doesn't throw.
+ new Request(request, { body: "..." });
+}, "Constructing a Request with a Request on which body.getReader() is called");
+
+test((t) => {
+ const request = new Request("...", { method: "POST", body: "..." });
+ request.body.getReader().read();
+ assert_throws_js(TypeError, () => new Request(request));
+ // This doesn't throw.
+ new Request(request, { body: "..." });
+}, "Constructing a Request with a Request on which body.getReader().read() is called");
+
+promise_test(async (t) => {
+ const request = new Request("...", { method: "POST", body: "..." });
+ const reader = request.body.getReader();
+ await reader.read();
+ reader.releaseLock();
+ assert_throws_js(TypeError, () => new Request(request));
+ // This doesn't throw.
+ new Request(request, { body: "..." });
+}, "Constructing a Request with a Request on which read() and releaseLock() are called");
+
+test((t) => {
+ new Request("...", { method, body: null });
+}, "It is OK to omit .duplex when the body is null.");
+
+test((t) => {
+ new Request("...", { method, body: "..." });
+}, "It is OK to omit .duplex when the body is a string.");
+
+test((t) => {
+ new Request("...", { method, body: new Uint8Array(3) });
+}, "It is OK to omit .duplex when the body is a Uint8Array.");
+
+test((t) => {
+ new Request("...", { method, body: new Blob([]) });
+}, "It is OK to omit .duplex when the body is a Blob.");
+
+test((t) => {
+ const body = new ReadableStream();
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body }));
+}, "It is error to omit .duplex when the body is a ReadableStream.");
+
+test((t) => {
+ new Request("...", { method, body: null, duplex: "half" });
+}, "It is OK to set .duplex = 'half' when the body is null.");
+
+test((t) => {
+ new Request("...", { method, body: "...", duplex: "half" });
+}, "It is OK to set .duplex = 'half' when the body is a string.");
+
+test((t) => {
+ new Request("...", { method, body: new Uint8Array(3), duplex: "half" });
+}, "It is OK to set .duplex = 'half' when the body is a Uint8Array.");
+
+test((t) => {
+ new Request("...", { method, body: new Blob([]), duplex: "half" });
+}, "It is OK to set .duplex = 'half' when the body is a Blob.");
+
+test((t) => {
+ const body = new ReadableStream();
+ new Request("...", { method, body, duplex: "half" });
+}, "It is OK to set .duplex = 'half' when the body is a ReadableStream.");
+
+test((t) => {
+ const body = null;
+ const duplex = "full";
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "It is error to set .duplex = 'full' when the body is null.");
+
+test((t) => {
+ const body = "...";
+ const duplex = "full";
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "It is error to set .duplex = 'full' when the body is a string.");
+
+test((t) => {
+ const body = new Uint8Array(3);
+ const duplex = "full";
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "It is error to set .duplex = 'full' when the body is a Uint8Array.");
+
+test((t) => {
+ const body = new Blob([]);
+ const duplex = "full";
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "It is error to set .duplex = 'full' when the body is a Blob.");
+
+test((t) => {
+ const body = new ReadableStream();
+ const duplex = "full";
+ assert_throws_js(TypeError,
+ () => new Request("...", { method, body, duplex }));
+}, "It is error to set .duplex = 'full' when the body is a ReadableStream.");
+
+test((t) => {
+ const body = new ReadableStream();
+ const duplex = "half";
+ const req1 = new Request("...", { method, body, duplex });
+ const req2 = new Request(req1);
+}, "It is OK to omit duplex when init.body is not given and input.body is given.");
+
diff --git a/testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html b/testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html
new file mode 100644
index 0000000000..548ab38d7e
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-keepalive-quota.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Request Keepalive Quota Tests</title>
+ <meta name="timeout" content="long">
+ <meta name="help" href="https://fetch.spec.whatwg.org/#request">
+ <meta name="help" href="https://fetch.spec.whatwg.org/#body-mixin">
+ <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+ <meta name="variant" content="?include=fast">
+ <meta name="variant" content="?include=slow-1">
+ <meta name="variant" content="?include=slow-2">
+ <meta name="variant" content="?include=slow-3">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/subset-tests-by-key.js"></script>
+ </head>
+ <body>
+ <script>
+ 'use strict';
+
+ // We want to ensure that our keepalive requests hang slightly before completing so we can validate
+ // the effects of a rolling quota. To do this we will utilize trickle.py with a 1s delay. This should
+ // prevent any of the Fetch's from finishing in this window.
+ const trickleURL = '../resources/trickle.py?count=1&ms=';
+ const noDelay = 0;
+ const standardDelay = 1000;
+ function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+ }
+
+ // We should expect 64KiB of rolling quota for any type of keep-alive request sent.
+ const expectedQuota = 65536;
+
+ function fetchKeepAliveRequest(delay, bodySize) {
+ // Create a body of the specified size that's filled with *'s
+ const body = '*'.repeat(bodySize);
+ return fetch(trickleURL + delay, {keepalive: true, body, method: 'POST'}).then(res => {
+ return res.text();
+ }).then(() => {
+ return wait(1);
+ });
+ }
+
+ // Test 1 Byte
+ subsetTestByKey("fast", promise_test, function(test) {
+ return fetchKeepAliveRequest(noDelay, 1 /* bodySize */);
+ }, 'A Keep-Alive fetch() with a small body should succeed.');
+
+ // Test Quota full limit
+ subsetTestByKey("fast", promise_test, function(test) {
+ return fetchKeepAliveRequest(noDelay, expectedQuota /* bodySize */);
+ }, 'A Keep-Alive fetch() with a body at the Quota Limit should succeed.');
+
+ // Test Quota + 1 Byte
+ subsetTestByKey("fast", promise_test, function(test) {
+ return promise_rejects_js(test, TypeError, fetchKeepAliveRequest(noDelay, expectedQuota + 1));
+ }, 'A Keep-Alive fetch() with a body over the Quota Limit should reject.');
+
+ // Test the Quota becomes available upon promise completion.
+ subsetTestByKey("slow-1", promise_test, function (test) {
+ // Fill our Quota then try to send a second fetch.
+ return fetchKeepAliveRequest(standardDelay, expectedQuota).then(() => {
+ // Now validate that we can send another Keep-Alive fetch for the full size of the quota.
+ return fetchKeepAliveRequest(noDelay, expectedQuota);
+ });
+ }, 'A Keep-Alive fetch() should return its allocated Quota upon promise resolution.');
+
+ // Ensure only the correct amount of Quota becomes available when a fetch completes.
+ subsetTestByKey("slow-2", promise_test, function(test) {
+ // Create a fetch that uses all but 1 Byte of the Quota and runs for 2x as long as the other requests.
+ const first = fetchKeepAliveRequest(standardDelay * 2, expectedQuota - 1);
+
+ // Now create a single Byte request that will complete quicker.
+ const second = fetchKeepAliveRequest(standardDelay, 1 /* bodySize */).then(() => {
+ // We shouldn't be able to create a 2 Byte request right now as only 1 Byte should have freed up.
+ return promise_rejects_js(test, TypeError, fetchKeepAliveRequest(noDelay, 2 /* bodySize */));
+ }).then(() => {
+ // Now validate that we can send another Keep-Alive fetch for just 1 Byte.
+ return fetchKeepAliveRequest(noDelay, 1 /* bodySize */);
+ });
+
+ return Promise.all([first, second]);
+ }, 'A Keep-Alive fetch() should return only its allocated Quota upon promise resolution.');
+
+ // Test rejecting a fetch() after the quota is used up.
+ subsetTestByKey("slow-3", promise_test, function (test) {
+ // Fill our Quota then try to send a second fetch.
+ const p = fetchKeepAliveRequest(standardDelay, expectedQuota);
+
+ const q = promise_rejects_js(test, TypeError, fetchKeepAliveRequest(noDelay, 1 /* bodySize */));
+ return Promise.all([p, q]);
+ }, 'A Keep-Alive fetch() should not be allowed if the Quota is used up.');
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fetch/api/request/request-keepalive.any.js b/testing/web-platform/tests/fetch/api/request/request-keepalive.any.js
new file mode 100644
index 0000000000..cb4506db46
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-keepalive.any.js
@@ -0,0 +1,17 @@
+// META: global=window,worker
+// META: title=Request keepalive
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+test(() => {
+ assert_false(new Request('/').keepalive, 'default');
+ assert_true(new Request('/', {keepalive: true}).keepalive, 'true');
+ assert_false(new Request('/', {keepalive: false}).keepalive, 'false');
+ assert_true(new Request('/', {keepalive: 1}).keepalive, 'truish');
+ assert_false(new Request('/', {keepalive: 0}).keepalive, 'falsy');
+}, 'keepalive flag');
+
+test(() => {
+ const init = {method: 'POST', keepalive: true, body: new ReadableStream()};
+ assert_throws_js(TypeError, () => {new Request('/', init)});
+}, 'keepalive flag with stream body');
diff --git a/testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html b/testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html
new file mode 100644
index 0000000000..7be3608d73
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-reset-attributes.https.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<body>
+<script>
+const worker = 'resources/request-reset-attributes-worker.js';
+
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+promise_test(async (t) => {
+ const scope = 'resources/hello.txt?name=isReloadNavigation';
+ let frame;
+ let reg;
+
+ try {
+ reg = await service_worker_unregister_and_register(t, worker, scope);
+ await wait_for_state(t, reg.installing, 'activated');
+ frame = await with_iframe(scope);
+ assert_equals(frame.contentDocument.body.textContent,
+ 'old: false, new: false');
+ await new Promise((resolve) => {
+ frame.onload = resolve;
+ frame.contentWindow.location.reload();
+ });
+ assert_equals(frame.contentDocument.body.textContent,
+ 'old: true, new: false');
+ } finally {
+ if (frame) {
+ frame.remove();
+ }
+ if (reg) {
+ await reg.unregister();
+ }
+ }
+ }, 'Request.isReloadNavigation is reset with non-empty RequestInit');
+
+promise_test(async (t) => {
+ const scope = 'resources/hello.html?name=isHistoryNavigation';
+ let frame;
+ let reg;
+
+ try {
+ reg = await service_worker_unregister_and_register(t, worker, scope);
+ await wait_for_state(t, reg.installing, 'activated');
+ frame = await with_iframe(scope);
+ assert_equals(frame.contentDocument.body.textContent,
+ 'old: false, new: false');
+ // Use step_timeout(0) to ensure the history entry is created for Blink
+ // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
+ await wait(0);
+ await new Promise((resolve) => {
+ frame.onload = resolve;
+ frame.src = 'resources/hello.html?ignore';
+ });
+ await wait(0);
+ await new Promise((resolve) => {
+ frame.onload = resolve;
+ frame.contentWindow.history.go(-1);
+ });
+ assert_equals(frame.contentDocument.body.textContent,
+ 'old: true, new: false');
+ } finally {
+ if (frame) {
+ frame.remove();
+ }
+ if (reg) {
+ await reg.unregister();
+ }
+ }
+}, 'Request.isHistoryNavigation is reset with non-empty RequestInit');
+
+promise_test(async (t) => {
+ const scope = 'resources/hello.txt?name=mode';
+ let frame;
+ let reg;
+
+ try {
+ reg = await service_worker_unregister_and_register(t, worker, scope);
+ await wait_for_state(t, reg.installing, 'activated');
+ frame = await with_iframe(scope);
+ assert_equals(frame.contentDocument.body.textContent,
+ 'old: navigate, new: same-origin');
+ } finally {
+ if (frame) {
+ frame.remove();
+ }
+ if (reg) {
+ await reg.unregister();
+ }
+ }
+ }, 'Request.mode is reset with non-empty RequestInit when it\'s "navigate"');
+</script>
diff --git a/testing/web-platform/tests/fetch/api/request/request-structure.any.js b/testing/web-platform/tests/fetch/api/request/request-structure.any.js
new file mode 100644
index 0000000000..3d55c70ac1
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-structure.any.js
@@ -0,0 +1,133 @@
+// META: global=window,worker
+// META: title=Request structure
+
+var request = new Request("");
+var methods = ["clone",
+ //Request implements Body
+ "arrayBuffer",
+ "blob",
+ "formData",
+ "json",
+ "text"
+ ];
+var attributes = ["method",
+ "url",
+ "headers",
+ "destination",
+ "referrer",
+ "referrerPolicy",
+ "mode",
+ "credentials",
+ "cache",
+ "redirect",
+ "integrity",
+ "isReloadNavigation",
+ "isHistoryNavigation",
+ "duplex",
+ //Request implements Body
+ "bodyUsed"
+ ];
+
+function isReadOnly(request, attributeToCheck) {
+ var defaultValue = undefined;
+ var newValue = undefined;
+ switch (attributeToCheck) {
+ case "method":
+ defaultValue = "GET";
+ newValue = "POST";
+ break;
+
+ case "url":
+ //default value is base url
+ //i.e http://example.com/fetch/api/request-structure.html
+ newValue = "http://url.test";
+ break;
+
+ case "headers":
+ request.headers = new Headers ({"name":"value"});
+ assert_false(request.headers.has("name"), "Headers attribute is read only");
+ return;
+
+ case "destination":
+ defaultValue = "";
+ newValue = "worker";
+ break;
+
+ case "referrer":
+ defaultValue = "about:client";
+ newValue = "http://url.test";
+ break;
+
+ case "referrerPolicy":
+ defaultValue = "";
+ newValue = "unsafe-url";
+ break;
+
+ case "mode":
+ defaultValue = "cors";
+ newValue = "navigate";
+ break;
+
+ case "credentials":
+ defaultValue = "same-origin";
+ newValue = "cors";
+ break;
+
+ case "cache":
+ defaultValue = "default";
+ newValue = "reload";
+ break;
+
+ case "redirect":
+ defaultValue = "follow";
+ newValue = "manual";
+ break;
+
+ case "integrity":
+ newValue = "CannotWriteIntegrity";
+ break;
+
+ case "bodyUsed":
+ defaultValue = false;
+ newValue = true;
+ break;
+
+ case "isReloadNavigation":
+ defaultValue = false;
+ newValue = true;
+ break;
+
+ case "isHistoryNavigation":
+ defaultValue = false;
+ newValue = true;
+ break;
+
+ case "duplex":
+ defaultValue = "half";
+ newValue = "full";
+ break;
+
+ default:
+ return;
+ }
+
+ request[attributeToCheck] = newValue;
+ if (defaultValue === undefined)
+ assert_not_equals(request[attributeToCheck], newValue, "Attribute " + attributeToCheck + " is read only");
+ else
+ assert_equals(request[attributeToCheck], defaultValue,
+ "Attribute " + attributeToCheck + " is read only. Default value is " + defaultValue);
+}
+
+for (var idx in methods) {
+ test(function() {
+ assert_true(methods[idx] in request, "request has " + methods[idx] + " method");
+ }, "Request has " + methods[idx] + " method");
+}
+
+for (var idx in attributes) {
+ test(function() {
+ assert_true(attributes[idx] in request, "request has " + attributes[idx] + " attribute");
+ isReadOnly(request, attributes[idx]);
+ }, "Check " + attributes[idx] + " attribute");
+}
diff --git a/testing/web-platform/tests/fetch/api/request/resources/cache.py b/testing/web-platform/tests/fetch/api/request/resources/cache.py
new file mode 100644
index 0000000000..ca0bd644b4
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/resources/cache.py
@@ -0,0 +1,67 @@
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ token = request.GET.first(b"token", None)
+ if b"querystate" in request.GET:
+ from json import JSONEncoder
+ response.headers.set(b"Content-Type", b"text/plain")
+ return JSONEncoder().encode(request.server.stash.take(token))
+ content = request.GET.first(b"content", None)
+ tag = request.GET.first(b"tag", None)
+ date = request.GET.first(b"date", None)
+ expires = request.GET.first(b"expires", None)
+ vary = request.GET.first(b"vary", None)
+ cc = request.GET.first(b"cache_control", None)
+ redirect = request.GET.first(b"redirect", None)
+ inm = request.headers.get(b"If-None-Match", None)
+ ims = request.headers.get(b"If-Modified-Since", None)
+ pragma = request.headers.get(b"Pragma", None)
+ cache_control = request.headers.get(b"Cache-Control", None)
+ ignore = b"ignore" in request.GET
+
+ if tag:
+ tag = b'"%s"' % tag
+
+ server_state = request.server.stash.take(token)
+ if not server_state:
+ server_state = []
+ state = dict()
+ if not ignore:
+ if inm:
+ state[u"If-None-Match"] = isomorphic_decode(inm)
+ if ims:
+ state[u"If-Modified-Since"] = isomorphic_decode(ims)
+ if pragma:
+ state[u"Pragma"] = isomorphic_decode(pragma)
+ if cache_control:
+ state[u"Cache-Control"] = isomorphic_decode(cache_control)
+ server_state.append(state)
+ request.server.stash.put(token, server_state)
+
+ if tag:
+ response.headers.set(b"ETag", b'%s' % tag)
+ elif date:
+ response.headers.set(b"Last-Modified", date)
+ if expires:
+ response.headers.set(b"Expires", expires)
+ if vary:
+ response.headers.set(b"Vary", vary)
+ if cc:
+ response.headers.set(b"Cache-Control", cc)
+
+ # The only-if-cached redirect tests wants CORS to be okay, the other tests
+ # are all same-origin anyways and don't care.
+ response.headers.set(b"Access-Control-Allow-Origin", b"*")
+
+ if redirect:
+ response.headers.set(b"Location", redirect)
+ response.status = (302, b"Redirect")
+ return b""
+ elif ((inm is not None and inm == tag) or
+ (ims is not None and ims == date)):
+ response.status = (304, b"Not Modified")
+ return b""
+ else:
+ response.status = (200, b"OK")
+ response.headers.set(b"Content-Type", b"text/plain")
+ return content
diff --git a/testing/web-platform/tests/fetch/api/request/resources/hello.txt b/testing/web-platform/tests/fetch/api/request/resources/hello.txt
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/resources/hello.txt
@@ -0,0 +1 @@
+hello
diff --git a/testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js b/testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js
new file mode 100644
index 0000000000..4b264ca2fe
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/resources/request-reset-attributes-worker.js
@@ -0,0 +1,19 @@
+self.addEventListener('fetch', (event) => {
+ const params = new URL(event.request.url).searchParams;
+ if (params.has('ignore')) {
+ return;
+ }
+ if (!params.has('name')) {
+ event.respondWith(Promise.reject(TypeError('No name is provided.')));
+ return;
+ }
+
+ const name = params.get('name');
+ const old_attribute = event.request[name];
+ // If any of |init|'s member is present...
+ const init = {cache: 'no-store'}
+ const new_attribute = (new Request(event.request, init))[name];
+
+ event.respondWith(
+ new Response(`old: ${old_attribute}, new: ${new_attribute}`));
+ });
diff --git a/testing/web-platform/tests/fetch/api/request/url-encoding.html b/testing/web-platform/tests/fetch/api/request/url-encoding.html
new file mode 100644
index 0000000000..31c1ed3920
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/url-encoding.html
@@ -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>