Skip to content

Commit

Permalink
url: offload URLSearchParams initialization
Browse files Browse the repository at this point in the history
PR-URL: #46867
Backport-PR-URL: #48345
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Tiancheng "Timothy" Gu <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Filip Skokan <[email protected]>
  • Loading branch information
anonrig authored and danielleadams committed Jul 12, 2023
1 parent 3db235b commit c79e1b7
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 53 deletions.
5 changes: 2 additions & 3 deletions lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const {
const Agent = require('_http_agent');
const { Buffer } = require('buffer');
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
const { URL, urlToHttpOptions, searchParamsSymbol } = require('internal/url');
const { URL, urlToHttpOptions, isURL } = require('internal/url');
const {
kOutHeaders,
kNeedDrain,
Expand Down Expand Up @@ -138,8 +138,7 @@ function ClientRequest(input, options, cb) {
if (typeof input === 'string') {
const urlStr = input;
input = urlToHttpOptions(new URL(urlStr));
} else if (input && input[searchParamsSymbol] &&
input[searchParamsSymbol][searchParamsSymbol]) {
} else if (isURL(input)) {
// url.URL instance
input = urlToHttpOptions(input);
} else {
Expand Down
6 changes: 2 additions & 4 deletions lib/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const { ClientRequest } = require('_http_client');
let debug = require('internal/util/debuglog').debuglog('https', (fn) => {
debug = fn;
});
const { URL, urlToHttpOptions, searchParamsSymbol } = require('internal/url');
const { URL, urlToHttpOptions, isURL } = require('internal/url');

function Server(opts, requestListener) {
if (!(this instanceof Server)) return new Server(opts, requestListener);
Expand Down Expand Up @@ -344,9 +344,7 @@ function request(...args) {
if (typeof args[0] === 'string') {
const urlStr = ArrayPrototypeShift(args);
options = urlToHttpOptions(new URL(urlStr));
} else if (args[0] && args[0][searchParamsSymbol] &&
args[0][searchParamsSymbol][searchParamsSymbol]) {
// url.URL instance
} else if (isURL(args[0])) {
options = urlToHttpOptions(ArrayPrototypeShift(args));
}

Expand Down
85 changes: 39 additions & 46 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ class URLSearchParams {
} else {
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
init = toUSVString(init);
initSearchParams(this, init);
this[searchParams] = init ? parseParams(init) : [];
}
}

Expand Down Expand Up @@ -557,7 +557,7 @@ ObjectDefineProperties(URLSearchParams.prototype, {
},
});

function isURLThis(self) {
function isURL(self) {
return self != null && ObjectPrototypeHasOwnProperty(self, context);
}

Expand Down Expand Up @@ -625,160 +625,161 @@ class URL {
ctx.password = password;
ctx.port = port;
ctx.hash = hash;
if (!this[searchParams]) { // Invoked from URL constructor
this[searchParams] = new URLSearchParams();
this[searchParams][context] = this;
if (this[searchParams]) {
this[searchParams][searchParams] = parseParams(search);
}
initSearchParams(this[searchParams], ctx.search);
};

toString() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].href;
}

get href() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].href;
}

set href(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
const valid = updateUrl(this[context].href, updateActions.kHref, `${value}`, this.#onParseComplete);
if (!valid) { throw ERR_INVALID_URL(`${value}`); }
}

// readonly
get origin() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].origin;
}

get protocol() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].protocol;
}

set protocol(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kProtocol, `${value}`, this.#onParseComplete);
}

get username() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].username;
}

set username(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kUsername, `${value}`, this.#onParseComplete);
}

get password() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].password;
}

set password(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kPassword, `${value}`, this.#onParseComplete);
}

get host() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
const port = this[context].port;
const suffix = port.length > 0 ? `:${port}` : '';
return this[context].hostname + suffix;
}

set host(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kHost, `${value}`, this.#onParseComplete);
}

get hostname() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].hostname;
}

set hostname(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kHostname, `${value}`, this.#onParseComplete);
}

get port() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].port;
}

set port(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kPort, `${value}`, this.#onParseComplete);
}

get pathname() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].pathname;
}

set pathname(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kPathname, `${value}`, this.#onParseComplete);
}

get search() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].search;
}

set search(search) {
if (!isURLThis(this))
set search(value) {
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
search = toUSVString(search);
updateUrl(this[context].href, updateActions.kSearch, search, this.#onParseComplete);
initSearchParams(this[searchParams], this[context].search);
updateUrl(this[context].href, updateActions.kSearch, toUSVString(value), this.#onParseComplete);
}

// readonly
get searchParams() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
// Create URLSearchParams on demand to greatly improve the URL performance.
if (this[searchParams] == null) {
this[searchParams] = new URLSearchParams(this[context].search);
this[searchParams][context] = this;
}
return this[searchParams];
}

get hash() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].hash;
}

set hash(value) {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
updateUrl(this[context].href, updateActions.kHash, `${value}`, this.#onParseComplete);
}

toJSON() {
if (!isURLThis(this))
if (!isURL(this))
throw new ERR_INVALID_THIS('URL');
return this[context].href;
}
Expand Down Expand Up @@ -836,14 +837,6 @@ ObjectDefineProperties(URL, {
revokeObjectURL: kEnumerableProperty,
});

function initSearchParams(url, init) {
if (!init) {
url[searchParams] = [];
return;
}
url[searchParams] = parseParams(init);
}

// application/x-www-form-urlencoded parser
// Ref: https://url.spec.whatwg.org/#concept-urlencoded-parser
function parseParams(qs) {
Expand Down Expand Up @@ -1168,9 +1161,9 @@ function urlToHttpOptions(url) {
__proto__: null,
...url, // In case the url object was extended by the user.
protocol: url.protocol,
hostname: typeof url.hostname === 'string' && StringPrototypeStartsWith(hostname, '[') ?
StringPrototypeSlice(hostname, 1, -1) :
hostname,
hostname: url.hostname && StringPrototypeStartsWith(url.hostname, '[') ?
StringPrototypeSlice(url.hostname, 1, -1) :
url.hostname,
hash: url.hash,
search: search,
pathname: pathname,
Expand Down Expand Up @@ -1339,6 +1332,6 @@ module.exports = {
domainToASCII,
domainToUnicode,
urlToHttpOptions,
searchParamsSymbol: searchParams,
encodeStr,
isURL,
};
7 changes: 7 additions & 0 deletions test/parallel/test-whatwg-url-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ const { URL, URLSearchParams, format } = require('url');
assert.strictEqual(params.size, 3);
}

{
const u = new URL('https://abc.com/?q=old');
const s = u.searchParams;
u.href = 'http://abc.com/?q=new';
assert.strictEqual(s.get('q'), 'new');
}

function stringifyName(name) {
if (typeof name === 'symbol') {
const { description } = name;
Expand Down

0 comments on commit c79e1b7

Please sign in to comment.