From e48763840625c037282681456ecd1e1cb034f636 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 22 Jan 2023 14:13:24 +0100 Subject: [PATCH] url: refactor to use more primordials PR-URL: https://github.com/nodejs/node/pull/45966 Reviewed-By: Zeyu "Alex" Yang Reviewed-By: Yagiz Nizipli Reviewed-By: Joyee Cheung --- benchmark/url/whatwgurl-to-and-from-path.js | 30 +++++++++++++ lib/internal/freeze_intrinsics.js | 3 +- lib/internal/per_context/primordials.js | 2 + lib/internal/url.js | 49 +++++++++++---------- 4 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 benchmark/url/whatwgurl-to-and-from-path.js diff --git a/benchmark/url/whatwgurl-to-and-from-path.js b/benchmark/url/whatwgurl-to-and-from-path.js new file mode 100644 index 00000000000000..3b87c0670a8fee --- /dev/null +++ b/benchmark/url/whatwgurl-to-and-from-path.js @@ -0,0 +1,30 @@ +'use strict'; +const common = require('../common.js'); +const { fileURLToPath, pathToFileURL } = require('node:url'); +const isWindows = process.platform === 'win32'; + +const bench = common.createBenchmark(main, { + input: isWindows ? [ + 'file:///c/', + ] : [ + 'file:///dev/null', + 'file:///dev/null?key=param&bool', + 'file:///dev/null?key=param&bool#hash', + ], + method: isWindows ? [ + 'fileURLToPath', + ] : [ + 'fileURLToPath', + 'pathToFileURL', + ], + n: [5e6], +}); + +function main({ n, input, method }) { + method = method === 'fileURLOrPath' ? fileURLToPath : pathToFileURL; + bench.start(); + for (let i = 0; i < n; i++) { + method(input); + } + bench.end(n); +} diff --git a/lib/internal/freeze_intrinsics.js b/lib/internal/freeze_intrinsics.js index 02732ea4cad88d..7d030e42910e5a 100644 --- a/lib/internal/freeze_intrinsics.js +++ b/lib/internal/freeze_intrinsics.js @@ -61,6 +61,7 @@ const { Int32ArrayPrototype, Int8Array, Int8ArrayPrototype, + IteratorPrototype, Map, MapPrototype, Number, @@ -211,7 +212,7 @@ module.exports = function() { // 27 Control Abstraction Objects // 27.1 Iteration - ObjectGetPrototypeOf(ArrayIteratorPrototype), // 27.1.2 IteratorPrototype + IteratorPrototype, // 27.1.2 IteratorPrototype // 27.1.3 AsyncIteratorPrototype ObjectGetPrototypeOf(ObjectGetPrototypeOf(ObjectGetPrototypeOf( (async function*() {})() diff --git a/lib/internal/per_context/primordials.js b/lib/internal/per_context/primordials.js index 370f5953dc78e5..d7ca58d0469c07 100644 --- a/lib/internal/per_context/primordials.js +++ b/lib/internal/per_context/primordials.js @@ -260,6 +260,8 @@ function copyPrototype(src, dest, prefix) { copyPrototype(original.prototype, primordials, `${name}Prototype`); }); +primordials.IteratorPrototype = Reflect.getPrototypeOf(primordials.ArrayIteratorPrototype); + /* eslint-enable node-core/prefer-primordials */ const { diff --git a/lib/internal/url.js b/lib/internal/url.js index 47d540f8f30840..8aca33b68f61ec 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -9,6 +9,7 @@ const { ArrayPrototypeSlice, FunctionPrototypeBind, Int8Array, + IteratorPrototype, Number, ObjectDefineProperties, ObjectDefineProperty, @@ -18,11 +19,13 @@ const { ReflectApply, ReflectGetOwnPropertyDescriptor, ReflectOwnKeys, + RegExpPrototypeSymbolReplace, String, + StringPrototypeCharAt, StringPrototypeCharCodeAt, + StringPrototypeCodePointAt, StringPrototypeIncludes, - StringPrototypeReplace, - StringPrototypeReplaceAll, + StringPrototypeIndexOf, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, @@ -44,6 +47,7 @@ const { removeColors, toUSVString, kEnumerableProperty, + SideEffectFreeRegExpPrototypeSymbolReplace, } = require('internal/util'); const { @@ -112,6 +116,8 @@ const { revokeDataObject, } = internalBinding('blob'); +const FORWARD_SLASH = /\//g; + const context = Symbol('context'); const cannotBeBase = Symbol('cannot-be-base'); const cannotHaveUsernamePasswordPort = @@ -138,11 +144,6 @@ function lazyCryptoRandom() { return cryptoRandom; } -// https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object -const IteratorPrototype = ObjectGetPrototypeOf( - ObjectGetPrototypeOf([][SymbolIterator]()) -); - // Refs: https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-opaque const kOpaqueOrigin = 'null'; @@ -1376,7 +1377,7 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParams Iterator', { }, [] ); - const breakLn = inspect(output, innerOpts).includes('\n'); + const breakLn = StringPrototypeIncludes(inspect(output, innerOpts), '\n'); const outputStrs = ArrayPrototypeMap(output, (p) => inspect(p, innerOpts)); let outputStr; if (breakLn) { @@ -1434,7 +1435,7 @@ function getPathFromURLWin32(url) { let pathname = url.pathname; for (let n = 0; n < pathname.length; n++) { if (pathname[n] === '%') { - const third = pathname.codePointAt(n + 2) | 0x20; + const third = StringPrototypeCodePointAt(pathname, n + 2) | 0x20; if ((pathname[n + 1] === '2' && third === 102) || // 2f 2F / (pathname[n + 1] === '5' && third === 99)) { // 5c 5C \ throw new ERR_INVALID_FILE_URL_PATH( @@ -1443,7 +1444,7 @@ function getPathFromURLWin32(url) { } } } - pathname = StringPrototypeReplaceAll(pathname, '/', '\\'); + pathname = SideEffectFreeRegExpPrototypeSymbolReplace(FORWARD_SLASH, pathname, '\\'); pathname = decodeURIComponent(pathname); if (hostname !== '') { // If hostname is set, then we have a UNC path @@ -1455,13 +1456,13 @@ function getPathFromURLWin32(url) { return `\\\\${domainToUnicode(hostname)}${pathname}`; } // Otherwise, it's a local path that requires a drive letter - const letter = pathname.codePointAt(1) | 0x20; - const sep = pathname[2]; + const letter = StringPrototypeCodePointAt(pathname, 1) | 0x20; + const sep = StringPrototypeCharAt(pathname, 2); if (letter < CHAR_LOWERCASE_A || letter > CHAR_LOWERCASE_Z || // a..z A..Z (sep !== ':')) { throw new ERR_INVALID_FILE_URL_PATH('must be absolute'); } - return pathname.slice(1); + return StringPrototypeSlice(pathname, 1); } function getPathFromURLPosix(url) { @@ -1471,7 +1472,7 @@ function getPathFromURLPosix(url) { const pathname = url.pathname; for (let n = 0; n < pathname.length; n++) { if (pathname[n] === '%') { - const third = pathname.codePointAt(n + 2) | 0x20; + const third = StringPrototypeCodePointAt(pathname, n + 2) | 0x20; if (pathname[n + 1] === '2' && third === 102) { throw new ERR_INVALID_FILE_URL_PATH( 'must not include encoded / characters' @@ -1511,16 +1512,16 @@ const tabRegEx = /\t/g; function encodePathChars(filepath) { if (StringPrototypeIncludes(filepath, '%')) - filepath = StringPrototypeReplace(filepath, percentRegEx, '%25'); + filepath = RegExpPrototypeSymbolReplace(percentRegEx, filepath, '%25'); // In posix, backslash is a valid character in paths: if (!isWindows && StringPrototypeIncludes(filepath, '\\')) - filepath = StringPrototypeReplace(filepath, backslashRegEx, '%5C'); + filepath = RegExpPrototypeSymbolReplace(backslashRegEx, filepath, '%5C'); if (StringPrototypeIncludes(filepath, '\n')) - filepath = StringPrototypeReplace(filepath, newlineRegEx, '%0A'); + filepath = RegExpPrototypeSymbolReplace(newlineRegEx, filepath, '%0A'); if (StringPrototypeIncludes(filepath, '\r')) - filepath = StringPrototypeReplace(filepath, carriageReturnRegEx, '%0D'); + filepath = RegExpPrototypeSymbolReplace(carriageReturnRegEx, filepath, '%0D'); if (StringPrototypeIncludes(filepath, '\t')) - filepath = StringPrototypeReplace(filepath, tabRegEx, '%09'); + filepath = RegExpPrototypeSymbolReplace(tabRegEx, filepath, '%09'); return filepath; } @@ -1528,25 +1529,25 @@ function pathToFileURL(filepath) { const outURL = new URL('file://'); if (isWindows && StringPrototypeStartsWith(filepath, '\\\\')) { // UNC path format: \\server\share\resource - const paths = StringPrototypeSplit(filepath, '\\'); - if (paths.length <= 3) { + const hostnameEndIndex = StringPrototypeIndexOf(filepath, '\\', 2); + if (hostnameEndIndex === -1) { throw new ERR_INVALID_ARG_VALUE( 'filepath', filepath, 'Missing UNC resource path' ); } - const hostname = paths[2]; - if (hostname.length === 0) { + if (hostnameEndIndex === 2) { throw new ERR_INVALID_ARG_VALUE( 'filepath', filepath, 'Empty UNC servername' ); } + const hostname = StringPrototypeSlice(filepath, 2, hostnameEndIndex); outURL.hostname = domainToASCII(hostname); outURL.pathname = encodePathChars( - ArrayPrototypeJoin(ArrayPrototypeSlice(paths, 3), '/')); + RegExpPrototypeSymbolReplace(backslashRegEx, StringPrototypeSlice(filepath, hostnameEndIndex), '/')); } else { let resolved = path.resolve(filepath); // path.resolve strips trailing slashes so we must add them back