url: more sophisticated brand check for URLSearchParams

Use private properties and static {} blocks.

PR-URL: https://github.com/nodejs/node/pull/47414
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
Timothy Gu
2023-04-06 21:17:14 -07:00
committed by Rich Trott
parent fe3c96bf4f
commit 4b448c8aef

View File

@@ -13,10 +13,7 @@ const {
IteratorPrototype,
Number,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetOwnPropertySymbols,
ObjectGetPrototypeOf,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectGetOwnPropertyDescriptor,
ReflectOwnKeys,
RegExpPrototypeSymbolReplace,
@@ -90,8 +87,7 @@ const bindingUrl = internalBinding('url');
const FORWARD_SLASH = /\//g;
const context = Symbol('context');
const searchParams = Symbol('query');
const contextForInspect = Symbol('context');
const updateActions = {
kProtocol: 0,
@@ -172,15 +168,124 @@ class URLContext {
}
}
function isURLSearchParams(self) {
return self && self[searchParams] && !self[searchParams][searchParams];
let setURLSearchParamsContext;
let getURLSearchParamsList;
let setURLSearchParams;
class URLSearchParamsIterator {
#target;
#kind;
#index;
// https://heycam.github.io/webidl/#dfn-default-iterator-object
constructor(target, kind) {
this.#target = target;
this.#kind = kind;
this.#index = 0;
}
next() {
if (typeof this !== 'object' || this === null || !(#target in this))
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
const index = this.#index;
const values = getURLSearchParamsList(this.#target);
const len = values.length;
if (index >= len) {
return {
value: undefined,
done: true,
};
}
const name = values[index];
const value = values[index + 1];
this.#index = index + 2;
let result;
if (this.#kind === 'key') {
result = name;
} else if (this.#kind === 'value') {
result = value;
} else {
result = [name, value];
}
return {
value: result,
done: false,
};
}
[inspect.custom](recurseTimes, ctx) {
if (!this || typeof this !== 'object' || !(#target in this))
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
if (typeof recurseTimes === 'number' && recurseTimes < 0)
return ctx.stylize('[Object]', 'special');
const innerOpts = { ...ctx };
if (recurseTimes !== null) {
innerOpts.depth = recurseTimes - 1;
}
const index = this.#index;
const values = getURLSearchParamsList(this.#target);
const output = ArrayPrototypeReduce(
ArrayPrototypeSlice(values, index),
(prev, cur, i) => {
const key = i % 2 === 0;
if (this.#kind === 'key' && key) {
ArrayPrototypePush(prev, cur);
} else if (this.#kind === 'value' && !key) {
ArrayPrototypePush(prev, cur);
} else if (this.#kind === 'key+value' && !key) {
ArrayPrototypePush(prev, [values[index + i - 1], cur]);
}
return prev;
},
[],
);
const breakLn = StringPrototypeIncludes(inspect(output, innerOpts), '\n');
const outputStrs = ArrayPrototypeMap(output, (p) => inspect(p, innerOpts));
let outputStr;
if (breakLn) {
outputStr = `\n ${ArrayPrototypeJoin(outputStrs, ',\n ')}`;
} else {
outputStr = ` ${ArrayPrototypeJoin(outputStrs, ', ')}`;
}
return `${this[SymbolToStringTag]} {${outputStr} }`;
}
}
// https://heycam.github.io/webidl/#dfn-iterator-prototype-object
delete URLSearchParamsIterator.prototype.constructor;
ObjectSetPrototypeOf(URLSearchParamsIterator.prototype, IteratorPrototype);
ObjectDefineProperties(URLSearchParamsIterator.prototype, {
[SymbolToStringTag]: { __proto__: null, configurable: true, value: 'URLSearchParams Iterator' },
next: kEnumerableProperty,
});
class URLSearchParams {
[searchParams] = [];
#searchParams = [];
// "associated url object"
[context] = null;
#context;
static {
setURLSearchParamsContext = (obj, ctx) => {
obj.#context = ctx;
};
getURLSearchParamsList = (obj) => obj.#searchParams;
setURLSearchParams = (obj, query) => {
if (query === undefined) {
obj.#searchParams = [];
} else {
obj.#searchParams = parseParams(query);
}
};
}
// URL Standard says the default value is '', but as undefined and '' have
// the same result, undefined is used to prevent unnecessary parsing.
@@ -191,11 +296,11 @@ class URLSearchParams {
// Do nothing
} else if (typeof init === 'object' || typeof init === 'function') {
const method = init[SymbolIterator];
if (method === this[SymbolIterator]) {
if (method === this[SymbolIterator] && #searchParams in init) {
// While the spec does not have this branch, we can use it as a
// shortcut to avoid having to go through the costly generic iterator.
const childParams = init[searchParams];
this[searchParams] = childParams.slice();
const childParams = init.#searchParams;
this.#searchParams = childParams.slice();
} else if (method != null) {
// Sequence<sequence<USVString>>
if (typeof method !== 'function') {
@@ -221,7 +326,7 @@ class URLSearchParams {
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
}
// Append (innerSequence[0], innerSequence[1]) to querys list.
ArrayPrototypePush(this[searchParams], toUSVString(pair[0]), toUSVString(pair[1]));
ArrayPrototypePush(this.#searchParams, toUSVString(pair[0]), toUSVString(pair[1]));
} else {
if (((typeof pair !== 'object' && typeof pair !== 'function') ||
typeof pair[SymbolIterator] !== 'function')) {
@@ -232,7 +337,7 @@ class URLSearchParams {
for (const element of pair) {
length++;
ArrayPrototypePush(this[searchParams], toUSVString(element));
ArrayPrototypePush(this.#searchParams, toUSVString(element));
}
// If innerSequence's size is not 2, then throw a TypeError.
@@ -257,9 +362,9 @@ class URLSearchParams {
// In that case, we retain the later one. Refer to WPT.
const keyIdx = visited.get(typedKey);
if (keyIdx !== undefined) {
this[searchParams][keyIdx] = typedValue;
this.#searchParams[keyIdx] = typedValue;
} else {
visited.set(typedKey, ArrayPrototypePush(this[searchParams],
visited.set(typedKey, ArrayPrototypePush(this.#searchParams,
typedKey,
typedValue) - 1);
}
@@ -269,12 +374,12 @@ class URLSearchParams {
} else {
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
init = toUSVString(init);
this[searchParams] = init ? parseParams(init) : [];
this.#searchParams = init ? parseParams(init) : [];
}
}
[inspect.custom](recurseTimes, ctx) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (typeof recurseTimes === 'number' && recurseTimes < 0)
@@ -287,7 +392,7 @@ class URLSearchParams {
}
const innerInspect = (v) => inspect(v, innerOpts);
const list = this[searchParams];
const list = this.#searchParams;
const output = [];
for (let i = 0; i < list.length; i += 2)
ArrayPrototypePush(
@@ -310,13 +415,13 @@ class URLSearchParams {
}
get size() {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
return this[searchParams].length / 2;
return this.#searchParams.length / 2;
}
append(name, value) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (arguments.length < 2) {
@@ -325,21 +430,21 @@ class URLSearchParams {
name = toUSVString(name);
value = toUSVString(value);
ArrayPrototypePush(this[searchParams], name, value);
if (this[context]) {
this[context].search = this.toString();
ArrayPrototypePush(this.#searchParams, name, value);
if (this.#context) {
this.#context.search = this.toString();
}
}
delete(name) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (arguments.length < 1) {
throw new ERR_MISSING_ARGS('name');
}
const list = this[searchParams];
const list = this.#searchParams;
name = toUSVString(name);
for (let i = 0; i < list.length;) {
const cur = list[i];
@@ -349,20 +454,20 @@ class URLSearchParams {
i += 2;
}
}
if (this[context]) {
this[context].search = this.toString();
if (this.#context) {
this.#context.search = this.toString();
}
}
get(name) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (arguments.length < 1) {
throw new ERR_MISSING_ARGS('name');
}
const list = this[searchParams];
const list = this.#searchParams;
name = toUSVString(name);
for (let i = 0; i < list.length; i += 2) {
if (list[i] === name) {
@@ -373,14 +478,14 @@ class URLSearchParams {
}
getAll(name) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (arguments.length < 1) {
throw new ERR_MISSING_ARGS('name');
}
const list = this[searchParams];
const list = this.#searchParams;
const values = [];
name = toUSVString(name);
for (let i = 0; i < list.length; i += 2) {
@@ -392,14 +497,14 @@ class URLSearchParams {
}
has(name) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (arguments.length < 1) {
throw new ERR_MISSING_ARGS('name');
}
const list = this[searchParams];
const list = this.#searchParams;
name = toUSVString(name);
for (let i = 0; i < list.length; i += 2) {
if (list[i] === name) {
@@ -410,14 +515,14 @@ class URLSearchParams {
}
set(name, value) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('name', 'value');
}
const list = this[searchParams];
const list = this.#searchParams;
name = toUSVString(name);
value = toUSVString(value);
@@ -446,13 +551,16 @@ class URLSearchParams {
ArrayPrototypePush(list, name, value);
}
if (this[context]) {
this[context].search = this.toString();
if (this.#context) {
this.#context.search = this.toString();
}
}
sort() {
const a = this[searchParams];
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
const a = this.#searchParams;
const len = a.length;
if (len <= 2) {
@@ -492,8 +600,8 @@ class URLSearchParams {
}
}
if (this[context]) {
this[context].search = this.toString();
if (this.#context) {
this.#context.search = this.toString();
}
}
@@ -501,19 +609,19 @@ class URLSearchParams {
// Define entries here rather than [Symbol.iterator] as the function name
// must be set to `entries`.
entries() {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
return createSearchParamsIterator(this, 'key+value');
return new URLSearchParamsIterator(this, 'key+value');
}
forEach(callback, thisArg = undefined) {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
validateFunction(callback, 'callback');
let list = this[searchParams];
let list = this.#searchParams;
let i = 0;
while (i < list.length) {
@@ -521,33 +629,33 @@ class URLSearchParams {
const value = list[i + 1];
callback.call(thisArg, value, key, this);
// In case the URL object's `search` is updated
list = this[searchParams];
list = this.#searchParams;
i += 2;
}
}
// https://heycam.github.io/webidl/#es-iterable
keys() {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
return createSearchParamsIterator(this, 'key');
return new URLSearchParamsIterator(this, 'key');
}
values() {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
return createSearchParamsIterator(this, 'value');
return new URLSearchParamsIterator(this, 'value');
}
// https://heycam.github.io/webidl/#es-stringifier
// https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
toString() {
if (!isURLSearchParams(this))
if (typeof this !== 'object' || this === null || !(#searchParams in this))
throw new ERR_INVALID_THIS('URLSearchParams');
return serializeParams(this[searchParams]);
return serializeParams(this.#searchParams);
}
}
@@ -635,7 +743,7 @@ class URL {
obj.hash = this.hash;
if (opts.showHidden) {
obj[context] = this.#context;
obj[contextForInspect] = this.#context;
}
return `${constructor.name} ${inspect(obj, opts)}`;
@@ -668,9 +776,9 @@ class URL {
if (this.#searchParams) {
if (this.#context.hasSearch) {
this.#searchParams[searchParams] = parseParams(this.search);
setURLSearchParams(this.#searchParams, this.search);
} else {
this.#searchParams[searchParams] = [];
setURLSearchParams(this.#searchParams, undefined);
}
}
}
@@ -846,7 +954,7 @@ class URL {
// Create URLSearchParams on demand to greatly improve the URL performance.
if (this.#searchParams == null) {
this.#searchParams = new URLSearchParams(this.search);
this.#searchParams[context] = this;
setURLSearchParamsContext(this.#searchParams, this);
}
return this.#searchParams;
}
@@ -1099,38 +1207,6 @@ function serializeParams(array) {
return output;
}
// Mainly to mitigate func-name-matching ESLint rule
function defineIDLClass(proto, classStr, obj) {
// https://heycam.github.io/webidl/#dfn-class-string
ObjectDefineProperty(proto, SymbolToStringTag, {
__proto__: null,
writable: false,
enumerable: false,
configurable: true,
value: classStr,
});
// https://heycam.github.io/webidl/#es-operations
for (const key of ObjectKeys(obj)) {
ObjectDefineProperty(proto, key, {
__proto__: null,
writable: true,
enumerable: true,
configurable: true,
value: obj[key],
});
}
for (const key of ObjectGetOwnPropertySymbols(obj)) {
ObjectDefineProperty(proto, key, {
__proto__: null,
writable: true,
enumerable: false,
configurable: true,
value: obj[key],
});
}
}
// for merge sort
function merge(out, start, mid, end, lBuffer, rBuffer) {
const sizeLeft = mid - start;
@@ -1160,102 +1236,6 @@ function merge(out, start, mid, end, lBuffer, rBuffer) {
out[o++] = rBuffer[r++];
}
// https://heycam.github.io/webidl/#dfn-default-iterator-object
function createSearchParamsIterator(target, kind) {
const iterator = { __proto__: URLSearchParamsIteratorPrototype };
iterator[context] = {
target,
kind,
index: 0,
};
return iterator;
}
// https://heycam.github.io/webidl/#dfn-iterator-prototype-object
const URLSearchParamsIteratorPrototype = { __proto__: IteratorPrototype };
defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParams Iterator', {
next() {
if (!this ||
ObjectGetPrototypeOf(this) !== URLSearchParamsIteratorPrototype) {
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
}
const {
target,
kind,
index,
} = this[context];
const values = target[searchParams];
const len = values.length;
if (index >= len) {
return {
value: undefined,
done: true,
};
}
const name = values[index];
const value = values[index + 1];
this[context].index = index + 2;
let result;
if (kind === 'key') {
result = name;
} else if (kind === 'value') {
result = value;
} else {
result = [name, value];
}
return {
value: result,
done: false,
};
},
[inspect.custom](recurseTimes, ctx) {
if (this == null || this[context] == null || this[context].target == null)
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
if (typeof recurseTimes === 'number' && recurseTimes < 0)
return ctx.stylize('[Object]', 'special');
const innerOpts = { ...ctx };
if (recurseTimes !== null) {
innerOpts.depth = recurseTimes - 1;
}
const {
target,
kind,
index,
} = this[context];
const output = ArrayPrototypeReduce(
ArrayPrototypeSlice(target[searchParams], index),
(prev, cur, i) => {
const key = i % 2 === 0;
if (kind === 'key' && key) {
ArrayPrototypePush(prev, cur);
} else if (kind === 'value' && !key) {
ArrayPrototypePush(prev, cur);
} else if (kind === 'key+value' && !key) {
ArrayPrototypePush(prev, [target[searchParams][index + i - 1], cur]);
}
return prev;
},
[],
);
const breakLn = StringPrototypeIncludes(inspect(output, innerOpts), '\n');
const outputStrs = ArrayPrototypeMap(output, (p) => inspect(p, innerOpts));
let outputStr;
if (breakLn) {
outputStr = `\n ${ArrayPrototypeJoin(outputStrs, ',\n ')}`;
} else {
outputStr = ` ${ArrayPrototypeJoin(outputStrs, ', ')}`;
}
return `${this[SymbolToStringTag]} {${outputStr} }`;
},
});
function domainToASCII(domain) {
if (arguments.length < 1)
throw new ERR_MISSING_ARGS('domain');