Skip to content

Commit 90cd20d

Browse files
committed
url: use private properties for brand check
PR-URL: nodejs#46904 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 38b4692 commit 90cd20d

File tree

6 files changed

+74
-119
lines changed

6 files changed

+74
-119
lines changed

lib/internal/modules/cjs/loader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const { BuiltinModule } = require('internal/bootstrap/loaders');
7979
const {
8080
maybeCacheSourceMap,
8181
} = require('internal/source_map/source_map_cache');
82-
const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url');
82+
const { pathToFileURL, fileURLToPath, isURL } = require('internal/url');
8383
const {
8484
deprecate,
8585
emitExperimentalWarning,
@@ -1363,7 +1363,7 @@ const createRequireError = 'must be a file URL object, file URL string, or ' +
13631363
function createRequire(filename) {
13641364
let filepath;
13651365

1366-
if (isURLInstance(filename) ||
1366+
if (isURL(filename) ||
13671367
(typeof filename === 'string' && !path.isAbsolute(filename))) {
13681368
try {
13691369
filepath = fileURLToPath(filename);

lib/internal/modules/esm/loader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const {
3131
ERR_INVALID_RETURN_VALUE,
3232
ERR_UNKNOWN_MODULE_FORMAT,
3333
} = require('internal/errors').codes;
34-
const { pathToFileURL, isURLInstance, URL } = require('internal/url');
34+
const { pathToFileURL, isURL, URL } = require('internal/url');
3535
const { emitExperimentalWarning } = require('internal/util');
3636
const {
3737
isAnyArrayBuffer,
@@ -792,7 +792,7 @@ class ESMLoader {
792792
if (
793793
!isMain &&
794794
typeof parentURL !== 'string' &&
795-
!isURLInstance(parentURL)
795+
!isURL(parentURL)
796796
) {
797797
throw new ERR_INVALID_ARG_TYPE(
798798
'parentURL',

lib/internal/url.js

Lines changed: 58 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const {
77
ArrayPrototypePush,
88
ArrayPrototypeReduce,
99
ArrayPrototypeSlice,
10-
FunctionPrototypeBind,
10+
Boolean,
1111
Int8Array,
1212
IteratorPrototype,
1313
Number,
@@ -17,7 +17,6 @@ const {
1717
ObjectGetOwnPropertySymbols,
1818
ObjectGetPrototypeOf,
1919
ObjectKeys,
20-
ObjectPrototypeHasOwnProperty,
2120
ReflectGetOwnPropertyDescriptor,
2221
ReflectOwnKeys,
2322
RegExpPrototypeSymbolReplace,
@@ -536,16 +535,27 @@ ObjectDefineProperties(URLSearchParams.prototype, {
536535
},
537536
});
538537

538+
/**
539+
* Checks if a value has the shape of a WHATWG URL object.
540+
*
541+
* Using a symbol or instanceof would not be able to recognize URL objects
542+
* coming from other implementations (e.g. in Electron), so instead we are
543+
* checking some well known properties for a lack of a better test.
544+
*
545+
* @param {*} self
546+
* @returns {self is URL}
547+
*/
539548
function isURL(self) {
540-
return self != null && ObjectPrototypeHasOwnProperty(self, context);
549+
return Boolean(self?.href && self.origin);
541550
}
542551

543552
class URL {
553+
#context = new URLContext();
554+
#searchParams;
555+
544556
constructor(input, base = undefined) {
545557
// toUSVString is not needed.
546558
input = `${input}`;
547-
this[context] = new URLContext();
548-
this.#onParseComplete = FunctionPrototypeBind(this.#onParseComplete, this);
549559

550560
if (base !== undefined) {
551561
base = `${base}`;
@@ -561,11 +571,6 @@ class URL {
561571
}
562572

563573
[inspect.custom](depth, opts) {
564-
if (this == null ||
565-
ObjectGetPrototypeOf(this[context]) !== URLContext.prototype) {
566-
throw new ERR_INVALID_THIS('URL');
567-
}
568-
569574
if (typeof depth === 'number' && depth < 0)
570575
return this;
571576

@@ -586,182 +591,133 @@ class URL {
586591
obj.hash = this.hash;
587592

588593
if (opts.showHidden) {
589-
obj[context] = this[context];
594+
obj[context] = this.#context;
590595
}
591596

592597
return `${constructor.name} ${inspect(obj, opts)}`;
593598
}
594599

595600
#onParseComplete = (href, origin, protocol, hostname, pathname,
596601
search, username, password, port, hash) => {
597-
const ctx = this[context];
598-
ctx.href = href;
599-
ctx.origin = origin;
600-
ctx.protocol = protocol;
601-
ctx.hostname = hostname;
602-
ctx.pathname = pathname;
603-
ctx.search = search;
604-
ctx.username = username;
605-
ctx.password = password;
606-
ctx.port = port;
607-
ctx.hash = hash;
608-
if (this[searchParams]) {
609-
this[searchParams][searchParams] = parseParams(search);
602+
this.#context.href = href;
603+
this.#context.origin = origin;
604+
this.#context.protocol = protocol;
605+
this.#context.hostname = hostname;
606+
this.#context.pathname = pathname;
607+
this.#context.search = search;
608+
this.#context.username = username;
609+
this.#context.password = password;
610+
this.#context.port = port;
611+
this.#context.hash = hash;
612+
if (this.#searchParams) {
613+
this.#searchParams[searchParams] = parseParams(search);
610614
}
611615
};
612616

613617
toString() {
614-
if (!isURL(this))
615-
throw new ERR_INVALID_THIS('URL');
616-
return this[context].href;
618+
return this.#context.href;
617619
}
618620

619621
get href() {
620-
if (!isURL(this))
621-
throw new ERR_INVALID_THIS('URL');
622-
return this[context].href;
622+
return this.#context.href;
623623
}
624624

625625
set href(value) {
626-
if (!isURL(this))
627-
throw new ERR_INVALID_THIS('URL');
628-
const valid = updateUrl(this[context].href, updateActions.kHref, `${value}`, this.#onParseComplete);
626+
const valid = updateUrl(this.#context.href, updateActions.kHref, `${value}`, this.#onParseComplete);
629627
if (!valid) { throw ERR_INVALID_URL(`${value}`); }
630628
}
631629

632630
// readonly
633631
get origin() {
634-
if (!isURL(this))
635-
throw new ERR_INVALID_THIS('URL');
636-
return this[context].origin;
632+
return this.#context.origin;
637633
}
638634

639635
get protocol() {
640-
if (!isURL(this))
641-
throw new ERR_INVALID_THIS('URL');
642-
return this[context].protocol;
636+
return this.#context.protocol;
643637
}
644638

645639
set protocol(value) {
646-
if (!isURL(this))
647-
throw new ERR_INVALID_THIS('URL');
648-
updateUrl(this[context].href, updateActions.kProtocol, `${value}`, this.#onParseComplete);
640+
updateUrl(this.#context.href, updateActions.kProtocol, `${value}`, this.#onParseComplete);
649641
}
650642

651643
get username() {
652-
if (!isURL(this))
653-
throw new ERR_INVALID_THIS('URL');
654-
return this[context].username;
644+
return this.#context.username;
655645
}
656646

657647
set username(value) {
658-
if (!isURL(this))
659-
throw new ERR_INVALID_THIS('URL');
660-
updateUrl(this[context].href, updateActions.kUsername, `${value}`, this.#onParseComplete);
648+
updateUrl(this.#context.href, updateActions.kUsername, `${value}`, this.#onParseComplete);
661649
}
662650

663651
get password() {
664-
if (!isURL(this))
665-
throw new ERR_INVALID_THIS('URL');
666-
return this[context].password;
652+
return this.#context.password;
667653
}
668654

669655
set password(value) {
670-
if (!isURL(this))
671-
throw new ERR_INVALID_THIS('URL');
672-
updateUrl(this[context].href, updateActions.kPassword, `${value}`, this.#onParseComplete);
656+
updateUrl(this.#context.href, updateActions.kPassword, `${value}`, this.#onParseComplete);
673657
}
674658

675659
get host() {
676-
if (!isURL(this))
677-
throw new ERR_INVALID_THIS('URL');
678-
const port = this[context].port;
660+
const port = this.#context.port;
679661
const suffix = port.length > 0 ? `:${port}` : '';
680-
return this[context].hostname + suffix;
662+
return this.#context.hostname + suffix;
681663
}
682664

683665
set host(value) {
684-
if (!isURL(this))
685-
throw new ERR_INVALID_THIS('URL');
686-
updateUrl(this[context].href, updateActions.kHost, `${value}`, this.#onParseComplete);
666+
updateUrl(this.#context.href, updateActions.kHost, `${value}`, this.#onParseComplete);
687667
}
688668

689669
get hostname() {
690-
if (!isURL(this))
691-
throw new ERR_INVALID_THIS('URL');
692-
return this[context].hostname;
670+
return this.#context.hostname;
693671
}
694672

695673
set hostname(value) {
696-
if (!isURL(this))
697-
throw new ERR_INVALID_THIS('URL');
698-
updateUrl(this[context].href, updateActions.kHostname, `${value}`, this.#onParseComplete);
674+
updateUrl(this.#context.href, updateActions.kHostname, `${value}`, this.#onParseComplete);
699675
}
700676

701677
get port() {
702-
if (!isURL(this))
703-
throw new ERR_INVALID_THIS('URL');
704-
return this[context].port;
678+
return this.#context.port;
705679
}
706680

707681
set port(value) {
708-
if (!isURL(this))
709-
throw new ERR_INVALID_THIS('URL');
710-
updateUrl(this[context].href, updateActions.kPort, `${value}`, this.#onParseComplete);
682+
updateUrl(this.#context.href, updateActions.kPort, `${value}`, this.#onParseComplete);
711683
}
712684

713685
get pathname() {
714-
if (!isURL(this))
715-
throw new ERR_INVALID_THIS('URL');
716-
return this[context].pathname;
686+
return this.#context.pathname;
717687
}
718688

719689
set pathname(value) {
720-
if (!isURL(this))
721-
throw new ERR_INVALID_THIS('URL');
722-
updateUrl(this[context].href, updateActions.kPathname, `${value}`, this.#onParseComplete);
690+
updateUrl(this.#context.href, updateActions.kPathname, `${value}`, this.#onParseComplete);
723691
}
724692

725693
get search() {
726-
if (!isURL(this))
727-
throw new ERR_INVALID_THIS('URL');
728-
return this[context].search;
694+
return this.#context.search;
729695
}
730696

731697
set search(value) {
732-
if (!isURL(this))
733-
throw new ERR_INVALID_THIS('URL');
734-
updateUrl(this[context].href, updateActions.kSearch, toUSVString(value), this.#onParseComplete);
698+
updateUrl(this.#context.href, updateActions.kSearch, toUSVString(value), this.#onParseComplete);
735699
}
736700

737701
// readonly
738702
get searchParams() {
739-
if (!isURL(this))
740-
throw new ERR_INVALID_THIS('URL');
741703
// Create URLSearchParams on demand to greatly improve the URL performance.
742-
if (this[searchParams] == null) {
743-
this[searchParams] = new URLSearchParams(this[context].search);
744-
this[searchParams][context] = this;
704+
if (this.#searchParams == null) {
705+
this.#searchParams = new URLSearchParams(this.#context.search);
706+
this.#searchParams[context] = this;
745707
}
746-
return this[searchParams];
708+
return this.#searchParams;
747709
}
748710

749711
get hash() {
750-
if (!isURL(this))
751-
throw new ERR_INVALID_THIS('URL');
752-
return this[context].hash;
712+
return this.#context.hash;
753713
}
754714

755715
set hash(value) {
756-
if (!isURL(this))
757-
throw new ERR_INVALID_THIS('URL');
758-
updateUrl(this[context].href, updateActions.kHash, `${value}`, this.#onParseComplete);
716+
updateUrl(this.#context.href, updateActions.kHash, `${value}`, this.#onParseComplete);
759717
}
760718

761719
toJSON() {
762-
if (!isURL(this))
763-
throw new ERR_INVALID_THIS('URL');
764-
return this[context].href;
720+
return this.#context.href;
765721
}
766722

767723
static createObjectURL(obj) {
@@ -1209,7 +1165,7 @@ function getPathFromURLPosix(url) {
12091165
function fileURLToPath(path) {
12101166
if (typeof path === 'string')
12111167
path = new URL(path);
1212-
else if (!isURLInstance(path))
1168+
else if (!isURL(path))
12131169
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
12141170
if (path.protocol !== 'file:')
12151171
throw new ERR_INVALID_URL_SCHEME('file');
@@ -1285,12 +1241,8 @@ function pathToFileURL(filepath) {
12851241
return outURL;
12861242
}
12871243

1288-
function isURLInstance(fileURLOrPath) {
1289-
return fileURLOrPath != null && fileURLOrPath.href && fileURLOrPath.origin;
1290-
}
1291-
12921244
function toPathIfFileURL(fileURLOrPath) {
1293-
if (!isURLInstance(fileURLOrPath))
1245+
if (!isURL(fileURLOrPath))
12941246
return fileURLOrPath;
12951247
return fileURLToPath(fileURLOrPath);
12961248
}
@@ -1300,7 +1252,6 @@ module.exports = {
13001252
fileURLToPath,
13011253
pathToFileURL,
13021254
toPathIfFileURL,
1303-
isURLInstance,
13041255
URL,
13051256
URLSearchParams,
13061257
domainToASCII,

lib/internal/worker.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const {
5555
WritableWorkerStdio,
5656
} = workerIo;
5757
const { deserializeError } = require('internal/error_serdes');
58-
const { fileURLToPath, isURLInstance, pathToFileURL } = require('internal/url');
58+
const { fileURLToPath, isURL, pathToFileURL } = require('internal/url');
5959
const { kEmptyObject } = require('internal/util');
6060
const { validateArray, validateString } = require('internal/validators');
6161

@@ -145,13 +145,13 @@ class Worker extends EventEmitter {
145145
}
146146
url = null;
147147
doEval = 'classic';
148-
} else if (isURLInstance(filename) && filename.protocol === 'data:') {
148+
} else if (isURL(filename) && filename.protocol === 'data:') {
149149
url = null;
150150
doEval = 'module';
151151
filename = `import ${JSONStringify(`${filename}`)}`;
152152
} else {
153153
doEval = false;
154-
if (isURLInstance(filename)) {
154+
if (isURL(filename)) {
155155
url = filename;
156156
filename = fileURLToPath(filename);
157157
} else if (typeof filename !== 'string') {

test/parallel/test-whatwg-url-custom-inspect.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ assert.strictEqual(
6161

6262
assert.strictEqual(
6363
util.inspect({ a: url }, { depth: 0 }),
64-
'{ a: [URL] }');
64+
'{ a: URL {} }');
6565

6666
class MyURL extends URL {}
6767
assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {'));

0 commit comments

Comments
 (0)