Skip to content

Commit 747ff43

Browse files
TimothyGuRafaelGSS
authored andcommittedApr 13, 2023
url: more sophisticated brand check for URLSearchParams
Use private properties and static {} blocks. PR-URL: #47414 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent c177653 commit 747ff43

File tree

1 file changed

+166
-186
lines changed

1 file changed

+166
-186
lines changed
 

‎lib/internal/url.js

+166-186
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ const {
1313
IteratorPrototype,
1414
Number,
1515
ObjectDefineProperties,
16-
ObjectDefineProperty,
17-
ObjectGetOwnPropertySymbols,
18-
ObjectGetPrototypeOf,
19-
ObjectKeys,
16+
ObjectSetPrototypeOf,
2017
ReflectGetOwnPropertyDescriptor,
2118
ReflectOwnKeys,
2219
RegExpPrototypeSymbolReplace,
@@ -90,8 +87,7 @@ const bindingUrl = internalBinding('url');
9087

9188
const FORWARD_SLASH = /\//g;
9289

93-
const context = Symbol('context');
94-
const searchParams = Symbol('query');
90+
const contextForInspect = Symbol('context');
9591

9692
const updateActions = {
9793
kProtocol: 0,
@@ -172,15 +168,124 @@ class URLContext {
172168
}
173169
}
174170

175-
function isURLSearchParams(self) {
176-
return self && self[searchParams] && !self[searchParams][searchParams];
171+
let setURLSearchParamsContext;
172+
let getURLSearchParamsList;
173+
let setURLSearchParams;
174+
175+
class URLSearchParamsIterator {
176+
#target;
177+
#kind;
178+
#index;
179+
180+
// https://heycam.github.io/webidl/#dfn-default-iterator-object
181+
constructor(target, kind) {
182+
this.#target = target;
183+
this.#kind = kind;
184+
this.#index = 0;
185+
}
186+
187+
next() {
188+
if (typeof this !== 'object' || this === null || !(#target in this))
189+
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
190+
191+
const index = this.#index;
192+
const values = getURLSearchParamsList(this.#target);
193+
const len = values.length;
194+
if (index >= len) {
195+
return {
196+
value: undefined,
197+
done: true,
198+
};
199+
}
200+
201+
const name = values[index];
202+
const value = values[index + 1];
203+
this.#index = index + 2;
204+
205+
let result;
206+
if (this.#kind === 'key') {
207+
result = name;
208+
} else if (this.#kind === 'value') {
209+
result = value;
210+
} else {
211+
result = [name, value];
212+
}
213+
214+
return {
215+
value: result,
216+
done: false,
217+
};
218+
}
219+
220+
[inspect.custom](recurseTimes, ctx) {
221+
if (!this || typeof this !== 'object' || !(#target in this))
222+
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
223+
224+
if (typeof recurseTimes === 'number' && recurseTimes < 0)
225+
return ctx.stylize('[Object]', 'special');
226+
227+
const innerOpts = { ...ctx };
228+
if (recurseTimes !== null) {
229+
innerOpts.depth = recurseTimes - 1;
230+
}
231+
const index = this.#index;
232+
const values = getURLSearchParamsList(this.#target);
233+
const output = ArrayPrototypeReduce(
234+
ArrayPrototypeSlice(values, index),
235+
(prev, cur, i) => {
236+
const key = i % 2 === 0;
237+
if (this.#kind === 'key' && key) {
238+
ArrayPrototypePush(prev, cur);
239+
} else if (this.#kind === 'value' && !key) {
240+
ArrayPrototypePush(prev, cur);
241+
} else if (this.#kind === 'key+value' && !key) {
242+
ArrayPrototypePush(prev, [values[index + i - 1], cur]);
243+
}
244+
return prev;
245+
},
246+
[],
247+
);
248+
const breakLn = StringPrototypeIncludes(inspect(output, innerOpts), '\n');
249+
const outputStrs = ArrayPrototypeMap(output, (p) => inspect(p, innerOpts));
250+
let outputStr;
251+
if (breakLn) {
252+
outputStr = `\n ${ArrayPrototypeJoin(outputStrs, ',\n ')}`;
253+
} else {
254+
outputStr = ` ${ArrayPrototypeJoin(outputStrs, ', ')}`;
255+
}
256+
return `${this[SymbolToStringTag]} {${outputStr} }`;
257+
}
177258
}
178259

260+
// https://heycam.github.io/webidl/#dfn-iterator-prototype-object
261+
delete URLSearchParamsIterator.prototype.constructor;
262+
ObjectSetPrototypeOf(URLSearchParamsIterator.prototype, IteratorPrototype);
263+
264+
ObjectDefineProperties(URLSearchParamsIterator.prototype, {
265+
[SymbolToStringTag]: { __proto__: null, configurable: true, value: 'URLSearchParams Iterator' },
266+
next: kEnumerableProperty,
267+
});
268+
269+
179270
class URLSearchParams {
180-
[searchParams] = [];
271+
#searchParams = [];
181272

182273
// "associated url object"
183-
[context] = null;
274+
#context;
275+
276+
static {
277+
setURLSearchParamsContext = (obj, ctx) => {
278+
obj.#context = ctx;
279+
};
280+
getURLSearchParamsList = (obj) => obj.#searchParams;
281+
setURLSearchParams = (obj, query) => {
282+
if (query === undefined) {
283+
obj.#searchParams = [];
284+
} else {
285+
obj.#searchParams = parseParams(query);
286+
}
287+
};
288+
}
184289

185290
// URL Standard says the default value is '', but as undefined and '' have
186291
// the same result, undefined is used to prevent unnecessary parsing.
@@ -191,11 +296,11 @@ class URLSearchParams {
191296
// Do nothing
192297
} else if (typeof init === 'object' || typeof init === 'function') {
193298
const method = init[SymbolIterator];
194-
if (method === this[SymbolIterator]) {
299+
if (method === this[SymbolIterator] && #searchParams in init) {
195300
// While the spec does not have this branch, we can use it as a
196301
// shortcut to avoid having to go through the costly generic iterator.
197-
const childParams = init[searchParams];
198-
this[searchParams] = childParams.slice();
302+
const childParams = init.#searchParams;
303+
this.#searchParams = childParams.slice();
199304
} else if (method != null) {
200305
// Sequence<sequence<USVString>>
201306
if (typeof method !== 'function') {
@@ -221,7 +326,7 @@ class URLSearchParams {
221326
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
222327
}
223328
// Append (innerSequence[0], innerSequence[1]) to querys list.
224-
ArrayPrototypePush(this[searchParams], toUSVString(pair[0]), toUSVString(pair[1]));
329+
ArrayPrototypePush(this.#searchParams, toUSVString(pair[0]), toUSVString(pair[1]));
225330
} else {
226331
if (((typeof pair !== 'object' && typeof pair !== 'function') ||
227332
typeof pair[SymbolIterator] !== 'function')) {
@@ -232,7 +337,7 @@ class URLSearchParams {
232337

233338
for (const element of pair) {
234339
length++;
235-
ArrayPrototypePush(this[searchParams], toUSVString(element));
340+
ArrayPrototypePush(this.#searchParams, toUSVString(element));
236341
}
237342

238343
// If innerSequence's size is not 2, then throw a TypeError.
@@ -257,9 +362,9 @@ class URLSearchParams {
257362
// In that case, we retain the later one. Refer to WPT.
258363
const keyIdx = visited.get(typedKey);
259364
if (keyIdx !== undefined) {
260-
this[searchParams][keyIdx] = typedValue;
365+
this.#searchParams[keyIdx] = typedValue;
261366
} else {
262-
visited.set(typedKey, ArrayPrototypePush(this[searchParams],
367+
visited.set(typedKey, ArrayPrototypePush(this.#searchParams,
263368
typedKey,
264369
typedValue) - 1);
265370
}
@@ -269,12 +374,12 @@ class URLSearchParams {
269374
} else {
270375
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
271376
init = toUSVString(init);
272-
this[searchParams] = init ? parseParams(init) : [];
377+
this.#searchParams = init ? parseParams(init) : [];
273378
}
274379
}
275380

276381
[inspect.custom](recurseTimes, ctx) {
277-
if (!isURLSearchParams(this))
382+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
278383
throw new ERR_INVALID_THIS('URLSearchParams');
279384

280385
if (typeof recurseTimes === 'number' && recurseTimes < 0)
@@ -287,7 +392,7 @@ class URLSearchParams {
287392
}
288393
const innerInspect = (v) => inspect(v, innerOpts);
289394

290-
const list = this[searchParams];
395+
const list = this.#searchParams;
291396
const output = [];
292397
for (let i = 0; i < list.length; i += 2)
293398
ArrayPrototypePush(
@@ -310,13 +415,13 @@ class URLSearchParams {
310415
}
311416

312417
get size() {
313-
if (!isURLSearchParams(this))
418+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
314419
throw new ERR_INVALID_THIS('URLSearchParams');
315-
return this[searchParams].length / 2;
420+
return this.#searchParams.length / 2;
316421
}
317422

318423
append(name, value) {
319-
if (!isURLSearchParams(this))
424+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
320425
throw new ERR_INVALID_THIS('URLSearchParams');
321426

322427
if (arguments.length < 2) {
@@ -325,21 +430,21 @@ class URLSearchParams {
325430

326431
name = toUSVString(name);
327432
value = toUSVString(value);
328-
ArrayPrototypePush(this[searchParams], name, value);
329-
if (this[context]) {
330-
this[context].search = this.toString();
433+
ArrayPrototypePush(this.#searchParams, name, value);
434+
if (this.#context) {
435+
this.#context.search = this.toString();
331436
}
332437
}
333438

334439
delete(name) {
335-
if (!isURLSearchParams(this))
440+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
336441
throw new ERR_INVALID_THIS('URLSearchParams');
337442

338443
if (arguments.length < 1) {
339444
throw new ERR_MISSING_ARGS('name');
340445
}
341446

342-
const list = this[searchParams];
447+
const list = this.#searchParams;
343448
name = toUSVString(name);
344449
for (let i = 0; i < list.length;) {
345450
const cur = list[i];
@@ -349,20 +454,20 @@ class URLSearchParams {
349454
i += 2;
350455
}
351456
}
352-
if (this[context]) {
353-
this[context].search = this.toString();
457+
if (this.#context) {
458+
this.#context.search = this.toString();
354459
}
355460
}
356461

357462
get(name) {
358-
if (!isURLSearchParams(this))
463+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
359464
throw new ERR_INVALID_THIS('URLSearchParams');
360465

361466
if (arguments.length < 1) {
362467
throw new ERR_MISSING_ARGS('name');
363468
}
364469

365-
const list = this[searchParams];
470+
const list = this.#searchParams;
366471
name = toUSVString(name);
367472
for (let i = 0; i < list.length; i += 2) {
368473
if (list[i] === name) {
@@ -373,14 +478,14 @@ class URLSearchParams {
373478
}
374479

375480
getAll(name) {
376-
if (!isURLSearchParams(this))
481+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
377482
throw new ERR_INVALID_THIS('URLSearchParams');
378483

379484
if (arguments.length < 1) {
380485
throw new ERR_MISSING_ARGS('name');
381486
}
382487

383-
const list = this[searchParams];
488+
const list = this.#searchParams;
384489
const values = [];
385490
name = toUSVString(name);
386491
for (let i = 0; i < list.length; i += 2) {
@@ -392,14 +497,14 @@ class URLSearchParams {
392497
}
393498

394499
has(name) {
395-
if (!isURLSearchParams(this))
500+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
396501
throw new ERR_INVALID_THIS('URLSearchParams');
397502

398503
if (arguments.length < 1) {
399504
throw new ERR_MISSING_ARGS('name');
400505
}
401506

402-
const list = this[searchParams];
507+
const list = this.#searchParams;
403508
name = toUSVString(name);
404509
for (let i = 0; i < list.length; i += 2) {
405510
if (list[i] === name) {
@@ -410,14 +515,14 @@ class URLSearchParams {
410515
}
411516

412517
set(name, value) {
413-
if (!isURLSearchParams(this))
518+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
414519
throw new ERR_INVALID_THIS('URLSearchParams');
415520

416521
if (arguments.length < 2) {
417522
throw new ERR_MISSING_ARGS('name', 'value');
418523
}
419524

420-
const list = this[searchParams];
525+
const list = this.#searchParams;
421526
name = toUSVString(name);
422527
value = toUSVString(value);
423528

@@ -446,13 +551,16 @@ class URLSearchParams {
446551
ArrayPrototypePush(list, name, value);
447552
}
448553

449-
if (this[context]) {
450-
this[context].search = this.toString();
554+
if (this.#context) {
555+
this.#context.search = this.toString();
451556
}
452557
}
453558

454559
sort() {
455-
const a = this[searchParams];
560+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
561+
throw new ERR_INVALID_THIS('URLSearchParams');
562+
563+
const a = this.#searchParams;
456564
const len = a.length;
457565

458566
if (len <= 2) {
@@ -492,62 +600,62 @@ class URLSearchParams {
492600
}
493601
}
494602

495-
if (this[context]) {
496-
this[context].search = this.toString();
603+
if (this.#context) {
604+
this.#context.search = this.toString();
497605
}
498606
}
499607

500608
// https://heycam.github.io/webidl/#es-iterators
501609
// Define entries here rather than [Symbol.iterator] as the function name
502610
// must be set to `entries`.
503611
entries() {
504-
if (!isURLSearchParams(this))
612+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
505613
throw new ERR_INVALID_THIS('URLSearchParams');
506614

507-
return createSearchParamsIterator(this, 'key+value');
615+
return new URLSearchParamsIterator(this, 'key+value');
508616
}
509617

510618
forEach(callback, thisArg = undefined) {
511-
if (!isURLSearchParams(this))
619+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
512620
throw new ERR_INVALID_THIS('URLSearchParams');
513621

514622
validateFunction(callback, 'callback');
515623

516-
let list = this[searchParams];
624+
let list = this.#searchParams;
517625

518626
let i = 0;
519627
while (i < list.length) {
520628
const key = list[i];
521629
const value = list[i + 1];
522630
callback.call(thisArg, value, key, this);
523631
// In case the URL object's `search` is updated
524-
list = this[searchParams];
632+
list = this.#searchParams;
525633
i += 2;
526634
}
527635
}
528636

529637
// https://heycam.github.io/webidl/#es-iterable
530638
keys() {
531-
if (!isURLSearchParams(this))
639+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
532640
throw new ERR_INVALID_THIS('URLSearchParams');
533641

534-
return createSearchParamsIterator(this, 'key');
642+
return new URLSearchParamsIterator(this, 'key');
535643
}
536644

537645
values() {
538-
if (!isURLSearchParams(this))
646+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
539647
throw new ERR_INVALID_THIS('URLSearchParams');
540648

541-
return createSearchParamsIterator(this, 'value');
649+
return new URLSearchParamsIterator(this, 'value');
542650
}
543651

544652
// https://heycam.github.io/webidl/#es-stringifier
545653
// https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
546654
toString() {
547-
if (!isURLSearchParams(this))
655+
if (typeof this !== 'object' || this === null || !(#searchParams in this))
548656
throw new ERR_INVALID_THIS('URLSearchParams');
549657

550-
return serializeParams(this[searchParams]);
658+
return serializeParams(this.#searchParams);
551659
}
552660
}
553661

@@ -635,7 +743,7 @@ class URL {
635743
obj.hash = this.hash;
636744

637745
if (opts.showHidden) {
638-
obj[context] = this.#context;
746+
obj[contextForInspect] = this.#context;
639747
}
640748

641749
return `${constructor.name} ${inspect(obj, opts)}`;
@@ -668,9 +776,9 @@ class URL {
668776

669777
if (this.#searchParams) {
670778
if (this.#context.hasSearch) {
671-
this.#searchParams[searchParams] = parseParams(this.search);
779+
setURLSearchParams(this.#searchParams, this.search);
672780
} else {
673-
this.#searchParams[searchParams] = [];
781+
setURLSearchParams(this.#searchParams, undefined);
674782
}
675783
}
676784
}
@@ -846,7 +954,7 @@ class URL {
846954
// Create URLSearchParams on demand to greatly improve the URL performance.
847955
if (this.#searchParams == null) {
848956
this.#searchParams = new URLSearchParams(this.search);
849-
this.#searchParams[context] = this;
957+
setURLSearchParamsContext(this.#searchParams, this);
850958
}
851959
return this.#searchParams;
852960
}
@@ -1099,38 +1207,6 @@ function serializeParams(array) {
10991207
return output;
11001208
}
11011209

1102-
// Mainly to mitigate func-name-matching ESLint rule
1103-
function defineIDLClass(proto, classStr, obj) {
1104-
// https://heycam.github.io/webidl/#dfn-class-string
1105-
ObjectDefineProperty(proto, SymbolToStringTag, {
1106-
__proto__: null,
1107-
writable: false,
1108-
enumerable: false,
1109-
configurable: true,
1110-
value: classStr,
1111-
});
1112-
1113-
// https://heycam.github.io/webidl/#es-operations
1114-
for (const key of ObjectKeys(obj)) {
1115-
ObjectDefineProperty(proto, key, {
1116-
__proto__: null,
1117-
writable: true,
1118-
enumerable: true,
1119-
configurable: true,
1120-
value: obj[key],
1121-
});
1122-
}
1123-
for (const key of ObjectGetOwnPropertySymbols(obj)) {
1124-
ObjectDefineProperty(proto, key, {
1125-
__proto__: null,
1126-
writable: true,
1127-
enumerable: false,
1128-
configurable: true,
1129-
value: obj[key],
1130-
});
1131-
}
1132-
}
1133-
11341210
// for merge sort
11351211
function merge(out, start, mid, end, lBuffer, rBuffer) {
11361212
const sizeLeft = mid - start;
@@ -1160,102 +1236,6 @@ function merge(out, start, mid, end, lBuffer, rBuffer) {
11601236
out[o++] = rBuffer[r++];
11611237
}
11621238

1163-
// https://heycam.github.io/webidl/#dfn-default-iterator-object
1164-
function createSearchParamsIterator(target, kind) {
1165-
const iterator = { __proto__: URLSearchParamsIteratorPrototype };
1166-
iterator[context] = {
1167-
target,
1168-
kind,
1169-
index: 0,
1170-
};
1171-
return iterator;
1172-
}
1173-
1174-
// https://heycam.github.io/webidl/#dfn-iterator-prototype-object
1175-
const URLSearchParamsIteratorPrototype = { __proto__: IteratorPrototype };
1176-
1177-
defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParams Iterator', {
1178-
next() {
1179-
if (!this ||
1180-
ObjectGetPrototypeOf(this) !== URLSearchParamsIteratorPrototype) {
1181-
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
1182-
}
1183-
1184-
const {
1185-
target,
1186-
kind,
1187-
index,
1188-
} = this[context];
1189-
const values = target[searchParams];
1190-
const len = values.length;
1191-
if (index >= len) {
1192-
return {
1193-
value: undefined,
1194-
done: true,
1195-
};
1196-
}
1197-
1198-
const name = values[index];
1199-
const value = values[index + 1];
1200-
this[context].index = index + 2;
1201-
1202-
let result;
1203-
if (kind === 'key') {
1204-
result = name;
1205-
} else if (kind === 'value') {
1206-
result = value;
1207-
} else {
1208-
result = [name, value];
1209-
}
1210-
1211-
return {
1212-
value: result,
1213-
done: false,
1214-
};
1215-
},
1216-
[inspect.custom](recurseTimes, ctx) {
1217-
if (this == null || this[context] == null || this[context].target == null)
1218-
throw new ERR_INVALID_THIS('URLSearchParamsIterator');
1219-
1220-
if (typeof recurseTimes === 'number' && recurseTimes < 0)
1221-
return ctx.stylize('[Object]', 'special');
1222-
1223-
const innerOpts = { ...ctx };
1224-
if (recurseTimes !== null) {
1225-
innerOpts.depth = recurseTimes - 1;
1226-
}
1227-
const {
1228-
target,
1229-
kind,
1230-
index,
1231-
} = this[context];
1232-
const output = ArrayPrototypeReduce(
1233-
ArrayPrototypeSlice(target[searchParams], index),
1234-
(prev, cur, i) => {
1235-
const key = i % 2 === 0;
1236-
if (kind === 'key' && key) {
1237-
ArrayPrototypePush(prev, cur);
1238-
} else if (kind === 'value' && !key) {
1239-
ArrayPrototypePush(prev, cur);
1240-
} else if (kind === 'key+value' && !key) {
1241-
ArrayPrototypePush(prev, [target[searchParams][index + i - 1], cur]);
1242-
}
1243-
return prev;
1244-
},
1245-
[],
1246-
);
1247-
const breakLn = StringPrototypeIncludes(inspect(output, innerOpts), '\n');
1248-
const outputStrs = ArrayPrototypeMap(output, (p) => inspect(p, innerOpts));
1249-
let outputStr;
1250-
if (breakLn) {
1251-
outputStr = `\n ${ArrayPrototypeJoin(outputStrs, ',\n ')}`;
1252-
} else {
1253-
outputStr = ` ${ArrayPrototypeJoin(outputStrs, ', ')}`;
1254-
}
1255-
return `${this[SymbolToStringTag]} {${outputStr} }`;
1256-
},
1257-
});
1258-
12591239
function domainToASCII(domain) {
12601240
if (arguments.length < 1)
12611241
throw new ERR_MISSING_ARGS('domain');

0 commit comments

Comments
 (0)
Please sign in to comment.