/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/web/00_url.js
1 259 строк
35 KB
Bartek Iwańczuk
chore: bump node_compat test suite to Node.js 26.5.1 (#36391)
03 авг 2026, 12:52
Не верифицирован
03 авг 2026, 12:52
bca6182
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. // @ts-check /// <reference path="../../core/internal.d.ts" /> /// <reference path="../../core/lib.deno_core.d.ts" /> /// <reference path="../webidl/internal.d.ts" /> (function () { const { core, primordials } = __bootstrap; const { op_url_get_serialization, op_url_parse, op_url_parse_search_params, op_url_parse_with_base, op_url_reparse, op_url_stringify_search_params, } = core.ops; const { ArrayFrom, ArrayPrototypeJoin, ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeSome, ArrayPrototypeSort, ArrayPrototypeSplice, ObjectGetOwnPropertyDescriptor, ObjectKeys, ObjectPrototypeIsPrototypeOf, ReflectOwnKeys, SafeArrayIterator, StringPrototypeCharCodeAt, StringPrototypeSlice, StringPrototypeStartsWith, Symbol, SymbolFor, SymbolIterator, TypeError, Uint32Array, } = primordials; const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js"); const { createFilteredInspectProxy } = core.loadExtScript( "ext:deno_web/01_console.js", ); const { markNotSerializable } = core.loadExtScript( "ext:deno_web/13_message_port.js", ); const _list = Symbol("list"); const _urlObject = Symbol("url object"); // Pre-frozen argument-name arrays used to produce Node-compatible // `ERR_MISSING_ARGS` messages from `webidl.requiredArguments`. const NAME_ARG_NAMES = ["name"]; const APPEND_ARG_NAMES = ["name", "value"]; // WARNING: must match rust code's UrlSetter::* const SET_HASH = 0; const SET_HOST = 1; const SET_HOSTNAME = 2; const SET_PASSWORD = 3; const SET_PATHNAME = 4; const SET_PORT = 5; const SET_PROTOCOL = 6; const SET_SEARCH = 7; const SET_USERNAME = 8; // Helper functions /** * @param {string} href * @param {number} setter * @param {string} value * @returns {string} */ function opUrlReparse(href, setter, value) { const status = op_url_reparse( href, setter, value, componentsBuf, ); return getSerialization(status, href); } /** * @param {string} href * @param {string} [maybeBase] * @returns {number} */ function opUrlParse(href, maybeBase) { if (maybeBase === undefined) { return op_url_parse(href, componentsBuf); } return op_url_parse_with_base( href, maybeBase, componentsBuf, ); } /** * @param {number} status * @param {string} href * @param {string} [maybeBase] * @returns {string} */ function getSerialization(status, href, maybeBase) { if (status === 0) { return href; } else if (status === 1) { return op_url_get_serialization(); } else { throw new TypeError( `Invalid URL: '${href}'` + (maybeBase ? ` with base '${maybeBase}'` : ""), ); } } class URLSearchParams { [_list]; [_urlObject] = null; /** * @param {string | [string][] | Record<string, string>} init */ constructor(init = undefined) { this[webidl.brand] = webidl.brand; // `undefined` is the default value of an optional argument, so it means // "not passed". `null` is a value, and per WebIDL union resolution it // reaches the USVString overload as "null". if (init === undefined) { this[_list] = []; return; } if ( init !== null && (typeof init === "object" || typeof init === "function") ) { // Object overloads: either iterable of pairs or a record. const method = init[SymbolIterator]; if (method !== undefined && method !== null) { if (typeof method !== "function") { const err = new TypeError("Query pairs must be iterable"); err.code = "ERR_ARG_NOT_ITERABLE"; throw err; } // Sequence<sequence<USVString>> const pairs = []; // deno-lint-ignore deno-internal/prefer-primordials const iter = method.call(init); if (iter == null || typeof iter.next !== "function") { const err = new TypeError( "Each query pair must be an iterable [name, value] tuple", ); err.code = "ERR_INVALID_TUPLE"; throw err; } while (true) { // deno-lint-ignore deno-internal/prefer-primordials const res = iter.next(); if (res == null) { const err = new TypeError( "Each query pair must be an iterable [name, value] tuple", ); err.code = "ERR_INVALID_TUPLE"; throw err; } if (res.done === true) break; const pair = res.value; if ( (typeof pair !== "object" && typeof pair !== "function") || pair === null || typeof pair[SymbolIterator] !== "function" ) { const err = new TypeError( "Each query pair must be an iterable [name, value] tuple", ); err.code = "ERR_INVALID_TUPLE"; throw err; } const entry = []; for (const v of new SafeArrayIterator(ArrayFrom(pair))) { ArrayPrototypePush( entry, webidl.converters.USVString(v, undefined, undefined), ); } if (entry.length !== 2) { const err = new TypeError( "Each query pair must be an iterable [name, value] tuple", ); err.code = "ERR_INVALID_TUPLE"; throw err; } ArrayPrototypePush(pairs, entry); } this[_list] = pairs; return; } // Record<USVString, USVString>. We iterate own enumerable keys // (including Symbol keys so USVString coercion throws on them, like // Node does) and dedupe by the USVString-coerced name so that two keys // collapsing to U+FFFD overwrite each other instead of appearing twice // in the iterator output. const result = { __proto__: null }; const allKeys = ReflectOwnKeys(init); for (let i = 0; i < allKeys.length; i++) { const key = allKeys[i]; const desc = ObjectGetOwnPropertyDescriptor(init, key); if (desc !== undefined && desc.enumerable === true) { const name = webidl.converters.USVString(key, undefined, undefined); const value = webidl.converters.USVString( init[key], undefined, undefined, ); result[name] = value; } } const list = []; const resultKeys = ObjectKeys(result); for (let i = 0; i < resultKeys.length; i++) { ArrayPrototypePush(list, [resultKeys[i], result[resultKeys[i]]]); } this[_list] = list; return; } // USVString overload. let str = webidl.converters.USVString(init, undefined, undefined); if (str.length === 0) { this[_list] = []; return; } if (str[0] === "?") { str = StringPrototypeSlice(str, 1); } this[_list] = op_url_parse_search_params(str); } #updateUrlSearch() { const url = this[_urlObject]; if (url === null) { return; } // deno-lint-ignore deno-internal/prefer-primordials url[_updateUrlSearch](this.toString()); } /** * @param {string} name * @param {string} value */ append(name, value) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); const prefix = "Failed to execute 'append' on 'URLSearchParams'"; webidl.requiredArguments(arguments.length, 2, prefix, APPEND_ARG_NAMES); name = webidl.converters.USVString(name, prefix, "Argument 1"); value = webidl.converters.USVString(value, prefix, "Argument 2"); ArrayPrototypePush(this[_list], [name, value]); this.#updateUrlSearch(); } /** * @param {string} name * @param {string} [value] */ delete(name, value = undefined) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); const prefix = "Failed to execute 'append' on 'URLSearchParams'"; webidl.requiredArguments(arguments.length, 1, prefix, NAME_ARG_NAMES); name = webidl.converters.USVString(name, prefix, "Argument 1"); const list = this[_list]; let writeIdx = 0; if (value === undefined) { for (let i = 0; i < list.length; i++) { if (list[i][0] !== name) { list[writeIdx++] = list[i]; } } } else { value = webidl.converters.USVString(value, prefix, "Argument 2"); for (let i = 0; i < list.length; i++) { const entry = list[i]; if (entry[0] !== name || entry[1] !== value) { list[writeIdx++] = entry; } } } if (writeIdx !== list.length) { ArrayPrototypeSplice(list, writeIdx); } this.#updateUrlSearch(); } /** * @param {string} name * @returns {string[]} */ getAll(name) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); const prefix = "Failed to execute 'getAll' on 'URLSearchParams'"; webidl.requiredArguments(arguments.length, 1, prefix, NAME_ARG_NAMES); name = webidl.converters.USVString(name, prefix, "Argument 1"); const values = []; const entries = this[_list]; for (let i = 0; i < entries.length; ++i) { const entry = entries[i]; if (entry[0] === name) { ArrayPrototypePush(values, entry[1]); } } return values; } /** * @param {string} name * @return {string | null} */ get(name) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); const prefix = "Failed to execute 'get' on 'URLSearchParams'"; webidl.requiredArguments(arguments.length, 1, prefix, NAME_ARG_NAMES); name = webidl.converters.USVString(name, prefix, "Argument 1"); const entries = this[_list]; for (let i = 0; i < entries.length; ++i) { const entry = entries[i]; if (entry[0] === name) { return entry[1]; } } return null; } /** * @param {string} name * @param {string} [value] * @return {boolean} */ has(name, value = undefined) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); const prefix = "Failed to execute 'has' on 'URLSearchParams'"; webidl.requiredArguments(arguments.length, 1, prefix, NAME_ARG_NAMES); name = webidl.converters.USVString(name, prefix, "Argument 1"); if (value !== undefined) { value = webidl.converters.USVString(value, prefix, "Argument 2"); return ArrayPrototypeSome( this[_list], (entry) => entry[0] === name && entry[1] === value, ); } return ArrayPrototypeSome(this[_list], (entry) => entry[0] === name); } /** * @param {string} name * @param {string} value */ set(name, value) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); const prefix = "Failed to execute 'set' on 'URLSearchParams'"; webidl.requiredArguments(arguments.length, 2, prefix, APPEND_ARG_NAMES); name = webidl.converters.USVString(name, prefix, "Argument 1"); value = webidl.converters.USVString(value, prefix, "Argument 2"); const list = this[_list]; // 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 writeIdx = 0; let found = false; for (let i = 0; i < list.length; i++) { const entry = list[i]; if (entry[0] === name) { if (!found) { entry[1] = value; list[writeIdx++] = entry; found = true; } } else { list[writeIdx++] = entry; } } // Otherwise, append a new name-value pair whose name is name // and value is value, to list. if (!found) { ArrayPrototypePush(list, [name, value]); } else if (writeIdx !== list.length) { ArrayPrototypeSplice(list, writeIdx); } this.#updateUrlSearch(); } sort() { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); ArrayPrototypeSort( this[_list], (a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1), ); this.#updateUrlSearch(); } /** * @return {string} */ toString() { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); return op_url_stringify_search_params(this[_list]); } get size() { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); return this[_list].length; } // Node exposes this as a method on URLSearchParams.prototype so that // `Object.getOwnPropertyDescriptor(URLSearchParams.prototype, // Symbol.for("nodejs.util.inspect.custom"))` is not undefined. We delegate // to Deno's `privateCustomInspect` to preserve the Map-like formatting // (`URLSearchParams { 'a' => 'b' }`) that node compat tests expect. [SymbolFor("nodejs.util.inspect.custom")](_depth, inspectOptions, inspect) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); return this[SymbolFor("Deno.privateCustomInspect")]( inspect, inspectOptions, ); } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { webidl.assertBranded(this, URLSearchParamsPrototype, "URLSearchParams"); if ( typeof inspectOptions?.depth === "number" && inspectOptions.depth < 0 ) { return "[Object]"; } const entries = this[_list]; if (entries.length === 0) return "URLSearchParams {}"; const pairs = ArrayPrototypeMap( entries, (e) => `${inspect(e[0], inspectOptions)} => ${inspect(e[1], inspectOptions)}`, ); const inlined = ArrayPrototypeJoin(pairs, ", "); const breakLength = inspectOptions?.breakLength; const oneLine = `URLSearchParams { ${inlined} }`; if (typeof breakLength === "number" && oneLine.length > breakLength) { return `URLSearchParams {\n ${ArrayPrototypeJoin(pairs, ",\n ")} }`; } return oneLine; } } webidl.mixinPairIterable("URLSearchParams", URLSearchParams, _list, 0, 1); webidl.configureInterface(URLSearchParams); const URLSearchParamsPrototype = URLSearchParams.prototype; markNotSerializable(URLSearchParamsPrototype); webidl.converters["URLSearchParams"] = webidl.createInterfaceConverter( "URLSearchParams", URLSearchParamsPrototype, ); const _updateUrlSearch = Symbol("updateUrlSearch"); function trim(s) { if (s.length === 1) return ""; return s; } // Represents a "no port" value. A port in URL cannot be greater than 2^16 - 1 const NO_PORT = 65536; const skipInit = Symbol(); const componentsBuf = new Uint32Array(8); function isAsciiLowerAlpha(code) { return code >= 0x61 && code <= 0x7a; } function isAsciiUpperAlpha(code) { return code >= 0x41 && code <= 0x5a; } function isAsciiDigit(code) { return code >= 0x30 && code <= 0x39; } function isSimpleSpecialHostCanonical(host, start, end) { if (start === end) return false; if ( StringPrototypeCharCodeAt(host, start) === 0x2e || StringPrototypeCharCodeAt(host, end - 1) === 0x2e ) { return false; } let allNumeric = true; for (let i = start; i < end; i++) { const code = StringPrototypeCharCodeAt(host, i); if (!isAsciiDigit(code) && code !== 0x2e) { allNumeric = false; break; } } if (allNumeric) { let dots = 0; let part = 0; let partLen = 0; for (let i = start; i < end; i++) { const code = StringPrototypeCharCodeAt(host, i); if (code === 0x2e) { if ( partLen === 0 || part > 255 || (partLen > 1 && StringPrototypeCharCodeAt(host, i - partLen) === 0x30) ) { return false; } dots++; part = 0; partLen = 0; continue; } part = part * 10 + code - 0x30; partLen++; if (partLen > 3) return false; } return dots === 3 && partLen !== 0 && part <= 255 && (partLen === 1 || StringPrototypeCharCodeAt(host, end - partLen) !== 0x30); } if (isAsciiDigit(StringPrototypeCharCodeAt(host, start))) { return false; } let labelStart = start; let labelLen = 0; let finalLabelAllDigits = true; for (let i = start; i < end; i++) { const code = StringPrototypeCharCodeAt(host, i); if ( !isAsciiLowerAlpha(code) && !isAsciiDigit(code) && code !== 0x2e && code !== 0x2d ) { return false; } if (code === 0x2e) { if (labelLen === 0) return false; labelStart = i + 1; labelLen = 0; finalLabelAllDigits = true; continue; } if (!isAsciiDigit(code)) { finalLabelAllDigits = false; } labelLen++; if ( i === labelStart + 3 && StringPrototypeCharCodeAt(host, labelStart) === 0x78 && StringPrototypeCharCodeAt(host, labelStart + 1) === 0x6e && StringPrototypeCharCodeAt(host, labelStart + 2) === 0x2d && code === 0x2d ) { return false; } } return labelLen !== 0 && !finalLabelAllDigits && !(labelLen >= 2 && StringPrototypeCharCodeAt(host, labelStart) === 0x30 && StringPrototypeCharCodeAt(host, labelStart + 1) === 0x78); } function isSimpleSpecialPathCode(code) { return isAsciiLowerAlpha(code) || isAsciiUpperAlpha(code) || isAsciiDigit(code) || code === 0x2f || code === 0x2e || code === 0x5f || code === 0x7e || code === 0x2d || code === 0x21 || code === 0x24 || code === 0x26 || code === 0x27 || code === 0x28 || code === 0x29 || code === 0x2a || code === 0x2b || code === 0x2c || code === 0x3b || code === 0x3d || code === 0x3a || code === 0x40; } // Keep in sync with parse_simple_special_url() in ext/web/url.rs. function parseSimpleSpecialUrl(href) { let schemeEnd; let defaultPort; if (StringPrototypeStartsWith(href, "http://")) { schemeEnd = 4; defaultPort = 80; } else if (StringPrototypeStartsWith(href, "https://")) { schemeEnd = 5; defaultPort = 443; } else { return false; } const hostStart = schemeEnd + 3; const len = href.length; let pathStart = hostStart; while (pathStart < len) { const code = StringPrototypeCharCodeAt(href, pathStart); if (code === 0x2f) break; if ( isAsciiLowerAlpha(code) || isAsciiDigit(code) || code === 0x2e || code === 0x2d || code === 0x3a ) { pathStart++; continue; } return false; } if (pathStart === hostStart || pathStart === len) return false; let hostEnd = pathStart; let port = NO_PORT; for (let i = hostStart; i < pathStart; i++) { if (StringPrototypeCharCodeAt(href, i) !== 0x3a) continue; if (i === hostStart || i + 1 === pathStart) return false; hostEnd = i; port = 0; if ( i + 2 < pathStart && StringPrototypeCharCodeAt(href, i + 1) === 0x30 ) { return false; } for (let j = i + 1; j < pathStart; j++) { const code = StringPrototypeCharCodeAt(href, j); if (!isAsciiDigit(code)) return false; port = port * 10 + code - 0x30; if (port > 65535) return false; } if (port === defaultPort) return false; break; } if (!isSimpleSpecialHostCanonical(href, hostStart, hostEnd)) { return false; } let queryStart = 0; for (let i = pathStart; i < len; i++) { const code = StringPrototypeCharCodeAt(href, i); if ( code === 0x2e && i > pathStart && StringPrototypeCharCodeAt(href, i - 1) === 0x2f && (i + 1 === len || StringPrototypeCharCodeAt(href, i + 1) === 0x2f || StringPrototypeCharCodeAt(href, i + 1) === 0x3f || StringPrototypeCharCodeAt(href, i + 1) === 0x2e) ) { return false; } if (queryStart !== 0 && code === 0x27) return false; if (isSimpleSpecialPathCode(code)) continue; if (code === 0x3f && queryStart === 0) { queryStart = i; continue; } return false; } componentsBuf[0] = schemeEnd; componentsBuf[1] = hostStart; componentsBuf[2] = hostStart; componentsBuf[3] = hostEnd; componentsBuf[4] = port; componentsBuf[5] = pathStart; componentsBuf[6] = queryStart; componentsBuf[7] = 0; return true; } class URL { /** @type {URLSearchParams|null} */ #queryObject = null; /** @type {string} */ #serialization; /** @type {number} */ #schemeEnd; /** @type {number} */ #usernameEnd; /** @type {number} */ #hostStart; /** @type {number} */ #hostEnd; /** @type {number} */ #port; /** @type {number} */ #pathStart; /** @type {number} */ #queryStart; /** @type {number} */ #fragmentStart; [_updateUrlSearch](value) { this.#serialization = opUrlReparse( this.#serialization, SET_SEARCH, value, ); this.#updateComponents(); } /** * @param {string} url * @param {string} [base] */ constructor(url, base = undefined) { // skip initialization for URL.parse if (url === skipInit) { return; } const prefix = "Failed to construct 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); url = webidl.converters.DOMString(url, prefix, "Argument 1"); if (base !== undefined) { base = webidl.converters.DOMString(base, prefix, "Argument 2"); } this[webidl.brand] = webidl.brand; if (base === undefined && parseSimpleSpecialUrl(url)) { this.#serialization = url; } else { const status = opUrlParse(url, base); this.#serialization = getSerialization(status, url, base); } this.#updateComponents(); } /** * @param {string} url * @param {string} [base] */ static parse(url, base = undefined) { const prefix = "Failed to execute 'URL.parse'"; webidl.requiredArguments(arguments.length, 1, prefix); url = webidl.converters.DOMString(url, prefix, "Argument 1"); if (base !== undefined) { base = webidl.converters.DOMString(base, prefix, "Argument 2"); } const status = opUrlParse(url, base); if (status !== 0 && status !== 1) { return null; } // If initialized with webidl.createBranded, private properties are not be accessible, // so it is passed through the constructor const self = new this(skipInit); self[webidl.brand] = webidl.brand; self.#serialization = getSerialization(status, url, base); self.#updateComponents(); return self; } /** * @param {string} url * @param {string} [base] */ static canParse(url, base = undefined) { const prefix = "Failed to execute 'URL.canParse'"; webidl.requiredArguments(arguments.length, 1, prefix); url = webidl.converters.DOMString(url, prefix, "Argument 1"); if (base !== undefined) { base = webidl.converters.DOMString(base, prefix, "Argument 2"); } const status = opUrlParse(url, base); return status === 0 || status === 1; } #updateComponents() { ({ 0: this.#schemeEnd, 1: this.#usernameEnd, 2: this.#hostStart, 3: this.#hostEnd, 4: this.#port, 5: this.#pathStart, 6: this.#queryStart, 7: this.#fragmentStart, } = componentsBuf); } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(URLPrototype, this), keys: [ "href", "origin", "protocol", "username", "password", "host", "hostname", "port", "pathname", "hash", "search", ], }), inspectOptions, ); } // See URLSearchParams: Node exposes this as a method so that the descriptor // lookup is not undefined. Deno's own inspector still prefers // Deno.privateCustomInspect, so this is effectively the same code path. [SymbolFor("nodejs.util.inspect.custom")](_depth, inspectOptions, inspect) { return this[SymbolFor("Deno.privateCustomInspect")]( inspect, inspectOptions, ); } #updateSearchParams() { if (this.#queryObject !== null) { const params = this.#queryObject[_list]; const newParams = op_url_parse_search_params( StringPrototypeSlice(this.search, 1), ); ArrayPrototypeSplice( params, 0, params.length, ...new SafeArrayIterator(newParams), ); } } #hasAuthority() { // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L824 return StringPrototypeStartsWith( StringPrototypeSlice(this.#serialization, this.#schemeEnd), "://", ); } /** @return {string} */ get hash() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L263 return this.#fragmentStart ? trim(StringPrototypeSlice(this.#serialization, this.#fragmentStart)) : ""; } /** @param {string} value */ set hash(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'hash' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_HASH, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get host() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L101 return StringPrototypeSlice( this.#serialization, this.#hostStart, this.#pathStart, ); } /** @param {string} value */ set host(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'host' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_HOST, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get hostname() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L988 return StringPrototypeSlice( this.#serialization, this.#hostStart, this.#hostEnd, ); } /** @param {string} value */ set hostname(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'hostname' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_HOSTNAME, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get href() { webidl.assertBranded(this, URLPrototype); return this.#serialization; } /** @param {string} value */ set href(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'href' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); const status = opUrlParse(value); this.#serialization = getSerialization(status, value); this.#updateComponents(); this.#updateSearchParams(); } /** @return {string} */ get origin() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/origin.rs#L14 const scheme = StringPrototypeSlice( this.#serialization, 0, this.#schemeEnd, ); if ( scheme === "http" || scheme === "https" || scheme === "ftp" || scheme === "ws" || scheme === "wss" ) { return `${scheme}://${this.host}`; } if (scheme === "blob") { // TODO(@littledivy): Fast path. try { return new URL(this.pathname).origin; } catch { return "null"; } } return "null"; } /** @return {string} */ get password() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L914 if ( this.#hasAuthority() && this.#usernameEnd !== this.#serialization.length && this.#serialization[this.#usernameEnd] === ":" ) { return StringPrototypeSlice( this.#serialization, this.#usernameEnd + 1, this.#hostStart - 1, ); } return ""; } /** @param {string} value */ set password(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'password' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_PASSWORD, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get pathname() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L1203 if (!this.#queryStart && !this.#fragmentStart) { return StringPrototypeSlice(this.#serialization, this.#pathStart); } const nextComponentStart = this.#queryStart || this.#fragmentStart; return StringPrototypeSlice( this.#serialization, this.#pathStart, nextComponentStart, ); } /** @param {string} value */ set pathname(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'pathname' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_PATHNAME, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get port() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L196 if (this.#port === NO_PORT) { return StringPrototypeSlice( this.#serialization, this.#hostEnd, this.#pathStart, ); } else { return StringPrototypeSlice( this.#serialization, this.#hostEnd + 1, /* : */ this.#pathStart, ); } } /** @param {string} value */ set port(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'port' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_PORT, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get protocol() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L56 return StringPrototypeSlice( this.#serialization, 0, this.#schemeEnd + 1, /* : */ ); } /** @param {string} value */ set protocol(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'protocol' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_PROTOCOL, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {string} */ get search() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L249 const afterPath = this.#queryStart || this.#fragmentStart || this.#serialization.length; const afterQuery = this.#fragmentStart || this.#serialization.length; return trim( StringPrototypeSlice(this.#serialization, afterPath, afterQuery), ); } /** @param {string} value */ set search(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'search' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_SEARCH, value, ); this.#updateComponents(); this.#updateSearchParams(); } catch { /* pass */ } } /** @return {string} */ get username() { webidl.assertBranded(this, URLPrototype); // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L881 const schemeSeparatorLen = 3; /* :// */ if ( this.#hasAuthority() && this.#usernameEnd > this.#schemeEnd + schemeSeparatorLen ) { return StringPrototypeSlice( this.#serialization, this.#schemeEnd + schemeSeparatorLen, this.#usernameEnd, ); } else { return ""; } } /** @param {string} value */ set username(value) { webidl.assertBranded(this, URLPrototype); const prefix = "Failed to set 'username' on 'URL'"; webidl.requiredArguments(arguments.length, 1, prefix); value = webidl.converters.DOMString(value, prefix, "Argument 1"); try { this.#serialization = opUrlReparse( this.#serialization, SET_USERNAME, value, ); this.#updateComponents(); } catch { /* pass */ } } /** @return {URLSearchParams} */ get searchParams() { if (this.#queryObject == null) { this.#queryObject = new URLSearchParams(this.search); this.#queryObject[_urlObject] = this; } return this.#queryObject; } /** @return {string} */ toString() { webidl.assertBranded(this, URLPrototype); return this.#serialization; } /** @return {string} */ toJSON() { webidl.assertBranded(this, URLPrototype); return this.#serialization; } } webidl.configureInterface(URL); const URLPrototype = URL.prototype; markNotSerializable(URLPrototype); /** * This function implements application/x-www-form-urlencoded parsing. * https://url.spec.whatwg.org/#concept-urlencoded-parser * @param {Uint8Array} bytes * @returns {[string, string][]} */ function parseUrlEncoded(bytes) { return op_url_parse_search_params(null, bytes); } webidl .converters[ "sequence<sequence<USVString>> or record<USVString, USVString> or USVString" ] = (V, prefix, context, opts) => { // Union for (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) if (webidl.type(V) === "Object" && V !== null) { if (V[SymbolIterator] !== undefined) { return webidl.converters["sequence<sequence<USVString>>"]( V, prefix, context, opts, ); } return webidl.converters["record<USVString, USVString>"]( V, prefix, context, opts, ); } return webidl.converters.USVString(V, prefix, context, opts); }; return { parseUrlEncoded, URL, URLPrototype, URLSearchParams, URLSearchParamsPrototype, }; })();