Skip to content

Commit 438a98c

Browse files
TimothyGuevanlucas
authored andcommitted
url: make URLSearchParams/Iterator match spec
PR-URL: #11057 Fixes: #10799 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 55e98c6 commit 438a98c

File tree

2 files changed

+134
-100
lines changed

2 files changed

+134
-100
lines changed

lib/internal/url.js

Lines changed: 121 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,35 @@ function getObjectFromParams(array) {
647647
return obj;
648648
}
649649

650+
// Mainly to mitigate func-name-matching ESLint rule
651+
function defineIDLClass(proto, classStr, obj) {
652+
// https://heycam.github.io/webidl/#dfn-class-string
653+
Object.defineProperty(proto, Symbol.toStringTag, {
654+
writable: false,
655+
enumerable: false,
656+
configurable: true,
657+
value: classStr
658+
});
659+
660+
// https://heycam.github.io/webidl/#es-operations
661+
for (const key of Object.keys(obj)) {
662+
Object.defineProperty(proto, key, {
663+
writable: true,
664+
enumerable: true,
665+
configurable: true,
666+
value: obj[key]
667+
});
668+
}
669+
for (const key of Object.getOwnPropertySymbols(obj)) {
670+
Object.defineProperty(proto, key, {
671+
writable: true,
672+
enumerable: false,
673+
configurable: true,
674+
value: obj[key]
675+
});
676+
}
677+
}
678+
650679
class URLSearchParams {
651680
constructor(init = '') {
652681
if (init instanceof URLSearchParams) {
@@ -662,11 +691,39 @@ class URLSearchParams {
662691
this[context] = null;
663692
}
664693

665-
get [Symbol.toStringTag]() {
666-
return this instanceof URLSearchParams ?
667-
'URLSearchParams' : 'URLSearchParamsPrototype';
694+
[util.inspect.custom](recurseTimes, ctx) {
695+
if (!this || !(this instanceof URLSearchParams)) {
696+
throw new TypeError('Value of `this` is not a URLSearchParams');
697+
}
698+
699+
const separator = ', ';
700+
const innerOpts = Object.assign({}, ctx);
701+
if (recurseTimes !== null) {
702+
innerOpts.depth = recurseTimes - 1;
703+
}
704+
const innerInspect = (v) => util.inspect(v, innerOpts);
705+
706+
const list = this[searchParams];
707+
const output = [];
708+
for (var i = 0; i < list.length; i += 2)
709+
output.push(`${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`);
710+
711+
const colorRe = /\u001b\[\d\d?m/g;
712+
const length = output.reduce(
713+
(prev, cur) => prev + cur.replace(colorRe, '').length + separator.length,
714+
-separator.length
715+
);
716+
if (length > ctx.breakLength) {
717+
return `${this.constructor.name} {\n ${output.join(',\n ')} }`;
718+
} else if (output.length) {
719+
return `${this.constructor.name} { ${output.join(separator)} }`;
720+
} else {
721+
return `${this.constructor.name} {}`;
722+
}
668723
}
724+
}
669725

726+
defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
670727
append(name, value) {
671728
if (!this || !(this instanceof URLSearchParams)) {
672729
throw new TypeError('Value of `this` is not a URLSearchParams');
@@ -679,7 +736,7 @@ class URLSearchParams {
679736
value = String(value);
680737
this[searchParams].push(name, value);
681738
update(this[context], this);
682-
}
739+
},
683740

684741
delete(name) {
685742
if (!this || !(this instanceof URLSearchParams)) {
@@ -700,47 +757,7 @@ class URLSearchParams {
700757
}
701758
}
702759
update(this[context], this);
703-
}
704-
705-
set(name, value) {
706-
if (!this || !(this instanceof URLSearchParams)) {
707-
throw new TypeError('Value of `this` is not a URLSearchParams');
708-
}
709-
if (arguments.length < 2) {
710-
throw new TypeError('"name" and "value" arguments must be specified');
711-
}
712-
713-
const list = this[searchParams];
714-
name = String(name);
715-
value = String(value);
716-
717-
// If there are any name-value pairs whose name is `name`, in `list`, set
718-
// the value of the first such name-value pair to `value` and remove the
719-
// others.
720-
var found = false;
721-
for (var i = 0; i < list.length;) {
722-
const cur = list[i];
723-
if (cur === name) {
724-
if (!found) {
725-
list[i + 1] = value;
726-
found = true;
727-
i += 2;
728-
} else {
729-
list.splice(i, 2);
730-
}
731-
} else {
732-
i += 2;
733-
}
734-
}
735-
736-
// Otherwise, append a new name-value pair whose name is `name` and value
737-
// is `value`, to `list`.
738-
if (!found) {
739-
list.push(name, value);
740-
}
741-
742-
update(this[context], this);
743-
}
760+
},
744761

745762
get(name) {
746763
if (!this || !(this instanceof URLSearchParams)) {
@@ -758,7 +775,7 @@ class URLSearchParams {
758775
}
759776
}
760777
return null;
761-
}
778+
},
762779

763780
getAll(name) {
764781
if (!this || !(this instanceof URLSearchParams)) {
@@ -777,7 +794,7 @@ class URLSearchParams {
777794
}
778795
}
779796
return values;
780-
}
797+
},
781798

782799
has(name) {
783800
if (!this || !(this instanceof URLSearchParams)) {
@@ -795,7 +812,47 @@ class URLSearchParams {
795812
}
796813
}
797814
return false;
798-
}
815+
},
816+
817+
set(name, value) {
818+
if (!this || !(this instanceof URLSearchParams)) {
819+
throw new TypeError('Value of `this` is not a URLSearchParams');
820+
}
821+
if (arguments.length < 2) {
822+
throw new TypeError('"name" and "value" arguments must be specified');
823+
}
824+
825+
const list = this[searchParams];
826+
name = String(name);
827+
value = String(value);
828+
829+
// If there are any name-value pairs whose name is `name`, in `list`, set
830+
// the value of the first such name-value pair to `value` and remove the
831+
// others.
832+
var found = false;
833+
for (var i = 0; i < list.length;) {
834+
const cur = list[i];
835+
if (cur === name) {
836+
if (!found) {
837+
list[i + 1] = value;
838+
found = true;
839+
i += 2;
840+
} else {
841+
list.splice(i, 2);
842+
}
843+
} else {
844+
i += 2;
845+
}
846+
}
847+
848+
// Otherwise, append a new name-value pair whose name is `name` and value
849+
// is `value`, to `list`.
850+
if (!found) {
851+
list.push(name, value);
852+
}
853+
854+
update(this[context], this);
855+
},
799856

800857
// https://heycam.github.io/webidl/#es-iterators
801858
// Define entries here rather than [Symbol.iterator] as the function name
@@ -806,7 +863,7 @@ class URLSearchParams {
806863
}
807864

808865
return createSearchParamsIterator(this, 'key+value');
809-
}
866+
},
810867

