Skip to content

Commit 315c3aa

Browse files
TimothyGujasnell
authored andcommitted
url: more precise URLSearchParams constructor
PR-URL: #13026 Ref: web-platform-tests/wpt#5813 Reviewed-By: Daijiro Wachi <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent b3c9bff commit 315c3aa

File tree

2 files changed

+58
-30
lines changed

2 files changed

+58
-30
lines changed

lib/internal/url.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,8 @@ class URLSearchParams {
814814
constructor(init = undefined) {
815815
if (init === null || init === undefined) {
816816
this[searchParams] = [];
817-
} else if (typeof init === 'object') {
817+
} else if ((typeof init === 'object' && init !== null) ||
818+
typeof init === 'function') {
818819
const method = init[Symbol.iterator];
819820
if (method === this[Symbol.iterator]) {
820821
// While the spec does not have this branch, we can use it as a
@@ -830,12 +831,16 @@ class URLSearchParams {
830831
// Note: per spec we have to first exhaust the lists then process them
831832
const pairs = [];
832833
for (const pair of init) {
833-
if (typeof pair !== 'object' ||
834+
if ((typeof pair !== 'object' && typeof pair !== 'function') ||
835+
pair === null ||
834836
typeof pair[Symbol.iterator] !== 'function') {
835837
throw new errors.TypeError('ERR_INVALID_TUPLE', 'Each query pair',
836838
'[name, value]');
837839
}
838-
pairs.push(Array.from(pair));
840+
const convertedPair = [];
841+
for (const element of pair)
842+
convertedPair.push(toUSVString(element));
843+
pairs.push(convertedPair);
839844
}
840845

841846
this[searchParams] = [];
@@ -844,17 +849,21 @@ class URLSearchParams {
844849
throw new errors.TypeError('ERR_INVALID_TUPLE', 'Each query pair',
845850
'[name, value]');
846851
}
847-
const key = toUSVString(pair[0]);
848-
const value = toUSVString(pair[1]);
849-
this[searchParams].push(key, value);
852+
this[searchParams].push(pair[0], pair[1]);
850853
}
851854
} else {
852855
// record<USVString, USVString>
856+
// Need to use reflection APIs for full spec compliance.
853857
this[searchParams] = [];
854-
for (var key of Object.keys(init)) {
855-
key = toUSVString(key);
856-
const value = toUSVString(init[key]);
857-
this[searchParams].push(key, value);
858+
const keys = Reflect.ownKeys(init);
859+
for (var i = 0; i < keys.length; i++) {
860+
const key = keys[i];
861+
const desc = Reflect.getOwnPropertyDescriptor(init, key);
862+
if (desc !== undefined && desc.enumerable) {
863+
const typedKey = toUSVString(key);
864+
const typedValue = toUSVString(init[key]);
865+
this[searchParams].push(typedKey, typedValue);
866+
}
858867
}
859868
}
860869
} else {

test/parallel/test-whatwg-url-searchparams-constructor.js

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const {
1111
/* eslint-disable */
1212
var params; // Strict mode fix for WPT.
1313
/* WPT Refs:
14-
https://github.com/w3c/web-platform-tests/blob/e94c604916/url/urlsearchparams-constructor.html
14+
https://github.com/w3c/web-platform-tests/blob/54c3502d7b/url/urlsearchparams-constructor.html
1515
License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
1616
*/
1717
test(function() {
@@ -87,6 +87,17 @@ test(function() {
8787
assert_equals(params.get('a b'), 'c');
8888
}, 'Parse +');
8989

90+
test(function() {
91+
const testValue = '+15555555555';
92+
const params = new URLSearchParams();
93+
params.set('query', testValue);
94+
var newParams = new URLSearchParams(params.toString());
95+
96+
assert_equals(params.toString(), 'query=%2B15555555555');
97+
assert_equals(params.get('query'), testValue);
98+
assert_equals(newParams.get('query'), testValue);
99+
}, 'Parse encoded +');
100+
90101
test(function() {
91102
var params = new URLSearchParams('a=b c');
92103
assert_equals(params.get('a'), 'b c');
@@ -156,7 +167,8 @@ test(function() {
156167
[
157168
{ "input": {"+": "%C2"}, "output": [["+", "%C2"]], "name": "object with +" },
158169
{ "input": {c: "x", a: "?"}, "output": [["c", "x"], ["a", "?"]], "name": "object with two keys" },
159-
{ "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" }
170+
{ "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" },
171+
{ "input": {"a\0b": "42", "c\uD83D": "23", "d\u1234": "foo"}, "output": [["a\0b", "42"], ["c\uFFFD", "23"], ["d\u1234", "foo"]], "name": "object with NULL, non-ASCII, and surrogate keys" }
160172
].forEach((val) => {
161173
test(() => {
162174
let params = new URLSearchParams(val.input),
@@ -179,12 +191,12 @@ test(() => {
179191
/* eslint-enable */
180192

181193
// Tests below are not from WPT.
182-
{
183-
// assert.throws(() => {
184-
// new URLSearchParams({
185-
// toString() { throw new TypeError('Illegal invocation'); }
186-
// });
187-
// }, TypeError);
194+
function makeIterableFunc(array) {
195+
return Object.assign(() => {}, {
196+
[Symbol.iterator]() {
197+
return array[Symbol.iterator]();
198+
}
199+
});
188200
}
189201

190202
{
@@ -200,17 +212,25 @@ test(() => {
200212
});
201213

202214
let params;
203-
// URLSearchParams constructor, undefined and null as argument
204215
params = new URLSearchParams(undefined);
205216
assert.strictEqual(params.toString(), '');
206217
params = new URLSearchParams(null);
207218
assert.strictEqual(params.toString(), '');
219+
params = new URLSearchParams(
220+
makeIterableFunc([['key', 'val'], ['key2', 'val2']])
221+
);
222+
assert.strictEqual(params.toString(), 'key=val&key2=val2');
223+
params = new URLSearchParams(
224+
makeIterableFunc([['key', 'val'], ['key2', 'val2']].map(makeIterableFunc))
225+
);
226+
assert.strictEqual(params.toString(), 'key=val&key2=val2');
208227
assert.throws(() => new URLSearchParams([[1]]), tupleError);
209228
assert.throws(() => new URLSearchParams([[1, 2, 3]]), tupleError);
210229
assert.throws(() => new URLSearchParams({ [Symbol.iterator]: 42 }),
211230
iterableError);
212231
assert.throws(() => new URLSearchParams([{}]), tupleError);
213232
assert.throws(() => new URLSearchParams(['a']), tupleError);
233+
assert.throws(() => new URLSearchParams([null]), tupleError);
214234
assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]),
215235
tupleError);
216236
}
@@ -221,15 +241,14 @@ test(() => {
221241
valueOf() { throw new Error('valueOf'); }
222242
};
223243
const sym = Symbol();
224-
225-
assert.throws(() => new URLSearchParams({ a: obj }), /^Error: toString$/);
226-
assert.throws(() => new URLSearchParams([['a', obj]]), /^Error: toString$/);
227-
assert.throws(() => new URLSearchParams(sym),
228-
/^TypeError: Cannot convert a Symbol value to a string$/);
229-
assert.throws(() => new URLSearchParams({ a: sym }),
230-
/^TypeError: Cannot convert a Symbol value to a string$/);
231-
assert.throws(() => new URLSearchParams([[sym, 'a']]),
232-
/^TypeError: Cannot convert a Symbol value to a string$/);
233-
assert.throws(() => new URLSearchParams([['a', sym]]),
234-
/^TypeError: Cannot convert a Symbol value to a string$/);
244+
const toStringError = /^Error: toString$/;
245+
const symbolError = /^TypeError: Cannot convert a Symbol value to a string$/;
246+
247+
assert.throws(() => new URLSearchParams({ a: obj }), toStringError);
248+
assert.throws(() => new URLSearchParams([['a', obj]]), toStringError);
249+
assert.throws(() => new URLSearchParams(sym), symbolError);
250+
assert.throws(() => new URLSearchParams({ [sym]: 'a' }), symbolError);
251+
assert.throws(() => new URLSearchParams({ a: sym }), symbolError);
252+
assert.throws(() => new URLSearchParams([[sym, 'a']]), symbolError);
253+
assert.throws(() => new URLSearchParams([['a', sym]]), symbolError);
235254
}

0 commit comments

Comments
 (0)