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 at least 2 "Create headers with null should throw"
WPT subtests which I have imported in this patch.
This commit is contained in:
Feng Yu 2024-12-09 11:47:14 -08:00 committed by Tim Flynn
parent 023c3aa5b0
commit f2eaf3381f
Notes: github-actions[bot] 2024-12-10 14:47:27 +00:00
7 changed files with 717 additions and 3 deletions

View file

@ -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_undefined())
@cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@));
)~~~");
} else {
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 == "\"\"") {
} 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@));
)~~~");

View file

@ -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

View file

@ -0,0 +1,18 @@
Harness status: OK
Found 13 tests
13 Pass
Pass Passing nothing to Headers constructor
Pass Passing undefined to Headers constructor
Pass Passing null to Headers constructor
Pass Basic operation with one property
Pass Basic operation with one property and a proto
Pass Correct operation ordering with two properties
Pass Correct operation ordering with two properties one of which has an invalid name
Pass Correct operation ordering with two properties one of which has an invalid value
Pass Correct operation ordering with non-enumerable properties
Pass Correct operation ordering with undefined descriptors
Pass Correct operation ordering with repeated keys
Pass Basic operation with Symbol keys
Pass Operation with non-enumerable Symbol keys

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>Headers structure</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../../../fetch/api/headers/headers-basic.any.js"></script>

View file

@ -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");

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../../../fetch/api/headers/headers-record.any.js"></script>

View file

@ -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");