From c8b22185f7ea5080f81a88dfc0b6ac68f7e31d24 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 20 Nov 2020 10:20:15 +0100 Subject: [PATCH] url: refactor to use more primordials PR-URL: https://github.com/nodejs/node/pull/36316 Reviewed-By: Rich Trott --- lib/internal/url.js | 148 ++++++++++++++++++++++++++------------------ 1 file changed, 89 insertions(+), 59 deletions(-) diff --git a/lib/internal/url.js b/lib/internal/url.js index 32e298b412c555..24d85353686534 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -2,6 +2,12 @@ const { Array, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeReduce, + ArrayPrototypeSlice, + FunctionPrototypeBind, Int8Array, Number, ObjectCreate, @@ -10,9 +16,17 @@ const { ObjectGetOwnPropertySymbols, ObjectGetPrototypeOf, ObjectKeys, + ReflectApply, ReflectGetOwnPropertyDescriptor, ReflectOwnKeys, + RegExpPrototypeExec, String, + StringPrototypeCharCodeAt, + StringPrototypeIncludes, + StringPrototypeReplace, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeStartsWith, Symbol, SymbolIterator, SymbolToStringTag, @@ -101,7 +115,7 @@ function toUSVString(val) { const str = `${val}`; // As of V8 5.5, `str.search()` (and `unpairedSurrogateRe[@@search]()`) are // slower than `unpairedSurrogateRe.exec()`. - const match = unpairedSurrogateRe.exec(str); + const match = RegExpPrototypeExec(unpairedSurrogateRe, str); if (!match) return str; return _toUSVString(str, match.index); @@ -166,8 +180,8 @@ class URLSearchParams { } const convertedPair = []; for (const element of pair) - convertedPair.push(toUSVString(element)); - pairs.push(convertedPair); + ArrayPrototypePush(convertedPair, toUSVString(element)); + ArrayPrototypePush(pairs, convertedPair); } this[searchParams] = []; @@ -175,7 +189,7 @@ class URLSearchParams { if (pair.length !== 2) { throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); } - this[searchParams].push(pair[0], pair[1]); + ArrayPrototypePush(this[searchParams], pair[0], pair[1]); } } else { // Record @@ -221,16 +235,21 @@ class URLSearchParams { const list = this[searchParams]; const output = []; for (let i = 0; i < list.length; i += 2) - output.push(`${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`); + ArrayPrototypePush( + output, + `${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`); - const length = output.reduce( + const length = ArrayPrototypeReduce( + output, (prev, cur) => prev + removeColors(cur).length + separator.length, -separator.length ); if (length > ctx.breakLength) { - return `${this.constructor.name} {\n ${output.join(',\n ')} }`; + return `${this.constructor.name} {\n` + + ` ${ArrayPrototypeJoin(output, ',\n ')} }`; } else if (output.length) { - return `${this.constructor.name} { ${output.join(separator)} }`; + return `${this.constructor.name} { ` + + `${ArrayPrototypeJoin(output, separator)} }`; } return `${this.constructor.name} {}`; } @@ -290,9 +309,9 @@ function onParsePortComplete(flags, protocol, username, password, function onParseHostComplete(flags, protocol, username, password, host, port, path, query, fragment) { - onParseHostnameComplete.apply(this, arguments); + ReflectApply(onParseHostnameComplete, this, arguments); if (port !== null || ((flags & URL_FLAGS_IS_DEFAULT_SCHEME_PORT) !== 0)) - onParsePortComplete.apply(this, arguments); + ReflectApply(onParsePortComplete, this, arguments); } function onParsePathComplete(flags, protocol, username, password, @@ -332,8 +351,8 @@ class URL { base_context = new URL(base)[context]; } this[context] = new URLContext(); - parse(input, -1, base_context, undefined, onParseComplete.bind(this), - onParseError); + parse(input, -1, base_context, undefined, + FunctionPrototypeBind(onParseComplete, this), onParseError); } get [special]() { @@ -454,8 +473,8 @@ ObjectDefineProperties(URL.prototype, { set(input) { // toUSVString is not needed. input = `${input}`; - parse(input, -1, undefined, undefined, onParseComplete.bind(this), - onParseError); + parse(input, -1, undefined, undefined, + FunctionPrototypeBind(onParseComplete, this), onParseError); } }, origin: { // readonly @@ -502,7 +521,7 @@ ObjectDefineProperties(URL.prototype, { return; } parse(scheme, kSchemeStart, null, ctx, - onParseProtocolComplete.bind(this)); + FunctionPrototypeBind(onParseProtocolComplete, this)); } }, username: { @@ -565,7 +584,8 @@ ObjectDefineProperties(URL.prototype, { // Cannot set the host if cannot-be-base is set return; } - parse(host, kHost, null, ctx, onParseHostComplete.bind(this)); + parse(host, kHost, null, ctx, + FunctionPrototypeBind(onParseHostComplete, this)); } }, hostname: { @@ -602,7 +622,8 @@ ObjectDefineProperties(URL.prototype, { ctx.port = null; return; } - parse(port, kPort, null, ctx, onParsePortComplete.bind(this)); + parse(port, kPort, null, ctx, + FunctionPrototypeBind(onParsePortComplete, this)); } }, pathname: { @@ -614,7 +635,7 @@ ObjectDefineProperties(URL.prototype, { return ctx.path[0]; if (ctx.path.length === 0) return ''; - return `/${ctx.path.join('/')}`; + return `/${ArrayPrototypeJoin(ctx.path, '/')}`; }, set(path) { // toUSVString is not needed. @@ -641,11 +662,12 @@ ObjectDefineProperties(URL.prototype, { ctx.query = null; ctx.flags &= ~URL_FLAGS_HAS_QUERY; } else { - if (search[0] === '?') search = search.slice(1); + if (search[0] === '?') search = StringPrototypeSlice(search, 1); ctx.query = ''; ctx.flags |= URL_FLAGS_HAS_QUERY; if (search) { - parse(search, kQuery, null, ctx, onParseSearchComplete.bind(this)); + parse(search, kQuery, null, ctx, + FunctionPrototypeBind(onParseSearchComplete, this)); } } initSearchParams(this[searchParams], search); @@ -676,10 +698,11 @@ ObjectDefineProperties(URL.prototype, { ctx.flags &= ~URL_FLAGS_HAS_FRAGMENT; return; } - if (hash[0] === '#') hash = hash.slice(1); + if (hash[0] === '#') hash = StringPrototypeSlice(hash, 1); ctx.fragment = ''; ctx.flags |= URL_FLAGS_HAS_FRAGMENT; - parse(hash, kFragment, null, ctx, onParseHashComplete.bind(this)); + parse(hash, kFragment, null, ctx, + FunctionPrototypeBind(onParseHashComplete, this)); } }, toJSON: { @@ -728,7 +751,7 @@ function parseParams(qs) { let encodeCheck = 0; let i; for (i = 0; i < qs.length; ++i) { - const code = qs.charCodeAt(i); + const code = StringPrototypeCharCodeAt(qs, i); // Try matching key/value pair separator if (code === CHAR_AMPERSAND) { @@ -776,7 +799,7 @@ function parseParams(qs) { // Handle + and percent decoding. if (code === CHAR_PLUS) { if (lastPos < i) - buf += qs.slice(lastPos, i); + buf += StringPrototypeSlice(qs, lastPos, i); buf += ' '; lastPos = i + 1; } else if (!encoded) { @@ -804,14 +827,14 @@ function parseParams(qs) { return out; if (lastPos < i) - buf += qs.slice(lastPos, i); + buf += StringPrototypeSlice(qs, lastPos, i); if (encoded) buf = querystring.unescape(buf); - out.push(buf); + ArrayPrototypePush(out, buf); // If `buf` is the key, add an empty value. if (!seenSep) - out.push(''); + ArrayPrototypePush(out, ''); return out; } @@ -925,7 +948,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { name = toUSVString(name); value = toUSVString(value); - this[searchParams].push(name, value); + ArrayPrototypePush(this[searchParams], name, value); update(this[context], this); }, @@ -1039,7 +1062,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { // Otherwise, append a new name-value pair whose name is `name` and value // is `value`, to `list`. if (!found) { - list.push(name, value); + ArrayPrototypePush(list, name, value); } update(this[context], this); @@ -1225,24 +1248,28 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParams Iterator', { kind, index } = this[context]; - const output = target[searchParams].slice(index).reduce((prev, cur, i) => { - const key = i % 2 === 0; - if (kind === 'key' && key) { - prev.push(cur); - } else if (kind === 'value' && !key) { - prev.push(cur); - } else if (kind === 'key+value' && !key) { - prev.push([target[searchParams][index + i - 1], cur]); - } - return prev; - }, []); + 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 = inspect(output, innerOpts).includes('\n'); - const outputStrs = output.map((p) => inspect(p, innerOpts)); + const outputStrs = ArrayPrototypeMap(output, (p) => inspect(p, innerOpts)); let outputStr; if (breakLn) { - outputStr = `\n ${outputStrs.join(',\n ')}`; + outputStr = `\n ${ArrayPrototypeJoin(outputStrs, ',\n ')}`; } else { - outputStr = ` ${outputStrs.join(', ')}`; + outputStr = ` ${ArrayPrototypeJoin(outputStrs, ', ')}`; } return `${this[SymbolToStringTag]} {${outputStr} }`; } @@ -1270,8 +1297,9 @@ function domainToUnicode(domain) { function urlToOptions(url) { const options = { protocol: url.protocol, - hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[') ? - url.hostname.slice(1, -1) : + hostname: typeof url.hostname === 'string' && + StringPrototypeStartsWith(url.hostname, '[') ? + StringPrototypeSlice(url.hostname, 1, -1) : url.hostname, hash: url.hash, search: url.search, @@ -1371,25 +1399,25 @@ const carriageReturnRegEx = /\r/g; const tabRegEx = /\t/g; function encodePathChars(filepath) { - if (filepath.includes('%')) - filepath = filepath.replace(percentRegEx, '%25'); + if (StringPrototypeIncludes(filepath, '%')) + filepath = StringPrototypeReplace(filepath, percentRegEx, '%25'); // In posix, backslash is a valid character in paths: - if (!isWindows && filepath.includes('\\')) - filepath = filepath.replace(backslashRegEx, '%5C'); - if (filepath.includes('\n')) - filepath = filepath.replace(newlineRegEx, '%0A'); - if (filepath.includes('\r')) - filepath = filepath.replace(carriageReturnRegEx, '%0D'); - if (filepath.includes('\t')) - filepath = filepath.replace(tabRegEx, '%09'); + if (!isWindows && StringPrototypeIncludes(filepath, '\\')) + filepath = StringPrototypeReplace(filepath, backslashRegEx, '%5C'); + if (StringPrototypeIncludes(filepath, '\n')) + filepath = StringPrototypeReplace(filepath, newlineRegEx, '%0A'); + if (StringPrototypeIncludes(filepath, '\r')) + filepath = StringPrototypeReplace(filepath, carriageReturnRegEx, '%0D'); + if (StringPrototypeIncludes(filepath, '\t')) + filepath = StringPrototypeReplace(filepath, tabRegEx, '%09'); return filepath; } function pathToFileURL(filepath) { const outURL = new URL('file://'); - if (isWindows && filepath.startsWith('\\\\')) { + if (isWindows && StringPrototypeStartsWith(filepath, '\\\\')) { // UNC path format: \\server\share\resource - const paths = filepath.split('\\'); + const paths = StringPrototypeSplit(filepath, '\\'); if (paths.length <= 3) { throw new ERR_INVALID_ARG_VALUE( 'filepath', @@ -1406,11 +1434,13 @@ function pathToFileURL(filepath) { ); } outURL.hostname = domainToASCII(hostname); - outURL.pathname = encodePathChars(paths.slice(3).join('/')); + outURL.pathname = encodePathChars( + ArrayPrototypeJoin(ArrayPrototypeSlice(paths, 3), '/')); } else { let resolved = path.resolve(filepath); // path.resolve strips trailing slashes so we must add them back - const filePathLast = filepath.charCodeAt(filepath.length - 1); + const filePathLast = StringPrototypeCharCodeAt(filepath, + filepath.length - 1); if ((filePathLast === CHAR_FORWARD_SLASH || (isWindows && filePathLast === CHAR_BACKWARD_SLASH)) && resolved[resolved.length - 1] !== path.sep)