diff --git a/ext/url/00_url.js b/ext/url/00_url.js
index 3b081218bb6fb5..577caba902f18f 100644
--- a/ext/url/00_url.js
+++ b/ext/url/00_url.js
@@ -351,17 +351,29 @@ function trim(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);
+
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) {
@@ -378,18 +390,46 @@ class 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;
const status = opUrlParse(url, base);
+ this[webidl.brand] = webidl.brand;
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]
@@ -799,7 +839,7 @@ class URL {
}
}
- /** @return {string} */
+ /** @return {URLSearchParams} */
get searchParams() {
if (this.#queryObject == null) {
this.#queryObject = new URLSearchParams(this.search);
diff --git a/ext/url/lib.deno_url.d.ts b/ext/url/lib.deno_url.d.ts
index a184ee4a204245..0ade8c85afeb62 100644
--- a/ext/url/lib.deno_url.d.ts
+++ b/ext/url/lib.deno_url.d.ts
@@ -195,6 +195,7 @@ declare interface URL {
declare var URL: {
readonly prototype: URL;
new (url: string | URL, base?: string | URL): URL;
+ parse(url: string | URL, base?: string | URL): URL | null;
canParse(url: string | URL, base?: string | URL): boolean;
createObjectURL(blob: Blob): string;
revokeObjectURL(url: string): void;
diff --git a/tests/wpt/runner/expectation.json b/tests/wpt/runner/expectation.json
index ec917c497ca09a..3f73bef89bd3b1 100644
--- a/tests/wpt/runner/expectation.json
+++ b/tests/wpt/runner/expectation.json
@@ -3967,8 +3967,7 @@
"Parsing: //example.org/../path> against ",
"Parsing: //example.org/../../> against ",
"Parsing: //example.org/../path/../../> against ",
- "Parsing: //example.org/../path/../../path> against ",
- "Parsing: \\//\\/a/../> against "
+ "Parsing: //example.org/../path/../../path> against "
],
"url-constructor.any.html?include=file": [
"Parsing: > against ",
@@ -4030,8 +4029,7 @@
"Parsing: //example.org/../path> against ",
"Parsing: //example.org/../../> against ",
"Parsing: //example.org/../path/../../> against ",
- "Parsing: //example.org/../path/../../path> against ",
- "Parsing: \\//\\/a/../> against "
+ "Parsing: //example.org/../path/../../path> against "
],
"url-constructor.any.worker.html?include=file": [
"Parsing: > against ",
@@ -4636,7 +4634,1805 @@
"url-setters.any.worker.html?include=mailto": true,
"url-statics-canparse.any.html": true,
"url-statics-canparse.any.worker.html": true,
- "urlsearchparams-size.any.worker.html": true
+ "urlsearchparams-size.any.worker.html": true,
+ "a-element-origin.html": [
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: <\t :foo.com \n> against ",
+ "Parsing origin: < foo.com > against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: <> against ",
+ "Parsing origin: < \t> against ",
+ "Parsing origin: <:foo.com/> against ",
+ "Parsing origin: <:foo.com\\> against ",
+ "Parsing origin: <:> against ",
+ "Parsing origin: <:a> against ",
+ "Parsing origin: <:/> against ",
+ "Parsing origin: <:\\> against ",
+ "Parsing origin: <:#> against ",
+ "Parsing origin: <#> against ",
+ "Parsing origin: <#/> against ",
+ "Parsing origin: <#\\> against ",
+ "Parsing origin: <#;?> against ",
+ "Parsing origin: > against ",
+ "Parsing origin: > against ",
+ "Parsing origin: <:23> against ",
+ "Parsing origin: against ",
+ "Parsing origin: <\\x> against ",
+ "Parsing origin: <\\\\x\\hello> against ",
+ "Parsing origin: <::> against ",
+ "Parsing origin: <::23> against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: /foo/bar> against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: <[61:24:74]:98> against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: <#β> against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: against ",
+ "Parsing origin: