UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

1,864 lines 50.2 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/url.js

import * as __hoisted_internal_crypto_random__ from "nstdlib/lib/internal/crypto/random";
import { inspect } from "nstdlib/lib/internal/util/inspect";
import {
  encodeStr,
  hexTable,
  isHexTable,
} from "nstdlib/lib/internal/querystring";
import {
  getConstructorOf,
  removeColors,
  kEnumerableProperty,
  kEmptyObject,
  SideEffectFreeRegExpPrototypeSymbolReplace,
  isWindows,
} from "nstdlib/lib/internal/util";
import { platform } from "nstdlib/lib/internal/process/per_thread";
import { markTransferMode } from "nstdlib/lib/internal/worker/js_transferable";
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import {
  CHAR_AMPERSAND,
  CHAR_BACKWARD_SLASH,
  CHAR_EQUAL,
  CHAR_FORWARD_SLASH,
  CHAR_LOWERCASE_A,
  CHAR_LOWERCASE_Z,
  CHAR_PERCENT,
  CHAR_PLUS,
} from "nstdlib/lib/internal/constants";
import * as path from "nstdlib/lib/path";
import { validateFunction } from "nstdlib/lib/internal/validators";
import * as querystring from "nstdlib/lib/querystring";
import * as bindingUrl from "nstdlib/stub/binding/url";
import * as __hoisted_internal_blob__ from "nstdlib/lib/internal/blob";

const {
  ERR_ARG_NOT_ITERABLE,
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_ARG_VALUE,
  ERR_INVALID_FILE_URL_HOST,
  ERR_INVALID_FILE_URL_PATH,
  ERR_INVALID_THIS,
  ERR_INVALID_TUPLE,
  ERR_INVALID_URL,
  ERR_INVALID_URL_SCHEME,
  ERR_MISSING_ARGS,
  ERR_NO_CRYPTO,
} = __codes__;

const FORWARD_SLASH = /\//g;

const contextForInspect = Symbol("context");

// `unsafeProtocol`, `hostlessProtocol` and `slashedProtocol` is
// deliberately moved to `internal/url` rather than `url`.
// Workers does not bootstrap URL module. Therefore, `SafeSet`
// is not initialized on bootstrap. This case breaks the
// test-require-delete-array-iterator test.

// Protocols that can allow "unsafe" and "unwise" chars.
const unsafeProtocol = new Set(["javascript", "javascript:"]);
// Protocols that never have a hostname.
const hostlessProtocol = new Set(["javascript", "javascript:"]);
// Protocols that always contain a // bit.
const slashedProtocol = new Set([
  "http",
  "http:",
  "https",
  "https:",
  "ftp",
  "ftp:",
  "gopher",
  "gopher:",
  "file",
  "file:",
  "ws",
  "ws:",
  "wss",
  "wss:",
]);

const updateActions = {
  kProtocol: 0,
  kHost: 1,
  kHostname: 2,
  kPort: 3,
  kUsername: 4,
  kPassword: 5,
  kPathname: 6,
  kSearch: 7,
  kHash: 8,
  kHref: 9,
};
let blob;
let cryptoRandom;

function lazyBlob() {
  blob ??= __hoisted_internal_blob__;
  return blob;
}

function lazyCryptoRandom() {
  try {
    cryptoRandom ??= __hoisted_internal_crypto_random__;
  } catch {
    // If Node.js built without crypto support, we'll fall
    // through here and handle it later.
  }
  return cryptoRandom;
}

// This class provides the internal state of a URL object. An instance of this
// class is stored in every URL object and is accessed internally by setters
// and getters. It roughly corresponds to the concept of a URL record in the
// URL Standard, with a few differences. It is also the object transported to
// the C++ binding.
// Refs: https://url.spec.whatwg.org/#concept-url
class URLContext {
  // This is the maximum value uint32_t can get.
  // Ada uses uint32_t(-1) for declaring omitted values.
  static #omitted = 4294967295;

  href = "";
  protocol_end = 0;
  username_end = 0;
  host_start = 0;
  host_end = 0;
  pathname_start = 0;
  search_start = 0;
  hash_start = 0;
  port = 0;
  /**
   * Refers to `ada::scheme::type`
   *
   * enum type : uint8_t {
   *   HTTP = 0,
   *   NOT_SPECIAL = 1,
   *   HTTPS = 2,
   *   WS = 3,
   *   FTP = 4,
   *   WSS = 5,
   *   FILE = 6
   * };
   * @type {number}
   */
  scheme_type = 1;

  get hasPort() {
    return this.port !== URLContext.#omitted;
  }

  get hasSearch() {
    return this.search_start !== URLContext.#omitted;
  }

  get hasHash() {
    return this.hash_start !== URLContext.#omitted;
  }
}

let setURLSearchParamsModified;
let setURLSearchParamsContext;
let getURLSearchParamsList;
let setURLSearchParams;

class URLSearchParamsIterator {
  #target;
  #kind;
  #index;

  // https://heycam.github.io/webidl/#dfn-default-iterator-object
  constructor(target, kind) {
    this.#target = target;
    this.#kind = kind;
    this.#index = 0;
  }

