From e2f47c359165afe5524ae6a0028a9dfaaa04d5aa Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Mon, 9 Dec 2024 11:47:14 -0800 Subject: [PATCH] LibWeb: Throw TypeError for `new Headers(null)` The WebIDL for the `Headers` constructor specifies that the `init` parameter is optional and must be of type `HeadersInit`. While the parameter can be omitted (or explicitly set to `undefined`), `null` is not a valid value. This change fixes a "Create headers with null should throw" WPT subtest which I has imported in this patch. --- .../BindingsGenerator/IDLGenerators.cpp | 12 +- .../fetch/api/headers/headers-basic.any.txt | 28 ++ .../fetch/api/headers/headers-basic.any.html | 15 + .../fetch/api/headers/headers-basic.any.js | 275 ++++++++++++++++++ 4 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 748e2ba241f40..5b4da377ae92b 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -1550,14 +1550,20 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter @union_type@ @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { - if (!optional_default_value.has_value() || optional_default_value == "null"sv) { + if (!optional_default_value.has_value()) { union_generator.append(R"~~~( Optional<@union_type@> @cpp_name@; - if (!@js_name@@js_suffix@.is_nullish()) + if (!@js_name@@js_suffix@.is_undefined()) @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { - if (optional_default_value == "\"\"") { + if (optional_default_value == "null"sv) { + union_generator.append(R"~~~( + Optional<@union_type@> @cpp_name@; + if (!@js_name@@js_suffix@.is_nullish()) + @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); +)~~~"); + } else if (optional_default_value == "\"\"") { union_generator.append(R"~~~( @union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? String {} : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt new file mode 100644 index 0000000000000..193bdd49d9d57 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt @@ -0,0 +1,28 @@ +Harness status: OK + +Found 23 tests + +23 Pass +Pass Create headers from no parameter +Pass Create headers from undefined parameter +Pass Create headers from empty object +Pass Create headers with null should throw +Pass Create headers with 1 should throw +Pass Create headers with sequence +Pass Create headers with record +Pass Create headers with existing headers +Pass Create headers with existing headers with custom iterator +Pass Check append method +Pass Check set method +Pass Check has method +Pass Check delete method +Pass Check get method +Pass Check keys method +Pass Check values method +Pass Check entries method +Pass Check Symbol.iterator method +Pass Check forEach method +Pass Iteration skips elements removed while iterating +Pass Removing elements already iterated over causes an element to be skipped during iteration +Pass Appending a value pair during iteration causes it to be reached during iteration +Pass Prepending a value pair before the current element position causes it to be skipped during iteration and adds the current element a second time \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html new file mode 100644 index 0000000000000..130582930b82a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html @@ -0,0 +1,15 @@ + + +Headers structure + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js new file mode 100644 index 0000000000000..ead1047645a15 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/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");