summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/api/headers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/fetch/api/headers
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/api/headers')
-rw-r--r--testing/web-platform/tests/fetch/api/headers/header-setcookie.any.js266
-rw-r--r--testing/web-platform/tests/fetch/api/headers/header-values-normalize.any.js72
-rw-r--r--testing/web-platform/tests/fetch/api/headers/header-values.any.js63
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-basic.any.js275
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-casing.any.js54
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-combine.any.js66
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-errors.any.js96
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-no-cors.any.js59
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-normalize.any.js56
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-record.any.js357
-rw-r--r--testing/web-platform/tests/fetch/api/headers/headers-structure.any.js20
11 files changed, 1384 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/api/headers/header-setcookie.any.js b/testing/web-platform/tests/fetch/api/headers/header-setcookie.any.js
new file mode 100644
index 0000000000..cafb780c2c
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/header-setcookie.any.js
@@ -0,0 +1,266 @@
+// META: title=Headers set-cookie special cases
+// META: global=window,worker
+
+const headerList = [
+ ["set-cookie", "foo=bar"],
+ ["Set-Cookie", "fizz=buzz; domain=example.com"],
+];
+
+const setCookie2HeaderList = [
+ ["set-cookie2", "foo2=bar2"],
+ ["Set-Cookie2", "fizz2=buzz2; domain=example2.com"],
+];
+
+function assert_nested_array_equals(actual, expected) {
+ assert_equals(actual.length, expected.length, "Array length is not equal");
+ for (let i = 0; i < expected.length; i++) {
+ assert_array_equals(actual[i], expected[i]);
+ }
+}
+
+test(function () {
+ const headers = new Headers(headerList);
+ assert_equals(
+ headers.get("set-cookie"),
+ "foo=bar, fizz=buzz; domain=example.com",
+ );
+}, "Headers.prototype.get combines set-cookie headers in order");
+
+test(function () {
+ const headers = new Headers(headerList);
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["set-cookie", "foo=bar"],
+ ["set-cookie", "fizz=buzz; domain=example.com"],
+ ]);
+}, "Headers iterator does not combine set-cookie headers");
+
+test(function () {
+ const headers = new Headers(setCookie2HeaderList);
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
+ ]);
+}, "Headers iterator does not special case set-cookie2 headers");
+
+test(function () {
+ const headers = new Headers([...headerList, ...setCookie2HeaderList]);
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["set-cookie", "foo=bar"],
+ ["set-cookie", "fizz=buzz; domain=example.com"],
+ ["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
+ ]);
+}, "Headers iterator does not combine set-cookie & set-cookie2 headers");
+
+test(function () {
+ // Values are in non alphabetic order, and the iterator should yield in the
+ // headers in the exact order of the input.
+ const headers = new Headers([
+ ["set-cookie", "z=z"],
+ ["set-cookie", "a=a"],
+ ["set-cookie", "n=n"],
+ ]);
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["set-cookie", "z=z"],
+ ["set-cookie", "a=a"],
+ ["set-cookie", "n=n"],
+ ]);
+}, "Headers iterator preserves set-cookie ordering");
+
+test(
+ function () {
+ const headers = new Headers([
+ ["xylophone-header", "1"],
+ ["best-header", "2"],
+ ["set-cookie", "3"],
+ ["a-cool-header", "4"],
+ ["set-cookie", "5"],
+ ["a-cool-header", "6"],
+ ["best-header", "7"],
+ ]);
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["a-cool-header", "4, 6"],
+ ["best-header", "2, 7"],
+ ["set-cookie", "3"],
+ ["set-cookie", "5"],
+ ["xylophone-header", "1"],
+ ]);
+ },
+ "Headers iterator preserves per header ordering, but sorts keys alphabetically",
+);
+
+test(
+ function () {
+ const headers = new Headers([
+ ["xylophone-header", "7"],
+ ["best-header", "6"],
+ ["set-cookie", "5"],
+ ["a-cool-header", "4"],
+ ["set-cookie", "3"],
+ ["a-cool-header", "2"],
+ ["best-header", "1"],
+ ]);
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["a-cool-header", "4, 2"],
+ ["best-header", "6, 1"],
+ ["set-cookie", "5"],
+ ["set-cookie", "3"],
+ ["xylophone-header", "7"],
+ ]);
+ },
+ "Headers iterator preserves per header ordering, but sorts keys alphabetically (and ignores value ordering)",
+);
+
+test(function () {
+ const headers = new Headers([["fizz", "buzz"], ["X-Header", "test"]]);
+ const iterator = headers[Symbol.iterator]();
+ assert_array_equals(iterator.next().value, ["fizz", "buzz"]);
+ headers.append("Set-Cookie", "a=b");
+ assert_array_equals(iterator.next().value, ["set-cookie", "a=b"]);
+ headers.append("Accept", "text/html");
+ assert_array_equals(iterator.next().value, ["set-cookie", "a=b"]);
+ assert_array_equals(iterator.next().value, ["x-header", "test"]);
+ headers.append("set-cookie", "c=d");
+ assert_array_equals(iterator.next().value, ["x-header", "test"]);
+ assert_true(iterator.next().done);
+}, "Headers iterator is correctly updated with set-cookie changes");
+
+test(function () {
+ const headers = new Headers([
+ ["set-cookie", "a"],
+ ["set-cookie", "b"],
+ ["set-cookie", "c"]
+ ]);
+ const iterator = headers[Symbol.iterator]();
+ assert_array_equals(iterator.next().value, ["set-cookie", "a"]);
+ headers.delete("set-cookie");
+ headers.append("set-cookie", "d");
+ headers.append("set-cookie", "e");
+ headers.append("set-cookie", "f");
+ assert_array_equals(iterator.next().value, ["set-cookie", "e"]);
+ assert_array_equals(iterator.next().value, ["set-cookie", "f"]);
+ assert_true(iterator.next().done);
+}, "Headers iterator is correctly updated with set-cookie changes #2");
+
+test(function () {
+ const headers = new Headers(headerList);
+ assert_true(headers.has("sEt-cOoKiE"));
+}, "Headers.prototype.has works for set-cookie");
+
+test(function () {
+ const headers = new Headers(setCookie2HeaderList);
+ headers.append("set-Cookie", "foo=bar");
+ headers.append("sEt-cOoKiE", "fizz=buzz");
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["set-cookie", "foo=bar"],
+ ["set-cookie", "fizz=buzz"],
+ ["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
+ ]);
+}, "Headers.prototype.append works for set-cookie");
+
+test(function () {
+ const headers = new Headers(headerList);
+ headers.set("set-cookie", "foo2=bar2");
+ const list = [...headers];
+ assert_nested_array_equals(list, [
+ ["set-cookie", "foo2=bar2"],
+ ]);
+}, "Headers.prototype.set works for set-cookie");
+
+test(function () {
+ const headers = new Headers(headerList);
+ headers.delete("set-Cookie");
+ const list = [...headers];
+ assert_nested_array_equals(list, []);
+}, "Headers.prototype.delete works for set-cookie");
+
+test(function () {
+ const headers = new Headers();
+ assert_array_equals(headers.getSetCookie(), []);
+}, "Headers.prototype.getSetCookie with no headers present");
+
+test(function () {
+ const headers = new Headers([headerList[0]]);
+ assert_array_equals(headers.getSetCookie(), ["foo=bar"]);
+}, "Headers.prototype.getSetCookie with one header");
+
+test(function () {
+ const headers = new Headers({ "Set-Cookie": "foo=bar" });
+ assert_array_equals(headers.getSetCookie(), ["foo=bar"]);
+}, "Headers.prototype.getSetCookie with one header created from an object");
+
+test(function () {
+ const headers = new Headers(headerList);
+ assert_array_equals(headers.getSetCookie(), [
+ "foo=bar",
+ "fizz=buzz; domain=example.com",
+ ]);
+}, "Headers.prototype.getSetCookie with multiple headers");
+
+test(function () {
+ const headers = new Headers([["set-cookie", ""]]);
+ assert_array_equals(headers.getSetCookie(), [""]);
+}, "Headers.prototype.getSetCookie with an empty header");
+
+test(function () {
+ const headers = new Headers([["set-cookie", "x"], ["set-cookie", "x"]]);
+ assert_array_equals(headers.getSetCookie(), ["x", "x"]);
+}, "Headers.prototype.getSetCookie with two equal headers");
+
+test(function () {
+ const headers = new Headers([
+ ["set-cookie2", "x"],
+ ["set-cookie", "y"],
+ ["set-cookie2", "z"],
+ ]);
+ assert_array_equals(headers.getSetCookie(), ["y"]);
+}, "Headers.prototype.getSetCookie ignores set-cookie2 headers");
+
+test(function () {
+ // Values are in non alphabetic order, and the iterator should yield in the
+ // headers in the exact order of the input.
+ const headers = new Headers([
+ ["set-cookie", "z=z"],
+ ["set-cookie", "a=a"],
+ ["set-cookie", "n=n"],
+ ]);
+ assert_array_equals(headers.getSetCookie(), ["z=z", "a=a", "n=n"]);
+}, "Headers.prototype.getSetCookie preserves header ordering");
+
+test(function () {
+ const headers = new Headers({"Set-Cookie": " a=b\n"});
+ headers.append("set-cookie", "\n\rc=d ");
+ assert_nested_array_equals([...headers], [
+ ["set-cookie", "a=b"],
+ ["set-cookie", "c=d"]
+ ]);
+ headers.set("set-cookie", "\te=f ");
+ assert_nested_array_equals([...headers], [["set-cookie", "e=f"]]);
+}, "Adding Set-Cookie headers normalizes their value");
+
+test(function () {
+ assert_throws_js(TypeError, () => {
+ new Headers({"set-cookie": "\0"});
+ });
+
+ const headers = new Headers();
+ assert_throws_js(TypeError, () => {
+ headers.append("Set-Cookie", "a\nb");
+ });
+ assert_throws_js(TypeError, () => {
+ headers.set("Set-Cookie", "a\rb");
+ });
+}, "Adding invalid Set-Cookie headers throws");
+
+test(function () {
+ const response = new Response();
+ response.headers.append("Set-Cookie", "foo=bar");
+ assert_array_equals(response.headers.getSetCookie(), []);
+ response.headers.append("sEt-cOokIe", "bar=baz");
+ assert_array_equals(response.headers.getSetCookie(), []);
+}, "Set-Cookie is a forbidden response header");
diff --git a/testing/web-platform/tests/fetch/api/headers/header-values-normalize.any.js b/testing/web-platform/tests/fetch/api/headers/header-values-normalize.any.js
new file mode 100644
index 0000000000..5710554ada
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/header-values-normalize.any.js
@@ -0,0 +1,72 @@
+// META: title=Header value normalizing test
+// META: global=window,worker
+// META: timeout=long
+
+"use strict";
+
+for(let i = 0; i < 0x21; i++) {
+ let fail = false,
+ strip = false
+
+ // REMOVE 0x0B/0x0C exception once https://github.com/web-platform-tests/wpt/issues/8372 is fixed
+ if(i === 0x0B || i === 0x0C)
+ continue
+
+ if(i === 0) {
+ fail = true
+ }
+
+ if(i === 0x09 || i === 0x0A || i === 0x0D || i === 0x20) {
+ strip = true
+ }
+
+ let url = "../resources/inspect-headers.py?headers=val1|val2|val3",
+ val = String.fromCharCode(i),
+ expectedVal = strip ? "" : val,
+ val1 = val,
+ expectedVal1 = expectedVal,
+ val2 = "x" + val,
+ expectedVal2 = "x" + expectedVal,
+ val3 = val + "x",
+ expectedVal3 = expectedVal + "x"
+
+ // XMLHttpRequest is not available in service workers
+ if (!self.GLOBAL.isWorker()) {
+ async_test((t) => {
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", url)
+ if(fail) {
+ assert_throws_dom("SyntaxError", () => xhr.setRequestHeader("val1", val1))
+ assert_throws_dom("SyntaxError", () => xhr.setRequestHeader("val2", val2))
+ assert_throws_dom("SyntaxError", () => xhr.setRequestHeader("val3", val3))
+ t.done()
+ } else {
+ xhr.setRequestHeader("val1", val1)
+ xhr.setRequestHeader("val2", val2)
+ xhr.setRequestHeader("val3", val3)
+ xhr.onload = t.step_func_done(() => {
+ assert_equals(xhr.getResponseHeader("x-request-val1"), expectedVal1)
+ assert_equals(xhr.getResponseHeader("x-request-val2"), expectedVal2)
+ assert_equals(xhr.getResponseHeader("x-request-val3"), expectedVal3)
+ })
+ xhr.send()
+ }
+ }, "XMLHttpRequest with value " + encodeURI(val))
+ }
+
+ promise_test((t) => {
+ if(fail) {
+ return Promise.all([
+ promise_rejects_js(t, TypeError, fetch(url, { headers: {"val1": val1} })),
+ promise_rejects_js(t, TypeError, fetch(url, { headers: {"val2": val2} })),
+ promise_rejects_js(t, TypeError, fetch(url, { headers: {"val3": val3} }))
+ ])
+ } else {
+ return fetch(url, { headers: {"val1": val1, "val2": val2, "val3": val3} }).then((res) => {
+ assert_equals(res.headers.get("x-request-val1"), expectedVal1)
+ assert_equals(res.headers.get("x-request-val2"), expectedVal2)
+ assert_equals(res.headers.get("x-request-val3"), expectedVal3)
+ })
+ }
+ }, "fetch() with value " + encodeURI(val))
+}
diff --git a/testing/web-platform/tests/fetch/api/headers/header-values.any.js b/testing/web-platform/tests/fetch/api/headers/header-values.any.js
new file mode 100644
index 0000000000..bb7570c5a3
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/header-values.any.js
@@ -0,0 +1,63 @@
+// META: title=Header value test
+// META: global=window,worker
+// META: timeout=long
+
+"use strict";
+
+// Invalid values
+[0, 0x0A, 0x0D].forEach(val => {
+ val = "x" + String.fromCharCode(val) + "x"
+
+ // XMLHttpRequest is not available in service workers
+ if (!self.GLOBAL.isWorker()) {
+ test(() => {
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", "/")
+ assert_throws_dom("SyntaxError", () => xhr.setRequestHeader("value-test", val))
+ }, "XMLHttpRequest with value " + encodeURI(val) + " needs to throw")
+ }
+
+ promise_test(t => promise_rejects_js(t, TypeError, fetch("/", { headers: {"value-test": val} })), "fetch() with value " + encodeURI(val) + " needs to throw")
+})
+
+// Valid values
+let headerValues =[]
+for(let i = 0; i < 0x100; i++) {
+ if(i === 0 || i === 0x0A || i === 0x0D) {
+ continue
+ }
+ headerValues.push("x" + String.fromCharCode(i) + "x")
+}
+var url = "../resources/inspect-headers.py?headers="
+headerValues.forEach((_, i) => {
+ url += "val" + i + "|"
+})
+
+// XMLHttpRequest is not available in service workers
+if (!self.GLOBAL.isWorker()) {
+ async_test((t) => {
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", url)
+ headerValues.forEach((val, i) => {
+ xhr.setRequestHeader("val" + i, val)
+ })
+ xhr.onload = t.step_func_done(() => {
+ headerValues.forEach((val, i) => {
+ assert_equals(xhr.getResponseHeader("x-request-val" + i), val)
+ })
+ })
+ xhr.send()
+ }, "XMLHttpRequest with all valid values")
+}
+
+promise_test((t) => {
+ const headers = new Headers
+ headerValues.forEach((val, i) => {
+ headers.append("val" + i, val)
+ })
+ return fetch(url, { headers }).then((res) => {
+ headerValues.forEach((val, i) => {
+ assert_equals(res.headers.get("x-request-val" + i), val)
+ })
+ })
+}, "fetch() with all valid values")
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-basic.any.js b/testing/web-platform/tests/fetch/api/headers/headers-basic.any.js
new file mode 100644
index 0000000000..ead1047645
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-basic.any.js
@@ -0,0 +1,275 @@
+// META: title=Headers structure
+// META: global=window,worker
+
+"use strict";
+
+test(function() {
+ new Headers();
+}, "Create headers from no parameter");
+
+test(function() {
+ new Headers(undefined);
+}, "Create headers from undefined parameter");
+
+test(function() {
+ new Headers({});
+}, "Create headers from empty object");
+
+var parameters = [null, 1];
+parameters.forEach(function(parameter) {
+ test(function() {
+ assert_throws_js(TypeError, function() { new Headers(parameter) });
+ }, "Create headers with " + parameter + " should throw");
+});
+
+var headerDict = {"name1": "value1",
+ "name2": "value2",
+ "name3": "value3",
+ "name4": null,
+ "name5": undefined,
+ "name6": 1,
+ "Content-Type": "value4"
+};
+
+var headerSeq = [];
+for (var name in headerDict)
+ headerSeq.push([name, headerDict[name]]);
+
+test(function() {
+ var headers = new Headers(headerSeq);
+ for (name in headerDict) {
+ assert_equals(headers.get(name), String(headerDict[name]),
+ "name: " + name + " has value: " + headerDict[name]);
+ }
+ assert_equals(headers.get("length"), null, "init should be treated as a sequence, not as a dictionary");
+}, "Create headers with sequence");
+
+test(function() {
+ var headers = new Headers(headerDict);
+ for (name in headerDict) {
+ assert_equals(headers.get(name), String(headerDict[name]),
+ "name: " + name + " has value: " + headerDict[name]);
+ }
+}, "Create headers with record");
+
+test(function() {
+ var headers = new Headers(headerDict);
+ var headers2 = new Headers(headers);
+ for (name in headerDict) {
+ assert_equals(headers2.get(name), String(headerDict[name]),
+ "name: " + name + " has value: " + headerDict[name]);
+ }
+}, "Create headers with existing headers");
+
+test(function() {
+ var headers = new Headers()
+ headers[Symbol.iterator] = function *() {
+ yield ["test", "test"]
+ }
+ var headers2 = new Headers(headers)
+ assert_equals(headers2.get("test"), "test")
+}, "Create headers with existing headers with custom iterator");
+
+test(function() {
+ var headers = new Headers();
+ for (name in headerDict) {
+ headers.append(name, headerDict[name]);
+ assert_equals(headers.get(name), String(headerDict[name]),
+ "name: " + name + " has value: " + headerDict[name]);
+ }
+}, "Check append method");
+
+test(function() {
+ var headers = new Headers();
+ for (name in headerDict) {
+ headers.set(name, headerDict[name]);
+ assert_equals(headers.get(name), String(headerDict[name]),
+ "name: " + name + " has value: " + headerDict[name]);
+ }
+}, "Check set method");
+
+test(function() {
+ var headers = new Headers(headerDict);
+ for (name in headerDict)
+ assert_true(headers.has(name),"headers has name " + name);
+
+ assert_false(headers.has("nameNotInHeaders"),"headers do not have header: nameNotInHeaders");
+}, "Check has method");
+
+test(function() {
+ var headers = new Headers(headerDict);
+ for (name in headerDict) {
+ assert_true(headers.has(name),"headers have a header: " + name);
+ headers.delete(name)
+ assert_true(!headers.has(name),"headers do not have anymore a header: " + name);
+ }
+}, "Check delete method");
+
+test(function() {
+ var headers = new Headers(headerDict);
+ for (name in headerDict)
+ assert_equals(headers.get(name), String(headerDict[name]),
+ "name: " + name + " has value: " + headerDict[name]);
+
+ assert_equals(headers.get("nameNotInHeaders"), null, "header: nameNotInHeaders has no value");
+}, "Check get method");
+
+var headerEntriesDict = {"name1": "value1",
+ "Name2": "value2",
+ "name": "value3",
+ "content-Type": "value4",
+ "Content-Typ": "value5",
+ "Content-Types": "value6"
+};
+var sortedHeaderDict = {};
+var headerValues = [];
+var sortedHeaderKeys = Object.keys(headerEntriesDict).map(function(value) {
+ sortedHeaderDict[value.toLowerCase()] = headerEntriesDict[value];
+ headerValues.push(headerEntriesDict[value]);
+ return value.toLowerCase();
+}).sort();
+
+var iteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
+function checkIteratorProperties(iterator) {
+ var prototype = Object.getPrototypeOf(iterator);
+ assert_equals(Object.getPrototypeOf(prototype), iteratorPrototype);
+
+ var descriptor = Object.getOwnPropertyDescriptor(prototype, "next");
+ assert_true(descriptor.configurable, "configurable");
+ assert_true(descriptor.enumerable, "enumerable");
+ assert_true(descriptor.writable, "writable");
+}
+
+test(function() {
+ var headers = new Headers(headerEntriesDict);
+ var actual = headers.keys();
+ checkIteratorProperties(actual);
+
+ sortedHeaderKeys.forEach(function(key) {
+ const entry = actual.next();
+ assert_false(entry.done);
+ assert_equals(entry.value, key);
+ });
+ assert_true(actual.next().done);
+ assert_true(actual.next().done);
+
+ for (const key of headers.keys())
+ assert_true(sortedHeaderKeys.indexOf(key) != -1);
+}, "Check keys method");
+
+test(function() {
+ var headers = new Headers(headerEntriesDict);
+ var actual = headers.values();
+ checkIteratorProperties(actual);
+
+ sortedHeaderKeys.forEach(function(key) {
+ const entry = actual.next();
+ assert_false(entry.done);
+ assert_equals(entry.value, sortedHeaderDict[key]);
+ });
+ assert_true(actual.next().done);
+ assert_true(actual.next().done);
+
+ for (const value of headers.values())
+ assert_true(headerValues.indexOf(value) != -1);
+}, "Check values method");
+
+test(function() {
+ var headers = new Headers(headerEntriesDict);
+ var actual = headers.entries();
+ checkIteratorProperties(actual);
+
+ sortedHeaderKeys.forEach(function(key) {
+ const entry = actual.next();
+ assert_false(entry.done);
+ assert_equals(entry.value[0], key);
+ assert_equals(entry.value[1], sortedHeaderDict[key]);
+ });
+ assert_true(actual.next().done);
+ assert_true(actual.next().done);
+
+ for (const entry of headers.entries())
+ assert_equals(entry[1], sortedHeaderDict[entry[0]]);
+}, "Check entries method");
+
+test(function() {
+ var headers = new Headers(headerEntriesDict);
+ var actual = headers[Symbol.iterator]();
+
+ sortedHeaderKeys.forEach(function(key) {
+ const entry = actual.next();
+ assert_false(entry.done);
+ assert_equals(entry.value[0], key);
+ assert_equals(entry.value[1], sortedHeaderDict[key]);
+ });
+ assert_true(actual.next().done);
+ assert_true(actual.next().done);
+}, "Check Symbol.iterator method");
+
+test(function() {
+ var headers = new Headers(headerEntriesDict);
+ var reference = sortedHeaderKeys[Symbol.iterator]();
+ headers.forEach(function(value, key, container) {
+ assert_equals(headers, container);
+ const entry = reference.next();
+ assert_false(entry.done);
+ assert_equals(key, entry.value);
+ assert_equals(value, sortedHeaderDict[entry.value]);
+ });
+ assert_true(reference.next().done);
+}, "Check forEach method");
+
+test(() => {
+ const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0"});
+ const actualKeys = [];
+ const actualValues = [];
+ for (const [header, value] of headers) {
+ actualKeys.push(header);
+ actualValues.push(value);
+ headers.delete("foo");
+ }
+ assert_array_equals(actualKeys, ["bar", "baz"]);
+ assert_array_equals(actualValues, ["0", "1"]);
+}, "Iteration skips elements removed while iterating");
+
+test(() => {
+ const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"});
+ const actualKeys = [];
+ const actualValues = [];
+ for (const [header, value] of headers) {
+ actualKeys.push(header);
+ actualValues.push(value);
+ if (header === "baz")
+ headers.delete("bar");
+ }
+ assert_array_equals(actualKeys, ["bar", "baz", "quux"]);
+ assert_array_equals(actualValues, ["0", "1", "3"]);
+}, "Removing elements already iterated over causes an element to be skipped during iteration");
+
+test(() => {
+ const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"});
+ const actualKeys = [];
+ const actualValues = [];
+ for (const [header, value] of headers) {
+ actualKeys.push(header);
+ actualValues.push(value);
+ if (header === "baz")
+ headers.append("X-yZ", "4");
+ }
+ assert_array_equals(actualKeys, ["bar", "baz", "foo", "quux", "x-yz"]);
+ assert_array_equals(actualValues, ["0", "1", "2", "3", "4"]);
+}, "Appending a value pair during iteration causes it to be reached during iteration");
+
+test(() => {
+ const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"});
+ const actualKeys = [];
+ const actualValues = [];
+ for (const [header, value] of headers) {
+ actualKeys.push(header);
+ actualValues.push(value);
+ if (header === "baz")
+ headers.append("abc", "-1");
+ }
+ assert_array_equals(actualKeys, ["bar", "baz", "baz", "foo", "quux"]);
+ assert_array_equals(actualValues, ["0", "1", "1", "2", "3"]);
+}, "Prepending a value pair before the current element position causes it to be skipped during iteration and adds the current element a second time");
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-casing.any.js b/testing/web-platform/tests/fetch/api/headers/headers-casing.any.js
new file mode 100644
index 0000000000..20b8a9d375
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-casing.any.js
@@ -0,0 +1,54 @@
+// META: title=Headers case management
+// META: global=window,worker
+
+"use strict";
+
+var headerDictCase = {"UPPERCASE": "value1",
+ "lowercase": "value2",
+ "mixedCase": "value3",
+ "Content-TYPE": "value4"
+ };
+
+function checkHeadersCase(originalName, headersToCheck, expectedDict) {
+ var lowCaseName = originalName.toLowerCase();
+ var upCaseName = originalName.toUpperCase();
+ var expectedValue = expectedDict[originalName];
+ assert_equals(headersToCheck.get(originalName), expectedValue,
+ "name: " + originalName + " has value: " + expectedValue);
+ assert_equals(headersToCheck.get(lowCaseName), expectedValue,
+ "name: " + lowCaseName + " has value: " + expectedValue);
+ assert_equals(headersToCheck.get(upCaseName), expectedValue,
+ "name: " + upCaseName + " has value: " + expectedValue);
+}
+
+test(function() {
+ var headers = new Headers(headerDictCase);
+ for (const name in headerDictCase)
+ checkHeadersCase(name, headers, headerDictCase)
+}, "Create headers, names use characters with different case");
+
+test(function() {
+ var headers = new Headers();
+ for (const name in headerDictCase) {
+ headers.append(name, headerDictCase[name]);
+ checkHeadersCase(name, headers, headerDictCase);
+ }
+}, "Check append method, names use characters with different case");
+
+test(function() {
+ var headers = new Headers();
+ for (const name in headerDictCase) {
+ headers.set(name, headerDictCase[name]);
+ checkHeadersCase(name, headers, headerDictCase);
+ }
+}, "Check set method, names use characters with different case");
+
+test(function() {
+ var headers = new Headers();
+ for (const name in headerDictCase)
+ headers.set(name, headerDictCase[name]);
+ for (const name in headerDictCase)
+ headers.delete(name.toLowerCase());
+ for (const name in headerDictCase)
+ assert_false(headers.has(name), "header " + name + " should have been deleted");
+}, "Check delete method, names use characters with different case");
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-combine.any.js b/testing/web-platform/tests/fetch/api/headers/headers-combine.any.js
new file mode 100644
index 0000000000..4f3b6d11df
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-combine.any.js
@@ -0,0 +1,66 @@
+// META: title=Headers have combined (and sorted) values
+// META: global=window,worker
+
+"use strict";
+
+var headerSeqCombine = [["single", "singleValue"],
+ ["double", "doubleValue1"],
+ ["double", "doubleValue2"],
+ ["triple", "tripleValue1"],
+ ["triple", "tripleValue2"],
+ ["triple", "tripleValue3"]
+];
+var expectedDict = {"single": "singleValue",
+ "double": "doubleValue1, doubleValue2",
+ "triple": "tripleValue1, tripleValue2, tripleValue3"
+};
+
+test(function() {
+ var headers = new Headers(headerSeqCombine);
+ for (const name in expectedDict)
+ assert_equals(headers.get(name), expectedDict[name]);
+}, "Create headers using same name for different values");
+
+test(function() {
+ var headers = new Headers(headerSeqCombine);
+ for (const name in expectedDict) {
+ assert_true(headers.has(name), "name: " + name + " has value(s)");
+ headers.delete(name);
+ assert_false(headers.has(name), "name: " + name + " has no value(s) anymore");
+ }
+}, "Check delete and has methods when using same name for different values");
+
+test(function() {
+ var headers = new Headers(headerSeqCombine);
+ for (const name in expectedDict) {
+ headers.set(name,"newSingleValue");
+ assert_equals(headers.get(name), "newSingleValue", "name: " + name + " has value: newSingleValue");
+ }
+}, "Check set methods when called with already used name");
+
+test(function() {
+ var headers = new Headers(headerSeqCombine);
+ for (const name in expectedDict) {
+ var value = headers.get(name);
+ headers.append(name,"newSingleValue");
+ assert_equals(headers.get(name), (value + ", " + "newSingleValue"));
+ }
+}, "Check append methods when called with already used name");
+
+test(() => {
+ const headers = new Headers([["1", "a"],["1", "b"]]);
+ for(let header of headers) {
+ assert_array_equals(header, ["1", "a, b"]);
+ }
+}, "Iterate combined values");
+
+test(() => {
+ const headers = new Headers([["2", "a"], ["1", "b"], ["2", "b"]]),
+ expected = [["1", "b"], ["2", "a, b"]];
+ let i = 0;
+ for(let header of headers) {
+ assert_array_equals(header, expected[i]);
+ i++;
+ }
+ assert_equals(i, 2);
+}, "Iterate combined values in sorted order")
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-errors.any.js b/testing/web-platform/tests/fetch/api/headers/headers-errors.any.js
new file mode 100644
index 0000000000..82dadd8234
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-errors.any.js
@@ -0,0 +1,96 @@
+// META: title=Headers errors
+// META: global=window,worker
+
+"use strict";
+
+test(function() {
+ assert_throws_js(TypeError, function() { new Headers([["name"]]); });
+}, "Create headers giving an array having one string as init argument");
+
+test(function() {
+ assert_throws_js(TypeError, function() { new Headers([["invalid", "invalidValue1", "invalidValue2"]]); });
+}, "Create headers giving an array having three strings as init argument");
+
+test(function() {
+ assert_throws_js(TypeError, function() { new Headers([["invalidĀ", "Value1"]]); });
+}, "Create headers giving bad header name as init argument");
+
+test(function() {
+ assert_throws_js(TypeError, function() { new Headers([["name", "invalidValueĀ"]]); });
+}, "Create headers giving bad header value as init argument");
+
+var badNames = ["invalidĀ", {}];
+var badValues = ["invalidĀ"];
+
+badNames.forEach(function(name) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.get(name); });
+ }, "Check headers get with an invalid name " + name);
+});
+
+badNames.forEach(function(name) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.delete(name); });
+ }, "Check headers delete with an invalid name " + name);
+});
+
+badNames.forEach(function(name) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.has(name); });
+ }, "Check headers has with an invalid name " + name);
+});
+
+badNames.forEach(function(name) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.set(name, "Value1"); });
+ }, "Check headers set with an invalid name " + name);
+});
+
+badValues.forEach(function(value) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.set("name", value); });
+ }, "Check headers set with an invalid value " + value);
+});
+
+badNames.forEach(function(name) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.append("invalidĀ", "Value1"); });
+ }, "Check headers append with an invalid name " + name);
+});
+
+badValues.forEach(function(value) {
+ test(function() {
+ var headers = new Headers();
+ assert_throws_js(TypeError, function() { headers.append("name", value); });
+ }, "Check headers append with an invalid value " + value);
+});
+
+test(function() {
+ var headers = new Headers([["name", "value"]]);
+ assert_throws_js(TypeError, function() { headers.forEach(); });
+ assert_throws_js(TypeError, function() { headers.forEach(undefined); });
+ assert_throws_js(TypeError, function() { headers.forEach(1); });
+}, "Headers forEach throws if argument is not callable");
+
+test(function() {
+ var headers = new Headers([["name1", "value1"], ["name2", "value2"], ["name3", "value3"]]);
+ var counter = 0;
+ try {
+ headers.forEach(function(value, name) {
+ counter++;
+ if (name == "name2")
+ throw "error";
+ });
+ } catch (e) {
+ assert_equals(counter, 2);
+ assert_equals(e, "error");
+ return;
+ }
+ assert_unreached();
+}, "Headers forEach loop should stop if callback is throwing exception");
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-no-cors.any.js b/testing/web-platform/tests/fetch/api/headers/headers-no-cors.any.js
new file mode 100644
index 0000000000..60dbb9ef67
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-no-cors.any.js
@@ -0,0 +1,59 @@
+// META: global=window,worker
+
+"use strict";
+
+promise_test(() => fetch("../cors/resources/not-cors-safelisted.json").then(res => res.json().then(runTests)), "Loading data…");
+
+const longValue = "s".repeat(127);
+
+[
+ {
+ "headers": ["accept", "accept-language", "content-language"],
+ "values": [longValue, "", longValue]
+ },
+ {
+ "headers": ["accept", "accept-language", "content-language"],
+ "values": ["", longValue]
+ },
+ {
+ "headers": ["content-type"],
+ "values": ["text/plain;" + "s".repeat(116), "text/plain"]
+ }
+].forEach(testItem => {
+ testItem.headers.forEach(header => {
+ test(() => {
+ const noCorsHeaders = new Request("about:blank", { mode: "no-cors" }).headers;
+ testItem.values.forEach((value) => {
+ noCorsHeaders.append(header, value);
+ assert_equals(noCorsHeaders.get(header), testItem.values[0], '1');
+ });
+ noCorsHeaders.set(header, testItem.values.join(", "));
+ assert_equals(noCorsHeaders.get(header), testItem.values[0], '2');
+ noCorsHeaders.delete(header);
+ assert_false(noCorsHeaders.has(header));
+ }, "\"no-cors\" Headers object cannot have " + header + " set to " + testItem.values.join(", "));
+ });
+});
+
+function runTests(testArray) {
+ testArray = testArray.concat([
+ ["dpr", "2"],
+ ["rtt", "1.0"],
+ ["downlink", "-1.0"],
+ ["ect", "6g"],
+ ["save-data", "on"],
+ ["viewport-width", "100"],
+ ["width", "100"],
+ ["unknown", "doesitmatter"]
+ ]);
+ testArray.forEach(testItem => {
+ const [headerName, headerValue] = testItem;
+ test(() => {
+ const noCorsHeaders = new Request("about:blank", { mode: "no-cors" }).headers;
+ noCorsHeaders.append(headerName, headerValue);
+ assert_false(noCorsHeaders.has(headerName));
+ noCorsHeaders.set(headerName, headerValue);
+ assert_false(noCorsHeaders.has(headerName));
+ }, "\"no-cors\" Headers object cannot have " + headerName + "/" + headerValue + " as header");
+ });
+}
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-normalize.any.js b/testing/web-platform/tests/fetch/api/headers/headers-normalize.any.js
new file mode 100644
index 0000000000..68cf5b85f3
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-normalize.any.js
@@ -0,0 +1,56 @@
+// META: title=Headers normalize values
+// META: global=window,worker
+
+"use strict";
+
+const expectations = {
+ "name1": [" space ", "space"],
+ "name2": ["\ttab\t", "tab"],
+ "name3": [" spaceAndTab\t", "spaceAndTab"],
+ "name4": ["\r\n newLine", "newLine"], //obs-fold cases
+ "name5": ["newLine\r\n ", "newLine"],
+ "name6": ["\r\n\tnewLine", "newLine"],
+ "name7": ["\t\f\tnewLine\n", "\f\tnewLine"],
+ "name8": ["newLine\xa0", "newLine\xa0"], // \xa0 == non breaking space
+};
+
+test(function () {
+ const headerDict = Object.fromEntries(
+ Object.entries(expectations).map(([name, [actual]]) => [name, actual]),
+ );
+ var headers = new Headers(headerDict);
+ for (const name in expectations) {
+ const expected = expectations[name][1];
+ assert_equals(
+ headers.get(name),
+ expected,
+ "name: " + name + " has normalized value: " + expected,
+ );
+ }
+}, "Create headers with not normalized values");
+
+test(function () {
+ var headers = new Headers();
+ for (const name in expectations) {
+ headers.append(name, expectations[name][0]);
+ const expected = expectations[name][1];
+ assert_equals(
+ headers.get(name),
+ expected,
+ "name: " + name + " has value: " + expected,
+ );
+ }
+}, "Check append method with not normalized values");
+
+test(function () {
+ var headers = new Headers();
+ for (const name in expectations) {
+ headers.set(name, expectations[name][0]);
+ const expected = expectations[name][1];
+ assert_equals(
+ headers.get(name),
+ expected,
+ "name: " + name + " has value: " + expected,
+ );
+ }
+}, "Check set method with not normalized values");
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-record.any.js b/testing/web-platform/tests/fetch/api/headers/headers-record.any.js
new file mode 100644
index 0000000000..fa853914f4
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-record.any.js
@@ -0,0 +1,357 @@
+// META: global=window,worker
+
+"use strict";
+
+var log = [];
+function clearLog() {
+ log = [];
+}
+function addLogEntry(name, args) {
+ log.push([ name, ...args ]);
+}
+
+var loggingHandler = {
+};
+
+setup(function() {
+ for (let prop of Object.getOwnPropertyNames(Reflect)) {
+ loggingHandler[prop] = function(...args) {
+ addLogEntry(prop, args);
+ return Reflect[prop](...args);
+ }
+ }
+});
+
+test(function() {
+ var h = new Headers();
+ assert_equals([...h].length, 0);
+}, "Passing nothing to Headers constructor");
+
+test(function() {
+ var h = new Headers(undefined);
+ assert_equals([...h].length, 0);
+}, "Passing undefined to Headers constructor");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ var h = new Headers(null);
+ });
+}, "Passing null to Headers constructor");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = { a: "b" };
+ var proxy = new Proxy(record, loggingHandler);
+ var h = new Headers(proxy);
+
+ assert_equals(log.length, 4);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+
+ // Check the results.
+ assert_equals([...h].length, 1);
+ assert_array_equals([...h.keys()], ["a"]);
+ assert_true(h.has("a"));
+ assert_equals(h.get("a"), "b");
+}, "Basic operation with one property");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var recordProto = { c: "d" };
+ var record = Object.create(recordProto, { a: { value: "b", enumerable: true } });
+ var proxy = new Proxy(record, loggingHandler);
+ var h = new Headers(proxy);
+
+ assert_equals(log.length, 4);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+
+ // Check the results.
+ assert_equals([...h].length, 1);
+ assert_array_equals([...h.keys()], ["a"]);
+ assert_true(h.has("a"));
+ assert_equals(h.get("a"), "b");
+}, "Basic operation with one property and a proto");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = { a: "b", c: "d" };
+ var proxy = new Proxy(record, loggingHandler);
+ var h = new Headers(proxy);
+
+ assert_equals(log.length, 6);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+ // Then the second [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "c"]);
+ // Then the second [[Get]] from step 5.2.
+ assert_array_equals(log[5], ["get", record, "c", proxy]);
+
+ // Check the results.
+ assert_equals([...h].length, 2);
+ assert_array_equals([...h.keys()], ["a", "c"]);
+ assert_true(h.has("a"));
+ assert_equals(h.get("a"), "b");
+ assert_true(h.has("c"));
+ assert_equals(h.get("c"), "d");
+}, "Correct operation ordering with two properties");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = { a: "b", "\uFFFF": "d" };
+ var proxy = new Proxy(record, loggingHandler);
+ assert_throws_js(TypeError, function() {
+ var h = new Headers(proxy);
+ });
+
+ assert_equals(log.length, 5);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+ // Then the second [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "\uFFFF"]);
+ // The second [[Get]] never happens, because we convert the invalid name to a
+ // ByteString first and throw.
+}, "Correct operation ordering with two properties one of which has an invalid name");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = { a: "\uFFFF", c: "d" }
+ var proxy = new Proxy(record, loggingHandler);
+ assert_throws_js(TypeError, function() {
+ var h = new Headers(proxy);
+ });
+
+ assert_equals(log.length, 4);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+ // Nothing else after this, because converting the result of that [[Get]] to a
+ // ByteString throws.
+}, "Correct operation ordering with two properties one of which has an invalid value");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = {};
+ Object.defineProperty(record, "a", { value: "b", enumerable: false });
+ Object.defineProperty(record, "c", { value: "d", enumerable: true });
+ Object.defineProperty(record, "e", { value: "f", enumerable: false });
+ var proxy = new Proxy(record, loggingHandler);
+ var h = new Headers(proxy);
+
+ assert_equals(log.length, 6);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // No [[Get]] because not enumerable
+ // Then the second [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[3], ["getOwnPropertyDescriptor", record, "c"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[4], ["get", record, "c", proxy]);
+ // Then the third [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[5], ["getOwnPropertyDescriptor", record, "e"]);
+ // No [[Get]] because not enumerable
+
+ // Check the results.
+ assert_equals([...h].length, 1);
+ assert_array_equals([...h.keys()], ["c"]);
+ assert_true(h.has("c"));
+ assert_equals(h.get("c"), "d");
+}, "Correct operation ordering with non-enumerable properties");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = {a: "b", c: "d", e: "f"};
+ var lyingHandler = {
+ getOwnPropertyDescriptor: function(target, name) {
+ if (name == "a" || name == "e") {
+ return undefined;
+ }
+ return Reflect.getOwnPropertyDescriptor(target, name);
+ }
+ };
+ var lyingProxy = new Proxy(record, lyingHandler);
+ var proxy = new Proxy(lyingProxy, loggingHandler);
+ var h = new Headers(proxy);
+
+ assert_equals(log.length, 6);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", lyingProxy]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", lyingProxy, "a"]);
+ // No [[Get]] because no descriptor
+ // Then the second [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[3], ["getOwnPropertyDescriptor", lyingProxy, "c"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[4], ["get", lyingProxy, "c", proxy]);
+ // Then the third [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[5], ["getOwnPropertyDescriptor", lyingProxy, "e"]);
+ // No [[Get]] because no descriptor
+
+ // Check the results.
+ assert_equals([...h].length, 1);
+ assert_array_equals([...h.keys()], ["c"]);
+ assert_true(h.has("c"));
+ assert_equals(h.get("c"), "d");
+}, "Correct operation ordering with undefined descriptors");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = {a: "b", c: "d"};
+ var lyingHandler = {
+ ownKeys: function() {
+ return [ "a", "c", "a", "c" ];
+ },
+ };
+ var lyingProxy = new Proxy(record, lyingHandler);
+ var proxy = new Proxy(lyingProxy, loggingHandler);
+
+ // Returning duplicate keys from ownKeys() throws a TypeError.
+ assert_throws_js(TypeError,
+ function() { var h = new Headers(proxy); });
+
+ assert_equals(log.length, 2);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", lyingProxy]);
+}, "Correct operation ordering with repeated keys");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = {
+ a: "b",
+ [Symbol.toStringTag]: {
+ // Make sure the ToString conversion of the value happens
+ // after the ToString conversion of the key.
+ toString: function () { addLogEntry("toString", [this]); return "nope"; }
+ },
+ c: "d" };
+ var proxy = new Proxy(record, loggingHandler);
+ assert_throws_js(TypeError,
+ function() { var h = new Headers(proxy); });
+
+ assert_equals(log.length, 7);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+ // Then the second [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "c"]);
+ // Then the second [[Get]] from step 5.2.
+ assert_array_equals(log[5], ["get", record, "c", proxy]);
+ // Then the third [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[6], ["getOwnPropertyDescriptor", record,
+ Symbol.toStringTag]);
+ // Then we throw an exception converting the Symbol to a string, before we do
+ // the third [[Get]].
+}, "Basic operation with Symbol keys");
+
+test(function() {
+ this.add_cleanup(clearLog);
+ var record = {
+ a: {
+ toString: function() { addLogEntry("toString", [this]); return "b"; }
+ },
+ [Symbol.toStringTag]: {
+ toString: function () { addLogEntry("toString", [this]); return "nope"; }
+ },
+ c: {
+ toString: function() { addLogEntry("toString", [this]); return "d"; }
+ }
+ };
+ // Now make that Symbol-named property not enumerable.
+ Object.defineProperty(record, Symbol.toStringTag, { enumerable: false });
+ assert_array_equals(Reflect.ownKeys(record),
+ ["a", "c", Symbol.toStringTag]);
+
+ var proxy = new Proxy(record, loggingHandler);
+ var h = new Headers(proxy);
+
+ assert_equals(log.length, 9);
+ // The first thing is the [[Get]] of Symbol.iterator to figure out whether
+ // we're a sequence, during overload resolution.
+ assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
+ // Then we have the [[OwnPropertyKeys]] from
+ // https://webidl.spec.whatwg.org/#es-to-record step 4.
+ assert_array_equals(log[1], ["ownKeys", record]);
+ // Then the [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
+ // Then the [[Get]] from step 5.2.
+ assert_array_equals(log[3], ["get", record, "a", proxy]);
+ // Then the ToString on the value.
+ assert_array_equals(log[4], ["toString", record.a]);
+ // Then the second [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[5], ["getOwnPropertyDescriptor", record, "c"]);
+ // Then the second [[Get]] from step 5.2.
+ assert_array_equals(log[6], ["get", record, "c", proxy]);
+ // Then the ToString on the value.
+ assert_array_equals(log[7], ["toString", record.c]);
+ // Then the third [[GetOwnProperty]] from step 5.1.
+ assert_array_equals(log[8], ["getOwnPropertyDescriptor", record,
+ Symbol.toStringTag]);
+ // No [[Get]] because not enumerable.
+
+ // Check the results.
+ assert_equals([...h].length, 2);
+ assert_array_equals([...h.keys()], ["a", "c"]);
+ assert_true(h.has("a"));
+ assert_equals(h.get("a"), "b");
+ assert_true(h.has("c"));
+ assert_equals(h.get("c"), "d");
+}, "Operation with non-enumerable Symbol keys");
diff --git a/testing/web-platform/tests/fetch/api/headers/headers-structure.any.js b/testing/web-platform/tests/fetch/api/headers/headers-structure.any.js
new file mode 100644
index 0000000000..d826bcab2a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/headers/headers-structure.any.js
@@ -0,0 +1,20 @@
+// META: title=Headers basic
+// META: global=window,worker
+
+"use strict";
+
+var headers = new Headers();
+var methods = ["append",
+ "delete",
+ "get",
+ "has",
+ "set",
+ //Headers is iterable
+ "entries",
+ "keys",
+ "values"
+ ];
+for (var idx in methods)
+ test(function() {
+ assert_true(methods[idx] in headers, "headers has " + methods[idx] + " method");
+ }, "Headers has " + methods[idx] + " method");