From fe1db8f04769a84ba76c8a3f68ce6a4f2fb02cde Mon Sep 17 00:00:00 2001 From: XadillaX Date: Tue, 31 Aug 2021 02:07:39 +0800 Subject: [PATCH] url,lib: pass urlsearchparams-constructor.any.js According to WPT: 1. `URLSearchParams` constructor should throw exactly `TypeError` if any Error occurrs. 2. When a record passed to `URLSearchParams` constructor, two different key may result same after `toUVString()`. We should leave only the later one. --- lib/internal/url.js | 64 +++++++++++++++++++++++++++++++--------- test/wpt/status/url.json | 3 -- test/wpt/test-url.js | 6 ++++ 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lib/internal/url.js b/lib/internal/url.js index bc5d70d8bf17c7..a67e99fa92101c 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -7,6 +7,7 @@ const { ArrayPrototypePush, ArrayPrototypeReduce, ArrayPrototypeSlice, + Error, FunctionPrototypeBind, Int8Array, Number, @@ -29,6 +30,7 @@ const { Symbol, SymbolIterator, SymbolToStringTag, + TypeError, decodeURIComponent, } = primordials; @@ -118,6 +120,8 @@ const cannotHaveUsernamePasswordPort = const special = Symbol('special'); const searchParams = Symbol('query'); const kFormat = Symbol('format'); +const initURLSearchParamsFromRecord = + Symbol('init-url-search-params-from-record'); let blob; let cryptoRandom; @@ -174,6 +178,20 @@ function isURLSearchParams(self) { return self && self[searchParams] && !self[searchParams][searchParams]; } +// WPT needs Error in URLSearchParams' constructor exactly be an instance of +// TypeError. +function throwTypeError(message) { + if (message instanceof Error) { + const err = new TypeError(message.message); // eslint-disable-line + err.stack = message.stack; + if (message.code) err.code = message.code; + if (message.name) err.name = message.name; + throw err; + } + + throw new TypeError(message); // eslint-disable-line +} + class URLSearchParams { // URL Standard says the default value is '', but as undefined and '' have // the same result, undefined is used to prevent unnecessary parsing. @@ -191,7 +209,7 @@ class URLSearchParams { this[searchParams] = childParams.slice(); } else if (method !== null && method !== undefined) { if (typeof method !== 'function') { - throw new ERR_ARG_NOT_ITERABLE('Query pairs'); + throwTypeError(new ERR_ARG_NOT_ITERABLE('Query pairs')); } // Sequence> @@ -201,7 +219,8 @@ class URLSearchParams { if ((typeof pair !== 'object' && typeof pair !== 'function') || pair === null || typeof pair[SymbolIterator] !== 'function') { - throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); + throwTypeError( + new ERR_INVALID_TUPLE('Each query pair', '[name, value]')); } const convertedPair = []; for (const element of pair) @@ -217,18 +236,10 @@ class URLSearchParams { ArrayPrototypePush(this[searchParams], pair[0], pair[1]); } } else { - // Record - // Need to use reflection APIs for full spec compliance. - this[searchParams] = []; - const keys = ReflectOwnKeys(init); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const desc = ReflectGetOwnPropertyDescriptor(init, key); - if (desc !== undefined && desc.enumerable) { - const typedKey = toUSVString(key); - const typedValue = toUSVString(init[key]); - this[searchParams].push(typedKey, typedValue); - } + try { + this[initURLSearchParamsFromRecord](init); + } catch (e) { + throwTypeError(e); } } } else { @@ -242,6 +253,31 @@ class URLSearchParams { this[context] = null; } + [initURLSearchParamsFromRecord](init) { + // Record + // Need to use reflection APIs for full spec compliance. + const visited = {}; + this[searchParams] = []; + const keys = ReflectOwnKeys(init); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const desc = ReflectGetOwnPropertyDescriptor(init, key); + if (desc !== undefined && desc.enumerable) { + const typedKey = toUSVString(key); + const typedValue = toUSVString(init[key]); + + // Two different key may result same after `toUSVString()`, we only + // leave the later one. Refers to WPT. + if (visited[typedKey] !== undefined) { + this[searchParams][visited[typedKey]] = typedValue; + } else { + this[searchParams].push(typedKey, typedValue); + visited[typedKey] = this[searchParams].length - 1; + } + } + } + } + [inspect.custom](recurseTimes, ctx) { if (!isURLSearchParams(this)) throw new ERR_INVALID_THIS('URLSearchParams'); diff --git a/test/wpt/status/url.json b/test/wpt/status/url.json index fdf562c2c89c99..bb5b0215febf69 100644 --- a/test/wpt/status/url.json +++ b/test/wpt/status/url.json @@ -12,9 +12,6 @@ "urlencoded-parser.any.js": { "fail": "missing Request and Response" }, - "urlsearchparams-constructor.any.js": { - "fail": "FormData is not defined" - }, "url-constructor.any.js": { "requires": ["small-icu"] }, diff --git a/test/wpt/test-url.js b/test/wpt/test-url.js index cca2184b47720b..e508383318152c 100644 --- a/test/wpt/test-url.js +++ b/test/wpt/test-url.js @@ -11,6 +11,12 @@ runner.setScriptModifier((obj) => { // created via `document.createElement`. So we need to ignore them and just // test `URL`. obj.code = obj.code.replace(/\["url", "a", "area"\]/, '[ "url" ]'); + } else if (obj.filename.includes('urlsearchparams-constructor.any.js')) { + // Ignore test named `URLSearchParams constructor, FormData.` because we do + // not have `FormData`. + obj.code = obj.code.replace( + /('URLSearchParams constructor, object\.'\);[\w\W]+)test\(function\(\) {[\w\W]*?}, 'URLSearchParams constructor, FormData\.'\);/, + '$1'); } }); runner.pretendGlobalThisAs('Window');