811868
forEach(callback, thisArg = undefined) {
812869
if (!this || !(this instanceof URLSearchParams)) {
@@ -827,7 +884,7 @@ class URLSearchParams {
827884
list = this[searchParams];
828885
i += 2;
829886
}
830-
}
887+
},
831888

832889
// https://heycam.github.io/webidl/#es-iterable
833890
keys() {
@@ -836,16 +893,17 @@ class URLSearchParams {
836893
}
837894

838895
return createSearchParamsIterator(this, 'key');
839-
}
896+
},
840897

841898
values() {
842899
if (!this || !(this instanceof URLSearchParams)) {
843900
throw new TypeError('Value of `this` is not a URLSearchParams');
844901
}
845902

846903
return createSearchParamsIterator(this, 'value');
847-
}
904+
},
848905

906+
// https://heycam.github.io/webidl/#es-stringifier
849907
// https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
850908
toString() {
851909
if (!this || !(this instanceof URLSearchParams)) {
@@ -854,37 +912,14 @@ class URLSearchParams {
854912

855913
return querystring.stringify(getObjectFromParams(this[searchParams]));
856914
}
857-
}
858-
// https://heycam.github.io/webidl/#es-iterable-entries
859-
URLSearchParams.prototype[Symbol.iterator] = URLSearchParams.prototype.entries;
860-
861-
URLSearchParams.prototype[util.inspect.custom] =
862-
function inspect(recurseTimes, ctx) {
863-
const separator = ', ';
864-
const innerOpts = Object.assign({}, ctx);
865-
if (recurseTimes !== null) {
866-
innerOpts.depth = recurseTimes - 1;
867-
}
868-
const innerInspect = (v) => util.inspect(v, innerOpts);
869-
870-
const list = this[searchParams];
871-
const output = [];
872-
for (var i = 0; i < list.length; i += 2)
873-
output.push(`${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`);
915+
});
874916

