text: // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
  
  ((window) => {
    const core = window.Deno.core;
    const { getRandomValues } = window.__bootstrap.crypto;
    const { customInspect } = window.__bootstrap.console;
    const { isIterable, requiredArguments } = window.__bootstrap.webUtil;
  
    /** https://url.spec.whatwg.org/#idna */
    function domainToAscii(
      domain,
      { beStrict = false } = {},
    ) {
      return core.jsonOpSync("op_domain_to_ascii", { domain, beStrict });
    }
  
    function decodeSearchParam(p) {
      return decodeURIComponent(p.replace(/\+/g, " "));
    }
  
    const urls = new WeakMap();
  
    class URLSearchParams {
      #params = [];
  
      constructor(init = "") {
        if (typeof init === "string") {
          this.#handleStringInitialization(init);
          return;
        }
  
        if (Array.isArray(init) || isIterable(init)) {
          this.#handleArrayInitialization(init);
          return;
        }
  
        if (Object(init) !== init) {
          return;
        }
  
        if (init instanceof URLSearchParams) {
          this.#params = [...init.#params];
          return;
        }
  
        // Overload: record<USVString, USVString>
        for (const key of Object.keys(init)) {
          this.#append(key, init[key]);
        }
  
        urls.set(this, null);
      }
  
      #handleStringInitialization = (init) => {
        // Overload: USVString
        // If init is a string and starts with U+003F (?),
        // remove the first code point from init.
        if (init.charCodeAt(0) === 0x003f) {
          init = init.slice(1);
        }
  
        for (const pair of init.split("&")) {
          // Empty params are ignored
          if (pair.length === 0) {
            continue;
          }
          const position = pair.indexOf("=");
          const name = pair.slice(0, position === -1 ? pair.length : position);
          const value = pair.slice(name.length + 1);
          this.#append(decodeSearchParam(name), decodeSearchParam(value));
        }
      };
  
      #handleArrayInitialization = (
        init,
      ) => {
        // Overload: sequence<sequence<USVString>>
        for (const tuple of init) {
          // If pair does not contain exactly two items, then throw a TypeError.
          if (tuple.length !== 2) {
            throw new TypeError(
              "URLSearchParams.constructor tuple array argument must only contain pair elements",
            );
          }
          this.#append(tuple[0], tuple[1]);
        }
      };
  
      #updateSteps = () => {
        const url = urls.get(this);
        if (url == null) {
          return;
        }
        parts.get(url).query = this.toString();
      };
  
      #append = (name, value) => {
        this.#params.push([String(name), String(value)]);
      };
  
      append(name, value) {
        requiredArguments("URLSearchParams.append", arguments.length, 2);
        this.#append(name, value);
        this.#updateSteps();
      }
  
      delete(name) {
        requiredArguments("URLSearchParams.delete", arguments.length, 1);
        name = String(name);
        let i = 0;
        while (i < this.#params.length) {
          if (this.#params[i][0] === name) {
            this.#params.splice(i, 1);
          } else {
            i++;
          }
        }
        this.#updateSteps();
      }
  
      getAll(name) {
        requiredArguments("URLSearchParams.getAll", arguments.length, 1);
        name = String(name);
        const values = [];
        for (const entry of this.#params) {
          if (entry[0] === name) {
            values.push(entry[1]);
          }
        }
  
        return values;
      }
  
      get(name) {
        requiredArguments("URLSearchParams.get", arguments.length, 1);
        name = String(name);
        for (const entry of this.#params) {
          if (entry[0] === name) {
            return entry[1];
          }
        }
  
        return null;
      }
  
      has(name) {
        requiredArguments("URLSearchParams.has", arguments.length, 1);
        name = String(name);
        return this.#params.some((entry) => entry[0] === name);
      }
  
      set(name, value) {
        requiredArguments("URLSearchParams.set", arguments.length, 2);
  
        // 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.
        name = String(name);
        value = String(value);
        let found = false;
        let i = 0;
        while (i < this.#params.length) {
          if (this.#params[i][0] === name) {
            if (!found) {
              this.#params[i][1] = value;
              found = true;
              i++;
            } else {
              this.#params.splice(i, 1);
            }
          } else {
            i++;
          }
        }
  
        // Otherwise, append a new name-value pair whose name is name
        // and value is value, to list.
        if (!found) {
          this.#append(name, value);
        }
  
        this.#updateSteps();
      }
  
      sort() {
        this.#params.sort((a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1));
        this.#updateSteps();
      }
  
      forEach(
        callbackfn,
        thisArg,
      ) {
        requiredArguments("URLSearchParams.forEach", arguments.length, 1);
  
        if (typeof thisArg !== "undefined") {
          callbackfn = callbackfn.bind(thisArg);
        }
  
        for (const [key, value] of this.#params) {
          callbackfn(value, key, this);
        }
      }
  
      *keys() {
        for (const [key] of this.#params) {
          yield key;
        }
      }
  
      *values() {
        for (const [, value] of this.#params) {
          yield value;
        }
      }
  
      *entries() {
        yield* this.#params;
      }
  
      *[Symbol.iterator]() {
        yield* this.#params;
      }
  
      toString() {
        return this.#params
          .map(
            (tuple) =>
              `${encodeSearchParam(tuple[0])}=${encodeSearchParam(tuple[1])}`,
          )
          .join("&");
      }
    }
  
    const searchParamsMethods = [
      "append",
      "delete",
      "set",
    ];
  
    const specialSchemes = ["ftp", "file", "http", "https", "ws", "wss"];
  
    // https://url.spec.whatwg.org/#special-scheme
    const schemePorts = {
      ftp: "21",
      file: "",
      http: "80",
      https: "443",
      ws: "80",
      wss: "443",
    };
    const MAX_PORT = 2 ** 16 - 1;
  
    // Remove the part of the string that matches the pattern and return the
    // remainder (RHS) as well as the first captured group of the matched substring
    // (LHS). e.g.
    //      takePattern("https://deno.land:80", /^([a-z]+):[/]{2}/)
    //        = ["http", "deno.land:80"]
    //      takePattern("deno.land:80", /^(\[[0-9a-fA-F.:]{2,}\]|[^:]+)/)
    //        = ["deno.land", "80"]
    function takePattern(string, pattern) {
      let capture = "";
      const rest = string.replace(pattern, (_, capture_) => {
        capture = capture_;
        return "";
      });
      return [capture, rest];
    }
  
    function parse(url, baseParts = null) {
      const parts = {};
      let restUrl;
      let usedNonBase = false;
      [parts.protocol, restUrl] = takePattern(
        url.trim(),
        /^([A-Za-z][+-.0-9A-Za-z]*):/,
      );
      parts.protocol = parts.protocol.toLowerCase();
      if (parts.protocol == "") {
        if (baseParts == null) {
          return null;
        }
        parts.protocol = baseParts.protocol;
      } else if (
        parts.protocol != baseParts?.protocol ||
        !specialSchemes.includes(parts.protocol)
      ) {
        usedNonBase = true;
      }
      const isSpecial = specialSchemes.includes(parts.protocol);
      if (parts.protocol == "file") {
        parts.slashes = "//";
        parts.username = "";
        parts.password = "";
        if (usedNonBase || restUrl.match(/^[/\\]{2}/)) {
          [parts.hostname, restUrl] = takePattern(
            restUrl,
            /^[/\\]{2}([^/\\?#]*)/,
          );
          usedNonBase = true;
        } else {
          parts.hostname = baseParts.hostname;
        }
        parts.port = "";
      } else {
        if (usedNonBase || restUrl.match(/^[/\\]{2}/)) {
          let restAuthority;
          if (isSpecial) {
            parts.slashes = "//";
            [restAuthority, restUrl] = takePattern(
              restUrl,
              /^[/\\]*([^/\\?#]*)/,
            );
          } else {
            parts.slashes = restUrl.match(/^[/\\]{2}/) ? "//" : "";
            [restAuthority, restUrl] = takePattern(
              restUrl,
              /^[/\\]{2}([^/\\?#]*)/,
            );
          }
          let restAuthentication;
          [restAuthentication, restAuthority] = takePattern(
            restAuthority,
            /^(.*)@/,
          );
          [parts.username, restAuthentication] = takePattern(
            restAuthentication,
            /^([^:]*)/,
          );
          parts.username = encodeUserinfo(parts.username);
          [parts.password] = takePattern(restAuthentication, /^:(.*)/);
          parts.password = encodeUserinfo(parts.password);
          [parts.hostname, restAuthority] = takePattern(
            restAuthority,
            /^(\[[0-9a-fA-F.:]{2,}\]|[^:]+)/,
          );
          [parts.port] = takePattern(restAuthority, /^:(.*)/);
          if (!isValidPort(parts.port)) {
            return null;
          }
          if (parts.hostname == "" && isSpecial) {
            return null;
          }
          usedNonBase = true;
        } else {
          parts.slashes = baseParts.slashes;
          parts.username = baseParts.username;
          parts.password = baseParts.password;
          parts.hostname = baseParts.hostname;
          parts.port = baseParts.port;
        }
      }
      try {
        parts.hostname = encodeHostname(parts.hostname, isSpecial);
      } catch {
        return null;
      }
      [parts.path, restUrl] = takePattern(restUrl, /^([^?#]*)/);
      parts.path = encodePathname(parts.path.replace(/\\/g, "/"));
      if (usedNonBase) {
        parts.path = normalizePath(parts.path, parts.protocol == "file");
      } else {
        if (parts.path != "") {
          usedNonBase = true;
        }
        parts.path = resolvePathFromBase(
          parts.path,
          baseParts.path || "/",
          baseParts.protocol == "file",
        );
      }
      // Drop the hostname if a drive letter is parsed.
      if (parts.protocol == "file" && parts.path.match(/^\/+[A-Za-z]:(\/|$)/)) {
        parts.hostname = "";
      }
      if (usedNonBase || restUrl.startsWith("?")) {
        [parts.query, restUrl] = takePattern(restUrl, /^(\?[^#]*)/);
        parts.query = encodeSearch(parts.query, isSpecial);
        usedNonBase = true;
      } else {
        parts.query = baseParts.query;
      }
      [parts.hash] = takePattern(restUrl, /^(#.*)/);
      parts.hash = encodeHash(parts.hash);
      return parts;
    }
  
    // Based on https://github.com/kelektiv/node-uuid
    // TODO(kevinkassimo): Use deno_std version once possible.
    function generateUUID() {
      return "00000000-0000-4000-8000-000000000000".replace(/[0]/g, () =>
        // random integer from 0 to 15 as a hex digit.
        (getRandomValues(new Uint8Array(1))[0] % 16).toString(16));
    }
  
    // Keep it outside of URL to avoid any attempts of access.
    const blobURLMap = new Map();
  
    // Resolves `.`s and `..`s where possible.
    // Preserves repeating and trailing `/`s by design.
    // Assumes drive letter file paths will have a leading slash.
    function normalizePath(path, isFilePath) {
      const isAbsolute = path.startsWith("/");
      path = path.replace(/^\//, "");
      const pathSegments = path.split("/");
  
      let driveLetter = null;
      if (isFilePath && pathSegments[0].match(/^[A-Za-z]:$/)) {
        driveLetter = pathSegments.shift();
      }
  
      if (isFilePath && isAbsolute) {
        while (pathSegments.length > 1 && pathSegments[0] == "") {
          pathSegments.shift();
        }
      }
  
      let ensureTrailingSlash = false;
      const newPathSegments = [];
      for (let i = 0; i < pathSegments.length; i++) {
        const previous = newPathSegments[newPathSegments.length - 1];
        if (
          pathSegments[i] == ".." &&
          previous != ".." &&
          (previous != undefined || isAbsolute)
        ) {
          newPathSegments.pop();
          ensureTrailingSlash = true;
        } else if (pathSegments[i] == ".") {
          ensureTrailingSlash = true;
        } else {
          newPathSegments.push(pathSegments[i]);
          ensureTrailingSlash = false;
        }
      }
      if (driveLetter != null) {
        newPathSegments.unshift(driveLetter);
      }
      if (newPathSegments.length == 0 && !isAbsolute) {
        newPathSegments.push(".");
        ensureTrailingSlash = false;
      }
  
      let newPath = newPathSegments.join("/");
      if (isAbsolute) {
        newPath = `/${newPath}`;
      }
      if (ensureTrailingSlash) {
        newPath = newPath.replace(/\/*$/, "/");
      }
      return newPath;
    }
  
    // Standard URL basing logic, applied to paths.
    function resolvePathFromBase(path, basePath, isFilePath) {
      let basePrefix;
      let suffix;
      const baseDriveLetter = basePath.match(/^\/+[A-Za-z]:(?=\/|$)/)?.[0];
      if (isFilePath && path.match(/^\/+[A-Za-z]:(\/|$)/)) {
        basePrefix = "";
        suffix = path;
      } else if (path.startsWith("/")) {
        if (isFilePath && baseDriveLetter) {
          basePrefix = baseDriveLetter;
          suffix = path;
        } else {
          basePrefix = "";
          suffix = path;
        }
      } else if (path != "") {
        basePath = normalizePath(basePath, isFilePath);
        path = normalizePath(path, isFilePath);
        // Remove everything after the last `/` in `basePath`.
        if (baseDriveLetter && isFilePath) {
          basePrefix = `${baseDriveLetter}${
            basePath.slice(baseDriveLetter.length).replace(/[^\/]*$/, "")
          }`;
        } else {
          basePrefix = basePath.replace(/[^\/]*$/, "");
        }
        basePrefix = basePrefix.replace(/\/*$/, "/");
        // If `normalizedPath` ends with `.` or `..`, add a trailing slash.
        suffix = path.replace(/(?<=(^|\/)(\.|\.\.))$/, "/");
      } else {
        basePrefix = basePath;
        suffix = "";
      }
      return normalizePath(basePrefix + suffix, isFilePath);
    }
  
    function isValidPort(value) {
      // https://url.spec.whatwg.org/#port-state
      if (value === "") return true;
  
      const port = Number(value);
      return Number.isInteger(port) && port >= 0 && port <= MAX_PORT;
    }
  
    const parts = new WeakMap();
  
    class URL {
      #searchParams = null;
  
      [customInspect]() {
        const keys = [
          "href",
          "origin",
          "protocol",
          "username",
          "password",
          "host",
          "hostname",
          "port",
          "pathname",
          "hash",
          "search",
        ];
        const objectString = keys
          .map((key) => `${key}: "${this[key] || ""}"`)
          .join(", ");
        return `URL { ${objectString} }`;
      }
  
      #updateSearchParams = () => {
        const searchParams = new URLSearchParams(this.search);
  
        for (const methodName of searchParamsMethods) {
          const method = searchParams[methodName];
          searchParams[methodName] = (...args) => {
            method.apply(searchParams, args);
            this.search = searchParams.toString();
          };
        }
        this.#searchParams = searchParams;
  
        urls.set(searchParams, this);
      };
  
      get hash() {
        return parts.get(this).hash;
      }
  
      set hash(value) {
        value = unescape(String(value));
        if (!value) {
          parts.get(this).hash = "";
        } else {
          if (value.charAt(0) !== "#") {
            value = `#${value}`;
          }
          // hashes can contain % and # unescaped
          parts.get(this).hash = encodeHash(value);
        }
      }
  
      get host() {
        return `${this.hostname}${this.port ? `:${this.port}` : ""}`;
      }
  
      set host(value) {
        value = String(value);
        const url = new URL(`http://${value}`);
        parts.get(this).hostname = url.hostname;
        parts.get(this).port = url.port;
      }
  
      get hostname() {
        return parts.get(this).hostname;
      }
  
      set hostname(value) {
        value = String(value);
        try {
          const isSpecial = specialSchemes.includes(parts.get(this).protocol);
          parts.get(this).hostname = encodeHostname(value, isSpecial);
        } catch {}
      }
  
      get href() {
        const authentication = this.username || this.password
          ? `${this.username}${this.password ? ":" + this.password : ""}@`
          : "";
        const host = this.host;
        const slashes = host ? "//" : parts.get(this).slashes;
        let pathname = this.pathname;
        if (pathname.charAt(0) != "/" && pathname != "" && host != "") {
          pathname = `/${pathname}`;
        }
        return `${this.protocol}${slashes}${authentication}${host}${pathname}${this.search}${this.hash}`;
      }
  
      set href(value) {
        value = String(value);
        if (value !== this.href) {
          const url = new URL(value);
          parts.set(this, { ...parts.get(url) });
          this.#updateSearchParams();
        }
      }
  
      get origin() {
        if (this.host) {
          return `${this.protocol}//${this.host}`;
        }
        return "null";
      }
  
      get password() {
        return parts.get(this).password;
      }
  
      set password(value) {
        value = String(value);
        parts.get(this).password = encodeUserinfo(value);
      }
  
      get pathname() {
        let path = parts.get(this).path;
        if (specialSchemes.includes(parts.get(this).protocol)) {
          if (path.charAt(0) != "/") {
            path = `/${path}`;
          }
        }
        return path;
      }
  
      set pathname(value) {
        parts.get(this).path = encodePathname(String(value));
      }
  
      get port() {
        const port = parts.get(this).port;
        if (schemePorts[parts.get(this).protocol] === port) {
          return "";
        }
  
        return port;
      }
  
      set port(value) {
        if (!isValidPort(value)) {
          return;
        }
        parts.get(this).port = value.toString();
      }
  
      get protocol() {
        return `${parts.get(this).protocol}:`;
      }
  
      set protocol(value) {
        value = String(value);
        if (value) {
          if (value.charAt(value.length - 1) === ":") {
            value = value.slice(0, -1);
          }
          parts.get(this).protocol = encodeURIComponent(value);
        }
      }
  
      get search() {
        return parts.get(this).query;
      }
  
      set search(value) {
        value = String(value);
        const query = value == "" || value.charAt(0) == "?" ? value : `?${value}`;
        const isSpecial = specialSchemes.includes(parts.get(this).protocol);
        parts.get(this).query = encodeSearch(query, isSpecial);
        this.#updateSearchParams();
      }
  
      get username() {
        return parts.get(this).username;
      }
  
      set username(value) {
        value = String(value);
        parts.get(this).username = encodeUserinfo(value);
      }
  
      get searchParams() {
        return this.#searchParams;
      }
  
      constructor(url, base) {
        let baseParts = null;
        new.target;
        if (base) {
          baseParts = base instanceof URL ? parts.get(base) : parse(base);
          if (baseParts == null) {
            throw new TypeError("Invalid base URL.");
          }
        }
  
        const urlParts = url instanceof URL
          ? parts.get(url)
          : parse(url, baseParts);
        if (urlParts == null) {
          throw new TypeError("Invalid URL.");
        }
        parts.set(this, urlParts);
  
        this.#updateSearchParams();
      }
  
      toString() {
        return this.href;
      }
  
      toJSON() {
        return this.href;
      }
  
      // TODO(kevinkassimo): implement MediaSource version in the future.
      static createObjectURL(blob) {
        const origin = "http://deno-opaque-origin";
        const key = `blob:${origin}/${generateUUID()}`;
        blobURLMap.set(key, blob);
        return key;
      }
  
      static revokeObjectURL(url) {
        let urlObject;
        try {
          urlObject = new URL(url);
        } catch {
          throw new TypeError("Provided URL string is not valid");
        }
        if (urlObject.protocol !== "blob:") {
          return;
        }
        // Origin match check seems irrelevant for now, unless we implement
        // persisten storage for per globalThis.location.origin at some point.
        blobURLMap.delete(url);
      }
    }
  
    function parseIpv4Number(s) {
      if (s.match(/^(0[Xx])[0-9A-Za-z]+$/)) {
        return Number(s);
      }
      if (s.match(/^[0-9]+$/)) {
        return Number(s.startsWith("0") ? `0o${s}` : s);
      }
      return NaN;
    }
  
    function parseIpv4(s) {
      const parts = s.split(".");
      if (parts[parts.length - 1] == "" && parts.length > 1) {
        parts.pop();
      }
      if (parts.includes("") || parts.length > 4) {
        return s;
      }
      const numbers = parts.map(parseIpv4Number);
      if (numbers.includes(NaN)) {
        return s;
      }
      const last = numbers.pop();
      if (last >= 256 ** (4 - numbers.length) || numbers.find((n) => n >= 256)) {
        throw new TypeError("Invalid hostname.");
      }
      const ipv4 = numbers.reduce((sum, n, i) => sum + n * 256 ** (3 - i), last);
      const ipv4Hex = ipv4.toString(16).padStart(8, "0");
      const ipv4HexParts = ipv4Hex.match(/(..)(..)(..)(..)$/).slice(1);
      return ipv4HexParts.map((s) => String(Number(`0x${s}`))).join(".");
    }
  
    function charInC0ControlSet(c) {
      return (c >= "\u0000" && c <= "\u001F") || c > "\u007E";
    }
  
    function charInSearchSet(c, isSpecial) {
      // deno-fmt-ignore
      return charInC0ControlSet(c) || ["\u0020", "\u0022", "\u0023", "\u003C", "\u003E"].includes(c) || isSpecial && c == "\u0027" || c > "\u007E";
    }
  
    function charInFragmentSet(c) {
      // deno-fmt-ignore
      return charInC0ControlSet(c) || ["\u0020", "\u0022", "\u003C", "\u003E", "\u0060"].includes(c);
    }
  
    function charInPathSet(c) {
      // deno-fmt-ignore
      return charInFragmentSet(c) || ["\u0023", "\u003F", "\u007B", "\u007D"].includes(c);
    }
  
    function charInUserinfoSet(c) {
      // "\u0027" ("'") seemingly isn't in the spec, but matches Chrome and Firefox.
      // deno-fmt-ignore
      return charInPathSet(c) || ["\u0027", "\u002F", "\u003A", "\u003B", "\u003D", "\u0040", "\u005B", "\u005C", "\u005D", "\u005E", "\u007C"].includes(c);
    }
  
    function charIsForbiddenInHost(c) {
      // deno-fmt-ignore
      return ["\u0000", "\u0009", "\u000A", "\u000D", "\u0020", "\u0023", "\u0025", "\u002F", "\u003A", "\u003C", "\u003E", "\u003F", "\u0040", "\u005B", "\u005C", "\u005D", "\u005E"].includes(c);
    }
  
    function charInFormUrlencodedSet(c) {
      // deno-fmt-ignore
      return charInUserinfoSet(c) || ["\u0021", "\u0024", "\u0025", "\u0026", "\u0027", "\u0028", "\u0029", "\u002B", "\u002C", "\u007E"].includes(c);
    }
  
    const encoder = new TextEncoder();
  
    function encodeChar(c) {
      return [...encoder.encode(c)]
        .map((n) => `%${n.toString(16)}`)
        .join("")
        .toUpperCase();
    }
  
    function encodeUserinfo(s) {
      return [...s].map((c) => (charInUserinfoSet(c) ? encodeChar(c) : c)).join(
        "",
      );
    }
  
    function encodeHostname(s, isSpecial = true) {
      // IPv6 parsing.
      if (s.startsWith("[") && s.endsWith("]")) {
        if (!s.match(/^\[[0-9A-Fa-f.:]{2,}\]$/)) {
          throw new TypeError("Invalid hostname.");
        }
        // IPv6 address compress
        return s.toLowerCase().replace(/\b:?(?:0+:?){2,}/, "::");
      }
  
      let result = s;
  
      if (!isSpecial) {
        // Check against forbidden host code points except for "%".
        for (const c of result) {
          if (charIsForbiddenInHost(c) && c != "\u0025") {
            throw new TypeError("Invalid hostname.");
          }
        }
  
        // Percent-encode C0 control set.
        result = [...result]
          .map((c) => (charInC0ControlSet(c) ? encodeChar(c) : c))
          .join("");
  
        return result;
      }
  
      // Percent-decode.
      if (result.match(/%(?![0-9A-Fa-f]{2})/) != null) {
        throw new TypeError("Invalid hostname.");
      }
      result = result.replace(
        /%(.{2})/g,
        (_, hex) => String.fromCodePoint(Number(`0x${hex}`)),
      );
  
      // IDNA domain to ASCII.
      result = domainToAscii(result);
  
      // Check against forbidden host code points.
      for (const c of result) {
        if (charIsForbiddenInHost(c)) {
          throw new TypeError("Invalid hostname.");
        }
      }
  
      // IPv4 parsing.
      if (isSpecial) {
        result = parseIpv4(result);
      }
  
      return result;
    }
  
    function encodePathname(s) {
      return [...s].map((c) => (charInPathSet(c) ? encodeChar(c) : c)).join("");
    }
  
    function encodeSearch(s, isSpecial) {
      return [...s].map((
        c,
      ) => (charInSearchSet(c, isSpecial) ? encodeChar(c) : c)).join("");
    }
  
    function encodeHash(s) {
      return [...s].map((c) => (charInFragmentSet(c) ? encodeChar(c) : c)).join(
        "",
      );
    }
  
    function encodeSearchParam(s) {
      return [...s].map((c) => (charInFormUrlencodedSet(c) ? encodeChar(c) : c))
        .join("").replace(/%20/g, "+");
    }
  
    window.__bootstrap.url = {
      URL,
      URLSearchParams,
      blobURLMap,
    };
  })(this);
  