Skip to content

Commit

Permalink
url,lib: pass urlsearchparams-constructor.any.js
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
XadillaX committed Aug 31, 2021
1 parent b63e449 commit fe1db8f
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 17 deletions.
64 changes: 50 additions & 14 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
ArrayPrototypePush,
ArrayPrototypeReduce,
ArrayPrototypeSlice,
Error,
FunctionPrototypeBind,
Int8Array,
Number,
Expand All @@ -29,6 +30,7 @@ const {
Symbol,
SymbolIterator,
SymbolToStringTag,
TypeError,
decodeURIComponent,
} = primordials;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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<sequence<USVString>>
Expand All @@ -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)
Expand All @@ -217,18 +236,10 @@ class URLSearchParams {
ArrayPrototypePush(this[searchParams], pair[0], pair[1]);
}
} else {
// Record<USVString, USVString>
// 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 {
Expand All @@ -242,6 +253,31 @@ class URLSearchParams {
this[context] = null;
}

[initURLSearchParamsFromRecord](init) {
// Record<USVString, USVString>
// 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');
Expand Down
3 changes: 0 additions & 3 deletions test/wpt/status/url.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
},
Expand Down
6 changes: 6 additions & 0 deletions test/wpt/test-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit fe1db8f

Please sign in to comment.