From 2a66d5de01b584b7138084eb427c9ac09c254986 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Wed, 16 Jun 2021 18:40:35 +0200 Subject: [PATCH] fix: align URL / URLSearchParams to spec (#11005) --- Cargo.lock | 1 + extensions/fetch/20_headers.js | 11 +- extensions/url/00_url.js | 428 +++++++++++++++++++----------- extensions/url/Cargo.toml | 1 + extensions/url/benches/url_ops.rs | 1 + extensions/webidl/00_webidl.js | 97 ++++--- tools/wpt/expectation.json | 30 +-- 7 files changed, 350 insertions(+), 219 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61176425b4..b882fa43d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,6 +766,7 @@ version = "0.9.0" dependencies = [ "deno_bench_util", "deno_core", + "deno_webidl", "idna", "percent-encoding", "serde", diff --git a/extensions/fetch/20_headers.js b/extensions/fetch/20_headers.js index a0c0014805..26500b602f 100644 --- a/extensions/fetch/20_headers.js +++ b/extensions/fetch/20_headers.js @@ -387,18 +387,9 @@ webidl.configurePrototype(Headers); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["ByteString"]); - webidl.converters["sequence>"] = webidl - .createSequenceConverter(webidl.converters["sequence"]); - webidl.converters["record"] = webidl - .createRecordConverter( - webidl.converters["ByteString"], - webidl.converters["ByteString"], - ); webidl.converters["HeadersInit"] = (V, opts) => { // Union for (sequence> or record) - if (typeof V === "object" && V !== null) { + if (webidl.type(V) === "Object" && V !== null) { if (V[Symbol.iterator] !== undefined) { return webidl.converters["sequence>"](V, opts); } diff --git a/extensions/url/00_url.js b/extensions/url/00_url.js index f6f3335dda..1c66c9d572 100644 --- a/extensions/url/00_url.js +++ b/extensions/url/00_url.js @@ -3,23 +3,24 @@ ((window) => { const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; - function requiredArguments(name, length, required) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - const paramLists = new WeakMap(); - const urls = new WeakMap(); + const _list = Symbol("list"); + const _urlObject = Symbol("url object"); class URLSearchParams { - #params = []; + [_list]; + [_urlObject] = null; constructor(init = "") { + const prefix = "Failed to construct 'URL'"; + init = webidl.converters + ["sequence> or record or USVString"]( + init, + { prefix, context: "Argument 1" }, + ); + this[webidl.brand] = webidl.brand; + if (typeof init === "string") { // Overload: USVString // If init is a string and starts with U+003F (?), @@ -27,59 +28,65 @@ if (init[0] == "?") { init = init.slice(1); } - - this.#params = core.opSync("op_url_parse_search_params", init); - } else if ( - Array.isArray(init) || - typeof init?.[Symbol.iterator] == "function" - ) { + this[_list] = core.opSync("op_url_parse_search_params", init); + } else if (Array.isArray(init)) { // Overload: sequence> - for (const pair of init) { - // If pair does not contain exactly two items, then throw a TypeError. + this[_list] = init.map((pair, i) => { if (pair.length !== 2) { throw new TypeError( - "URLSearchParams.constructor sequence argument must only contain pair elements", + `${prefix}: Item ${i + + 0} in the parameter list does have length 2 exactly.`, ); } - this.#params.push([String(pair[0]), String(pair[1])]); - } - } else if (Object(init) !== init) { - // pass - } else if (init instanceof URLSearchParams) { - this.#params = [...init.#params]; + return [pair[0], pair[1]]; + }); } else { // Overload: record - for (const key of Object.keys(init)) { - this.#params.push([key, String(init[key])]); - } + this[_list] = Object.keys(init).map((key) => [key, init[key]]); } - - paramLists.set(this, this.#params); - urls.set(this, null); } #updateUrlSearch() { - const url = urls.get(this); - if (url == null) { + const url = this[_urlObject]; + if (url === null) { return; } - const parseArgs = { href: url.href, setSearch: this.toString() }; - parts.set(url, core.opSync("op_url_parse", parseArgs)); + const parts = core.opSync("op_url_parse", { + href: url.href, + setSearch: this.toString(), + }); + url[_url] = parts; } append(name, value) { - requiredArguments("URLSearchParams.append", arguments.length, 2); - this.#params.push([String(name), String(value)]); + webidl.assertBranded(this, URLSearchParams); + const prefix = "Failed to execute 'append' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 2", + }); + this[_list].push([name, value]); this.#updateUrlSearch(); } delete(name) { - requiredArguments("URLSearchParams.delete", arguments.length, 1); - name = String(name); + webidl.assertBranded(this, URLSearchParams); + const prefix = "Failed to execute 'append' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const list = this[_list]; let i = 0; - while (i < this.#params.length) { - if (this.#params[i][0] === name) { - this.#params.splice(i, 1); + while (i < list.length) { + if (list[i][0] === name) { + list.splice(i, 1); } else { i++; } @@ -88,54 +95,77 @@ } getAll(name) { - requiredArguments("URLSearchParams.getAll", arguments.length, 1); - name = String(name); + webidl.assertBranded(this, URLSearchParams); + const prefix = "Failed to execute 'getAll' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); const values = []; - for (const entry of this.#params) { + for (const entry of this[_list]) { if (entry[0] === name) { values.push(entry[1]); } } - return values; } get(name) { - requiredArguments("URLSearchParams.get", arguments.length, 1); - name = String(name); - for (const entry of this.#params) { + webidl.assertBranded(this, URLSearchParams); + const prefix = "Failed to execute 'get' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + for (const entry of this[_list]) { if (entry[0] === name) { return entry[1]; } } - return null; } has(name) { - requiredArguments("URLSearchParams.has", arguments.length, 1); - name = String(name); - return this.#params.some((entry) => entry[0] === name); + webidl.assertBranded(this, URLSearchParams); + const prefix = "Failed to execute 'has' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + return this[_list].some((entry) => entry[0] === name); } set(name, value) { - requiredArguments("URLSearchParams.set", arguments.length, 2); + webidl.assertBranded(this, URLSearchParams); + const prefix = "Failed to execute 'set' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 2", + }); + + const list = this[_list]; // If there are any name-value pairs whose name is name, in list, // set the value of the first such name-value pair to value // and remove the others. - name = String(name); - value = String(value); let found = false; let i = 0; - while (i < this.#params.length) { - if (this.#params[i][0] === name) { + while (i < list.length) { + if (list[i][0] === name) { if (!found) { - this.#params[i][1] = value; + list[i][1] = value; found = true; i++; } else { - this.#params.splice(i, 1); + list.splice(i, 1); } } else { i++; @@ -145,69 +175,51 @@ // Otherwise, append a new name-value pair whose name is name // and value is value, to list. if (!found) { - this.#params.push([String(name), String(value)]); + list.push([name, value]); } this.#updateUrlSearch(); } sort() { - this.#params.sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1)); + webidl.assertBranded(this, URLSearchParams); + this[_list].sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1)); this.#updateUrlSearch(); } - forEach(callbackfn, thisArg) { - requiredArguments("URLSearchParams.forEach", arguments.length, 1); - - if (typeof thisArg !== "undefined") { - callbackfn = callbackfn.bind(thisArg); - } - - for (const [key, value] of this.#params) { - callbackfn(value, key, this); - } - } - - *keys() { - for (const [key] of this.#params) { - yield key; - } - } - - *values() { - for (const [, value] of this.#params) { - yield value; - } - } - - *entries() { - yield* this.#params; - } - - *[Symbol.iterator]() { - yield* this.#params; - } - toString() { - return core.opSync("op_url_stringify_search_params", this.#params); + webidl.assertBranded(this, URLSearchParams); + return core.opSync("op_url_stringify_search_params", this[_list]); + } + + get [Symbol.toStringTag]() { + return "URLSearchParams"; } } - const parts = new WeakMap(); + webidl.mixinPairIterable("URLSearchParams", URLSearchParams, _list, 0, 1); + + webidl.configurePrototype(URLSearchParams); + + const _url = Symbol("url"); class URL { - #searchParams = null; + [_url]; + #queryObject = null; - constructor(url, base) { - new.target; - - if (url instanceof URL && base === undefined) { - parts.set(this, parts.get(url)); - } else { - base = base !== undefined ? String(base) : base; - const parseArgs = { href: String(url), baseHref: base }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + constructor(url, base = undefined) { + const prefix = "Failed to construct 'URL'"; + url = webidl.converters.USVString(url, { prefix, context: "Argument 1" }); + if (base !== undefined) { + base = webidl.converters.USVString(base, { + prefix, + context: "Argument 2", + }); } + this[webidl.brand] = webidl.brand; + + const parts = core.opSync("op_url_parse", { href: url, baseHref: base }); + this[_url] = parts; } [Symbol.for("Deno.customInspect")](inspect) { @@ -228,8 +240,8 @@ } #updateSearchParams() { - if (this.#searchParams != null) { - const params = paramLists.get(this.#searchParams); + if (this.#queryObject !== null) { + const params = this.#queryObject[_list]; const newParams = core.opSync( "op_url_parse_search_params", this.search.slice(1), @@ -239,122 +251,208 @@ } get hash() { - return parts.get(this).hash; + webidl.assertBranded(this, URL); + return this[_url].hash; } set hash(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'hash' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setHash: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setHash: value, + }); } catch { /* pass */ } } get host() { - return parts.get(this).host; + webidl.assertBranded(this, URL); + return this[_url].host; } set host(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'host' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setHost: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setHost: value, + }); } catch { /* pass */ } } get hostname() { - return parts.get(this).hostname; + webidl.assertBranded(this, URL); + return this[_url].hostname; } set hostname(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'hostname' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setHostname: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setHostname: value, + }); } catch { /* pass */ } } get href() { - return parts.get(this).href; + webidl.assertBranded(this, URL); + return this[_url].href; } set href(value) { - try { - const parseArgs = { href: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); - } catch { - throw new TypeError("Invalid URL"); - } + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'href' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); + this[_url] = core.opSync("op_url_parse", { + href: value, + }); this.#updateSearchParams(); } get origin() { - return parts.get(this).origin; + webidl.assertBranded(this, URL); + return this[_url].origin; } get password() { - return parts.get(this).password; + webidl.assertBranded(this, URL); + return this[_url].password; } set password(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'password' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setPassword: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setPassword: value, + }); } catch { /* pass */ } } get pathname() { - return parts.get(this).pathname; + webidl.assertBranded(this, URL); + return this[_url].pathname; } set pathname(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'pathname' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setPathname: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setPathname: value, + }); } catch { /* pass */ } } get port() { - return parts.get(this).port; + webidl.assertBranded(this, URL); + return this[_url].port; } set port(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'port' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setPort: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setPort: value, + }); } catch { /* pass */ } } get protocol() { - return parts.get(this).protocol; + webidl.assertBranded(this, URL); + return this[_url].protocol; } set protocol(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'protocol' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setProtocol: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setProtocol: value, + }); } catch { /* pass */ } } get search() { - return parts.get(this).search; + webidl.assertBranded(this, URL); + return this[_url].search; } set search(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'search' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setSearch: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setSearch: value, + }); this.#updateSearchParams(); } catch { /* pass */ @@ -362,35 +460,53 @@ } get username() { - return parts.get(this).username; + webidl.assertBranded(this, URL); + return this[_url].username; } set username(value) { + webidl.assertBranded(this, URL); + const prefix = "Failed to set 'username' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 1", + }); try { - const parseArgs = { href: this.href, setUsername: String(value) }; - parts.set(this, core.opSync("op_url_parse", parseArgs)); + this[_url] = core.opSync("op_url_parse", { + href: this.href, + setUsername: value, + }); } catch { /* pass */ } } get searchParams() { - if (this.#searchParams == null) { - this.#searchParams = new URLSearchParams(this.search); - urls.set(this.#searchParams, this); + if (this.#queryObject == null) { + this.#queryObject = new URLSearchParams(this.search); + this.#queryObject[_urlObject] = this; } - return this.#searchParams; + return this.#queryObject; } toString() { + webidl.assertBranded(this, URL); return this.href; } toJSON() { + webidl.assertBranded(this, URL); return this.href; } + + get [Symbol.toStringTag]() { + return "URL"; + } } + webidl.configurePrototype(URL); + /** * This function implements application/x-www-form-urlencoded parsing. * https://url.spec.whatwg.org/#concept-urlencoded-parser @@ -401,6 +517,20 @@ return core.opSync("op_url_parse_search_params", null, bytes); } + webidl + .converters[ + "sequence> or record or USVString" + ] = (V, opts) => { + // Union for (sequence> or record or USVString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[Symbol.iterator] !== undefined) { + return webidl.converters["sequence>"](V, opts); + } + return webidl.converters["record"](V, opts); + } + return webidl.converters.USVString(V, opts); + }; + window.__bootstrap.url = { URL, URLSearchParams, diff --git a/extensions/url/Cargo.toml b/extensions/url/Cargo.toml index 17ba8c7f1b..6434531bf2 100644 --- a/extensions/url/Cargo.toml +++ b/extensions/url/Cargo.toml @@ -21,6 +21,7 @@ serde = { version = "1.0.125", features = ["derive"] } [dev-dependencies] deno_bench_util = { version = "0.3.0", path = "../../bench_util" } +deno_webidl = { version = "0.9.0", path = "../webidl" } [[bench]] name = "url_ops" diff --git a/extensions/url/benches/url_ops.rs b/extensions/url/benches/url_ops.rs index c390af0d87..ed27b6f80d 100644 --- a/extensions/url/benches/url_ops.rs +++ b/extensions/url/benches/url_ops.rs @@ -6,6 +6,7 @@ use deno_core::Extension; fn setup() -> Vec { vec![ + deno_webidl::init(), deno_url::init(), Extension::builder() .js(vec![( diff --git a/extensions/webidl/00_webidl.js b/extensions/webidl/00_webidl.js index a54d2fe6ef..e6cda92d4c 100644 --- a/extensions/webidl/00_webidl.js +++ b/extensions/webidl/00_webidl.js @@ -568,6 +568,17 @@ ); converters["Promise"] = createPromiseConverter(() => undefined); + converters["sequence"] = createSequenceConverter( + converters.ByteString, + ); + converters["sequence>"] = createSequenceConverter( + converters["sequence"], + ); + converters["record"] = createRecordConverter( + converters.ByteString, + converters.ByteString, + ); + function requiredArguments(length, required, opts = {}) { if (length < required) { const errMsg = `${ @@ -708,7 +719,7 @@ // https://heycam.github.io/webidl/#es-sequence function createSequenceConverter(converter) { return function (V, opts = {}) { - if (typeof V !== "object") { + if (type(V) !== "Object") { throw makeException( TypeError, "can not be converted to sequence.", @@ -746,7 +757,7 @@ function createRecordConverter(keyConverter, valueConverter) { return (V, opts) => { - if (typeof V !== "object") { + if (type(V) !== "Object") { throw makeException( TypeError, "can not be converted to dictionary.", @@ -879,41 +890,64 @@ return iterator; } - const methods = { - entries() { - assertBranded(this, prototype); - return createDefaultIterator(this, "key+value"); + function entries() { + assertBranded(this, prototype); + return createDefaultIterator(this, "key+value"); + } + + const properties = { + entries: { + value: entries, + writable: true, + enumerable: true, + configurable: true, }, - [Symbol.iterator]() { - assertBranded(this, prototype); - return createDefaultIterator(this, "key+value"); + [Symbol.iterator]: { + value: entries, + writable: true, + enumerable: false, + configurable: true, }, - keys() { - assertBranded(this, prototype); - return createDefaultIterator(this, "key"); + keys: { + value: function keys() { + assertBranded(this, prototype); + return createDefaultIterator(this, "key"); + }, + writable: true, + enumerable: true, + configurable: true, }, - values() { - assertBranded(this, prototype); - return createDefaultIterator(this, "value"); + values: { + value: function values() { + assertBranded(this, prototype); + return createDefaultIterator(this, "value"); + }, + writable: true, + enumerable: true, + configurable: true, }, - forEach(idlCallback, thisArg) { - assertBranded(this, prototype); - const prefix = `Failed to execute 'forEach' on '${name}'`; - requiredArguments(arguments.length, 1, { prefix }); - idlCallback = converters["Function"](idlCallback, { - prefix, - context: "Argument 1", - }); - idlCallback = idlCallback.bind(thisArg ?? globalThis); - const pairs = this[dataSymbol]; - for (let i = 0; i < pairs.length; i++) { - const entry = pairs[i]; - idlCallback(entry[valueKey], entry[keyKey], this); - } + forEach: { + value: function forEach(idlCallback, thisArg = undefined) { + assertBranded(this, prototype); + const prefix = `Failed to execute 'forEach' on '${name}'`; + requiredArguments(arguments.length, 1, { prefix }); + idlCallback = converters["Function"](idlCallback, { + prefix, + context: "Argument 1", + }); + idlCallback = idlCallback.bind(thisArg ?? globalThis); + const pairs = this[dataSymbol]; + for (let i = 0; i < pairs.length; i++) { + const entry = pairs[i]; + idlCallback(entry[valueKey], entry[keyKey], this); + } + }, + writable: true, + enumerable: true, + configurable: true, }, }; - - return Object.assign(prototype.prototype, methods); + return Object.defineProperties(prototype.prototype, properties); } function configurePrototype(prototype) { @@ -938,6 +972,7 @@ window.__bootstrap ??= {}; window.__bootstrap.webidl = { + type, makeException, converters, requiredArguments, diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index e4ce77f9b1..54e8fe7d03 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -586,34 +586,7 @@ "historical.any.html": [ " and .searchParams should be undefined" ], - "idlharness.any.html": [ - "URL interface object length", - "URL interface: attribute href", - "URL interface: stringifier", - "URL interface: attribute origin", - "URL interface: attribute protocol", - "URL interface: attribute username", - "URL interface: attribute password", - "URL interface: attribute host", - "URL interface: attribute hostname", - "URL interface: attribute port", - "URL interface: attribute pathname", - "URL interface: attribute search", - "URL interface: attribute searchParams", - "URL interface: attribute hash", - "URL interface: operation toJSON()", - "Stringification of new URL(\"http://foo\")", - "URLSearchParams interface: operation append(USVString, USVString)", - "URLSearchParams interface: operation delete(USVString)", - "URLSearchParams interface: operation get(USVString)", - "URLSearchParams interface: operation getAll(USVString)", - "URLSearchParams interface: operation has(USVString)", - "URLSearchParams interface: operation set(USVString, USVString)", - "URLSearchParams interface: operation sort()", - "URLSearchParams interface: iterable", - "URLSearchParams interface: stringifier", - "Stringification of new URLSearchParams(\"hi=there&thank=you\")" - ], + "idlharness.any.html": true, "url-constructor.any.html": [ "Parsing: against ", "Parsing: against ", @@ -879,7 +852,6 @@ "redirect-to-dataurl.any.html": true }, "idlharness.any.html": [ - "Headers interface: iterable", "Request interface: attribute destination", "Request interface: attribute referrer", "Request interface: attribute referrerPolicy",