summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/api/basic
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/fetch/api/basic')
-rw-r--r--testing/web-platform/tests/fetch/api/basic/accept-header.any.js34
-rw-r--r--testing/web-platform/tests/fetch/api/basic/block-mime-as-script.html43
-rw-r--r--testing/web-platform/tests/fetch/api/basic/conditional-get.any.js38
-rw-r--r--testing/web-platform/tests/fetch/api/basic/error-after-response.any.js24
-rw-r--r--testing/web-platform/tests/fetch/api/basic/header-value-combining.any.js15
-rw-r--r--testing/web-platform/tests/fetch/api/basic/header-value-null-byte.any.js5
-rw-r--r--testing/web-platform/tests/fetch/api/basic/historical.any.js17
-rw-r--r--testing/web-platform/tests/fetch/api/basic/http-response-code.any.js14
-rw-r--r--testing/web-platform/tests/fetch/api/basic/integrity.sub.any.js87
-rw-r--r--testing/web-platform/tests/fetch/api/basic/keepalive.any.js42
-rw-r--r--testing/web-platform/tests/fetch/api/basic/mediasource.window.js5
-rw-r--r--testing/web-platform/tests/fetch/api/basic/mode-no-cors.sub.any.js29
-rw-r--r--testing/web-platform/tests/fetch/api/basic/mode-same-origin.any.js28
-rw-r--r--testing/web-platform/tests/fetch/api/basic/referrer.any.js29
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-forbidden-headers.any.js82
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-head.any.js6
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-headers-case.any.js13
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-headers-nonascii.any.js29
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-headers.any.js82
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-private-network-headers.tentative.any.js18
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-referrer-redirected-worker.html17
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-referrer.any.js24
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-upload.any.js135
-rw-r--r--testing/web-platform/tests/fetch/api/basic/request-upload.h2.any.js186
-rw-r--r--testing/web-platform/tests/fetch/api/basic/response-null-body.any.js38
-rw-r--r--testing/web-platform/tests/fetch/api/basic/response-url.sub.any.js16
-rw-r--r--testing/web-platform/tests/fetch/api/basic/scheme-about.any.js26
-rw-r--r--testing/web-platform/tests/fetch/api/basic/scheme-blob.sub.any.js125
-rw-r--r--testing/web-platform/tests/fetch/api/basic/scheme-data.any.js43
-rw-r--r--testing/web-platform/tests/fetch/api/basic/scheme-others.sub.any.js31
-rw-r--r--testing/web-platform/tests/fetch/api/basic/status.h2.any.js17
-rw-r--r--testing/web-platform/tests/fetch/api/basic/stream-response.any.js40
-rw-r--r--testing/web-platform/tests/fetch/api/basic/stream-safe-creation.any.js54
-rw-r--r--testing/web-platform/tests/fetch/api/basic/text-utf8.any.js74
-rw-r--r--testing/web-platform/tests/fetch/api/basic/url-parsing.sub.html33
35 files changed, 1499 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/api/basic/accept-header.any.js b/testing/web-platform/tests/fetch/api/basic/accept-header.any.js
new file mode 100644
index 0000000000..cd54cf2a03
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/accept-header.any.js
@@ -0,0 +1,34 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+promise_test(function() {
+ return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept").then(function(response) {
+ assert_equals(response.status, 200, "HTTP status is 200");
+ assert_equals(response.type , "basic", "Response's type is basic");
+ assert_equals(response.headers.get("x-request-accept"), "*/*", "Request has accept header with value '*/*'");
+ });
+}, "Request through fetch should have 'accept' header with value '*/*'");
+
+promise_test(function() {
+ return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept", {"headers": [["Accept", "custom/*"]]}).then(function(response) {
+ assert_equals(response.status, 200, "HTTP status is 200");
+ assert_equals(response.type , "basic", "Response's type is basic");
+ assert_equals(response.headers.get("x-request-accept"), "custom/*", "Request has accept header with value 'custom/*'");
+ });
+}, "Request through fetch should have 'accept' header with value 'custom/*'");
+
+promise_test(function() {
+ return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept-Language").then(function(response) {
+ assert_equals(response.status, 200, "HTTP status is 200");
+ assert_equals(response.type , "basic", "Response's type is basic");
+ assert_true(response.headers.has("x-request-accept-language"));
+ });
+}, "Request through fetch should have a 'accept-language' header");
+
+promise_test(function() {
+ return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept-Language", {"headers": [["Accept-Language", "bzh"]]}).then(function(response) {
+ assert_equals(response.status, 200, "HTTP status is 200");
+ assert_equals(response.type , "basic", "Response's type is basic");
+ assert_equals(response.headers.get("x-request-accept-language"), "bzh", "Request has accept header with value 'bzh'");
+ });
+}, "Request through fetch should have 'accept-language' header with value 'bzh'");
diff --git a/testing/web-platform/tests/fetch/api/basic/block-mime-as-script.html b/testing/web-platform/tests/fetch/api/basic/block-mime-as-script.html
new file mode 100644
index 0000000000..afc2bbbafb
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/block-mime-as-script.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Block mime type as script</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div></div>
+<script>
+ var noop = function() {};
+
+ ["non-empty", "empty"].forEach(function(content) {
+ ["text/csv",
+ "audio/aiff",
+ "audio/midi",
+ "audio/whatever",
+ "video/avi",
+ "video/fli",
+ "video/whatever",
+ "image/jpeg",
+ "image/gif",
+ "image/whatever"].forEach(function(test_case) {
+ async_test(function(t) {
+ var script = document.createElement("script");
+ script.onerror = t.step_func_done(noop);
+ script.onload = t.unreached_func("Unexpected load event");
+ script.src = "../resources/script-with-header.py?content=" + content +
+ "&mime=" + test_case;
+ document.body.appendChild(script);
+ }, "Should fail loading " + content + " script with " + test_case +
+ " MIME type");
+ });
+ });
+
+ ["html", "plain"].forEach(function(test_case) {
+ async_test(function(t) {
+ var script = document.createElement("script");
+ script.onerror = t.unreached_func("Unexpected error event");
+ script.onload = t.step_func_done(noop);
+ script.src = "../resources/script-with-header.py?mime=text/" + test_case;
+ document.body.appendChild(script);
+ }, "Should load script with text/" + test_case + " MIME type");
+ });
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/basic/conditional-get.any.js b/testing/web-platform/tests/fetch/api/basic/conditional-get.any.js
new file mode 100644
index 0000000000..2f9fa81c02
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/conditional-get.any.js
@@ -0,0 +1,38 @@
+// META: title=Request ETag
+// META: global=window,worker
+// META: script=/common/utils.js
+
+promise_test(function() {
+ var cacheBuster = token(); // ensures first request is uncached
+ var url = "../resources/cache.py?v=" + cacheBuster;
+ var etag;
+
+ // make the first request
+ return fetch(url).then(function(response) {
+ // ensure we're getting the regular, uncached response
+ assert_equals(response.status, 200);
+ assert_equals(response.headers.get("X-HTTP-STATUS"), null)
+
+ return response.text(); // consuming the body, just to be safe
+ }).then(function(body) {
+ // make a second request
+ return fetch(url);
+ }).then(function(response) {
+ // while the server responds with 304 if our browser sent the correct
+ // If-None-Match request header, at the JavaScript level this surfaces
+ // as 200
+ assert_equals(response.status, 200);
+ assert_equals(response.headers.get("X-HTTP-STATUS"), "304")
+
+ etag = response.headers.get("ETag")
+
+ return response.text(); // consuming the body, just to be safe
+ }).then(function(body) {
+ // make a third request, explicitly setting If-None-Match request header
+ var headers = { "If-None-Match": etag }
+ return fetch(url, { headers: headers })
+ }).then(function(response) {
+ // 304 now surfaces thanks to the explicit If-None-Match request header
+ assert_equals(response.status, 304);
+ });
+}, "Testing conditional GET with ETags");
diff --git a/testing/web-platform/tests/fetch/api/basic/error-after-response.any.js b/testing/web-platform/tests/fetch/api/basic/error-after-response.any.js
new file mode 100644
index 0000000000..f7114425f9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/error-after-response.any.js
@@ -0,0 +1,24 @@
+// META: title=Fetch: network timeout after receiving the HTTP response headers
+// META: global=window,worker
+// META: timeout=long
+// META: script=../resources/utils.js
+
+function checkReader(test, reader, promiseToTest)
+{
+ return reader.read().then((value) => {
+ validateBufferFromString(value.value, "TEST_CHUNK", "Should receive first chunk");
+ return promise_rejects_js(test, TypeError, promiseToTest(reader));
+ });
+}
+
+promise_test((test) => {
+ return fetch("../resources/bad-chunk-encoding.py?count=1").then((response) => {
+ return checkReader(test, response.body.getReader(), reader => reader.read());
+ });
+}, "Response reader read() promise should reject after a network error happening after resolving fetch promise");
+
+promise_test((test) => {
+ return fetch("../resources/bad-chunk-encoding.py?count=1").then((response) => {
+ return checkReader(test, response.body.getReader(), reader => reader.closed);
+ });
+}, "Response reader closed promise should reject after a network error happening after resolving fetch promise");
diff --git a/testing/web-platform/tests/fetch/api/basic/header-value-combining.any.js b/testing/web-platform/tests/fetch/api/basic/header-value-combining.any.js
new file mode 100644
index 0000000000..bb70d87d25
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/header-value-combining.any.js
@@ -0,0 +1,15 @@
+// META: global=window,worker
+
+[
+ ["content-length", "0", "header-content-length"],
+ ["content-length", "0, 0", "header-content-length-twice"],
+ ["double-trouble", ", ", "headers-double-empty"],
+ ["foo-test", "1, 2, 3", "headers-basic"],
+ ["heya", ", \u000B\u000C, 1, , , 2", "headers-some-are-empty"],
+ ["www-authenticate", "1, 2, 3, 4", "headers-www-authenticate"],
+].forEach(testValues => {
+ promise_test(async t => {
+ const response = await fetch("../../../xhr/resources/" + testValues[2] + ".asis");
+ assert_equals(response.headers.get(testValues[0]), testValues[1]);
+ }, "response.headers.get('" + testValues[0] + "') expects " + testValues[1]);
+});
diff --git a/testing/web-platform/tests/fetch/api/basic/header-value-null-byte.any.js b/testing/web-platform/tests/fetch/api/basic/header-value-null-byte.any.js
new file mode 100644
index 0000000000..741d83bf7a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/header-value-null-byte.any.js
@@ -0,0 +1,5 @@
+// META: global=window,worker
+
+promise_test(t => {
+ return promise_rejects_js(t, TypeError, fetch("../../../xhr/resources/parse-headers.py?my-custom-header="+encodeURIComponent("x\0x")));
+}, "Ensure fetch() rejects null bytes in headers");
diff --git a/testing/web-platform/tests/fetch/api/basic/historical.any.js b/testing/web-platform/tests/fetch/api/basic/historical.any.js
new file mode 100644
index 0000000000..c808126216
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/historical.any.js
@@ -0,0 +1,17 @@
+// META: global=window,worker
+
+test(() => {
+ assert_false("getAll" in new Headers());
+ assert_false("getAll" in Headers.prototype);
+}, "Headers object no longer has a getAll() method");
+
+test(() => {
+ assert_false("type" in new Request("about:blank"));
+ assert_false("type" in Request.prototype);
+}, "'type' getter should not exist on Request objects");
+
+// See https://github.com/whatwg/fetch/pull/979 for the removal
+test(() => {
+ assert_false("trailer" in new Response());
+ assert_false("trailer" in Response.prototype);
+}, "Response object no longer has a trailer getter");
diff --git a/testing/web-platform/tests/fetch/api/basic/http-response-code.any.js b/testing/web-platform/tests/fetch/api/basic/http-response-code.any.js
new file mode 100644
index 0000000000..1fd312a3e9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/http-response-code.any.js
@@ -0,0 +1,14 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+promise_test(async (test) => {
+ const resp = await fetch(
+ "/fetch/connection-pool/resources/network-partition-key.py?"
+ + `status=425&uuid=${token()}&partition_id=${get_host_info().ORIGIN}`
+ + `&dispatch=check_partition&addcounter=true`);
+ assert_equals(resp.status, 425);
+ const text = await resp.text();
+ assert_equals(text, "ok. Request was sent 1 times. 1 connections were created.");
+}, "Fetch on 425 response should not be retried for non TLS early data.");
diff --git a/testing/web-platform/tests/fetch/api/basic/integrity.sub.any.js b/testing/web-platform/tests/fetch/api/basic/integrity.sub.any.js
new file mode 100644
index 0000000000..e3cfd1b2f6
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/integrity.sub.any.js
@@ -0,0 +1,87 @@
+// META: global=window,dedicatedworker,sharedworker
+// META: script=../resources/utils.js
+
+function integrity(desc, url, integrity, initRequestMode, shouldPass) {
+ var fetchRequestInit = {'integrity': integrity}
+ if (!!initRequestMode && initRequestMode !== "") {
+ fetchRequestInit.mode = initRequestMode;
+ }
+
+ if (shouldPass) {
+ promise_test(function(test) {
+ return fetch(url, fetchRequestInit).then(function(resp) {
+ if (initRequestMode !== "no-cors") {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ } else {
+ assert_equals(resp.status, 0, "Opaque response's status is 0");
+ assert_equals(resp.type, "opaque");
+ }
+ });
+ }, desc);
+ } else {
+ promise_test(function(test) {
+ return promise_rejects_js(test, TypeError, fetch(url, fetchRequestInit));
+ }, desc);
+ }
+}
+
+const topSha256 = "sha256-KHIDZcXnR2oBHk9DrAA+5fFiR6JjudYjqoXtMR1zvzk=";
+const topSha384 = "sha384-MgZYnnAzPM/MjhqfOIMfQK5qcFvGZsGLzx4Phd7/A8fHTqqLqXqKo8cNzY3xEPTL";
+const topSha512 = "sha512-D6yns0qxG0E7+TwkevZ4Jt5t7Iy3ugmAajG/dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg==";
+const topSha512wrongpadding = "sha512-D6yns0qxG0E7+TwkevZ4Jt5t7Iy3ugmAajG/dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg";
+const topSha512base64url = "sha512-D6yns0qxG0E7-TwkevZ4Jt5t7Iy3ugmAajG_dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg==";
+const topSha512base64url_nopadding = "sha512-D6yns0qxG0E7-TwkevZ4Jt5t7Iy3ugmAajG_dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg";
+const invalidSha256 = "sha256-dKUcPOn/AlUjWIwcHeHNqYXPlvyGiq+2dWOdFcE+24I=";
+const invalidSha512 = "sha512-oUceBRNxPxnY60g/VtPCj2syT4wo4EZh2CgYdWy9veW8+OsReTXoh7dizMGZafvx9+QhMS39L/gIkxnPIn41Zg==";
+
+const path = dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+const url = path;
+const corsUrl =
+ `http://{{host}}:{{ports[http][1]}}${path}?pipe=header(Access-Control-Allow-Origin,*)`;
+const corsUrl2 = `https://{{host}}:{{ports[https][0]}}${path}`
+
+integrity("Empty string integrity", url, "", /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("SHA-256 integrity", url, topSha256, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("SHA-384 integrity", url, topSha384, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("SHA-512 integrity", url, topSha512, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("SHA-512 integrity with missing padding", url, topSha512wrongpadding,
+ /* initRequestMode */ undefined, /* shouldPass */ true);
+integrity("SHA-512 integrity base64url encoded", url, topSha512base64url,
+ /* initRequestMode */ undefined, /* shouldPass */ true);
+integrity("SHA-512 integrity base64url encoded with missing padding", url,
+ topSha512base64url_nopadding, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("Invalid integrity", url, invalidSha256,
+ /* initRequestMode */ undefined, /* shouldPass */ false);
+integrity("Multiple integrities: valid stronger than invalid", url,
+ invalidSha256 + " " + topSha384, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("Multiple integrities: invalid stronger than valid",
+ url, invalidSha512 + " " + topSha384, /* initRequestMode */ undefined,
+ /* shouldPass */ false);
+integrity("Multiple integrities: invalid as strong as valid", url,
+ invalidSha512 + " " + topSha512, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("Multiple integrities: both are valid", url,
+ topSha384 + " " + topSha512, /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("Multiple integrities: both are invalid", url,
+ invalidSha256 + " " + invalidSha512, /* initRequestMode */ undefined,
+ /* shouldPass */ false);
+integrity("CORS empty integrity", corsUrl, "", /* initRequestMode */ undefined,
+ /* shouldPass */ true);
+integrity("CORS SHA-512 integrity", corsUrl, topSha512,
+ /* initRequestMode */ undefined, /* shouldPass */ true);
+integrity("CORS invalid integrity", corsUrl, invalidSha512,
+ /* initRequestMode */ undefined, /* shouldPass */ false);
+
+integrity("Empty string integrity for opaque response", corsUrl2, "",
+ /* initRequestMode */ "no-cors", /* shouldPass */ true);
+integrity("SHA-* integrity for opaque response", corsUrl2, topSha512,
+ /* initRequestMode */ "no-cors", /* shouldPass */ false);
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/basic/keepalive.any.js b/testing/web-platform/tests/fetch/api/basic/keepalive.any.js
new file mode 100644
index 0000000000..d6ec1f6792
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/keepalive.any.js
@@ -0,0 +1,42 @@
+// META: global=window
+// META: timeout=long
+// META: title=Fetch API: keepalive handling
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=../resources/keepalive-helper.js
+
+'use strict';
+
+const {
+ HTTP_NOTSAMESITE_ORIGIN,
+ HTTP_REMOTE_ORIGIN,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT
+} = get_host_info();
+
+/**
+ * In a different-site iframe, test to fetch a keepalive URL on the specified
+ * document event.
+ */
+function keepaliveSimpleRequestTest(method) {
+ for (const evt of ['load', 'pagehide', 'unload']) {
+ const desc =
+ `[keepalive] simple ${method} request on '${evt}' [no payload]`;
+ promise_test(async (test) => {
+ const token1 = token();
+ const iframe = document.createElement('iframe');
+ iframe.src = getKeepAliveIframeUrl(token1, method, {sendOn: evt});
+ document.body.appendChild(iframe);
+ await iframeLoaded(iframe);
+ if (evt != 'load') {
+ iframe.remove();
+ }
+ assert_equals(await getTokenFromMessage(), token1);
+
+ assertStashedTokenAsync(desc, token1);
+ }, `${desc}; setting up`);
+ }
+}
+
+for (const method of ['GET', 'POST']) {
+ keepaliveSimpleRequestTest(method);
+}
diff --git a/testing/web-platform/tests/fetch/api/basic/mediasource.window.js b/testing/web-platform/tests/fetch/api/basic/mediasource.window.js
new file mode 100644
index 0000000000..1f89595393
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/mediasource.window.js
@@ -0,0 +1,5 @@
+promise_test(t => {
+ const mediaSource = new MediaSource(),
+ mediaSourceURL = URL.createObjectURL(mediaSource);
+ return promise_rejects_js(t, TypeError, fetch(mediaSourceURL));
+}, "Cannot fetch blob: URL from a MediaSource");
diff --git a/testing/web-platform/tests/fetch/api/basic/mode-no-cors.sub.any.js b/testing/web-platform/tests/fetch/api/basic/mode-no-cors.sub.any.js
new file mode 100644
index 0000000000..a4abcac55f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/mode-no-cors.sub.any.js
@@ -0,0 +1,29 @@
+// META: script=../resources/utils.js
+
+function fetchNoCors(url, isOpaqueFiltered) {
+ var urlQuery = "?pipe=header(x-is-filtered,value)"
+ promise_test(function(test) {
+ if (isOpaqueFiltered)
+ return fetch(url + urlQuery, {"mode": "no-cors"}).then(function(resp) {
+ assert_equals(resp.status, 0, "Opaque filter: status is 0");
+ assert_equals(resp.statusText, "", "Opaque filter: statusText is \"\"");
+ assert_equals(resp.url, "", "Opaque filter: url is \"\"");
+ assert_equals(resp.type , "opaque", "Opaque filter: response's type is opaque");
+ assert_equals(resp.headers.get("x-is-filtered"), null, "Header x-is-filtered is filtered");
+ });
+ else
+ return fetch(url + urlQuery, {"mode": "no-cors"}).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type , "basic", "Response's type is basic");
+ assert_equals(resp.headers.get("x-is-filtered"), "value", "Header x-is-filtered is not filtered");
+ });
+ }, "Fetch "+ url + " with no-cors mode");
+}
+
+fetchNoCors(RESOURCES_DIR + "top.txt", false);
+fetchNoCors("http://{{host}}:{{ports[http][0]}}/fetch/api/resources/top.txt", false);
+fetchNoCors("https://{{host}}:{{ports[https][0]}}/fetch/api/resources/top.txt", true);
+fetchNoCors("http://{{host}}:{{ports[http][1]}}/fetch/api/resources/top.txt", true);
+
+done();
+
diff --git a/testing/web-platform/tests/fetch/api/basic/mode-same-origin.any.js b/testing/web-platform/tests/fetch/api/basic/mode-same-origin.any.js
new file mode 100644
index 0000000000..1457702f1b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/mode-same-origin.any.js
@@ -0,0 +1,28 @@
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function fetchSameOrigin(url, shouldPass) {
+ promise_test(function(test) {
+ if (shouldPass)
+ return fetch(url , {"mode": "same-origin"}).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type, "basic", "response type is basic");
+ });
+ else
+ return promise_rejects_js(test, TypeError, fetch(url, {mode: "same-origin"}));
+ }, "Fetch "+ url + " with same-origin mode");
+}
+
+var host_info = get_host_info();
+
+fetchSameOrigin(RESOURCES_DIR + "top.txt", true);
+fetchSameOrigin(host_info.HTTP_ORIGIN + "/fetch/api/resources/top.txt", true);
+fetchSameOrigin(host_info.HTTPS_ORIGIN + "/fetch/api/resources/top.txt", false);
+fetchSameOrigin(host_info.HTTP_REMOTE_ORIGIN + "/fetch/api/resources/top.txt", false);
+
+var redirPath = dirname(location.pathname) + RESOURCES_DIR + "redirect.py?location=";
+
+fetchSameOrigin(redirPath + RESOURCES_DIR + "top.txt", true);
+fetchSameOrigin(redirPath + host_info.HTTP_ORIGIN + "/fetch/api/resources/top.txt", true);
+fetchSameOrigin(redirPath + host_info.HTTPS_ORIGIN + "/fetch/api/resources/top.txt", false);
+fetchSameOrigin(redirPath + host_info.HTTP_REMOTE_ORIGIN + "/fetch/api/resources/top.txt", false);
diff --git a/testing/web-platform/tests/fetch/api/basic/referrer.any.js b/testing/web-platform/tests/fetch/api/basic/referrer.any.js
new file mode 100644
index 0000000000..85745e692a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/referrer.any.js
@@ -0,0 +1,29 @@
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function runTest(url, init, expectedReferrer, title) {
+ promise_test(function(test) {
+ url += (url.indexOf('?') !== -1 ? '&' : '?') + "headers=referer&cors";
+
+ return fetch(url , init).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.headers.get("x-request-referer"), expectedReferrer, "Request's referrer is correct");
+ });
+ }, title);
+}
+
+var fetchedUrl = RESOURCES_DIR + "inspect-headers.py";
+var corsFetchedUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py";
+var redirectUrl = RESOURCES_DIR + "redirect.py?location=" ;
+var corsRedirectUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "redirect.py?location=";
+
+runTest(fetchedUrl, { referrerPolicy: "origin-when-cross-origin"}, location.toString(), "origin-when-cross-origin policy on a same-origin URL");
+runTest(corsFetchedUrl, { referrerPolicy: "origin-when-cross-origin"}, get_host_info().HTTP_ORIGIN + "/", "origin-when-cross-origin policy on a cross-origin URL");
+runTest(redirectUrl + corsFetchedUrl, { referrerPolicy: "origin-when-cross-origin"}, get_host_info().HTTP_ORIGIN + "/", "origin-when-cross-origin policy on a cross-origin URL after same-origin redirection");
+runTest(corsRedirectUrl + fetchedUrl, { referrerPolicy: "origin-when-cross-origin"}, get_host_info().HTTP_ORIGIN + "/", "origin-when-cross-origin policy on a same-origin URL after cross-origin redirection");
+
+
+var referrerUrlWithCredentials = get_host_info().HTTP_ORIGIN.replace("http://", "http://username:password@");
+runTest(fetchedUrl, {referrer: referrerUrlWithCredentials}, get_host_info().HTTP_ORIGIN + "/", "Referrer with credentials should be stripped");
+var referrerUrlWithFragmentIdentifier = get_host_info().HTTP_ORIGIN + "#fragmentIdentifier";
+runTest(fetchedUrl, {referrer: referrerUrlWithFragmentIdentifier}, get_host_info().HTTP_ORIGIN + "/", "Referrer with fragment ID should be stripped");
diff --git a/testing/web-platform/tests/fetch/api/basic/request-forbidden-headers.any.js b/testing/web-platform/tests/fetch/api/basic/request-forbidden-headers.any.js
new file mode 100644
index 0000000000..d7560f03a2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-forbidden-headers.any.js
@@ -0,0 +1,82 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function requestValidOverrideHeaders(desc, validHeaders) {
+ var url = RESOURCES_DIR + "inspect-headers.py";
+ var requestInit = {"headers": validHeaders}
+ var urlParameters = "?headers=" + Object.keys(validHeaders).join("|");
+
+ promise_test(function(test){
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type , "basic", "Response's type is basic");
+ for (var header in validHeaders)
+ assert_equals(resp.headers.get("x-request-" + header), validHeaders[header], header + "is not skipped for non-forbidden methods");
+ });
+ }, desc);
+}
+
+requestForbiddenHeaders("Accept-Charset is a forbidden request header", {"Accept-Charset": "utf-8"});
+requestForbiddenHeaders("Accept-Encoding is a forbidden request header", {"Accept-Encoding": ""});
+
+requestForbiddenHeaders("Access-Control-Request-Headers is a forbidden request header", {"Access-Control-Request-Headers": ""});
+requestForbiddenHeaders("Access-Control-Request-Method is a forbidden request header", {"Access-Control-Request-Method": ""});
+requestForbiddenHeaders("Connection is a forbidden request header", {"Connection": "close"});
+requestForbiddenHeaders("Content-Length is a forbidden request header", {"Content-Length": "42"});
+requestForbiddenHeaders("Cookie is a forbidden request header", {"Cookie": "cookie=none"});
+requestForbiddenHeaders("Cookie2 is a forbidden request header", {"Cookie2": "cookie2=none"});
+requestForbiddenHeaders("Date is a forbidden request header", {"Date": "Wed, 04 May 1988 22:22:22 GMT"});
+requestForbiddenHeaders("DNT is a forbidden request header", {"DNT": "4"});
+requestForbiddenHeaders("Expect is a forbidden request header", {"Expect": "100-continue"});
+requestForbiddenHeaders("Host is a forbidden request header", {"Host": "http://wrong-host.com"});
+requestForbiddenHeaders("Keep-Alive is a forbidden request header", {"Keep-Alive": "timeout=15"});
+requestForbiddenHeaders("Origin is a forbidden request header", {"Origin": "http://wrong-origin.com"});
+requestForbiddenHeaders("Referer is a forbidden request header", {"Referer": "http://wrong-referer.com"});
+requestForbiddenHeaders("TE is a forbidden request header", {"TE": "trailers"});
+requestForbiddenHeaders("Trailer is a forbidden request header", {"Trailer": "Accept"});
+requestForbiddenHeaders("Transfer-Encoding is a forbidden request header", {"Transfer-Encoding": "chunked"});
+requestForbiddenHeaders("Upgrade is a forbidden request header", {"Upgrade": "HTTP/2.0"});
+requestForbiddenHeaders("Via is a forbidden request header", {"Via": "1.1 nowhere.com"});
+requestForbiddenHeaders("Proxy- is a forbidden request header", {"Proxy-": "value"});
+requestForbiddenHeaders("Proxy-Test is a forbidden request header", {"Proxy-Test": "value"});
+requestForbiddenHeaders("Sec- is a forbidden request header", {"Sec-": "value"});
+requestForbiddenHeaders("Sec-Test is a forbidden request header", {"Sec-Test": "value"});
+
+let forbiddenMethods = [
+ "TRACE",
+ "TRACK",
+ "CONNECT",
+ "trace",
+ "track",
+ "connect",
+ "trace,",
+ "GET,track ",
+ " connect",
+];
+
+let overrideHeaders = [
+ "x-http-method-override",
+ "x-http-method",
+ "x-method-override",
+ "X-HTTP-METHOD-OVERRIDE",
+ "X-HTTP-METHOD",
+ "X-METHOD-OVERRIDE",
+];
+
+for (forbiddenMethod of forbiddenMethods) {
+ for (overrideHeader of overrideHeaders) {
+ requestForbiddenHeaders(`header ${overrideHeader} is forbidden to use value ${forbiddenMethod}`, {[overrideHeader]: forbiddenMethod});
+ }
+}
+
+let permittedValues = [
+ "GETTRACE",
+ "GET",
+ "\",TRACE\",",
+];
+
+for (permittedValue of permittedValues) {
+ for (overrideHeader of overrideHeaders) {
+ requestValidOverrideHeaders(`header ${overrideHeader} is allowed to use value ${permittedValue}`, {[overrideHeader]: permittedValue});
+ }
+}
diff --git a/testing/web-platform/tests/fetch/api/basic/request-head.any.js b/testing/web-platform/tests/fetch/api/basic/request-head.any.js
new file mode 100644
index 0000000000..e0b6afa079
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-head.any.js
@@ -0,0 +1,6 @@
+// META: global=window,worker
+
+promise_test(function(test) {
+ var requestInit = {"method": "HEAD", "body": "test"};
+ return promise_rejects_js(test, TypeError, fetch(".", requestInit));
+}, "Fetch with HEAD with body");
diff --git a/testing/web-platform/tests/fetch/api/basic/request-headers-case.any.js b/testing/web-platform/tests/fetch/api/basic/request-headers-case.any.js
new file mode 100644
index 0000000000..4c10e717f8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-headers-case.any.js
@@ -0,0 +1,13 @@
+// META: global=window,worker
+
+promise_test(() => {
+ return fetch("/xhr/resources/echo-headers.py", {headers: [["THIS-is-A-test", 1], ["THIS-IS-A-TEST", 2]] }).then(res => res.text()).then(body => {
+ assert_regexp_match(body, /THIS-is-A-test: 1, 2/)
+ })
+}, "Multiple headers with the same name, different case (THIS-is-A-test first)")
+
+promise_test(() => {
+ return fetch("/xhr/resources/echo-headers.py", {headers: [["THIS-IS-A-TEST", 1], ["THIS-is-A-test", 2]] }).then(res => res.text()).then(body => {
+ assert_regexp_match(body, /THIS-IS-A-TEST: 1, 2/)
+ })
+}, "Multiple headers with the same name, different case (THIS-IS-A-TEST first)")
diff --git a/testing/web-platform/tests/fetch/api/basic/request-headers-nonascii.any.js b/testing/web-platform/tests/fetch/api/basic/request-headers-nonascii.any.js
new file mode 100644
index 0000000000..4a9a801138
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-headers-nonascii.any.js
@@ -0,0 +1,29 @@
+// META: global=window,worker
+
+// This tests characters that are not
+// https://infra.spec.whatwg.org/#ascii-code-point
+// but are still
+// https://infra.spec.whatwg.org/#byte-value
+// in request header values.
+// Such request header values are valid and thus sent to servers.
+// Characters outside the #byte-value range are tested e.g. in
+// fetch/api/headers/headers-errors.html.
+
+promise_test(() => {
+ return fetch(
+ "../resources/inspect-headers.py?headers=accept|x-test",
+ {headers: {
+ "Accept": "before-æøå-after",
+ "X-Test": "before-ß-after"
+ }})
+ .then(res => {
+ assert_equals(
+ res.headers.get("x-request-accept"),
+ "before-æøå-after",
+ "Accept Header");
+ assert_equals(
+ res.headers.get("x-request-x-test"),
+ "before-ß-after",
+ "X-Test Header");
+ });
+}, "Non-ascii bytes in request headers");
diff --git a/testing/web-platform/tests/fetch/api/basic/request-headers.any.js b/testing/web-platform/tests/fetch/api/basic/request-headers.any.js
new file mode 100644
index 0000000000..ac54256e4c
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-headers.any.js
@@ -0,0 +1,82 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function checkContentType(contentType, body)
+{
+ if (self.FormData && body instanceof self.FormData) {
+ assert_true(contentType.startsWith("multipart/form-data; boundary="), "Request should have header content-type starting with multipart/form-data; boundary=, but got " + contentType);
+ return;
+ }
+
+ var expectedContentType = "text/plain;charset=UTF-8";
+ if(body === null || body instanceof ArrayBuffer || body.buffer instanceof ArrayBuffer)
+ expectedContentType = null;
+ else if (body instanceof Blob)
+ expectedContentType = body.type ? body.type : null;
+ else if (body instanceof URLSearchParams)
+ expectedContentType = "application/x-www-form-urlencoded;charset=UTF-8";
+
+ assert_equals(contentType , expectedContentType, "Request should have header content-type: " + expectedContentType);
+}
+
+function requestHeaders(desc, url, method, body, expectedOrigin, expectedContentLength) {
+ var urlParameters = "?headers=origin|user-agent|accept-charset|content-length|content-type";
+ var requestInit = {"method": method}
+ promise_test(function(test){
+ if (typeof body === "function")
+ body = body();
+ if (body)
+ requestInit["body"] = body;
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type , "basic", "Response's type is basic");
+ assert_true(resp.headers.has("x-request-user-agent"), "Request has header user-agent");
+ assert_false(resp.headers.has("accept-charset"), "Request has header accept-charset");
+ assert_equals(resp.headers.get("x-request-origin") , expectedOrigin, "Request should have header origin: " + expectedOrigin);
+ if (expectedContentLength !== undefined)
+ assert_equals(resp.headers.get("x-request-content-length") , expectedContentLength, "Request should have header content-length: " + expectedContentLength);
+ checkContentType(resp.headers.get("x-request-content-type"), body);
+ });
+ }, desc);
+}
+
+var url = RESOURCES_DIR + "inspect-headers.py"
+
+requestHeaders("Fetch with GET", url, "GET", null, null, null);
+requestHeaders("Fetch with HEAD", url, "HEAD", null, null, null);
+requestHeaders("Fetch with PUT without body", url, "POST", null, location.origin, "0");
+requestHeaders("Fetch with PUT with body", url, "PUT", "Request's body", location.origin, "14");
+requestHeaders("Fetch with POST without body", url, "POST", null, location.origin, "0");
+requestHeaders("Fetch with POST with text body", url, "POST", "Request's body", location.origin, "14");
+requestHeaders("Fetch with POST with FormData body", url, "POST", function() { return new FormData(); }, location.origin);
+requestHeaders("Fetch with POST with URLSearchParams body", url, "POST", function() { return new URLSearchParams("name=value"); }, location.origin, "10");
+requestHeaders("Fetch with POST with Blob body", url, "POST", new Blob(["Test"]), location.origin, "4");
+requestHeaders("Fetch with POST with ArrayBuffer body", url, "POST", new ArrayBuffer(4), location.origin, "4");
+requestHeaders("Fetch with POST with Uint8Array body", url, "POST", new Uint8Array(4), location.origin, "4");
+requestHeaders("Fetch with POST with Int8Array body", url, "POST", new Int8Array(4), location.origin, "4");
+requestHeaders("Fetch with POST with Float32Array body", url, "POST", new Float32Array(1), location.origin, "4");
+requestHeaders("Fetch with POST with Float64Array body", url, "POST", new Float64Array(1), location.origin, "8");
+requestHeaders("Fetch with POST with DataView body", url, "POST", new DataView(new ArrayBuffer(8), 0, 4), location.origin, "4");
+requestHeaders("Fetch with POST with Blob body with mime type", url, "POST", new Blob(["Test"], { type: "text/maybe" }), location.origin, "4");
+requestHeaders("Fetch with Chicken", url, "Chicken", null, location.origin, null);
+requestHeaders("Fetch with Chicken with body", url, "Chicken", "Request's body", location.origin, "14");
+
+function requestOriginHeader(method, mode, needsOrigin) {
+ promise_test(function(test){
+ return fetch(url + "?headers=origin", {method:method, mode:mode}).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type , "basic", "Response's type is basic");
+ if(needsOrigin)
+ assert_equals(resp.headers.get("x-request-origin") , location.origin, "Request should have an Origin header with origin: " + location.origin);
+ else
+ assert_equals(resp.headers.get("x-request-origin"), null, "Request should not have an Origin header")
+ });
+ }, "Fetch with " + method + " and mode \"" + mode + "\" " + (needsOrigin ? "needs" : "does not need") + " an Origin header");
+}
+
+requestOriginHeader("GET", "cors", false);
+requestOriginHeader("POST", "same-origin", true);
+requestOriginHeader("POST", "no-cors", true);
+requestOriginHeader("PUT", "same-origin", true);
+requestOriginHeader("TacO", "same-origin", true);
+requestOriginHeader("TacO", "cors", true);
diff --git a/testing/web-platform/tests/fetch/api/basic/request-private-network-headers.tentative.any.js b/testing/web-platform/tests/fetch/api/basic/request-private-network-headers.tentative.any.js
new file mode 100644
index 0000000000..9662a91c17
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-private-network-headers.tentative.any.js
@@ -0,0 +1,18 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+requestForbiddenHeaders(
+ 'Access-Control-Request-Private-Network is a forbidden request header',
+ {'Access-Control-Request-Private-Network': ''});
+
+var invalidRequestHeaders = [
+ ["Access-Control-Request-Private-Network", "KO"],
+];
+
+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] + "\"");
+});
diff --git a/testing/web-platform/tests/fetch/api/basic/request-referrer-redirected-worker.html b/testing/web-platform/tests/fetch/api/basic/request-referrer-redirected-worker.html
new file mode 100644
index 0000000000..bdea1e1853
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-referrer-redirected-worker.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Fetch in worker: referrer header</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ let finalURL = "/fetch/api/basic/request-referrer.any.worker.js";
+ let url = "/fetch/api/resources/redirect.py?location=" +
+ encodeURIComponent(finalURL);
+ fetch_tests_from_worker(new Worker(url));
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fetch/api/basic/request-referrer.any.js b/testing/web-platform/tests/fetch/api/basic/request-referrer.any.js
new file mode 100644
index 0000000000..0c3357642d
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-referrer.any.js
@@ -0,0 +1,24 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function testReferrer(referrer, expected, desc) {
+ promise_test(function(test) {
+ var url = RESOURCES_DIR + "inspect-headers.py?headers=referer"
+ var req = new Request(url, { referrer: referrer });
+ return fetch(req).then(function(resp) {
+ var actual = resp.headers.get("x-request-referer");
+ if (expected) {
+ assert_equals(actual, expected, "request's referer should be: " + expected);
+ return;
+ }
+ if (actual) {
+ assert_equals(actual, "", "request's referer should be empty");
+ }
+ });
+ }, desc);
+}
+
+testReferrer("about:client", self.location.href, 'about:client referrer');
+
+var fooURL = new URL("./foo", self.location).href;
+testReferrer(fooURL, fooURL, 'url referrer');
diff --git a/testing/web-platform/tests/fetch/api/basic/request-upload.any.js b/testing/web-platform/tests/fetch/api/basic/request-upload.any.js
new file mode 100644
index 0000000000..9168aa1154
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-upload.any.js
@@ -0,0 +1,135 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function testUpload(desc, url, method, createBody, expectedBody) {
+ const requestInit = {method};
+ promise_test(function(test){
+ const body = createBody();
+ if (body) {
+ requestInit["body"] = body;
+ requestInit.duplex = "half";
+ }
+ return fetch(url, requestInit).then(function(resp) {
+ return resp.text().then((text)=> {
+ assert_equals(text, expectedBody);
+ });
+ });
+ }, desc);
+}
+
+function testUploadFailure(desc, url, method, createBody) {
+ const requestInit = {method};
+ promise_test(t => {
+ const body = createBody();
+ if (body) {
+ requestInit["body"] = body;
+ }
+ return promise_rejects_js(t, TypeError, fetch(url, requestInit));
+ }, desc);
+}
+
+const url = RESOURCES_DIR + "echo-content.py"
+
+testUpload("Fetch with PUT with body", url,
+ "PUT",
+ () => "Request's body",
+ "Request's body");
+testUpload("Fetch with POST with text body", url,
+ "POST",
+ () => "Request's body",
+ "Request's body");
+testUpload("Fetch with POST with URLSearchParams body", url,
+ "POST",
+ () => new URLSearchParams("name=value"),
+ "name=value");
+testUpload("Fetch with POST with Blob body", url,
+ "POST",
+ () => new Blob(["Test"]),
+ "Test");
+testUpload("Fetch with POST with ArrayBuffer body", url,
+ "POST",
+ () => new ArrayBuffer(4),
+ "\0\0\0\0");
+testUpload("Fetch with POST with Uint8Array body", url,
+ "POST",
+ () => new Uint8Array(4),
+ "\0\0\0\0");
+testUpload("Fetch with POST with Int8Array body", url,
+ "POST",
+ () => new Int8Array(4),
+ "\0\0\0\0");
+testUpload("Fetch with POST with Float32Array body", url,
+ "POST",
+ () => new Float32Array(1),
+ "\0\0\0\0");
+testUpload("Fetch with POST with Float64Array body", url,
+ "POST",
+ () => new Float64Array(1),
+ "\0\0\0\0\0\0\0\0");
+testUpload("Fetch with POST with DataView body", url,
+ "POST",
+ () => new DataView(new ArrayBuffer(8), 0, 4),
+ "\0\0\0\0");
+testUpload("Fetch with POST with Blob body with mime type", url,
+ "POST",
+ () => new Blob(["Test"], { type: "text/maybe" }),
+ "Test");
+
+testUploadFailure("Fetch with POST with ReadableStream containing String", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ controller.enqueue("Test");
+ controller.close();
+ }})
+ });
+testUploadFailure("Fetch with POST with ReadableStream containing null", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ controller.enqueue(null);
+ controller.close();
+ }})
+ });
+testUploadFailure("Fetch with POST with ReadableStream containing number", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ controller.enqueue(99);
+ controller.close();
+ }})
+ });
+testUploadFailure("Fetch with POST with ReadableStream containing ArrayBuffer", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ controller.enqueue(new ArrayBuffer());
+ controller.close();
+ }})
+ });
+testUploadFailure("Fetch with POST with ReadableStream containing Blob", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ controller.enqueue(new Blob());
+ controller.close();
+ }})
+ });
+
+promise_test(async (test) => {
+ const resp = await fetch(
+ "/fetch/connection-pool/resources/network-partition-key.py?"
+ + `status=421&uuid=${token()}&partition_id=${get_host_info().ORIGIN}`
+ + `&dispatch=check_partition&addcounter=true`,
+ {method: "POST", body: "foobar"});
+ assert_equals(resp.status, 421);
+ const text = await resp.text();
+ assert_equals(text, "ok. Request was sent 2 times. 2 connections were created.");
+}, "Fetch with POST with text body on 421 response should be retried once on new connection.");
+
+promise_test(async (test) => {
+ const body = new ReadableStream({start: c => c.close()});
+ await promise_rejects_js(test, TypeError, fetch('/', {method: 'POST', body}));
+}, "Streaming upload shouldn't work on Http/1.1.");
diff --git a/testing/web-platform/tests/fetch/api/basic/request-upload.h2.any.js b/testing/web-platform/tests/fetch/api/basic/request-upload.h2.any.js
new file mode 100644
index 0000000000..eedc2bf6a7
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/request-upload.h2.any.js
@@ -0,0 +1,186 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+const duplex = "half";
+
+async function assertUpload(url, method, createBody, expectedBody) {
+ const requestInit = {method};
+ const body = createBody();
+ if (body) {
+ requestInit["body"] = body;
+ requestInit.duplex = "half";
+ }
+ const resp = await fetch(url, requestInit);
+ const text = await resp.text();
+ assert_equals(text, expectedBody);
+}
+
+function testUpload(desc, url, method, createBody, expectedBody) {
+ promise_test(async () => {
+ await assertUpload(url, method, createBody, expectedBody);
+ }, desc);
+}
+
+function createStream(chunks) {
+ return new ReadableStream({
+ start: (controller) => {
+ for (const chunk of chunks) {
+ controller.enqueue(chunk);
+ }
+ controller.close();
+ }
+ });
+}
+
+const url = RESOURCES_DIR + "echo-content.h2.py"
+
+testUpload("Fetch with POST with empty ReadableStream", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ controller.close();
+ }})
+ },
+ "");
+
+testUpload("Fetch with POST with ReadableStream", url,
+ "POST",
+ () => {
+ return new ReadableStream({start: controller => {
+ const encoder = new TextEncoder();
+ controller.enqueue(encoder.encode("Test"));
+ controller.close();
+ }})
+ },
+ "Test");
+
+promise_test(async (test) => {
+ const body = new ReadableStream({start: controller => {
+ const encoder = new TextEncoder();
+ controller.enqueue(encoder.encode("Test"));
+ controller.close();
+ }});
+ const resp = await fetch(
+ "/fetch/connection-pool/resources/network-partition-key.py?"
+ + `status=421&uuid=${token()}&partition_id=${self.origin}`
+ + `&dispatch=check_partition&addcounter=true`,
+ {method: "POST", body: body, duplex});
+ assert_equals(resp.status, 421);
+ const text = await resp.text();
+ assert_equals(text, "ok. Request was sent 1 times. 1 connections were created.");
+}, "Fetch with POST with ReadableStream on 421 response should return the response and not retry.");
+
+promise_test(async (test) => {
+ const request = new Request('', {
+ body: new ReadableStream(),
+ method: 'POST',
+ duplex,
+ });
+
+ assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`);
+
+ const response = await fetch('data:a/a;charset=utf-8,test', {
+ method: 'POST',
+ body: new ReadableStream(),
+ duplex,
+ });
+
+ assert_equals(await response.text(), 'test', `Response has correct body`);
+}, "Feature detect for POST with ReadableStream");
+
+promise_test(async (test) => {
+ const request = new Request('data:a/a;charset=utf-8,test', {
+ body: new ReadableStream(),
+ method: 'POST',
+ duplex,
+ });
+
+ assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`);
+ const response = await fetch(request);
+ assert_equals(await response.text(), 'test', `Response has correct body`);
+}, "Feature detect for POST with ReadableStream, using request object");
+
+test(() => {
+ let duplexAccessed = false;
+
+ const request = new Request("", {
+ body: new ReadableStream(),
+ method: "POST",
+ get duplex() {
+ duplexAccessed = true;
+ return "half";
+ },
+ });
+
+ assert_equals(
+ request.headers.get("Content-Type"),
+ null,
+ `Request should not have a content-type set`
+ );
+ assert_true(duplexAccessed, `duplex dictionary property should be accessed`);
+}, "Synchronous feature detect");
+
+// The asserts the synchronousFeatureDetect isn't broken by a partial implementation.
+// An earlier feature detect was broken by Safari implementing streaming bodies as part of Request,
+// but it failed when passed to fetch().
+// This tests ensures that UAs must not implement RequestInit.duplex and streaming request bodies without also implementing the fetch() parts.
+promise_test(async () => {
+ let duplexAccessed = false;
+
+ const request = new Request("", {
+ body: new ReadableStream(),
+ method: "POST",
+ get duplex() {
+ duplexAccessed = true;
+ return "half";
+ },
+ });
+
+ const supported =
+ request.headers.get("Content-Type") === null && duplexAccessed;
+
+ // If the feature detect fails, assume the browser is being truthful (other tests pick up broken cases here)
+ if (!supported) return false;
+
+ await assertUpload(
+ url,
+ "POST",
+ () =>
+ new ReadableStream({
+ start: (controller) => {
+ const encoder = new TextEncoder();
+ controller.enqueue(encoder.encode("Test"));
+ controller.close();
+ },
+ }),
+ "Test"
+ );
+}, "Synchronous feature detect fails if feature unsupported");
+
+promise_test(async (t) => {
+ const body = createStream(["hello"]);
+ const method = "POST";
+ await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
+}, "Streaming upload with body containing a String");
+
+promise_test(async (t) => {
+ const body = createStream([null]);
+ const method = "POST";
+ await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
+}, "Streaming upload with body containing null");
+
+promise_test(async (t) => {
+ const body = createStream([33]);
+ const method = "POST";
+ await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
+}, "Streaming upload with body containing a number");
+
+promise_test(async (t) => {
+ const url = "/fetch/api/resources/authentication.py?realm=test";
+ const body = createStream([]);
+ const method = "POST";
+ await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
+}, "Streaming upload should fail on a 401 response");
+
diff --git a/testing/web-platform/tests/fetch/api/basic/response-null-body.any.js b/testing/web-platform/tests/fetch/api/basic/response-null-body.any.js
new file mode 100644
index 0000000000..bb05892657
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/response-null-body.any.js
@@ -0,0 +1,38 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+const nullBodyStatus = [204, 205, 304];
+const methods = ["GET", "POST", "OPTIONS"];
+
+for (const status of nullBodyStatus) {
+ for (const method of methods) {
+ promise_test(
+ async () => {
+ const url =
+ `${RESOURCES_DIR}status.py?code=${status}&content=hello-world`;
+ const resp = await fetch(url, { method });
+ assert_equals(resp.status, status);
+ assert_equals(resp.body, null, "the body should be null");
+ const text = await resp.text();
+ assert_equals(text, "", "null bodies result in empty text");
+ },
+ `Response.body is null for responses with status=${status} (method=${method})`,
+ );
+ }
+}
+
+promise_test(async () => {
+ const url = `${RESOURCES_DIR}status.py?code=200&content=hello-world`;
+ const resp = await fetch(url, { method: "HEAD" });
+ assert_equals(resp.status, 200);
+ assert_equals(resp.body, null, "the body should be null");
+ const text = await resp.text();
+ assert_equals(text, "", "null bodies result in empty text");
+}, `Response.body is null for responses with method=HEAD`);
+
+promise_test(async (t) => {
+ const integrity = "sha384-UT6f7WCFp32YJnp1is4l/ZYnOeQKpE8xjmdkLOwZ3nIP+tmT2aMRFQGJomjVf5cE";
+ const url = `${RESOURCES_DIR}status.py?code=204&content=hello-world`;
+ const promise = fetch(url, { method: "GET", integrity });
+ promise_rejects_js(t, TypeError, promise);
+}, "Null body status with subresource integrity should abort");
diff --git a/testing/web-platform/tests/fetch/api/basic/response-url.sub.any.js b/testing/web-platform/tests/fetch/api/basic/response-url.sub.any.js
new file mode 100644
index 0000000000..0d123c4294
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/response-url.sub.any.js
@@ -0,0 +1,16 @@
+function checkResponseURL(fetchedURL, expectedURL)
+{
+ promise_test(function() {
+ return fetch(fetchedURL).then(function(response) {
+ assert_equals(response.url, expectedURL);
+ });
+ }, "Testing response url getter with " +fetchedURL);
+}
+
+var baseURL = "http://{{host}}:{{ports[http][0]}}";
+checkResponseURL(baseURL + "/ada", baseURL + "/ada");
+checkResponseURL(baseURL + "/#", baseURL + "/");
+checkResponseURL(baseURL + "/#ada", baseURL + "/");
+checkResponseURL(baseURL + "#ada", baseURL + "/");
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/basic/scheme-about.any.js b/testing/web-platform/tests/fetch/api/basic/scheme-about.any.js
new file mode 100644
index 0000000000..9ef44183c1
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/scheme-about.any.js
@@ -0,0 +1,26 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function checkNetworkError(url, method) {
+ method = method || "GET";
+ const desc = "Fetching " + url.substring(0, 45) + " with method " + method + " is KO"
+ promise_test(function(test) {
+ var promise = fetch(url, { method: method });
+ return promise_rejects_js(test, TypeError, promise);
+ }, desc);
+}
+
+checkNetworkError("about:blank", "GET");
+checkNetworkError("about:blank", "PUT");
+checkNetworkError("about:blank", "POST");
+checkNetworkError("about:invalid.com");
+checkNetworkError("about:config");
+checkNetworkError("about:unicorn");
+
+promise_test(function(test) {
+ var promise = fetch("about:blank", {
+ "method": "GET",
+ "Range": "bytes=1-10"
+ });
+ return promise_rejects_js(test, TypeError, promise);
+}, "Fetching about:blank with range header does not affect behavior");
diff --git a/testing/web-platform/tests/fetch/api/basic/scheme-blob.sub.any.js b/testing/web-platform/tests/fetch/api/basic/scheme-blob.sub.any.js
new file mode 100644
index 0000000000..8afdc033c9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/scheme-blob.sub.any.js
@@ -0,0 +1,125 @@
+// META: script=../resources/utils.js
+
+function checkFetchResponse(url, data, mime, size, desc) {
+ promise_test(function(test) {
+ size = size.toString();
+ return fetch(url).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type, "basic", "response type is basic");
+ assert_equals(resp.headers.get("Content-Type"), mime, "Content-Type is " + resp.headers.get("Content-Type"));
+ assert_equals(resp.headers.get("Content-Length"), size, "Content-Length is " + resp.headers.get("Content-Length"));
+ return resp.text();
+ }).then(function(bodyAsText) {
+ assert_equals(bodyAsText, data, "Response's body is " + data);
+ });
+ }, desc);
+}
+
+var blob = new Blob(["Blob's data"], { "type" : "text/plain" });
+checkFetchResponse(URL.createObjectURL(blob), "Blob's data", "text/plain", blob.size,
+ "Fetching [GET] URL.createObjectURL(blob) is OK");
+
+function checkKoUrl(url, method, desc) {
+ promise_test(function(test) {
+ var promise = fetch(url, {"method": method});
+ return promise_rejects_js(test, TypeError, promise);
+ }, desc);
+}
+
+var blob2 = new Blob(["Blob's data"], { "type" : "text/plain" });
+checkKoUrl("blob:http://{{domains[www]}}:{{ports[http][0]}}/", "GET",
+ "Fetching [GET] blob:http://{{domains[www]}}:{{ports[http][0]}}/ is KO");
+
+var invalidRequestMethods = [
+ "POST",
+ "OPTIONS",
+ "HEAD",
+ "PUT",
+ "DELETE",
+ "INVALID",
+];
+invalidRequestMethods.forEach(function(method) {
+ checkKoUrl(URL.createObjectURL(blob2), method, "Fetching [" + method + "] URL.createObjectURL(blob) is KO");
+});
+
+checkKoUrl("blob:not-backed-by-a-blob/", "GET",
+ "Fetching [GET] blob:not-backed-by-a-blob/ is KO");
+
+let empty_blob = new Blob([]);
+checkFetchResponse(URL.createObjectURL(empty_blob), "", "", 0,
+ "Fetching URL.createObjectURL(empty_blob) is OK");
+
+let empty_type_blob = new Blob([], {type: ""});
+checkFetchResponse(URL.createObjectURL(empty_type_blob), "", "", 0,
+ "Fetching URL.createObjectURL(empty_type_blob) is OK");
+
+let empty_data_blob = new Blob([], {type: "text/plain"});
+checkFetchResponse(URL.createObjectURL(empty_data_blob), "", "text/plain", 0,
+ "Fetching URL.createObjectURL(empty_data_blob) is OK");
+
+let invalid_type_blob = new Blob([], {type: "invalid"});
+checkFetchResponse(URL.createObjectURL(invalid_type_blob), "", "", 0,
+ "Fetching URL.createObjectURL(invalid_type_blob) is OK");
+
+promise_test(function(test) {
+ return fetch("/images/blue.png").then(function(resp) {
+ return resp.arrayBuffer();
+ }).then(function(image_buffer) {
+ let blob = new Blob([image_buffer]);
+ return fetch(URL.createObjectURL(blob)).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type, "basic", "response type is basic");
+ assert_equals(resp.headers.get("Content-Type"), "", "Content-Type is " + resp.headers.get("Content-Type"));
+ })
+ });
+}, "Blob content is not sniffed for a content type [image/png]");
+
+let simple_xml_string = '<?xml version="1.0" encoding="UTF-8"?><x></x>';
+let xml_blob_no_type = new Blob([simple_xml_string]);
+checkFetchResponse(URL.createObjectURL(xml_blob_no_type), simple_xml_string, "", 45,
+ "Blob content is not sniffed for a content type [text/xml]");
+
+let simple_text_string = 'Hello, World!';
+promise_test(function(test) {
+ let blob = new Blob([simple_text_string], {"type": "text/plain"});
+ let slice = blob.slice(7, simple_text_string.length, "\0");
+ return fetch(URL.createObjectURL(slice)).then(function (resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type, "basic", "response type is basic");
+ assert_equals(resp.headers.get("Content-Type"), "");
+ assert_equals(resp.headers.get("Content-Length"), "6");
+ return resp.text();
+ }).then(function(bodyAsText) {
+ assert_equals(bodyAsText, "World!");
+ });
+}, "Set content type to the empty string for slice with invalid content type");
+
+promise_test(function(test) {
+ let blob = new Blob([simple_text_string], {"type": "text/plain"});
+ let slice = blob.slice(7, simple_text_string.length, "\0");
+ return fetch(URL.createObjectURL(slice)).then(function (resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type, "basic", "response type is basic");
+ assert_equals(resp.headers.get("Content-Type"), "");
+ assert_equals(resp.headers.get("Content-Length"), "6");
+ return resp.text();
+ }).then(function(bodyAsText) {
+ assert_equals(bodyAsText, "World!");
+ });
+}, "Set content type to the empty string for slice with no content type ");
+
+promise_test(function(test) {
+ let blob = new Blob([simple_xml_string]);
+ let slice = blob.slice(0, 38);
+ return fetch(URL.createObjectURL(slice)).then(function (resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.type, "basic", "response type is basic");
+ assert_equals(resp.headers.get("Content-Type"), "");
+ assert_equals(resp.headers.get("Content-Length"), "38");
+ return resp.text();
+ }).then(function(bodyAsText) {
+ assert_equals(bodyAsText, '<?xml version="1.0" encoding="UTF-8"?>');
+ });
+}, "Blob.slice should not sniff the content for a content type");
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/basic/scheme-data.any.js b/testing/web-platform/tests/fetch/api/basic/scheme-data.any.js
new file mode 100644
index 0000000000..55df43bd50
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/scheme-data.any.js
@@ -0,0 +1,43 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function checkFetchResponse(url, data, mime, fetchMode, method) {
+ var cut = (url.length >= 40) ? "[...]" : "";
+ var desc = "Fetching " + (method ? "[" + method + "] " : "") + url.substring(0, 40) + cut + " is OK";
+ var init = {"method": method || "GET"};
+ if (fetchMode) {
+ init.mode = fetchMode;
+ desc += " (" + fetchMode + ")";
+ }
+ promise_test(function(test) {
+ return fetch(url, init).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_equals(resp.statusText, "OK", "HTTP statusText is OK");
+ assert_equals(resp.type, "basic", "response type is basic");
+ assert_equals(resp.headers.get("Content-Type"), mime, "Content-Type is " + resp.headers.get("Content-Type"));
+ return resp.text();
+ }).then(function(body) {
+ assert_equals(body, data, "Response's body is correct");
+ });
+ }, desc);
+}
+
+checkFetchResponse("data:,response%27s%20body", "response's body", "text/plain;charset=US-ASCII");
+checkFetchResponse("data:,response%27s%20body", "response's body", "text/plain;charset=US-ASCII", "same-origin");
+checkFetchResponse("data:,response%27s%20body", "response's body", "text/plain;charset=US-ASCII", "cors");
+checkFetchResponse("data:text/plain;base64,cmVzcG9uc2UncyBib2R5", "response's body", "text/plain");
+checkFetchResponse("data:image/png;base64,cmVzcG9uc2UncyBib2R5",
+ "response's body",
+ "image/png");
+checkFetchResponse("data:,response%27s%20body", "response's body", "text/plain;charset=US-ASCII", null, "POST");
+checkFetchResponse("data:,response%27s%20body", "", "text/plain;charset=US-ASCII", null, "HEAD");
+
+function checkKoUrl(url, method, desc) {
+ var cut = (url.length >= 40) ? "[...]" : "";
+ desc = "Fetching [" + method + "] " + url.substring(0, 45) + cut + " is KO"
+ promise_test(function(test) {
+ return promise_rejects_js(test, TypeError, fetch(url, {"method": method}));
+ }, desc);
+}
+
+checkKoUrl("data:notAdataUrl.com", "GET");
diff --git a/testing/web-platform/tests/fetch/api/basic/scheme-others.sub.any.js b/testing/web-platform/tests/fetch/api/basic/scheme-others.sub.any.js
new file mode 100644
index 0000000000..550f69c41b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/scheme-others.sub.any.js
@@ -0,0 +1,31 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function checkKoUrl(url, desc) {
+ if (!desc)
+ desc = "Fetching " + url.substring(0, 45) + " is KO"
+ promise_test(function(test) {
+ var promise = fetch(url);
+ return promise_rejects_js(test, TypeError, promise);
+ }, desc);
+}
+
+var urlWithoutScheme = "://{{host}}:{{ports[http][0]}}/";
+checkKoUrl("aaa" + urlWithoutScheme);
+checkKoUrl("cap" + urlWithoutScheme);
+checkKoUrl("cid" + urlWithoutScheme);
+checkKoUrl("dav" + urlWithoutScheme);
+checkKoUrl("dict" + urlWithoutScheme);
+checkKoUrl("dns" + urlWithoutScheme);
+checkKoUrl("geo" + urlWithoutScheme);
+checkKoUrl("im" + urlWithoutScheme);
+checkKoUrl("imap" + urlWithoutScheme);
+checkKoUrl("ipp" + urlWithoutScheme);
+checkKoUrl("ldap" + urlWithoutScheme);
+checkKoUrl("mailto" + urlWithoutScheme);
+checkKoUrl("nfs" + urlWithoutScheme);
+checkKoUrl("pop" + urlWithoutScheme);
+checkKoUrl("rtsp" + urlWithoutScheme);
+checkKoUrl("snmp" + urlWithoutScheme);
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/basic/status.h2.any.js b/testing/web-platform/tests/fetch/api/basic/status.h2.any.js
new file mode 100644
index 0000000000..99fec88f50
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/status.h2.any.js
@@ -0,0 +1,17 @@
+// See also /xhr/status.h2.window.js
+
+[
+ 200,
+ 210,
+ 400,
+ 404,
+ 410,
+ 500,
+ 502
+].forEach(status => {
+ promise_test(async t => {
+ const response = await fetch("/xhr/resources/status.py?code=" + status);
+ assert_equals(response.status, status, "status should be " + status);
+ assert_equals(response.statusText, "", "statusText should be the empty string");
+ }, "statusText over H2 for status " + status + " should be the empty string");
+});
diff --git a/testing/web-platform/tests/fetch/api/basic/stream-response.any.js b/testing/web-platform/tests/fetch/api/basic/stream-response.any.js
new file mode 100644
index 0000000000..d964dda717
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/stream-response.any.js
@@ -0,0 +1,40 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function streamBody(reader, test, count = 0) {
+ return reader.read().then(function(data) {
+ if (!data.done && count < 2) {
+ count += 1;
+ return streamBody(reader, test, count);
+ } else {
+ test.step(function() {
+ assert_true(count >= 2, "Retrieve body progressively");
+ });
+ }
+ });
+}
+
+//simulate streaming:
+//count is large enough to let the UA deliver the body before it is completely retrieved
+promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "trickle.py?ms=30&count=100").then(function(resp) {
+ if (resp.body)
+ return streamBody(resp.body.getReader(), test);
+ else
+ test.step(function() {
+ assert_unreached( "Body does not exist in response");
+ });
+ });
+}, "Stream response's body when content-type is present");
+
+// This test makes sure that the response body is not buffered if no content type is provided.
+promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "trickle.py?ms=300&count=10&notype=true").then(function(resp) {
+ if (resp.body)
+ return streamBody(resp.body.getReader(), test);
+ else
+ test.step(function() {
+ assert_unreached( "Body does not exist in response");
+ });
+ });
+}, "Stream response's body when content-type is not present");
diff --git a/testing/web-platform/tests/fetch/api/basic/stream-safe-creation.any.js b/testing/web-platform/tests/fetch/api/basic/stream-safe-creation.any.js
new file mode 100644
index 0000000000..382efc1a8b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/stream-safe-creation.any.js
@@ -0,0 +1,54 @@
+// META: global=window,worker
+
+// These tests verify that stream creation is not affected by changes to
+// Object.prototype.
+
+const creationCases = {
+ fetch: async () => fetch(location.href),
+ request: () => new Request(location.href, {method: 'POST', body: 'hi'}),
+ response: () => new Response('bye'),
+ consumeEmptyResponse: () => new Response().text(),
+ consumeNonEmptyResponse: () => new Response(new Uint8Array([64])).text(),
+ consumeEmptyRequest: () => new Request(location.href).text(),
+ consumeNonEmptyRequest: () => new Request(location.href,
+ {method: 'POST', body: 'yes'}).arrayBuffer(),
+};
+
+for (const creationCase of Object.keys(creationCases)) {
+ for (const accessorName of ['start', 'type', 'size', 'highWaterMark']) {
+ promise_test(async t => {
+ Object.defineProperty(Object.prototype, accessorName, {
+ get() { throw Error(`Object.prototype.${accessorName} was accessed`); },
+ configurable: true
+ });
+ t.add_cleanup(() => {
+ delete Object.prototype[accessorName];
+ return Promise.resolve();
+ });
+ await creationCases[creationCase]();
+ }, `throwing Object.prototype.${accessorName} accessor should not affect ` +
+ `stream creation by '${creationCase}'`);
+
+ promise_test(async t => {
+ // -1 is a convenient value which is invalid, and should cause the
+ // constructor to throw, for all four fields.
+ Object.prototype[accessorName] = -1;
+ t.add_cleanup(() => {
+ delete Object.prototype[accessorName];
+ return Promise.resolve();
+ });
+ await creationCases[creationCase]();
+ }, `Object.prototype.${accessorName} accessor returning invalid value ` +
+ `should not affect stream creation by '${creationCase}'`);
+ }
+
+ promise_test(async t => {
+ Object.prototype.start = controller => controller.error(new Error('start'));
+ t.add_cleanup(() => {
+ delete Object.prototype.start;
+ return Promise.resolve();
+ });
+ await creationCases[creationCase]();
+ }, `Object.prototype.start function which errors the stream should not ` +
+ `affect stream creation by '${creationCase}'`);
+}
diff --git a/testing/web-platform/tests/fetch/api/basic/text-utf8.any.js b/testing/web-platform/tests/fetch/api/basic/text-utf8.any.js
new file mode 100644
index 0000000000..05c8c88825
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/text-utf8.any.js
@@ -0,0 +1,74 @@
+// META: title=Fetch: Request and Response text() should decode as UTF-8
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function testTextDecoding(body, expectedText, urlParameter, title)
+{
+ var arrayBuffer = stringToArray(body);
+
+ promise_test(function(test) {
+ var request = new Request("", {method: "POST", body: arrayBuffer});
+ return request.text().then(function(value) {
+ assert_equals(value, expectedText, "Request.text() should decode data as UTF-8");
+ });
+ }, title + " with Request.text()");
+
+ promise_test(function(test) {
+ var response = new Response(arrayBuffer);
+ return response.text().then(function(value) {
+ assert_equals(value, expectedText, "Response.text() should decode data as UTF-8");
+ });
+ }, title + " with Response.text()");
+
+ promise_test(function(test) {
+ return fetch("../resources/status.py?code=200&type=text%2Fplain%3Bcharset%3DUTF-8&content=" + urlParameter).then(function(response) {
+ return response.text().then(function(value) {
+ assert_equals(value, expectedText, "Fetched Response.text() should decode data as UTF-8");
+ });
+ });
+ }, title + " with fetched data (UTF-8 charset)");
+
+ promise_test(function(test) {
+ return fetch("../resources/status.py?code=200&type=text%2Fplain%3Bcharset%3DUTF-16&content=" + urlParameter).then(function(response) {
+ return response.text().then(function(value) {
+ assert_equals(value, expectedText, "Fetched Response.text() should decode data as UTF-8");
+ });
+ });
+ }, title + " with fetched data (UTF-16 charset)");
+
+ promise_test(function(test) {
+ return new Response(body).arrayBuffer().then(function(buffer) {
+ assert_array_equals(new Uint8Array(buffer), encode_utf8(body), "Response.arrayBuffer() should contain data encoded as UTF-8");
+ });
+ }, title + " (Response object)");
+
+ promise_test(function(test) {
+ return new Request("", {method: "POST", body: body}).arrayBuffer().then(function(buffer) {
+ assert_array_equals(new Uint8Array(buffer), encode_utf8(body), "Request.arrayBuffer() should contain data encoded as UTF-8");
+ });
+ }, title + " (Request object)");
+
+}
+
+var utf8WithBOM = "\xef\xbb\xbf\xe4\xb8\x89\xe6\x9d\x91\xe3\x81\x8b\xe3\x81\xaa\xe5\xad\x90";
+var utf8WithBOMAsURLParameter = "%EF%BB%BF%E4%B8%89%E6%9D%91%E3%81%8B%E3%81%AA%E5%AD%90";
+var utf8WithoutBOM = "\xe4\xb8\x89\xe6\x9d\x91\xe3\x81\x8b\xe3\x81\xaa\xe5\xad\x90";
+var utf8WithoutBOMAsURLParameter = "%E4%B8%89%E6%9D%91%E3%81%8B%E3%81%AA%E5%AD%90";
+var utf8Decoded = "三村かな子";
+testTextDecoding(utf8WithBOM, utf8Decoded, utf8WithBOMAsURLParameter, "UTF-8 with BOM");
+testTextDecoding(utf8WithoutBOM, utf8Decoded, utf8WithoutBOMAsURLParameter, "UTF-8 without BOM");
+
+var utf16BEWithBOM = "\xfe\xff\x4e\x09\x67\x51\x30\x4b\x30\x6a\x5b\x50";
+var utf16BEWithBOMAsURLParameter = "%fe%ff%4e%09%67%51%30%4b%30%6a%5b%50";
+var utf16BEWithBOMDecodedAsUTF8 = "��N\tgQ0K0j[P";
+testTextDecoding(utf16BEWithBOM, utf16BEWithBOMDecodedAsUTF8, utf16BEWithBOMAsURLParameter, "UTF-16BE with BOM decoded as UTF-8");
+
+var utf16LEWithBOM = "\xff\xfe\x09\x4e\x51\x67\x4b\x30\x6a\x30\x50\x5b";
+var utf16LEWithBOMAsURLParameter = "%ff%fe%09%4e%51%67%4b%30%6a%30%50%5b";
+var utf16LEWithBOMDecodedAsUTF8 = "��\tNQgK0j0P[";
+testTextDecoding(utf16LEWithBOM, utf16LEWithBOMDecodedAsUTF8, utf16LEWithBOMAsURLParameter, "UTF-16LE with BOM decoded as UTF-8");
+
+var utf16WithoutBOM = "\xe6\x00\xf8\x00\xe5\x00\x0a\x00\xc6\x30\xb9\x30\xc8\x30\x0a\x00";
+var utf16WithoutBOMAsURLParameter = "%E6%00%F8%00%E5%00%0A%00%C6%30%B9%30%C8%30%0A%00";
+var utf16WithoutBOMDecoded = "\ufffd\u0000\ufffd\u0000\ufffd\u0000\u000a\u0000\ufffd\u0030\ufffd\u0030\ufffd\u0030\u000a\u0000";
+testTextDecoding(utf16WithoutBOM, utf16WithoutBOMDecoded, utf16WithoutBOMAsURLParameter, "UTF-16 without BOM decoded as UTF-8");
diff --git a/testing/web-platform/tests/fetch/api/basic/url-parsing.sub.html b/testing/web-platform/tests/fetch/api/basic/url-parsing.sub.html
new file mode 100644
index 0000000000..fa47b29473
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/basic/url-parsing.sub.html
@@ -0,0 +1,33 @@
+<!-- Based on /html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html -->
+<!doctype html>
+<meta charset={{GET[encoding]}}> <!-- ends up as <meta charset> by default which is windows-1252 -->
+<meta name=variant content="?encoding=windows-1252">
+<meta name=variant content="?encoding=x-cp1251">
+<meta name=variant content="?encoding=utf8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function expected(encoding) {
+ return {
+ "UTF-8": "%C3%BF",
+ "windows-1251": "%26%23255%3B",
+ "windows-1252": "%FF"
+ }[encoding];
+}
+
+test(() => {
+ const request = new Request("?\u00FF");
+ assert_equals(request.url.split("?")[1], expected("UTF-8"));
+}, "Request uses the UTF-8 URL parser");
+
+test(() => {
+ const request = new Request("about:blank", { referrer: "?\u00FF" });
+ assert_equals(request.referrer.split("?")[1], expected("UTF-8"));
+}, "Request's referrer uses the UTF-8 URL parser");
+
+test(() => {
+ const response = Response.redirect("?\u00FF");
+ assert_equals(response.headers.get("Location").split("?")[1], expected("UTF-8"));
+}, "Response.redirect() uses the UTF-8 URL parser");
+</script>