summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock55
-rw-r--r--cli/tests/unit/urlpattern_test.ts45
-rw-r--r--ext/url/01_urlpattern.js269
-rw-r--r--ext/url/Cargo.toml1
-rw-r--r--ext/url/README.md5
-rw-r--r--ext/url/internal.d.ts4
-rw-r--r--ext/url/lib.deno_url.d.ts136
-rw-r--r--ext/url/lib.rs11
-rw-r--r--ext/url/urlpattern.rs40
-rw-r--r--runtime/js/99_main.js4
-rw-r--r--tools/wpt/expectation.json22
11 files changed, 589 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0ac666b83..0f87da0fd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -923,6 +923,7 @@ dependencies = [
"percent-encoding",
"serde",
"serde_repr",
+ "urlpattern",
]
[[package]]
@@ -4224,6 +4225,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4291,6 +4333,19 @@ dependencies = [
]
[[package]]
+name = "urlpattern"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbe1da4e25c8758a07ac5b97fe72dec49416ea0783bfa9d6c24793c3a34f1e4e"
+dependencies = [
+ "derive_more",
+ "regex",
+ "serde",
+ "unic-ucd-ident",
+ "url",
+]
+
+[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/tests/unit/urlpattern_test.ts b/cli/tests/unit/urlpattern_test.ts
new file mode 100644
index 000000000..37a662ac1
--- /dev/null
+++ b/cli/tests/unit/urlpattern_test.ts
@@ -0,0 +1,45 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+import { assert, assertEquals, unitTest } from "./test_util.ts";
+
+unitTest(function urlPatternFromString() {
+ const pattern = new URLPattern("https://deno.land/foo/:bar");
+ assertEquals(pattern.protocol, "https");
+ assertEquals(pattern.hostname, "deno.land");
+ assertEquals(pattern.pathname, "/foo/:bar");
+
+ assert(pattern.test("https://deno.land/foo/x"));
+ assert(!pattern.test("https://deno.com/foo/x"));
+ const match = pattern.exec("https://deno.land/foo/x");
+ assert(match);
+ assertEquals(match.pathname.input, "/foo/x");
+ assertEquals(match.pathname.groups, { bar: "x" });
+});
+
+unitTest(function urlPatternFromStringWithBase() {
+ const pattern = new URLPattern("/foo/:bar", "https://deno.land");
+ assertEquals(pattern.protocol, "https");
+ assertEquals(pattern.hostname, "deno.land");
+ assertEquals(pattern.pathname, "/foo/:bar");
+
+ assert(pattern.test("https://deno.land/foo/x"));
+ assert(!pattern.test("https://deno.com/foo/x"));
+ const match = pattern.exec("https://deno.land/foo/x");
+ assert(match);
+ assertEquals(match.pathname.input, "/foo/x");
+ assertEquals(match.pathname.groups, { bar: "x" });
+});
+
+unitTest(function urlPatternFromInit() {
+ const pattern = new URLPattern({
+ pathname: "/foo/:bar",
+ });
+ assertEquals(pattern.protocol, "*");
+ assertEquals(pattern.hostname, "*");
+ assertEquals(pattern.pathname, "/foo/:bar");
+
+ assert(pattern.test("https://deno.land/foo/x"));
+ assert(pattern.test("https://deno.com/foo/x"));
+ assert(!pattern.test("https://deno.com/bar/x"));
+
+ assert(pattern.test({ pathname: "/foo/x" }));
+});
diff --git a/ext/url/01_urlpattern.js b/ext/url/01_urlpattern.js
new file mode 100644
index 000000000..b6ff9e40e
--- /dev/null
+++ b/ext/url/01_urlpattern.js
@@ -0,0 +1,269 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+// @ts-check
+/// <reference path="../../core/internal.d.ts" />
+/// <reference path="../../core/lib.deno_core.d.ts" />
+/// <reference path="../webidl/internal.d.ts" />
+/// <reference path="./internal.d.ts" />
+/// <reference path="./lib.deno_url.d.ts" />
+
+"use strict";
+
+((window) => {
+ const core = window.Deno.core;
+ const webidl = window.__bootstrap.webidl;
+ const {
+ ArrayPrototypeMap,
+ ObjectKeys,
+ ObjectFromEntries,
+ RegExp,
+ RegExpPrototypeExec,
+ RegExpPrototypeTest,
+ Symbol,
+ SymbolFor,
+ TypeError,
+ } = window.__bootstrap.primordials;
+
+ const _components = Symbol("components");
+
+ /**
+ * @typedef Components
+ * @property {Component} protocol
+ * @property {Component} username
+ * @property {Component} password
+ * @property {Component} hostname
+ * @property {Component} port
+ * @property {Component} pathname
+ * @property {Component} search
+ * @property {Component} hash
+ */
+
+ /**
+ * @typedef Component
+ * @property {string} patternString
+ * @property {RegExp} regexp
+ * @property {string[]} groupNameList
+ */
+
+ class URLPattern {
+ /** @type {Components} */
+ [_components];
+
+ /**
+ * @param {URLPatternInput} input
+ * @param {string} [baseURL]
+ */
+ constructor(input, baseURL = undefined) {
+ this[webidl.brand] = webidl.brand;
+ const prefix = "Failed to construct 'URLPattern'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ input = webidl.converters.URLPatternInput(input, {
+ prefix,
+ context: "Argument 1",
+ });
+ if (baseURL !== undefined) {
+ baseURL = webidl.converters.USVString(baseURL, {
+ prefix,
+ context: "Argument 2",
+ });
+ }
+
+ const components = core.opSync("op_urlpattern_parse", input, baseURL);
+
+ for (const key of ObjectKeys(components)) {
+ try {
+ components[key].regexp = new RegExp(
+ components[key].regexpString,
+ "u",
+ );
+ } catch (e) {
+ throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`);
+ }
+ }
+
+ this[_components] = components;
+ }
+
+ get protocol() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].protocol.patternString;
+ }
+
+ get username() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].username.patternString;
+ }
+
+ get password() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].password.patternString;
+ }
+
+ get hostname() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].hostname.patternString;
+ }
+
+ get port() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].port.patternString;
+ }
+
+ get pathname() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].pathname.patternString;
+ }
+
+ get search() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].search.patternString;
+ }
+
+ get hash() {
+ webidl.assertBranded(this, URLPattern);
+ return this[_components].hash.patternString;
+ }
+
+ /**
+ * @param {URLPatternInput} input
+ * @param {string} [baseURL]
+ * @returns {boolean}
+ */
+ test(input, baseURL = undefined) {
+ webidl.assertBranded(this, URLPattern);
+ const prefix = "Failed to execute 'test' on 'URLPattern'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ input = webidl.converters.URLPatternInput(input, {
+ prefix,
+ context: "Argument 1",
+ });
+ if (baseURL !== undefined) {
+ baseURL = webidl.converters.USVString(baseURL, {
+ prefix,
+ context: "Argument 2",
+ });
+ }
+
+ const res = core.opSync(
+ "op_urlpattern_process_match_input",
+ input,
+ baseURL,
+ );
+ if (res === null) {
+ return false;
+ }
+
+ const [values] = res;
+
+ for (const key of ObjectKeys(values)) {
+ if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param {URLPatternInput} input
+ * @param {string} [baseURL]
+ * @returns {URLPatternResult | null}
+ */
+ exec(input, baseURL = undefined) {
+ webidl.assertBranded(this, URLPattern);
+ const prefix = "Failed to execute 'exec' on 'URLPattern'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ input = webidl.converters.URLPatternInput(input, {
+ prefix,
+ context: "Argument 1",
+ });
+ if (baseURL !== undefined) {
+ baseURL = webidl.converters.USVString(baseURL, {
+ prefix,
+ context: "Argument 2",
+ });
+ }
+
+ const res = core.opSync(
+ "op_urlpattern_process_match_input",
+ input,
+ baseURL,
+ );
+ if (res === null) {
+ return null;
+ }
+
+ const [values, inputs] = res;
+ if (inputs[1] === null) {
+ inputs.pop();
+ }
+
+ /** @type {URLPatternResult} */
+ const result = { inputs };
+
+ /** @type {string} */
+ for (const key of ObjectKeys(values)) {
+ /** @type {Component} */
+ const component = this[_components][key];
+ const input = values[key];
+ const match = RegExpPrototypeExec(component.regexp, input);
+ if (match === null) {
+ return null;
+ }
+ const groupEntries = ArrayPrototypeMap(
+ component.groupNameList,
+ (name, i) => [name, match[i + 1] ?? ""],
+ );
+ const groups = ObjectFromEntries(groupEntries);
+ result[key] = {
+ input,
+ groups,
+ };
+ }
+
+ return result;
+ }
+
+ [SymbolFor("Deno.customInspect")](inspect) {
+ return `URLPattern ${
+ inspect({
+ protocol: this.protocol,
+ username: this.username,
+ password: this.password,
+ hostname: this.hostname,
+ port: this.port,
+ pathname: this.pathname,
+ search: this.search,
+ hash: this.hash,
+ })
+ }`;
+ }
+ }
+
+ webidl.configurePrototype(URLPattern);
+
+ webidl.converters.URLPatternInit = webidl
+ .createDictionaryConverter("URLPatternInit", [
+ { key: "protocol", converter: webidl.converters.USVString },
+ { key: "username", converter: webidl.converters.USVString },
+ { key: "password", converter: webidl.converters.USVString },
+ { key: "hostname", converter: webidl.converters.USVString },
+ { key: "port", converter: webidl.converters.USVString },
+ { key: "pathname", converter: webidl.converters.USVString },
+ { key: "search", converter: webidl.converters.USVString },
+ { key: "hash", converter: webidl.converters.USVString },
+ { key: "baseURL", converter: webidl.converters.USVString },
+ ]);
+
+ webidl.converters["URLPatternInput"] = (V, opts) => {
+ // Union for (URLPatternInit or USVString)
+ if (typeof V == "object") {
+ return webidl.converters.URLPatternInit(V, opts);
+ }
+ return webidl.converters.USVString(V, opts);
+ };
+
+ window.__bootstrap.urlPattern = {
+ URLPattern,
+ };
+})(globalThis);
diff --git a/ext/url/Cargo.toml b/ext/url/Cargo.toml
index 830661015..8ad3a61f1 100644
--- a/ext/url/Cargo.toml
+++ b/ext/url/Cargo.toml
@@ -19,6 +19,7 @@ idna = "0.2.3"
percent-encoding = "2.1.0"
serde = { version = "1.0.129", features = ["derive"] }
serde_repr = "0.1.7"
+urlpattern = "0.1.2"
[dev-dependencies]
deno_bench_util = { version = "0.10.0", path = "../../bench_util" }
diff --git a/ext/url/README.md b/ext/url/README.md
index 991dd8b20..519c2823e 100644
--- a/ext/url/README.md
+++ b/ext/url/README.md
@@ -1,5 +1,6 @@
# deno_url
-This crate implements the URL API for Deno.
+This crate implements the URL, and URLPattern APIs for Deno.
-Spec: https://url.spec.whatwg.org/
+URL Spec: https://url.spec.whatwg.org/ URLPattern Spec:
+https://wicg.github.io/urlpattern/
diff --git a/ext/url/internal.d.ts b/ext/url/internal.d.ts
index ec2c2688c..45bf670aa 100644
--- a/ext/url/internal.d.ts
+++ b/ext/url/internal.d.ts
@@ -10,5 +10,9 @@ declare namespace globalThis {
URLSearchParams: typeof URLSearchParams;
parseUrlEncoded(bytes: Uint8Array): [string, string][];
};
+
+ declare var urlPattern: {
+ URLPattern: typeof URLPattern;
+ };
}
}
diff --git a/ext/url/lib.deno_url.d.ts b/ext/url/lib.deno_url.d.ts
index df7acbe60..b2ef4095f 100644
--- a/ext/url/lib.deno_url.d.ts
+++ b/ext/url/lib.deno_url.d.ts
@@ -172,3 +172,139 @@ declare class URL {
username: string;
toJSON(): string;
}
+
+declare interface URLPatternInit {
+ protocol?: string;
+ username?: string;
+ password?: string;
+ hostname?: string;
+ port?: string;
+ pathname?: string;
+ search?: string;
+ hash?: string;
+ baseURL?: string;
+}
+
+declare type URLPatternInput = string | URLPatternInit;
+
+declare interface URLPatternComponentResult {
+ input: string;
+ groups: Record<string, string>;
+}
+
+/** `URLPatternResult` is the object returned from `URLPattern.match`. */
+declare interface URLPatternResult {
+ /** The inputs provided when matching. */
+ inputs: [URLPatternInit] | [URLPatternInit, string];
+
+ /** The matched result for the `protocol` matcher. */
+ protocol: URLPatternComponentResult;
+ /** The matched result for the `username` matcher. */
+ username: URLPatternComponentResult;
+ /** The matched result for the `password` matcher. */
+ password: URLPatternComponentResult;
+ /** The matched result for the `hostname` matcher. */
+ hostname: URLPatternComponentResult;
+ /** The matched result for the `port` matcher. */
+ port: URLPatternComponentResult;
+ /** The matched result for the `pathname` matcher. */
+ pathname: URLPatternComponentResult;
+ /** The matched result for the `search` matcher. */
+ search: URLPatternComponentResult;
+ /** The matched result for the `hash` matcher. */
+ hash: URLPatternComponentResult;
+}
+
+/**
+ * The URLPattern API provides a web platform primitive for matching URLs based
+ * on a convenient pattern syntax.
+ *
+ * The syntax is based on path-to-regexp. Wildcards, named capture groups,
+ * regular groups, and group modifiers are all supported.
+ *
+ * ```ts
+ * // Specify the pattern as structured data.
+ * const pattern = new URLPattern({ pathname: "/users/:user" });
+ * const match = pattern.match("/users/joe");
+ * console.log(match.pathname.groups.user); // joe
+ * ```
+ *
+ * ```ts
+ * // Specify a fully qualified string pattern.
+ * const pattern = new URLPattern("https://example.com/books/:id");
+ * console.log(pattern.test("https://example.com/books/123")); // true
+ * console.log(pattern.test("https://deno.land/books/123")); // false
+ * ```
+ *
+ * ```ts
+ * // Specify a relative string pattern with a base URL.
+ * const pattern = new URLPattern("/:article", "https://blog.example.com");
+ * console.log(pattern.test("https://blog.example.com/article")); // true
+ * console.log(pattern.test("https://blog.example.com/article/123")); // false
+ * ```
+ */
+declare class URLPattern {
+ constructor(input: URLPatternInput, baseURL?: string);
+
+ /**
+ * Test if the given input matches the stored pattern.
+ *
+ * The input can either be provided as a url string (with an optional base),
+ * or as individual components in the form of an object.
+ *
+ * ```ts
+ * const pattern = new URLPattern("https://example.com/books/:id");
+ *
+ * // Test a url string.
+ * console.log(pattern.test("https://example.com/books/123")); // true
+ *
+ * // Test a relative url with a base.
+ * console.log(pattern.test("/books/123", "https://example.com")); // true
+ *
+ * // Test an object of url components.
+ * console.log(pattern.test({ pathname: "/books/123" })); // true
+ * ```
+ */
+ test(input: URLPatternInput, baseURL?: string): boolean;
+
+ /**
+ * Match the given input against the stored pattern.
+ *
+ * The input can either be provided as a url string (with an optional base),
+ * or as individual components in the form of an object.
+ *
+ * ```ts
+ * const pattern = new URLPattern("https://example.com/books/:id");
+ *
+ * // Match a url string.
+ * let match = pattern.match("https://example.com/books/123");
+ * console.log(match.pathname.groups.id); // 123
+ *
+ * // Match a relative url with a base.
+ * match = pattern.match("/books/123", "https://example.com");
+ * console.log(match.pathname.groups.id); // 123
+ *
+ * // Match an object of url components.
+ * match = pattern.match({ pathname: "/books/123" });
+ * console.log(match.pathname.groups.id); // 123
+ * ```
+ */
+ exec(input: URLPatternInput, baseURL?: string): URLPatternResult | null;
+
+ /** The pattern string for the `protocol`. */
+ readonly protocol: string;
+ /** The pattern string for the `username`. */
+ readonly username: string;
+ /** The pattern string for the `password`. */
+ readonly password: string;
+ /** The pattern string for the `hostname`. */
+ readonly hostname: string;
+ /** The pattern string for the `port`. */
+ readonly port: string;
+ /** The pattern string for the `pathname`. */
+ readonly pathname: string;
+ /** The pattern string for the `search`. */
+ readonly search: string;
+ /** The pattern string for the `hash`. */
+ readonly hash: string;
+}
diff --git a/ext/url/lib.rs b/ext/url/lib.rs
index d8987c816..0f8d5c599 100644
--- a/ext/url/lib.rs
+++ b/ext/url/lib.rs
@@ -1,5 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+mod urlpattern;
+
use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::uri_error;
@@ -14,11 +16,15 @@ use deno_core::ZeroCopyBuf;
use std::panic::catch_unwind;
use std::path::PathBuf;
+use crate::urlpattern::op_urlpattern_parse;
+use crate::urlpattern::op_urlpattern_process_match_input;
+
pub fn init() -> Extension {
Extension::builder()
.js(include_js_files!(
prefix "deno:ext/url",
"00_url.js",
+ "01_urlpattern.js",
))
.ops(vec![
("op_url_parse", op_sync(op_url_parse)),
@@ -31,6 +37,11 @@ pub fn init() -> Extension {
"op_url_stringify_search_params",
op_sync(op_url_stringify_search_params),
),
+ ("op_urlpattern_parse", op_sync(op_urlpattern_parse)),
+ (
+ "op_urlpattern_process_match_input",
+ op_sync(op_urlpattern_process_match_input),
+ ),
])
.build()
}
diff --git a/ext/url/urlpattern.rs b/ext/url/urlpattern.rs
new file mode 100644
index 000000000..b9f53665f
--- /dev/null
+++ b/ext/url/urlpattern.rs
@@ -0,0 +1,40 @@
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+
+use urlpattern::quirks;
+use urlpattern::quirks::MatchInput;
+use urlpattern::quirks::StringOrInit;
+use urlpattern::quirks::UrlPattern;
+
+pub fn op_urlpattern_parse(
+ _state: &mut deno_core::OpState,
+ input: StringOrInit,
+ base_url: Option<String>,
+) -> Result<UrlPattern, AnyError> {
+ let init = urlpattern::quirks::process_construct_pattern_input(
+ input,
+ base_url.as_deref(),
+ )
+ .map_err(|e| type_error(e.to_string()))?;
+
+ let pattern = urlpattern::quirks::parse_pattern(init)
+ .map_err(|e| type_error(e.to_string()))?;
+
+ Ok(pattern)
+}
+
+pub fn op_urlpattern_process_match_input(
+ _state: &mut deno_core::OpState,
+ input: StringOrInit,
+ base_url: Option<String>,
+) -> Result<Option<(MatchInput, quirks::Inputs)>, AnyError> {
+ let res = urlpattern::quirks::process_match_input(input, base_url.as_deref())
+ .map_err(|e| type_error(e.to_string()))?;
+
+ let (input, inputs) = match res {
+ Some((input, inputs)) => (input, inputs),
+ None => return Ok(None),
+ };
+
+ Ok(urlpattern::quirks::parse_match_input(input).map(|input| (input, inputs)))
+}
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 59b3a428f..d086a42b2 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -41,6 +41,7 @@ delete Object.prototype.__proto__;
const performance = window.__bootstrap.performance;
const crypto = window.__bootstrap.crypto;
const url = window.__bootstrap.url;
+ const urlPattern = window.__bootstrap.urlPattern;
const headers = window.__bootstrap.headers;
const streams = window.__bootstrap.streams;
const fileReader = window.__bootstrap.fileReader;
@@ -431,8 +432,9 @@ delete Object.prototype.__proto__;
};
const unstableWindowOrWorkerGlobalScope = {
- WebSocketStream: util.nonEnumerable(webSocket.WebSocketStream),
BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel),
+ URLPattern: util.nonEnumerable(urlPattern.URLPattern),
+ WebSocketStream: util.nonEnumerable(webSocket.WebSocketStream),
GPU: util.nonEnumerable(webgpu.GPU),
GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter),
diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json
index db3d5b60e..1c0ef521a 100644
--- a/tools/wpt/expectation.json
+++ b/tools/wpt/expectation.json
@@ -13490,6 +13490,14 @@
],
"toString.tentative.any.html": false,
"type.tentative.any.html": false
+ },
+ "function": {
+ "call.tentative.any.html": false,
+ "constructor.tentative.any.html": [
+ "construct with JS function"
+ ],
+ "table.tentative.any.html": false,
+ "type.tentative.any.html": false
}
},
"serialization": {
@@ -14527,5 +14535,19 @@
"performance.clearResourceTimings in workers",
"performance.setResourceTimingBufferSize in workers"
]
+ },
+ "urlpattern": {
+ "urlpattern.any.html": [
+ "Pattern: [{\"pathname\":\"/foo/bar\"}] Inputs: [\"./foo/bar\",\"https://example.com\"]"
+ ],
+ "urlpattern.any.worker.html": [
+ "Pattern: [{\"pathname\":\"/foo/bar\"}] Inputs: [\"./foo/bar\",\"https://example.com\"]"
+ ],
+ "urlpattern.https.any.html": [
+ "Pattern: [{\"pathname\":\"/foo/bar\"}] Inputs: [\"./foo/bar\",\"https://example.com\"]"
+ ],
+ "urlpattern.https.any.worker.html": [
+ "Pattern: [{\"pathname\":\"/foo/bar\"}] Inputs: [\"./foo/bar\",\"https://example.com\"]"
+ ]
}
} \ No newline at end of file