875-
const colorRe = /\u001b\[\d\d?m/g;
876-
const length = output.reduce(
877-
(prev, cur) => prev + cur.replace(colorRe, '').length + separator.length,
878-
-separator.length
879-
);
880-
if (length > ctx.breakLength) {
881-
return `${this.constructor.name} {\n ${output.join(',\n ')} }`;
882-
} else if (output.length) {
883-
return `${this.constructor.name} { ${output.join(separator)} }`;
884-
} else {
885-
return `${this.constructor.name} {}`;
886-
}
887-
};
917+
// https://heycam.github.io/webidl/#es-iterable-entries
918+
Object.defineProperty(URLSearchParams.prototype, Symbol.iterator, {
919+
writable: true,
920+
configurable: true,
921+
value: URLSearchParams.prototype.entries
922+
});
888923

889924
// https://heycam.github.io/webidl/#dfn-default-iterator-object
890925
function createSearchParamsIterator(target, kind) {
@@ -898,7 +933,9 @@ function createSearchParamsIterator(target, kind) {
898933
}
899934

900935
// https://heycam.github.io/webidl/#dfn-iterator-prototype-object
901-
const URLSearchParamsIteratorPrototype = Object.setPrototypeOf({
936+
const URLSearchParamsIteratorPrototype = Object.create(IteratorPrototype);
937+
938+
defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', {
902939
next() {
903940
if (!this ||
904941
Object.getPrototypeOf(this) !== URLSearchParamsIteratorPrototype) {
@@ -937,7 +974,7 @@ const URLSearchParamsIteratorPrototype = Object.setPrototypeOf({
937974
done: false
938975
};
939976
},
940-
[util.inspect.custom]: function inspect(recurseTimes, ctx) {
977+
[util.inspect.custom](recurseTimes, ctx) {
941978
const innerOpts = Object.assign({}, ctx);
942979
if (recurseTimes !== null) {
943980
innerOpts.depth = recurseTimes - 1;
@@ -968,15 +1005,6 @@ const URLSearchParamsIteratorPrototype = Object.setPrototypeOf({
9681005
}
9691006
return `${this[Symbol.toStringTag]} {${outputStr} }`;
9701007
}
971-
}, IteratorPrototype);
972-
973-
// Unlike interface and its prototype object, both default iterator object and
974-
// iterator prototype object of an interface have the same class string.
975-
Object.defineProperty(URLSearchParamsIteratorPrototype, Symbol.toStringTag, {
976-
value: 'URLSearchParamsIterator',
977-
writable: false,
978-
enumerable: false,
979-
configurable: true
9801008
});
9811009

9821010
function originFor(url, base) {

test/parallel/test-whatwg-url-tostringtag.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@ const toString = Object.prototype.toString;
77

88
const url = new URL('http://example.org');
99
const sp = url.searchParams;
10+
const spIterator = sp.entries();
1011

1112
const test = [
12-
[toString.call(url), 'URL'],
13-
[toString.call(sp), 'URLSearchParams'],
14-
[toString.call(Object.getPrototypeOf(sp)), 'URLSearchParamsPrototype'],
13+
[url, 'URL'],
14+
[sp, 'URLSearchParams'],
15+
[spIterator, 'URLSearchParamsIterator'],
1516
// Web IDL spec says we have to return 'URLPrototype', but it is too
1617
// expensive to implement; therefore, use Chrome's behavior for now, until
1718
// spec is changed.
18-
[toString.call(Object.getPrototypeOf(url)), 'URL']
19+
[Object.getPrototypeOf(url), 'URL'],
20+
[Object.getPrototypeOf(sp), 'URLSearchParams'],
21+
[Object.getPrototypeOf(spIterator), 'URLSearchParamsIterator'],
1922
];
2023

21-
test.forEach((row) => {
22-
assert.strictEqual(row[0], `[object ${row[1]}]`,
23-
`${row[0]} !== [object ${row[1]}]`);
24+
test.forEach(([obj, expected]) => {
25+
assert.strictEqual(obj[Symbol.toStringTag], expected,
26+
`${obj[Symbol.toStringTag]} !== ${expected}`);
27+
const str = toString.call(obj);
28+
assert.strictEqual(str, `[object ${expected}]`,
29+
`${str} !== [object ${expected}]`);
2430
});

0 commit comments

Comments
 (0)