  next() {
    if (typeof this !== "object" || this === null || !(#target in this))
      throw new ERR_INVALID_THIS("URLSearchParamsIterator");

    const index = this.#index;
    const values = getURLSearchParamsList(this.#target);
    const len = values.length;
    if (index >= len) {
      return {
        value: undefined,
        done: true,
      };
    }

    const name = values[index];
    const value = values[index + 1];
    this.#index = index + 2;

    let result;
    if (this.#kind === "key") {
      result = name;
    } else if (this.#kind === "value") {
      result = value;
    } else {
      result = [name, value];
    }

    return {
      value: result,
      done: false,
    };
  }

  [inspect.custom](recurseTimes, ctx) {
    if (!this || typeof this !== "object" || !(#target in this))
      throw new ERR_INVALID_THIS("URLSearchParamsIterator");

    if (typeof recurseTimes === "number" && recurseTimes < 0)
      return ctx.stylize("[Object]", "special");

    const innerOpts = { ...ctx };
    if (recurseTimes !== null) {
      innerOpts.depth = recurseTimes - 1;
    }
    const index = this.#index;
    const values = getURLSearchParamsList(this.#target);
    const output = Array.prototype.reduce.call(
      Array.prototype.slice.call(values, index),
      (prev, cur, i) => {
        const key = i % 2 === 0;
        if (this.#kind === "key" && key) {
          Array.prototype.push.call(prev, cur);
        } else if (this.#kind === "value" && !key) {
          Array.prototype.push.call(prev, cur);
        } else if (this.#kind === "key+value" && !key) {
          Array.prototype.push.call(prev, [values[index + i - 1], cur]);
        }
        return prev;
      },
      [],
    );
    const breakLn = String.prototype.includes.call(
      inspect(output, innerOpts),
      "\n",
    );
    const outputStrs = Array.prototype.map.call(output, (p) =>
      inspect(p, innerOpts),
    );
    let outputStr;
    if (breakLn) {
      outputStr = `\n  ${Array.prototype.join.call(outputStrs, ",\n  ")}`;
    } else {
      outputStr = ` ${Array.prototype.join.call(outputStrs, ", ")}`;
    }
    return `${this[Symbol.toStringTag]} {${outputStr} }`;
  }
}

// https://heycam.github.io/webidl/#dfn-iterator-prototype-object
delete URLSearchParamsIterator.prototype.constructor;
Object.setPrototypeOf(URLSearchParamsIterator.prototype, IteratorPrototype);

Object.defineProperties(URLSearchParamsIterator.prototype, {
  [Symbol.toStringTag]: {
    __proto__: null,
    configurable: true,
    value: "URLSearchParams Iterator",
  },
  next: kEnumerableProperty,
});

class URLSearchParams {
  #searchParams = [];

  // "associated url object"
  #context;

  static {
    setURLSearchParamsContext = (obj, ctx) => {
      obj.#context = ctx;
    };
    getURLSearchParamsList = (obj) => obj.#searchParams;
    setURLSearchParams = (obj, query) => {
      if (query === undefined) {
        obj.#searchParams = [];
      } else {
        obj.#searchParams = parseParams(query);
      }
    };
  }

  // URL Standard says the default value is '', but as undefined and '' have
  // the same result, undefined is used to prevent unnecessary parsing.
  // Default parameter is necessary to keep URLSearchParams.length === 0 in
  // accordance with Web IDL spec.
  constructor(init = undefined) {
    markTransferMode(this, false, false);

    if (init == null) {
      // Do nothing
    } else if (typeof init === "object" || typeof init === "function") {
      const method = init[Symbol.iterator];
      if (method === this[Symbol.iterator] && #searchParams in init) {
        // While the spec does not have this branch, we can use it as a
        // shortcut to avoid having to go through the costly generic iterator.
        const childParams = init.#searchParams;
        this.#searchParams = childParams.slice();
      } else if (method != null) {
        // Sequence<sequence<USVString>>
        if (typeof method !== "function") {
          throw new ERR_ARG_NOT_ITERABLE("Query pairs");
        }

        // The following implementation differs from the URL specification:
        // Sequences must first be converted from ECMAScript objects before
        // and operations are done on them, and the operation of converting
        // the sequences would first exhaust the iterators. If the iterator
        // returns something invalid in the middle, whether it would be called
        // after that would be an observable change to the users.
        // Exhausting the iterator and later converting them to USVString comes
        // with a significant cost (~40-80%). In order optimize URLSearchParams
        // creation duration, Node.js merges the iteration and converting
        // iterations into a single iteration.
        for (const pair of init) {
          if (pair == null) {
            throw new ERR_INVALID_TUPLE("Each query pair", "[name, value]");
          } else if (Array.isArray(pair)) {
            // If innerSequence's size is not 2, then throw a TypeError.
            if (pair.length !== 2) {
              throw new ERR_INVALID_TUPLE("Each query pair", "[name, value]");
            }
            // Append (innerSequence[0], innerSequence[1]) to query's list.
            Array.prototype.push.call(
              this.#searchParams,
              String.prototype.toWellFormed.call(`${pair[0]}`),
              String.prototype.toWellFormed.call(`${pair[1]}`),
            );
          } else {
            if (
              (typeof pair !== "object" && typeof pair !== "function") ||
              typeof pair[Symbol.iterator] !== "function"
            ) {
              throw new ERR_INVALID_TUPLE("Each query pair", "[name, value]");
            }

            let length = 0;

            for (const element of pair) {
              length++;
              Array.prototype.push.call(
                this.#searchParams,
                String.prototype.toWellFormed.call(`${element}`),
              );
            }

            // If innerSequence's size is not 2, then throw a TypeError.
            if (length !== 2) {
              throw new ERR_INVALID_TUPLE("Each query pair", "[name, value]");
            }
          }
        }
      } else {
        // Record<USVString, USVString>
        // Need to use reflection APIs for full spec compliance.
        const visited = new Map();
        const keys = Reflect.ownKeys(init);
        for (let i = 0; i < keys.length; i++) {
          const key = keys[i];
          const desc = Reflect.getOwnPropertyDescriptor(init, key);
          if (desc !== undefined && desc.enumerable) {
            const typedKey = String.prototype.toWellFormed.call(key);
            const typedValue = String.prototype.toWellFormed.call(
              `${init[key]}`,
            );

            // Two different keys may become the same USVString after normalization.
            // In that case, we retain the later one. Refer to WPT.
            const keyIdx = visited.get(typedKey);
            if (keyIdx !== undefined) {
              this.#searchParams[keyIdx] = typedValue;
            } else {
              visited.set(
                typedKey,
                Array.prototype.push.call(
                  this.#searchParams,
                  typedKey,
                  typedValue,
                ) - 1,
              );
            }
          }
        }
      }
    } else {
      // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
      init = String.prototype.toWellFormed.call(`${init}`);
      this.#searchParams = init ? parseParams(init) : [];
    }
  }

  [inspect.custom](recurseTimes, ctx) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (typeof recurseTimes === "number" && recurseTimes < 0)
      return ctx.stylize("[Object]", "special");

    const separator = ", ";
    const innerOpts = { ...ctx };
    if (recurseTimes !== null) {
      innerOpts.depth = recurseTimes - 1;
    }
    const innerInspect = (v) => inspect(v, innerOpts);

    const list = this.#searchParams;
    const output = [];
    for (let i = 0; i < list.length; i += 2)
      Array.prototype.push.call(
        output,
        `${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`,
      );

    const length = Array.prototype.reduce.call(
      output,
      (prev, cur) => prev + removeColors(cur).length + separator.length,
      -separator.length,
    );
    if (length > ctx.breakLength) {
      return (
        `${this.constructor.name} {\n` +
        `  ${Array.prototype.join.call(output, ",\n  ")} }`
      );
    } else if (output.length) {
      return (
        `${this.constructor.name} { ` +
        `${Array.prototype.join.call(output, separator)} }`
      );
    }
    return `${this.constructor.name} {}`;
  }

  get size() {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");
    return this.#searchParams.length / 2;
  }

  append(name, value) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (arguments.length < 2) {
      throw new ERR_MISSING_ARGS("name", "value");
    }

    name = String.prototype.toWellFormed.call(`${name}`);
    value = String.prototype.toWellFormed.call(`${value}`);
    Array.prototype.push.call(this.#searchParams, name, value);

    if (this.#context) {
      setURLSearchParamsModified(this.#context);
    }
  }

  delete(name, value = undefined) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (arguments.length < 1) {
      throw new ERR_MISSING_ARGS("name");
    }

    const list = this.#searchParams;
    name = String.prototype.toWellFormed.call(`${name}`);

    if (value !== undefined) {
      value = String.prototype.toWellFormed.call(`${value}`);
      for (let i = 0; i < list.length; ) {
        if (list[i] === name && list[i + 1] === value) {
          list.splice(i, 2);
        } else {
          i += 2;
        }
      }
    } else {
      for (let i = 0; i < list.length; ) {
        if (list[i] === name) {
          list.splice(i, 2);
        } else {
          i += 2;
        }
      }
    }

    if (this.#context) {
      setURLSearchParamsModified(this.#context);
    }
  }

  get(name) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (arguments.length < 1) {
      throw new ERR_MISSING_ARGS("name");
    }

    const list = this.#searchParams;
    name = String.prototype.toWellFormed.call(`${name}`);
    for (let i = 0; i < list.length; i += 2) {
      if (list[i] === name) {
        return list[i + 1];
      }
    }
    return null;
  }

  getAll(name) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (arguments.length < 1) {
      throw new ERR_MISSING_ARGS("name");
    }

    const list = this.#searchParams;
    const values = [];
    name = String.prototype.toWellFormed.call(`${name}`);
    for (let i = 0; i < list.length; i += 2) {
      if (list[i] === name) {
        values.push(list[i + 1]);
      }
    }
    return values;
  }

  has(name, value = undefined) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (arguments.length < 1) {
      throw new ERR_MISSING_ARGS("name");
    }

    const list = this.#searchParams;
    name = String.prototype.toWellFormed.call(`${name}`);

    if (value !== undefined) {
      value = String.prototype.toWellFormed.call(`${value}`);
    }

    for (let i = 0; i < list.length; i += 2) {
      if (list[i] === name) {
        if (value === undefined || list[i + 1] === value) {
          return true;
        }
      }
    }

    return false;
  }

  set(name, value) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    if (arguments.length < 2) {
      throw new ERR_MISSING_ARGS("name", "value");
    }

    const list = this.#searchParams;
    name = String.prototype.toWellFormed.call(`${name}`);
    value = String.prototype.toWellFormed.call(`${value}`);

    // If there are any name-value pairs whose name is `name`, in `list`, set
    // the value of the first such name-value pair to `value` and remove the
    // others.
    let found = false;
    for (let i = 0; i < list.length; ) {
      const cur = list[i];
      if (cur === name) {
        if (!found) {
          list[i + 1] = value;
          found = true;
          i += 2;
        } else {
          list.splice(i, 2);
        }
      } else {
        i += 2;
      }
    }

    // Otherwise, append a new name-value pair whose name is `name` and value
    // is `value`, to `list`.
    if (!found) {
      Array.prototype.push.call(list, name, value);
    }

    if (this.#context) {
      setURLSearchParamsModified(this.#context);
    }
  }

  sort() {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    const a = this.#searchParams;
    const len = a.length;

    if (len <= 2) {
      // Nothing needs to be done.
    } else if (len < 100) {
      // 100 is found through testing.
      // Simple stable in-place insertion sort
      // Derived from v8/src/js/array.js
      for (let i = 2; i < len; i += 2) {
        const curKey = a[i];
        const curVal = a[i + 1];
        let j;
        for (j = i - 2; j >= 0; j -= 2) {
          if (a[j] > curKey) {
            a[j + 2] = a[j];
            a[j + 3] = a[j + 1];
          } else {
            break;
          }
        }
        a[j + 2] = curKey;
        a[j + 3] = curVal;
      }
    } else {
      // Bottom-up iterative stable merge sort
      const lBuffer = new Array(len);
      const rBuffer = new Array(len);
      for (let step = 2; step < len; step *= 2) {
        for (let start = 0; start < len - 2; start += 2 * step) {
          const mid = start + step;
          let end = mid + step;
          end = end < len ? end : len;
          if (mid > end) continue;
          merge(a, start, mid, end, lBuffer, rBuffer);
        }
      }
    }

    if (this.#context) {
      setURLSearchParamsModified(this.#context);
    }
  }

  // https://heycam.github.io/webidl/#es-iterators
  // Define entries here rather than [Symbol.iterator] as the function name
  // must be set to `entries`.
  entries() {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    return new URLSearchParamsIterator(this, "key+value");
  }

  forEach(callback, thisArg = undefined) {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    validateFunction(callback, "callback");

    let list = this.#searchParams;

    let i = 0;
    while (i < list.length) {
      const key = list[i];
      const value = list[i + 1];
      callback.call(thisArg, value, key, this);
      // In case the URL object's `search` is updated
      list = this.#searchParams;
      i += 2;
    }
  }

  // https://heycam.github.io/webidl/#es-iterable
  keys() {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    return new URLSearchParamsIterator(this, "key");
  }

  values() {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    return new URLSearchParamsIterator(this, "value");
  }

  // https://heycam.github.io/webidl/#es-stringifier
  // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
  toString() {
    if (typeof this !== "object" || this === null || !(#searchParams in this))
      throw new ERR_INVALID_THIS("URLSearchParams");

    return serializeParams(this.#searchParams);
  }
}

Object.defineProperties(URLSearchParams.prototype, {
  append: kEnumerableProperty,
  delete: kEnumerableProperty,
  get: kEnumerableProperty,
  getAll: kEnumerableProperty,
  has: kEnumerableProperty,
  set: kEnumerableProperty,
  size: kEnumerableProperty,
  sort: kEnumerableProperty,
  entries: kEnumerableProperty,
  forEach: kEnumerableProperty,
  keys: kEnumerableProperty,
  values: kEnumerableProperty,
  toString: kEnumerableProperty,
  [Symbol.toStringTag]: {
    __proto__: null,
    configurable: true,
    value: "URLSearchParams",
  },

  // https://heycam.github.io/webidl/#es-iterable-entries
  [Symbol.iterator]: {
    __proto__: null,
    configurable: true,
    writable: true,
    value: URLSearchParams.prototype.entries,
  },
});

/**
 * Checks if a value has the shape of a WHATWG URL object.
 *
 * Using a symbol or instanceof would not be able to recognize URL objects
 * coming from other implementations (e.g. in Electron), so instead we are
 * checking some well known properties for a lack of a better test.
 *
 * We use `href` and `protocol` as they are the only properties that are
 * easy to retrieve and calculate due to the lazy nature of the getters.
 *
 * We check for `auth` and `path` attribute to distinguish legacy url instance with
 * WHATWG URL instance.
 * @param {*} self
 * @returns {self is URL}
 */
function isURL(self) {
  return Boolean(
    self?.href &&
      self.protocol &&
      self.auth === undefined &&
      self.path === undefined,
  );
}

/**
 * A unique symbol used as a private identifier to safely invoke the URL constructor
 * with a special parsing behavior. When passed as the third argument to the URL
 * constructor, it signals that the constructor should not throw an exception
 * for invalid URL inputs.
 */
const kParseURLSymbol = Symbol("kParseURL");

class URL {
  #context = new URLContext();
  #searchParams;
  #searchParamsModified;

  static {
    setURLSearchParamsModified = (obj) => {
      // When URLSearchParams changes, we lazily update URL on the next read/write for performance.
      obj.#searchParamsModified = true;

      // If URL has an existing search, remove it without cascading back to URLSearchParams.
      // Do this to avoid any internal confusion about whether URLSearchParams or URL is up-to-date.
      if (obj.#context.hasSearch) {
        obj.#updateContext(
          bindingUrl.update(obj.#context.href, updateActions.kSearch, ""),
        );
      }
    };
  }

  constructor(input, base = undefined, parseSymbol = undefined) {
    markTransferMode(this, false, false);

    if (arguments.length === 0) {
      throw new ERR_MISSING_ARGS("url");
    }

    // StringPrototypeToWellFormed is not needed.
    input = `${input}`;

    if (base !== undefined) {
      base = `${base}`;
    }

    const raiseException = parseSymbol !== kParseURLSymbol;
    const href = bindingUrl.parse(input, base, raiseException);
    if (href) {
      this.#updateContext(href);
    }
  }

  static parse(input, base = undefined) {
    if (arguments.length === 0) {
      throw new ERR_MISSING_ARGS("url");
    }
    const parsedURLObject = new URL(input, base, kParseURLSymbol);
    return parsedURLObject.href ? parsedURLObject : null;
  }

  [inspect.custom](depth, opts) {
    if (typeof depth === "number" && depth < 0) return this;

    const constructor = getConstructorOf(this) || URL;
    const obj = { __proto__: { constructor } };

    obj.href = this.href;
    obj.origin = this.origin;
    obj.protocol = this.protocol;
    obj.username = this.username;
    obj.password = this.password;
    obj.host = this.host;
    obj.hostname = this.hostname;
    obj.port = this.port;
    obj.pathname = this.pathname;
    obj.search = this.search;
    obj.searchParams = this.searchParams;
    obj.hash = this.hash;

    if (opts.showHidden) {
      obj[contextForInspect] = this.#context;
    }

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

  #getSearchFromContext() {
    if (!this.#context.hasSearch) return "";
    let endsAt = this.#context.href.length;
    if (this.#context.hasHash) endsAt = this.#context.hash_start;
    if (endsAt - this.#context.search_start <= 1) return "";
    return String.prototype.slice.call(
      this.#context.href,
      this.#context.search_start,
      endsAt,
    );
  }

  #getSearchFromParams() {
    if (!this.#searchParams?.size) return "";
    return `?${this.#searchParams}`;
  }

  #ensureSearchParamsUpdated() {
    // URL is updated lazily to greatly improve performance when URLSearchParams is updated repeatedly.
    // If URLSearchParams has been modified, reflect that back into URL, without cascading back.
    if (this.#searchParamsModified) {
      this.#searchParamsModified = false;
      this.#updateContext(
        bindingUrl.update(
          this.#context.href,
          updateActions.kSearch,
          this.#getSearchFromParams(),
        ),
      );
    }
  }

  /**
   * Update the internal context state for URL.
   * @param {string} href New href string from `bindingUrl.update`.
   * @param {boolean} [shouldUpdateSearchParams] If the update has potential to update search params (href/search).
   */
  #updateContext(href, shouldUpdateSearchParams = false) {
    const previousSearch =
      shouldUpdateSearchParams &&
      this.#searchParams &&
      (this.#searchParamsModified
        ? this.#getSearchFromParams()
        : this.#getSearchFromContext());

    this.#context.href = href;

    const {
      0: protocol_end,
      1: username_end,
      2: host_start,
      3: host_end,
      4: port,
      5: pathname_start,
      6: search_start,
      7: hash_start,
      8: scheme_type,
    } = bindingUrl.urlComponents;

    this.#context.protocol_end = protocol_end;
    this.#context.username_end = username_end;
    this.#context.host_start = host_start;
    this.#context.host_end = host_end;
    this.#context.port = port;
    this.#context.pathname_start = pathname_start;
    this.#context.search_start = search_start;
    this.#context.hash_start = hash_start;
    this.#context.scheme_type = scheme_type;

    if (this.#searchParams) {
      // If the search string has updated, URL becomes the source of truth, and we update URLSearchParams.
      // Only do this when we're expecting it to have changed, otherwise a change to hash etc.
      // would incorrectly compare the URLSearchParams state to the empty URL search state.
      if (shouldUpdateSearchParams) {
        const currentSearch = this.#getSearchFromContext();
        if (previousSearch !== currentSearch) {
          setURLSearchParams(this.#searchParams, currentSearch);
          this.#searchParamsModified = false;
        }
      }

      // If we have a URLSearchParams, ensure that URL is up-to-date with any modification to it.
      this.#ensureSearchParamsUpdated();
    }
  }

  toString() {
    // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync.
    this.#ensureSearchParamsUpdated();
    return this.#context.href;
  }

  get href() {
    // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync.
    this.#ensureSearchParamsUpdated();
    return this.#context.href;
  }

  set href(value) {
    value = `${value}`;
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kHref,
      value,
    );
    if (!href) {
      throw new ERR_INVALID_URL(value);
    }
    this.#updateContext(href, true);
  }

  // readonly
  get origin() {
    const protocol = String.prototype.slice.call(
      this.#context.href,
      0,
      this.#context.protocol_end,
    );

    // Check if scheme_type is not `NOT_SPECIAL`
    if (this.#context.scheme_type !== 1) {
      // Check if scheme_type is `FILE`
      if (this.#context.scheme_type === 6) {
        return "null";
      }
      return `${protocol}//${this.host}`;
    }

    if (protocol === "blob:") {
      const path = this.pathname;
      if (path.length > 0) {
        try {
          const out = new URL(path);
          // Only return origin of scheme is `http` or `https`
          // Otherwise return a new opaque origin (null).
          if (
            out.#context.scheme_type === 0 ||
            out.#context.scheme_type === 2
          ) {
            return `${out.protocol}//${out.host}`;
          }
        } catch {
          // Do nothing.
        }
      }
    }

    return "null";
  }

  get protocol() {
    return String.prototype.slice.call(
      this.#context.href,
      0,
      this.#context.protocol_end,
    );
  }

  set protocol(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kProtocol,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get username() {
    if (this.#context.protocol_end + 2 < this.#context.username_end) {
      return String.prototype.slice.call(
        this.#context.href,
        this.#context.protocol_end + 2,
        this.#context.username_end,
      );
    }
    return "";
  }

  set username(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kUsername,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get password() {
    if (this.#context.host_start - this.#context.username_end > 0) {
      return String.prototype.slice.call(
        this.#context.href,
        this.#context.username_end + 1,
        this.#context.host_start,
      );
    }
    return "";
  }

  set password(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kPassword,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get host() {
    let startsAt = this.#context.host_start;
    if (this.#context.href[startsAt] === "@") {
      startsAt++;
    }
    // If we have an empty host, then the space between components.host_end and
    // components.pathname_start may be occupied by /.
    if (startsAt === this.#context.host_end) {
      return "";
    }
    return String.prototype.slice.call(
      this.#context.href,
      startsAt,
      this.#context.pathname_start,
    );
  }

  set host(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kHost,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get hostname() {
    let startsAt = this.#context.host_start;
    // host_start might be "@" if the URL has credentials
    if (this.#context.href[startsAt] === "@") {
      startsAt++;
    }
    return String.prototype.slice.call(
      this.#context.href,
      startsAt,
      this.#context.host_end,
    );
  }

  set hostname(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kHostname,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get port() {
    if (this.#context.hasPort) {
      return `${this.#context.port}`;
    }
    return "";
  }

  set port(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kPort,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get pathname() {
    let endsAt;
    if (this.#context.hasSearch) {
      endsAt = this.#context.search_start;
    } else if (this.#context.hasHash) {
      endsAt = this.#context.hash_start;
    }
    return String.prototype.slice.call(
      this.#context.href,
      this.#context.pathname_start,
      endsAt,
    );
  }

  set pathname(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kPathname,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  get search() {
    // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync.
    this.#ensureSearchParamsUpdated();
    return this.#getSearchFromContext();
  }

  set search(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kSearch,
      String.prototype.toWellFormed.call(`${value}`),
    );
    if (href) {
      this.#updateContext(href, true);
    }
  }

  // readonly
  get searchParams() {
    // Create URLSearchParams on demand to greatly improve the URL performance.
    if (this.#searchParams == null) {
      this.#searchParams = new URLSearchParams(this.#getSearchFromContext());
      setURLSearchParamsContext(this.#searchParams, this);
      this.#searchParamsModified = false;
    }
    return this.#searchParams;
  }

  get hash() {
    if (
      !this.#context.hasHash ||
      this.#context.href.length - this.#context.hash_start <= 1
    ) {
      return "";
    }
    return String.prototype.slice.call(
      this.#context.href,
      this.#context.hash_start,
    );
  }

  set hash(value) {
    const href = bindingUrl.update(
      this.#context.href,
      updateActions.kHash,
      `${value}`,
    );
    if (href) {
      this.#updateContext(href);
    }
  }

  toJSON() {
    // Updates to URLSearchParams are lazily propagated to URL, so we need to check we're in sync.
    this.#ensureSearchParamsUpdated();
    return this.#context.href;
  }

  static canParse(url, base = undefined) {
    if (arguments.length === 0) {
      throw new ERR_MISSING_ARGS("url");
    }

    url = `${url}`;

    if (base !== undefined) {
      return bindingUrl.canParse(url, `${base}`);
    }

    // It is important to differentiate the canParse call statements
    // since they resolve into different v8 fast api overloads.
    return bindingUrl.canParse(url);
  }
}

Object.defineProperties(URL.prototype, {
  [Symbol.toStringTag]: { __proto__: null, configurable: true, value: "URL" },
  toString: kEnumerableProperty,
  href: kEnumerableProperty,
  origin: kEnumerableProperty,
  protocol: kEnumerableProperty,
  username: kEnumerableProperty,
  password: kEnumerableProperty,
  host: kEnumerableProperty,
  hostname: kEnumerableProperty,
  port: kEnumerableProperty,
  pathname: kEnumerableProperty,
  search: kEnumerableProperty,
  searchParams: kEnumerableProperty,
  hash: kEnumerableProperty,
  toJSON: kEnumerableProperty,
});

Object.defineProperties(URL, {
  canParse: {
    __proto__: null,
    configurable: true,
    writable: true,
    enumerable: true,
  },
  parse: {
    __proto__: null,
    configurable: true,
    writable: true,
    enumerable: true,
  },
});

function installObjectURLMethods() {
  const bindingBlob = require("binding/blob");

  function createObjectURL(obj) {
    const cryptoRandom = lazyCryptoRandom();
    if (cryptoRandom === undefined) throw new ERR_NO_CRYPTO();

    const blob = lazyBlob();
    if (!blob.isBlob(obj)) throw new ERR_INVALID_ARG_TYPE("obj", "Blob", obj);

    const id = cryptoRandom.randomUUID();

    bindingBlob.storeDataObject(id, obj[blob.kHandle], obj.size, obj.type);

    return `blob:nodedata:${id}`;
  }

  function revokeObjectURL(url) {
    if (arguments.length === 0) {
      throw new ERR_MISSING_ARGS("url");
    }

    bindingBlob.revokeObjectURL(`${url}`);
  }

  Object.defineProperties(URL, {
    createObjectURL: {
      __proto__: null,
      configurable: true,
      writable: true,
      enumerable: true,
      value: createObjectURL,
    },
    revokeObjectURL: {
      __proto__: null,
      configurable: true,
      writable: true,
      enumerable: true,
      value: revokeObjectURL,
    },
  });
}

// application/x-www-form-urlencoded parser
// Ref: https://url.spec.whatwg.org/#concept-urlencoded-parser
function parseParams(qs) {
  const out = [];
  let seenSep = false;
  let buf = "";
  let encoded = false;
  let encodeCheck = 0;
  let i = qs[0] === "?" ? 1 : 0;
  let pairStart = i;
  let lastPos = i;
  for (; i < qs.length; ++i) {
    const code = String.prototype.charCodeAt.call(qs, i);

    // Try matching key/value pair separator
    if (code === CHAR_AMPERSAND) {
      if (pairStart === i) {
        // We saw an empty substring between pair separators
        lastPos = pairStart = i + 1;
        continue;
      }

      if (lastPos < i) buf += qs.slice(lastPos, i);
      if (encoded) buf = querystring.unescape(buf);
      out.push(buf);

      // If `buf` is the key, add an empty value.
      if (!seenSep) out.push("");

      seenSep = false;
      buf = "";
      encoded = false;
      encodeCheck = 0;
      lastPos = pairStart = i + 1;
      continue;
    }

    // Try matching key/value separator (e.g. '=') if we haven't already
    if (!seenSep && code === CHAR_EQUAL) {
      // Key/value separator match!
      if (lastPos < i) buf += qs.slice(lastPos, i);
      if (encoded) buf = querystring.unescape(buf);
      out.push(buf);

      seenSep = true;
      buf = "";
      encoded = false;
      encodeCheck = 0;
      lastPos = i + 1;
      continue;
    }

    // Handle + and percent decoding.
    if (code === CHAR_PLUS) {
      if (lastPos < i) buf += String.prototype.slice.call(qs, lastPos, i);
      buf += " ";
      lastPos = i + 1;
    } else if (!encoded) {
      // Try to match an (valid) encoded byte (once) to minimize unnecessary
      // calls to string decoding functions
      if (code === CHAR_PERCENT) {
        encodeCheck = 1;
      } else if (encodeCheck > 0) {
        if (isHexTable[code] === 1) {
          if (++encodeCheck === 3) {
            encoded = true;
          }
        } else {
          encodeCheck = 0;
        }
      }
    }
  }

  // Deal with any leftover key or value data

  // There is a trailing &. No more processing is needed.
  if (pairStart === i) return out;

  if (lastPos < i) buf += String.prototype.slice.call(qs, lastPos, i);
  if (encoded) buf = querystring.unescape(buf);
  Array.prototype.push.call(out, buf);

  // If `buf` is the key, add an empty value.
  if (!seenSep) Array.prototype.push.call(out, "");

  return out;
}

// Adapted from querystring's implementation.
// Ref: https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
const noEscape = new Int8Array([
  /*
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
*/
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0, // 0x00 - 0x0F
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0, // 0x10 - 0x1F
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  1,
  0,
  0,
  1,
  1,
  0, // 0x20 - 0x2F
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  0,
  0,
  0,
  0,
  0,
  0, // 0x30 - 0x3F
  0,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, // 0x40 - 0x4F
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  0,
  0,
  0,
  0,
  1, // 0x50 - 0x5F
  0,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, // 0x60 - 0x6F
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  0,
  0,
  0,
  0,
  0, // 0x70 - 0x7F
]);

// Special version of hexTable that uses `+` for U+0020 SPACE.
const paramHexTable = hexTable.slice();
paramHexTable[0x20] = "+";

// application/x-www-form-urlencoded serializer
// Ref: https://url.spec.whatwg.org/#concept-urlencoded-serializer
function serializeParams(array) {
  const len = array.length;
  if (len === 0) return "";

  const firstEncodedParam = encodeStr(array[0], noEscape, paramHexTable);
  const firstEncodedValue = encodeStr(array[1], noEscape, paramHexTable);
  let output = `${firstEncodedParam}=${firstEncodedValue}`;

  for (let i = 2; i < len; i += 2) {
    const encodedParam = encodeStr(array[i], noEscape, paramHexTable);
    const encodedValue = encodeStr(array[i + 1], noEscape, paramHexTable);
    output += `&${encodedParam}=${encodedValue}`;
  }

  return output;
}

// for merge sort
function merge(out, start, mid, end, lBuffer, rBuffer) {
  const sizeLeft = mid - start;
  const sizeRight = end - mid;
  let l, r, o;

  for (l = 0; l < sizeLeft; l++) lBuffer[l] = out[start + l];
  for (r = 0; r < sizeRight; r++) rBuffer[r] = out[mid + r];

  l = 0;
  r = 0;
  o = start;
  while (l < sizeLeft && r < sizeRight) {
    if (lBuffer[l] <= rBuffer[r]) {
      out[o++] = lBuffer[l++];
      out[o++] = lBuffer[l++];
    } else {
      out[o++] = rBuffer[r++];
      out[o++] = rBuffer[r++];
    }
  }
  while (l < sizeLeft) out[o++] = lBuffer[l++];
  while (r < sizeRight) out[o++] = rBuffer[r++];
}

function domainToASCII(domain) {
  if (arguments.length < 1) throw new ERR_MISSING_ARGS("domain");

  // StringPrototypeToWellFormed is not needed.
  return bindingUrl.domainToASCII(`${domain}`);
}

function domainToUnicode(domain) {
  if (arguments.length < 1) throw new ERR_MISSING_ARGS("domain");

  // StringPrototypeToWellFormed is not needed.
  return bindingUrl.domainToUnicode(`${domain}`);
}

/**
 * Utility function that converts a URL object into an ordinary options object
 * as expected by the `http.request` and `https.request` APIs.
 * @param {URL} url
 * @returns {Record<string, unknown>}
 */
function urlToHttpOptions(url) {
  const { hostname, pathname, port, username, password, search } = url;
  const options = {
    __proto__: null,
    ...url, // In case the url object was extended by the user.
    protocol: url.protocol,
    hostname:
      hostname && String.prototype.startsWith.call(hostname, "[")
        ? String.prototype.slice.call(hostname, 1, -1)
        : hostname,
    hash: url.hash,
    search: search,
    pathname: pathname,
    path: `${pathname || ""}${search || ""}`,
    href: url.href,
  };
  if (port !== "") {
    options.port = Number(port);
  }
  if (username || password) {
    options.auth = `${decodeURIComponent(username)}:${decodeURIComponent(password)}`;
  }
  return options;
}

function getPathFromURLWin32(url) {
  const hostname = url.hostname;
  let pathname = url.pathname;
  for (let n = 0; n < pathname.length; n++) {
    if (pathname[n] === "%") {
      const third = String.prototype.codePointAt.call(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(
          "must not include encoded \\ or / characters",
        );
      }
    }
  }
  pathname = SideEffectFreeRegExpPrototypeSymbolReplace(
    FORWARD_SLASH,
    pathname,
    "\\",
  );
  pathname = decodeURIComponent(pathname);
  if (hostname !== "") {
    // If hostname is set, then we have a UNC path
    // Pass the hostname through domainToUnicode just in case
    // it is an IDN using punycode encoding. We do not need to worry
    // about percent encoding because the URL parser will have
    // already taken care of that for us. Note that this only
    // causes IDNs with an appropriate `xn--` prefix to be decoded.
    return `\\\\${domainToUnicode(hostname)}${pathname}`;
  }
  // Otherwise, it's a local path that requires a drive letter
  const letter = String.prototype.codePointAt.call(pathname, 1) | 0x20;
  const sep = String.prototype.charAt.call(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 String.prototype.slice.call(pathname, 1);
}

function getPathFromURLPosix(url) {
  if (url.hostname !== "") {
    throw new ERR_INVALID_FILE_URL_HOST(platform);
  }
  const pathname = url.pathname;
  for (let n = 0; n < pathname.length; n++) {
    if (pathname[n] === "%") {
      const third = String.prototype.codePointAt.call(pathname, n + 2) | 0x20;
      if (pathname[n + 1] === "2" && third === 102) {
        throw new ERR_INVALID_FILE_URL_PATH(
          "must not include encoded / characters",
        );
      }
    }
  }
  return decodeURIComponent(pathname);
}

function fileURLToPath(path, options = kEmptyObject) {
  const windows = options?.windows;
  if (typeof path === "string") path = new URL(path);
  else if (!isURL(path))
    throw new ERR_INVALID_ARG_TYPE("path", ["string", "URL"], path);
  if (path.protocol !== "file:") throw new ERR_INVALID_URL_SCHEME("file");
  return (windows ?? isWindows)
    ? getPathFromURLWin32(path)
    : getPathFromURLPosix(path);
}

// The following characters are percent-encoded when converting from file path
// to URL:
// - %: The percent character is the only character not encoded by the
//        `pathname` setter.
// - \: Backslash is encoded on non-windows platforms since it's a valid
//      character but the `pathname` setters replaces it by a forward slash.
// - LF: The newline character is stripped out by the `pathname` setter.
//       (See whatwg/url#419)
// - CR: The carriage return character is also stripped out by the `pathname`
//       setter.
// - TAB: The tab character is also stripped out by the `pathname` setter.
const percentRegEx = /%/g;
const backslashRegEx = /\\/g;
const newlineRegEx = /\n/g;
const carriageReturnRegEx = /\r/g;
const tabRegEx = /\t/g;
const questionRegex = /\?/g;
const hashRegex = /#/g;

function encodePathChars(filepath, options = kEmptyObject) {
  const windows = options?.windows;
  if (String.prototype.indexOf.call(filepath, "%") !== -1)
    filepath = RegExp.prototype[Symbol.replace].call(
      percentRegEx,
      filepath,
      "%25",
    );
  // In posix, backslash is a valid character in paths:
  if (
    !(windows ?? isWindows) &&
    String.prototype.indexOf.call(filepath, "\\") !== -1
  )
    filepath = RegExp.prototype[Symbol.replace].call(
      backslashRegEx,
      filepath,
      "%5C",
    );
  if (String.prototype.indexOf.call(filepath, "\n") !== -1)
    filepath = RegExp.prototype[Symbol.replace].call(
      newlineRegEx,
      filepath,
      "%0A",
    );
  if (String.prototype.indexOf.call(filepath, "\r") !== -1)
    filepath = RegExp.prototype[Symbol.replace].call(
      carriageReturnRegEx,
      filepath,
      "%0D",
    );
  if (String.prototype.indexOf.call(filepath, "\t") !== -1)
    filepath = RegExp.prototype[Symbol.replace].call(tabRegEx, filepath, "%09");
  return filepath;
}

function pathToFileURL(filepath, options = kEmptyObject) {
  const windows = options?.windows;
  if (
    (windows ?? isWindows) &&
    String.prototype.startsWith.call(filepath, "\\\\")
  ) {
    const outURL = new URL("file://");
    // UNC path format: \\server\share\resource
    // Handle extended UNC path and standard UNC path
    // "\\?\UNC\" path prefix should be ignored.
    // Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
    const isExtendedUNC = String.prototype.startsWith.call(
      filepath,
      "\\\\?\\UNC\\",
    );
    const prefixLength = isExtendedUNC ? 8 : 2;
    const hostnameEndIndex = String.prototype.indexOf.call(
      filepath,
      "\\",
      prefixLength,
    );
    if (hostnameEndIndex === -1) {
      throw new ERR_INVALID_ARG_VALUE(
        "path",
        filepath,
        "Missing UNC resource path",
      );
    }
    if (hostnameEndIndex === 2) {
      throw new ERR_INVALID_ARG_VALUE("path", filepath, "Empty UNC servername");
    }
    const hostname = String.prototype.slice.call(
      filepath,
      prefixLength,
      hostnameEndIndex,
    );
    outURL.hostname = domainToASCII(hostname);
    outURL.pathname = encodePathChars(
      RegExp.prototype[Symbol.replace].call(
        backslashRegEx,
        String.prototype.slice.call(filepath, hostnameEndIndex),
        "/",
      ),
      { windows },
    );
    return outURL;
  }
  let resolved =
    (windows ?? isWindows)
      ? path.win32.resolve(filepath)
      : path.posix.resolve(filepath);
  // path.resolve strips trailing slashes so we must add them back
  const filePathLast = String.prototype.charCodeAt.call(
    filepath,
    filepath.length - 1,
  );
  if (
    (filePathLast === CHAR_FORWARD_SLASH ||
      ((windows ?? isWindows) && filePathLast === CHAR_BACKWARD_SLASH)) &&
    resolved[resolved.length - 1] !== path.sep
  )
    resolved += "/";

  // Call encodePathChars first to avoid encoding % again for ? and #.
  resolved = encodePathChars(resolved, { windows });

  // Question and hash character should be included in pathname.
  // Therefore, encoding is required to eliminate parsing them in different states.
  // This is done as an optimization to not creating a URL instance and
  // later triggering pathname setter, which impacts performance
  if (String.prototype.indexOf.call(resolved, "?") !== -1)
    resolved = RegExp.prototype[Symbol.replace].call(
      questionRegex,
      resolved,
      "%3F",
    );
  if (String.prototype.indexOf.call(resolved, "#") !== -1)
    resolved = RegExp.prototype[Symbol.replace].call(
      hashRegex,
      resolved,
      "%23",
    );
  return new URL(`file://${resolved}`);
}

function toPathIfFileURL(fileURLOrPath) {
  if (!isURL(fileURLOrPath)) return fileURLOrPath;
  return fileURLToPath(fileURLOrPath);
}

/**
 * This util takes a string containing a URL and return the URL origin,
 * its meant to avoid calls to `new URL` constructor.
 * @param {string} url
 * @returns {URL['origin']}
 */
function getURLOrigin(url) {
  return bindingUrl.getOrigin(url);
}

export { fileURLToPath };
export { pathToFileURL };
export { toPathIfFileURL };
export { installObjectURLMethods };
export { URL };
export { URLSearchParams };
const _export_URLParse_ = URL.parse;
export { _export_URLParse_ as URLParse };
export { domainToASCII };
export { domainToUnicode };
export { urlToHttpOptions };
export { encodeStr };
export { isURL };
export { updateActions as urlUpdateActions };
export { getURLOrigin };
export { unsafeProtocol };
export { hostlessProtocol };
export { slashedProtocol };