Skip to content

Commit fe1db8f

Browse files
committed
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.
1 parent b63e449 commit fe1db8f

File tree

3 files changed

+56
-17
lines changed

3 files changed

+56
-17
lines changed

lib/internal/url.js

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
ArrayPrototypePush,
88
ArrayPrototypeReduce,
99
ArrayPrototypeSlice,
10+
Error,
1011
FunctionPrototypeBind,
1112
Int8Array,
1213
Number,
@@ -29,6 +30,7 @@ const {
2930
Symbol,
3031
SymbolIterator,
3132
SymbolToStringTag,
33+
TypeError,
3234
decodeURIComponent,
3335
} = primordials;
3436

@@ -118,6 +120,8 @@ const cannotHaveUsernamePasswordPort =
118120
const special = Symbol('special');
119121
const searchParams = Symbol('query');
120122
const kFormat = Symbol('format');
123+
const initURLSearchParamsFromRecord =
124+
Symbol('init-url-search-params-from-record');
121125

122126
let blob;
123127
let cryptoRandom;
@@ -174,6 +178,20 @@ function isURLSearchParams(self) {
174178
return self && self[searchParams] && !self[searchParams][searchParams];
175179
}
176180

181+
// WPT needs Error in URLSearchParams' constructor exactly be an instance of
182+
// TypeError.
183+
function throwTypeError(message) {
184+
if (message instanceof Error) {
185+
const err = new TypeError(message.message); // eslint-disable-line
186+
err.stack = message.stack;
187+
if (message.code) err.code = message.code;
188+
if (message.name) err.name = message.name;
189+
throw err;
190+
}
191+
192+
throw new TypeError(message); // eslint-disable-line
193+
}
194+
177195
class URLSearchParams {
178196
// URL Standard says the default value is '', but as undefined and '' have
179197
// the same result, undefined is used to prevent unnecessary parsing.
@@ -191,7 +209,7 @@ class URLSearchParams {
191209
this[searchParams] = childParams.slice();
192210
} else if (method !== null && method !== undefined) {
193211
if (typeof method !== 'function') {
194-
throw new ERR_ARG_NOT_ITERABLE('Query pairs');
212+
throwTypeError(new ERR_ARG_NOT_ITERABLE('Query pairs'));
195213
}
196214

197215
// Sequence<sequence<USVString>>
@@ -201,7 +219,8 @@ class URLSearchParams {
201219
if ((typeof pair !== 'object' && typeof pair !== 'function') ||
202220
pair === null ||
203221
typeof pair[SymbolIterator] !== 'function') {
204-
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
222+
throwTypeError(
223+
new ERR_INVALID_TUPLE('Each query pair', '[name, value]'));
205224
}
206225
const convertedPair = [];
207226
for (const element of pair)
@@ -217,18 +236,10 @@ class URLSearchParams {
217236
ArrayPrototypePush(this[searchParams], pair[0], pair[1]);
218237
}
219238
} else {
220-
// Record<USVString, USVString>
221-
// Need to use reflection APIs for full spec compliance.
222-
this[searchParams] = [];
223-
const keys = ReflectOwnKeys(init);
224-
for (let i = 0; i < keys.length; i++) {
225-
const key = keys[i];
226-
const desc = ReflectGetOwnPropertyDescriptor(init, key);
227-
if (desc !== undefined && desc.enumerable) {
228-
const typedKey = toUSVString(key);
229-
const typedValue = toUSVString(init[key]);
230-
this[searchParams].push(typedKey, typedValue);
231-
}
239+
try {
240+
this[initURLSearchParamsFromRecord](init);
241+
} catch (e) {
242+
throwTypeError(e);
232243
}
233244
}
234245
} else {
@@ -242,6 +253,31 @@ class URLSearchParams {
242253
this[context] = null;
243254
}
244255

256+
[initURLSearchParamsFromRecord](init) {
257+
// Record<USVString, USVString>
258+
// Need to use reflection APIs for full spec compliance.
259+
const visited = {};
260+
this[searchParams] = [];
261+
const keys = ReflectOwnKeys(init);
262+
for (let i = 0; i < keys.length; i++) {
263+
const key = keys[i];
264+
const desc = ReflectGetOwnPropertyDescriptor(init, key);
265+
if (desc !== undefined && desc.enumerable) {
266+
const typedKey = toUSVString(key);
267+
const typedValue = toUSVString(init[key]);
268+
269+
// Two different key may result same after `toUSVString()`, we only
270+
// leave the later one. Refers to WPT.
271+
if (visited[typedKey] !== undefined) {
272+
this[searchParams][visited[typedKey]] = typedValue;
273+
} else {
274+
this[searchParams].push(typedKey, typedValue);
275+
visited[typedKey] = this[searchParams].length - 1;
276+
}
277+
}
278+
}
279+
}
280+
245281
[inspect.custom](recurseTimes, ctx) {
246282
if (!isURLSearchParams(this))
247283
throw new ERR_INVALID_THIS('URLSearchParams');

test/wpt/status/url.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
"urlencoded-parser.any.js": {
1313
"fail": "missing Request and Response"
1414
},
15-
"urlsearchparams-constructor.any.js": {
16-
"fail": "FormData is not defined"
17-
},
1815
"url-constructor.any.js": {
1916
"requires": ["small-icu"]
2017
},

test/wpt/test-url.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ runner.setScriptModifier((obj) => {
1111
// created via `document.createElement`. So we need to ignore them and just
1212
// test `URL`.
1313
obj.code = obj.code.replace(/\["url", "a", "area"\]/, '[ "url" ]');
14+
} else if (obj.filename.includes('urlsearchparams-constructor.any.js')) {
15+
// Ignore test named `URLSearchParams constructor, FormData.` because we do
16+
// not have `FormData`.
17+
obj.code = obj.code.replace(
18+
/('URLSearchParams constructor, object\.'\);[\w\W]+)test\(function\(\) {[\w\W]*?}, 'URLSearchParams constructor, FormData\.'\);/,
19+
'$1');
1420
}
1521
});
1622
runner.pretendGlobalThisAs('Window');

0 commit comments

Comments
 (0)