Start implementing the URLPattern API (#36144)

* Start working on a basic URLPattern implementation

This is API part of Interop 2025, so we should definitely support it!

This change implements the basic workflow for parsing
and compiling URL patterns. Parts of it are stubbed out and will be
implemented later.

For now the API is preference-gated behind "dom_urlpattern_enabled".

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Preference-gate the URLPattern API

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Update WPT expectations

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Fix full wildcard value (Should be ".*" not "*")

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-03-27 11:39:57 +01:00 committed by GitHub
parent e4efdfe668
commit 517f99e067
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 906 additions and 189 deletions

View file

@ -114,6 +114,10 @@ pub struct Preferences {
pub dom_testing_element_activation_enabled: bool,
pub dom_testing_html_input_element_select_files_enabled: bool,
pub dom_testperf_enabled: bool,
/// Enable the [URLPattern] API.
///
/// [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
pub dom_urlpattern_enabled: bool,
pub dom_xpath_enabled: bool,
/// Enable WebGL2 APIs.
pub dom_webgl2_enabled: bool,
@ -280,6 +284,7 @@ impl Preferences {
dom_testing_element_activation_enabled: false,
dom_testing_html_input_element_select_files_enabled: false,
dom_testperf_enabled: false,
dom_urlpattern_enabled: false,
dom_webgl2_enabled: false,
dom_webgpu_enabled: false,
dom_webgpu_wgpu_backend: String::new(),

View file

@ -2991,7 +2991,10 @@ fn compile_pattern(cx: SafeJSContext, pattern_str: &str, out_regex: MutableHandl
if check_js_regex_syntax(cx, pattern_str) {
// ...and if it does make pattern that matches only the entirety of string
let pattern_str = format!("^(?:{})$", pattern_str);
new_js_regex(cx, &pattern_str, out_regex)
let flags = RegExpFlags {
flags_: RegExpFlag_Unicode,
};
new_js_regex(cx, &pattern_str, flags, out_regex)
} else {
false
}
@ -3026,17 +3029,22 @@ fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str) -> bool {
}
}
// TODO: This is also used in the URLPattern implementation. Consider moving it into mozjs or some other
// shared module
#[allow(unsafe_code)]
fn new_js_regex(cx: SafeJSContext, pattern: &str, mut out_regex: MutableHandleObject) -> bool {
pub(crate) fn new_js_regex(
cx: SafeJSContext,
pattern: &str,
flags: RegExpFlags,
mut out_regex: MutableHandleObject,
) -> bool {
let pattern: Vec<u16> = pattern.encode_utf16().collect();
unsafe {
out_regex.set(NewUCRegExpObject(
*cx,
pattern.as_ptr(),
pattern.len(),
RegExpFlags {
flags_: RegExpFlag_Unicode,
},
flags,
));
if out_regex.is_null() {
JS_ClearPendingException(*cx);

View file

@ -576,6 +576,7 @@ pub(crate) mod uievent;
pub(crate) mod underlyingsourcecontainer;
pub(crate) mod url;
pub(crate) mod urlhelper;
pub(crate) mod urlpattern;
pub(crate) mod urlsearchparams;
pub(crate) mod userscripts;
pub(crate) mod validation;

View file

@ -0,0 +1,814 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::ptr;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSObject, RegExpFlag_IgnoreCase, RegExpFlag_UnicodeSets, RegExpFlags};
use js::rust::HandleObject;
use script_bindings::error::{Error, Fallible};
use script_bindings::reflector::Reflector;
use script_bindings::root::DomRoot;
use script_bindings::script_runtime::CanGc;
use script_bindings::str::USVString;
use crate::dom::bindings::cell::RefCell;
use crate::dom::bindings::codegen::Bindings::URLPatternBinding::{
URLPatternInit, URLPatternMethods, URLPatternOptions,
};
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlinputelement::new_js_regex;
/// <https://urlpattern.spec.whatwg.org/#full-wildcard-regexp-value>
const FULL_WILDCARD_REGEXP_VALUE: &str = ".*";
/// <https://urlpattern.spec.whatwg.org/#urlpattern>
#[dom_struct]
pub(crate) struct URLPattern {
reflector: Reflector,
/// <https://urlpattern.spec.whatwg.org/#urlpattern-associated-url-pattern>
associated_url_pattern: RefCell<URLPatternInternal>,
}
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct URLPatternInternal {
/// <https://urlpattern.spec.whatwg.org/#url-pattern-protocol-component>
protocol: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-username-component>
username: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-password-component>
password: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-hostname-component>
hostname: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-port-component>
port: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-pathname-component>
pathname: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-search-component>
search: Component,
/// <https://urlpattern.spec.whatwg.org/#url-pattern-hash-component>
hash: Component,
}
/// <https://urlpattern.spec.whatwg.org/#component>
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct Component {
/// <https://urlpattern.spec.whatwg.org/#component-pattern-string>
pattern_string: USVString,
/// <https://urlpattern.spec.whatwg.org/#component-regular-expression>
#[ignore_malloc_size_of = "mozjs"]
regular_expression: Box<Heap<*mut JSObject>>,
/// <https://urlpattern.spec.whatwg.org/#component-group-name-list>
group_name_list: Vec<USVString>,
/// <https://urlpattern.spec.whatwg.org/#component-has-regexp-groups>
has_regexp_groups: bool,
}
/// <https://urlpattern.spec.whatwg.org/#part>
struct Part {
/// <https://urlpattern.spec.whatwg.org/#part-type>
part_type: PartType,
/// <https://urlpattern.spec.whatwg.org/#part-value>
value: String,
/// <https://urlpattern.spec.whatwg.org/#part-modifier>
modifier: PartModifier,
/// <https://urlpattern.spec.whatwg.org/#part-name>
name: String,
/// <https://urlpattern.spec.whatwg.org/#part-prefix>
prefix: String,
/// <https://urlpattern.spec.whatwg.org/#part-suffix>
suffix: String,
}
/// <https://urlpattern.spec.whatwg.org/#part-type>
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PartType {
/// <https://urlpattern.spec.whatwg.org/#part-type-fixed-text>
FixedText,
/// <https://urlpattern.spec.whatwg.org/#part-type-regexp>
Regexp,
/// <https://urlpattern.spec.whatwg.org/#part-type-segment-wildcard>
SegmentWildcard,
/// <https://urlpattern.spec.whatwg.org/#part-type-full-wildcard>
FullWildcard,
}
/// <https://urlpattern.spec.whatwg.org/#part-modifier>
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(dead_code)] // Parser is not implemented yet
enum PartModifier {
/// <https://urlpattern.spec.whatwg.org/#part-modifier-none>
None,
/// <https://urlpattern.spec.whatwg.org/#part-modifier-optional>
Optional,
/// <https://urlpattern.spec.whatwg.org/#part-modifier-zero-or-more>
ZeroOrMore,
/// <https://urlpattern.spec.whatwg.org/#part-modifier-one-or-more>
OneOrMore,
}
/// <https://urlpattern.spec.whatwg.org/#options>
#[derive(Clone, Copy, Default)]
#[allow(dead_code)] // Parser is not fully implemented yet
struct Options {
/// <https://urlpattern.spec.whatwg.org/#options-delimiter-code-point>
delimiter_code_point: Option<char>,
/// <https://urlpattern.spec.whatwg.org/#options-prefix-code-point>
prefix_code_point: Option<char>,
/// <https://urlpattern.spec.whatwg.org/#options-ignore-case>
ignore_case: bool,
}
impl Component {
fn new_unrooted() -> Self {
Self {
pattern_string: Default::default(),
regular_expression: Heap::boxed(ptr::null_mut()),
group_name_list: Default::default(),
has_regexp_groups: false,
}
}
}
impl URLPattern {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn new_inherited() -> URLPattern {
let associated_url_pattern = URLPatternInternal {
protocol: Component::new_unrooted(),
username: Component::new_unrooted(),
password: Component::new_unrooted(),
hostname: Component::new_unrooted(),
port: Component::new_unrooted(),
pathname: Component::new_unrooted(),
search: Component::new_unrooted(),
hash: Component::new_unrooted(),
};
URLPattern {
reflector: Reflector::new(),
associated_url_pattern: RefCell::new(associated_url_pattern),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<URLPattern> {
reflect_dom_object_with_proto(Box::new(URLPattern::new_inherited()), global, proto, can_gc)
}
/// <https://urlpattern.spec.whatwg.org/#urlpattern-initialize>
fn initialize(
global: &GlobalScope,
proto: Option<HandleObject>,
input: &URLPatternInit,
options: &URLPatternOptions,
can_gc: CanGc,
) -> Fallible<DomRoot<URLPattern>> {
// Step 1. Set thiss associated URL pattern to the result of create given input, baseURL, and options.
let pattern = URLPattern::new_with_proto(global, proto, can_gc);
URLPatternInternal::create(
input,
options,
&mut pattern.associated_url_pattern.borrow_mut(),
)?;
Ok(pattern)
}
}
impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-urlpattern-input-options>
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
input: &URLPatternInit,
options: &URLPatternOptions,
) -> Fallible<DomRoot<URLPattern>> {
// Step 1. Run initialize given this, input, null, and options.
URLPattern::initialize(global, proto, input, options, can_gc)
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol>
fn Protocol(&self) -> USVString {
// Step 1. Return thiss associated URL patterns protocol components pattern string.
self.associated_url_pattern
.borrow()
.protocol
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-username>
fn Username(&self) -> USVString {
// Step 1. Return thiss associated URL patterns username components pattern string.
self.associated_url_pattern
.borrow()
.username
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-password>
fn Password(&self) -> USVString {
// Step 1. Return thiss associated URL patterns password components pattern string.
self.associated_url_pattern
.borrow()
.password
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hostname>
fn Hostname(&self) -> USVString {
// Step 1. Return thiss associated URL patterns hostname components pattern string.
self.associated_url_pattern
.borrow()
.hostname
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-port>
fn Port(&self) -> USVString {
// Step 1. Return thiss associated URL patterns port components pattern string.
self.associated_url_pattern
.borrow()
.port
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-pathname>
fn Pathname(&self) -> USVString {
// Step 1. Return thiss associated URL patterns pathname components pattern string.
self.associated_url_pattern
.borrow()
.pathname
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-search>
fn Search(&self) -> USVString {
// Step 1. Return thiss associated URL patterns search components pattern string.
self.associated_url_pattern
.borrow()
.search
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hash>
fn Hash(&self) -> USVString {
// Step 1. Return thiss associated URL patterns hash components pattern string.
self.associated_url_pattern
.borrow()
.hash
.pattern_string
.clone()
}
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-hasregexpgroups>
fn HasRegExpGroups(&self) -> bool {
// Step 1. If thiss associated URL patterns has regexp groups, then return true.
// Step 2. Return false.
self.associated_url_pattern.borrow().has_regexp_groups()
}
}
impl URLPatternInternal {
/// <https://urlpattern.spec.whatwg.org/#url-pattern-create>
fn create(input: &URLPatternInit, options: &URLPatternOptions, out: &mut Self) -> Fallible<()> {
// Step 1. Let init be null.
// Step 2. If input is a scalar value string then:
// NOTE: We don't support strings as input yet
// Step 3. Otherwise:
// Step 3.1 Assert: input is a URLPatternInit.
// Step 3.2 If baseURL is not null, then throw a TypeError.
if input.baseURL.is_some() {
return Err(Error::Type("baseURL must be none".into()));
}
// Step 3.3 Set init to input.
let init = input;
// TODO Step 4. Let processedInit be the result of process a URLPatternInit given init, "pattern", null, null,
// null, null, null, null, null, and null.
let mut processed_init = process_a_url_pattern_init(init);
// Step 5. For each componentName of « "protocol", "username", "password", "hostname", "port",
// "pathname", "search", "hash" »:
// Step 5.1 If processedInit[componentName] does not exist, then set processedInit[componentName] to "*".
// NOTE: We do this later on
// Step 6. If processedInit["protocol"] is a special scheme and processedInit["port"] is a string
// which represents its corresponding default port in radix-10 using ASCII digits then set
// processedInit["port"] to the empty string.
let default_port = processed_init
.protocol
.as_deref()
.and_then(default_port_for_special_scheme);
let given_port = processed_init
.port
.as_deref()
.map(str::parse)
.transpose()
.ok()
.flatten();
if default_port == given_port {
processed_init.port = Some(Default::default());
}
// Step 7. Let urlPattern be a new URL pattern.
// NOTE: We construct the pattern once we have all the components
// Step 8. Set urlPatterns protocol component to the result of compiling a component given
// processedInit["protocol"], canonicalize a protocol, and default options.
Component::compile(
processed_init.protocol.as_deref().unwrap_or("*"),
Options::default(),
&mut out.protocol,
)?;
// Step 9. Set urlPatterns username component to the result of compiling a component given
// processedInit["username"], canonicalize a username, and default options.
Component::compile(
processed_init.username.as_deref().unwrap_or("*"),
Options::default(),
&mut out.username,
)?;
// Step 10. Set urlPatterns password component to the result of compiling a component given
// processedInit["password"], canonicalize a password, and default options.
Component::compile(
processed_init.password.as_deref().unwrap_or("*"),
Options::default(),
&mut out.password,
)?;
// FIXME: Steps 11 and 12: Compile host pattern correctly
Component::compile(
processed_init.hostname.as_deref().unwrap_or("*"),
Options::HOSTNAME,
&mut out.hostname,
)?;
// Step 13. Set urlPatterns port component to the result of compiling a component given
// processedInit["port"], canonicalize a port, and default options.
Component::compile(
processed_init.port.as_deref().unwrap_or("*"),
Options::default(),
&mut out.port,
)?;
// FIXME: Step 14: respect ignore case option from here on out
let _ = options;
// FIXME: Steps 15-16: Compile path pattern correctly
Component::compile(
processed_init.pathname.as_deref().unwrap_or("*"),
Options::PATHNAME,
&mut out.pathname,
)?;
// Step 17. Set urlPatterns search component to the result of compiling a component given
// processedInit["search"], canonicalize a search, and compileOptions.
Component::compile(
processed_init.search.as_deref().unwrap_or("*"),
Options::default(),
&mut out.search,
)?;
// Step 18. Set urlPatterns hash component to the result of compiling a component given
// processedInit["hash"], canonicalize a hash, and compileOptions.
Component::compile(
processed_init.hash.as_deref().unwrap_or("*"),
Options::default(),
&mut out.hash,
)?;
// Step 19. Return urlPattern.
// NOTE: not necessary since we use an out parameter
Ok(())
}
/// <https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups>
fn has_regexp_groups(&self) -> bool {
self.protocol.has_regexp_groups ||
self.username.has_regexp_groups ||
self.password.has_regexp_groups ||
self.hostname.has_regexp_groups ||
self.port.has_regexp_groups ||
self.pathname.has_regexp_groups ||
self.search.has_regexp_groups ||
self.hash.has_regexp_groups
}
}
impl Component {
/// <https://urlpattern.spec.whatwg.org/#compile-a-component>
fn compile(input: &str, options: Options, out: &mut Self) -> Fallible<()> {
// Step 1. Let part list be the result of running parse a pattern string given input, options,
// and encoding callback.
let part_list = parse_a_pattern_string(input, options);
// Step 2. Let (regular expression string, name list) be the result of running generate a regular expression and
// name list given part list and options.
let (regular_expression_string, name_list) =
generate_a_regular_expression_and_name_list(&part_list, options);
// Step 3. Let flags be an empty string.
// Step 4. If optionss ignore case is true then set flags to "vi".
let flags = if options.ignore_case {
RegExpFlags {
flags_: RegExpFlag_UnicodeSets | RegExpFlag_IgnoreCase,
}
}
// Step 5. Otherwise set flags to "v"
else {
RegExpFlags {
flags_: RegExpFlag_UnicodeSets,
}
};
// Step 6. Let regular expression be RegExpCreate(regular expression string, flags).
// If this throws an exception, catch it, and throw a TypeError.
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut regular_expression: *mut JSObject = ptr::null_mut());
let succeeded = new_js_regex(
cx,
&regular_expression_string,
flags,
regular_expression.handle_mut(),
);
if !succeeded {
return Err(Error::Type(format!(
"Failed to compile {regular_expression_string:?} as a regular expression"
)));
}
// TODO Step 7. Let pattern string be the result of running generate a pattern string given
// part list and options.
let pattern_string = Default::default();
// Step 8. Let has regexp groups be false.
// Step 9. For each part of part list:
// Step 9.1 If parts type is "regexp", then set has regexp groups to true.
let has_regexp_groups = part_list
.iter()
.any(|part| part.part_type == PartType::Regexp);
// Step 10. Return a new component whose pattern string is pattern string, regular expression
// is regular expression, group name list is name list, and has regexp groups is has regexp groups.
out.pattern_string = pattern_string;
out.regular_expression.set(*regular_expression.handle());
out.group_name_list = name_list;
out.has_regexp_groups = has_regexp_groups;
Ok(())
}
}
/// <https://urlpattern.spec.whatwg.org/#parse-a-pattern-string>
fn parse_a_pattern_string(pattern_string: &str, options: Options) -> Vec<Part> {
// FIXME: Implement this algorithm
let _ = pattern_string;
let _ = options;
vec![]
}
/// <https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list>
fn generate_a_regular_expression_and_name_list(
part_list: &[Part],
options: Options,
) -> (String, Vec<USVString>) {
// Step 1. Let result be "^".
let mut result = String::from("^");
// Step 2. Let name list be a new list.
let mut name_list = vec![];
// Step 3. For each part of part list:
for part in part_list {
// Step 3.1 If parts type is "fixed-text":
if part.part_type == PartType::FixedText {
// Step 3.1.1 If parts modifier is "none", then append the result of running escape a regexp string given
// parts value to the end of result.
if part.modifier == PartModifier::None {
result.push_str(&escape_a_regexp_string(&part.value));
}
// Step 3.1.2 Otherwise:
else {
// Step 3.1.2.1 Append "(?:" to the end of result.
result.push_str("(?:");
// Step 3.1.2.2 Append the result of running escape a regexp string given parts value
// to the end of result.
result.push_str(&escape_a_regexp_string(&part.value));
// Step 3.1.2.3 Append ")" to the end of result.
result.push(')');
// Step 3.1.2.4 Append the result of running convert a modifier to a string given parts
// modifier to the end of result.
result.push_str(part.modifier.convert_to_string());
}
// Step 3.1.3 Continue.
continue;
}
// Step 3.2 Assert: parts name is not the empty string.
debug_assert!(!part.name.is_empty());
// Step 3.3 Append parts name to name list.
name_list.push(USVString(part.name.to_string()));
// Step 3.4 Let regexp value be parts value.
let mut regexp_value = part.value.clone();
// Step 3.5 If parts type is "segment-wildcard", then set regexp value to the result of running
// generate a segment wildcard regexp given options.
if part.part_type == PartType::SegmentWildcard {
regexp_value = generate_a_segment_wildcard_regexp(options);
}
// Step 3.6 Otherwise if parts type is "full-wildcard", then set regexp value to full wildcard regexp value.
else if part.part_type == PartType::FullWildcard {
regexp_value = FULL_WILDCARD_REGEXP_VALUE.into();
}
// Step 3.7 If parts prefix is the empty string and parts suffix is the empty string:
if part.prefix.is_empty() && part.suffix.is_empty() {
// Step 3.7.1 If parts modifier is "none" or "optional", then:
if matches!(part.modifier, PartModifier::None | PartModifier::Optional) {
// Step 3.7.1.1 Append "(" to the end of result.
result.push('(');
// Step 3.7.1.2 Append regexp value to the end of result.
result.push_str(&regexp_value);
// Step 3.7.1.3 Append ")" to the end of result.
result.push(')');
// Step 3.7.1.4 Append the result of running convert a modifier to a string given parts modifier
// to the end of result.
result.push_str(part.modifier.convert_to_string());
}
// Step 3.7.2 Otherwise:
else {
// Step 3.7.2.1 Append "((?:" to the end of result.
result.push_str("((?:");
// Step 3.7.2.2 Append regexp value to the end of result.
result.push_str(&regexp_value);
// Step 3.7.2.3 Append ")" to the end of result.
result.push(')');
// Step 3.7.2.4 Append the result of running convert a modifier to a string given parts modifier
// to the end of result.
result.push_str(part.modifier.convert_to_string());
// Step 3.7.2.5 Append ")" to the end of result.
result.push(')');
}
// Step 3.7.3 Continue.
continue;
}
// Step 3.8 If parts modifier is "none" or "optional":
if matches!(part.modifier, PartModifier::None | PartModifier::Optional) {
// Step 3.8.1 Append "(?:" to the end of result.
result.push_str("(?:");
// Step 3.8.2 Append the result of running escape a regexp string given parts prefix
// to the end of result.
result.push_str(&escape_a_regexp_string(&part.prefix));
// Step 3.8.3 Append "(" to the end of result.
result.push('(');
// Step 3.8.4 Append regexp value to the end of result.
result.push_str(&regexp_value);
// Step 3.8.5 Append ")" to the end of result.
result.push(')');
// Step 3.8.6 Append the result of running escape a regexp string given parts suffix
// to the end of result.
result.push_str(&escape_a_regexp_string(&part.suffix));
// Step 3.8.7 Append ")" to the end of result.
result.push(')');
// Step 3.8.8 Append the result of running convert a modifier to a string given parts modifier to
// the end of result.
result.push_str(part.modifier.convert_to_string());
// Step 3.8.9 Continue.
continue;
}
// Step 3.9 Assert: parts modifier is "zero-or-more" or "one-or-more".
debug_assert!(matches!(
part.modifier,
PartModifier::ZeroOrMore | PartModifier::OneOrMore
));
// Step 3.10 Assert: parts prefix is not the empty string or parts suffix is not the empty string.
debug_assert!(!part.prefix.is_empty() || !part.suffix.is_empty());
// Step 3.11 Append "(?:" to the end of result.
result.push_str("(?:");
// Step 3.12 Append the result of running escape a regexp string given parts prefix to the end of result.
result.push_str(&escape_a_regexp_string(&part.prefix));
// Step 3.13 Append "((?:" to the end of result.
result.push_str("((?:");
// Step 3.14 Append regexp value to the end of result.
result.push_str(&regexp_value);
// Step 3.15 Append ")(?:" to the end of result.
result.push_str(")(?:");
// Step 3.16 Append the result of running escape a regexp string given parts suffix to the end of result.
result.push_str(&escape_a_regexp_string(&part.suffix));
// Step 3.17 Append the result of running escape a regexp string given parts prefix to the end of result.
result.push_str(&escape_a_regexp_string(&part.prefix));
// Step 3.18 Append "(?:" to the end of result.
result.push_str("(?:");
// Step 3.19 Append regexp value to the end of result.
result.push_str(&regexp_value);
// Step 3.20 Append "))*)" to the end of result.
result.push_str("))*)");
// Step 3.21 Append the result of running escape a regexp string given parts suffix to the end of result.
result.push_str(&escape_a_regexp_string(&part.suffix));
// Step 3.22 Append ")" to the end of result.
result.push(')');
// Step 3.23 If parts modifier is "zero-or-more" then append "?" to the end of result.
if part.modifier == PartModifier::ZeroOrMore {
result.push('?');
}
}
// Step 4. Append "$" to the end of result.
result.push('$');
// Step 5. Return (result, name list).
(result, name_list)
}
/// <https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit>
fn process_a_url_pattern_init(pattern_init: &URLPatternInit) -> URLPatternInit {
// FIXME: Implement this algorithm
pattern_init.clone()
}
// FIXME: Deduplicate this with the url crate
/// <https://url.spec.whatwg.org/#special-scheme>
fn default_port_for_special_scheme(scheme: &str) -> Option<u16> {
match scheme {
"ftp" => Some(21),
"http" | "ws" => Some(80),
"https" | "wss" => Some(443),
_ => None,
}
}
/// <https://urlpattern.spec.whatwg.org/#escape-a-regexp-string>
fn escape_a_regexp_string(input: &str) -> String {
// Step 1. Assert: input is an ASCII string.
debug_assert!(input.is_ascii());
// Step 2. Let result be the empty string.
let mut result = String::with_capacity(input.len());
// Step 3. Let index be 0.
// Step 4. While index is less than inputs length:
// Step 4.1 Let c be input[index].
// Step 4.2 Increment index by 1.
for c in input.chars() {
// Step 4.3 If c is one of: [..] then append "\" to the end of result.
if matches!(
c,
'.' | '+' |
'*' |
'?' |
'^' |
'$' |
'{' |
'}' |
'(' |
')' |
'[' |
']' |
'|' |
'/' |
'\\'
) {
result.push('\\');
}
// Step 4.4 Append c to the end of result.
result.push(c);
}
// Step 5. Return result.
result
}
/// <https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp>
fn generate_a_segment_wildcard_regexp(options: Options) -> String {
// Step 1. Let result be "[^".
let mut result = String::from("[^");
// Step 2. Append the result of running escape a regexp string given optionss
// delimiter code point to the end of result.
result.push_str(&escape_a_regexp_string(
&options
.delimiter_code_point
.map(|c| c.to_string())
.unwrap_or_default(),
));
// Step 3. Append "]+?" to the end of result.
result.push_str("]+?");
// Step 4. Return result.
result
}
impl PartModifier {
/// <https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string>
fn convert_to_string(&self) -> &'static str {
match self {
// Step 1. If modifier is "zero-or-more", then return "*".
Self::ZeroOrMore => "*",
// Step 2. If modifier is "optional", then return "?".
Self::Optional => "?",
// Step 3. If modifier is "one-or-more", then return "+".
Self::OneOrMore => "+",
// Step 4. Return the empty string.
_ => "",
}
}
}
impl Options {
/// <https://urlpattern.spec.whatwg.org/#hostname-options>
const HOSTNAME: Self = Self {
delimiter_code_point: Some('.'),
prefix_code_point: None,
ignore_case: false,
};
/// <https://urlpattern.spec.whatwg.org/#pathname-options>
const PATHNAME: Self = Self {
delimiter_code_point: Some('/'),
prefix_code_point: Some('/'),
ignore_case: false,
};
}

View file

@ -759,6 +759,10 @@ Dictionaries = {
'derives': ['Clone', 'Copy'],
},
'URLPatternInit': {
'derives': ['Clone'],
},
'XRWebGLLayerInit': {
'derives': ['Clone', 'Copy'],
},

View file

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// https://urlpattern.spec.whatwg.org/#urlpattern
typedef /* USVString or */ URLPatternInit URLPatternInput;
[Exposed=(Window,Worker), Pref="dom_urlpattern_enabled"]
interface URLPattern {
// constructor(URLPatternInput input, USVString baseURL, optional URLPatternOptions options = {});
[Throws] constructor(optional URLPatternInput input = {}, optional URLPatternOptions options = {});
// boolean test(optional URLPatternInput input = {}, optional USVString baseURL);
// URLPatternResult? exec(optional URLPatternInput input = {}, optional USVString baseURL);
readonly attribute USVString protocol;
readonly attribute USVString username;
readonly attribute USVString password;
readonly attribute USVString hostname;
readonly attribute USVString port;
readonly attribute USVString pathname;
readonly attribute USVString search;
readonly attribute USVString hash;
readonly attribute boolean hasRegExpGroups;
};
dictionary URLPatternInit {
USVString protocol;
USVString username;
USVString password;
USVString hostname;
USVString port;
USVString pathname;
USVString search;
USVString hash;
USVString baseURL;
};
dictionary URLPatternOptions {
boolean ignoreCase = false;
};
// dictionary URLPatternResult {
// sequence<URLPatternInput> inputs;
// URLPatternComponentResult protocol;
// URLPatternComponentResult username;
// URLPatternComponentResult password;
// URLPatternComponentResult hostname;
// URLPatternComponentResult port;
// URLPatternComponentResult pathname;
// URLPatternComponentResult search;
// URLPatternComponentResult hash;
// };
// dictionary URLPatternComponentResult {
// USVString input;
// record<USVString, (USVString or undefined)> groups;
// };

View file

@ -90,6 +90,7 @@ WEBIDL_STANDARDS = [
b"//html.spec.whatwg.org",
b"//streams.spec.whatwg.org",
b"//url.spec.whatwg.org",
b"//urlpattern.spec.whatwg.org",
b"//xhr.spec.whatwg.org",
b"//w3c.github.io",
b"//heycam.github.io/webidl",

View file

@ -1 +1 @@
prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true"]
prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true", "dom_urlpattern_enabled:true"]

View file

@ -464,9 +464,6 @@
[Pattern: ["http://🚲.com/"\] Inputs: ["http://🚲.com/"\]]
expected: FAIL
[Pattern: ["http://\\ud83d \\udeb2"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"hostname":"\\ud83d \\udeb2"}\] Inputs: undefined]
expected: FAIL
@ -629,12 +626,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["example.com/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["http{s}?://{*.}?example.com/:product/:endpoint"\] Inputs: ["https://sub.example.com/foo/bar"\]]
expected: FAIL
@ -683,33 +674,18 @@
[Pattern: ["https://example.com/"\] Inputs: ["https://example.com:8080/"\]]
expected: FAIL
[Pattern: ["data:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["data\\\\:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["{https://}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example(.com/)foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["(https://)example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub{.}}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub(?:.))?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
@ -722,9 +698,6 @@
[Pattern: ["foo://bar"\] Inputs: ["foo://bad_url_browser_interop"\]]
expected: FAIL
[Pattern: ["(café)://foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://example.com/foo?bar#baz"\] Inputs: [{"protocol":"https:","search":"?bar","hash":"#baz","baseURL":"http://example.com/foo"}\]]
expected: FAIL
@ -812,27 +785,12 @@
[Pattern: [{"hostname":"*\\\\:1\]"}\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://foo{{@}}example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["https://foo{@example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["data\\\\:text/javascript,let x = 100/:tens?5;"\] Inputs: ["data:text/javascript,let x = 100/5;"\]]
expected: FAIL
[Pattern: [{"pathname":"/:id/:id"}\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined]
expected: FAIL
[Pattern: ["/foo",""\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo"},"https://example.com"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":":name*"}\] Inputs: [{"pathname":"foobar"}\]]
expected: FAIL
@ -1022,9 +980,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080",{"ignoreCase":true}\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo?bar#baz",{"ignoreCase":true},"https://example.com:8080"\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\] Inputs: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\]]
expected: FAIL
@ -1516,9 +1471,6 @@
[Pattern: ["http://🚲.com/"\] Inputs: ["http://🚲.com/"\]]
expected: FAIL
[Pattern: ["http://\\ud83d \\udeb2"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"hostname":"\\ud83d \\udeb2"}\] Inputs: undefined]
expected: FAIL
@ -1681,12 +1633,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["example.com/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["http{s}?://{*.}?example.com/:product/:endpoint"\] Inputs: ["https://sub.example.com/foo/bar"\]]
expected: FAIL
@ -1735,33 +1681,18 @@
[Pattern: ["https://example.com/"\] Inputs: ["https://example.com:8080/"\]]
expected: FAIL
[Pattern: ["data:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["data\\\\:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["{https://}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example(.com/)foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["(https://)example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub{.}}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub(?:.))?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
@ -1774,9 +1705,6 @@
[Pattern: ["foo://bar"\] Inputs: ["foo://bad_url_browser_interop"\]]
expected: FAIL
[Pattern: ["(café)://foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://example.com/foo?bar#baz"\] Inputs: [{"protocol":"https:","search":"?bar","hash":"#baz","baseURL":"http://example.com/foo"}\]]
expected: FAIL
@ -1864,27 +1792,12 @@
[Pattern: [{"hostname":"*\\\\:1\]"}\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://foo{{@}}example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["https://foo{@example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["data\\\\:text/javascript,let x = 100/:tens?5;"\] Inputs: ["data:text/javascript,let x = 100/5;"\]]
expected: FAIL
[Pattern: [{"pathname":"/:id/:id"}\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined]
expected: FAIL
[Pattern: ["/foo",""\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo"},"https://example.com"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":":name*"}\] Inputs: [{"pathname":"foobar"}\]]
expected: FAIL
@ -2074,9 +1987,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080",{"ignoreCase":true}\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo?bar#baz",{"ignoreCase":true},"https://example.com:8080"\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\] Inputs: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\]]
expected: FAIL

View file

@ -467,9 +467,6 @@
[Pattern: ["http://🚲.com/"\] Inputs: ["http://🚲.com/"\]]
expected: FAIL
[Pattern: ["http://\\ud83d \\udeb2"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"hostname":"\\ud83d \\udeb2"}\] Inputs: undefined]
expected: FAIL
@ -632,12 +629,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["example.com/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["http{s}?://{*.}?example.com/:product/:endpoint"\] Inputs: ["https://sub.example.com/foo/bar"\]]
expected: FAIL
@ -686,33 +677,18 @@
[Pattern: ["https://example.com/"\] Inputs: ["https://example.com:8080/"\]]
expected: FAIL
[Pattern: ["data:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["data\\\\:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["{https://}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example(.com/)foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["(https://)example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub{.}}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub(?:.))?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
@ -725,9 +701,6 @@
[Pattern: ["foo://bar"\] Inputs: ["foo://bad_url_browser_interop"\]]
expected: FAIL
[Pattern: ["(café)://foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://example.com/foo?bar#baz"\] Inputs: [{"protocol":"https:","search":"?bar","hash":"#baz","baseURL":"http://example.com/foo"}\]]
expected: FAIL
@ -815,27 +788,12 @@
[Pattern: [{"hostname":"*\\\\:1\]"}\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://foo{{@}}example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["https://foo{@example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["data\\\\:text/javascript,let x = 100/:tens?5;"\] Inputs: ["data:text/javascript,let x = 100/5;"\]]
expected: FAIL
[Pattern: [{"pathname":"/:id/:id"}\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined]
expected: FAIL
[Pattern: ["/foo",""\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo"},"https://example.com"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":":name*"}\] Inputs: [{"pathname":"foobar"}\]]
expected: FAIL
@ -1025,9 +983,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080",{"ignoreCase":true}\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo?bar#baz",{"ignoreCase":true},"https://example.com:8080"\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\] Inputs: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\]]
expected: FAIL
@ -1516,9 +1471,6 @@
[Pattern: ["http://🚲.com/"\] Inputs: ["http://🚲.com/"\]]
expected: FAIL
[Pattern: ["http://\\ud83d \\udeb2"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"hostname":"\\ud83d \\udeb2"}\] Inputs: undefined]
expected: FAIL
@ -1681,12 +1633,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080"\] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["example.com/foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["http{s}?://{*.}?example.com/:product/:endpoint"\] Inputs: ["https://sub.example.com/foo/bar"\]]
expected: FAIL
@ -1735,33 +1681,18 @@
[Pattern: ["https://example.com/"\] Inputs: ["https://example.com:8080/"\]]
expected: FAIL
[Pattern: ["data:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["data\\\\:foobar"\] Inputs: ["data:foobar"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub.}?example{.com/}foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["{https://}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub.)?example(.com/)foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["(https://)example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://{sub{.}}example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
[Pattern: ["https://(sub(?:.))?example.com/foo"\] Inputs: ["https://example.com/foo"\]]
expected: FAIL
@ -1774,9 +1705,6 @@
[Pattern: ["foo://bar"\] Inputs: ["foo://bad_url_browser_interop"\]]
expected: FAIL
[Pattern: ["(café)://foo"\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://example.com/foo?bar#baz"\] Inputs: [{"protocol":"https:","search":"?bar","hash":"#baz","baseURL":"http://example.com/foo"}\]]
expected: FAIL
@ -1864,27 +1792,12 @@
[Pattern: [{"hostname":"*\\\\:1\]"}\] Inputs: undefined]
expected: FAIL
[Pattern: ["https://foo{{@}}example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["https://foo{@example.com"\] Inputs: ["https://foo@example.com"\]]
expected: FAIL
[Pattern: ["data\\\\:text/javascript,let x = 100/:tens?5;"\] Inputs: ["data:text/javascript,let x = 100/5;"\]]
expected: FAIL
[Pattern: [{"pathname":"/:id/:id"}\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo","baseURL":""}\] Inputs: undefined]
expected: FAIL
[Pattern: ["/foo",""\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":"/foo"},"https://example.com"\] Inputs: undefined]
expected: FAIL
[Pattern: [{"pathname":":name*"}\] Inputs: [{"pathname":"foobar"}\]]
expected: FAIL
@ -2074,9 +1987,6 @@
[Pattern: ["/foo?bar#baz","https://example.com:8080",{"ignoreCase":true}\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: ["/foo?bar#baz",{"ignoreCase":true},"https://example.com:8080"\] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}\]]
expected: FAIL
[Pattern: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\] Inputs: [{"search":"foo","baseURL":"https://example.com/a/+/b"}\]]
expected: FAIL

View file

@ -13503,14 +13503,14 @@
]
],
"interfaces.https.html": [
"ad0b03ac70483c152220978cee8b49e74b330fc6",
"3c6184a527d0f5b993e753ce22012210c56a40e5",
[
null,
{}
]
],
"interfaces.worker.js": [
"f93f9c9f6c877e1915217b64591fe7e34fa7244c",
"dde3a2cb97675c27d292fb3f15e98bc383d58a4a",
[
"mozilla/interfaces.worker.html",
{}

View file

@ -1 +1 @@
prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true"]
prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true", "dom_urlpattern_enabled:true"]

View file

@ -282,6 +282,7 @@ test_interfaces([
"TreeWalker",
"UIEvent",
"URL",
"URLPattern",
"URLSearchParams",
"ValidityState",
"VideoTrack",

View file

@ -68,6 +68,7 @@ test_interfaces([
"TextDecoder",
"TextEncoder",
"URL",
"URLPattern",
"URLSearchParams",
"WebGLActiveInfo",
"WebGLBuffer",