use std::convert::Infallible; use avril_core::{Extension, Op, v8}; use scoped_tls_hkt::scoped_thread_local; use urlpattern::{RegexSyntax, UrlPatternOptions, quirks, regexp::RegExp}; // The parser's RegExp trait has no context parameter. Borrow the current op's evaluator // only for the synchronous parse, without retaining V8 handles or erasing scope lifetimes. type Evaluator<'a> = dyn FnMut(&str, &str, Option<&str>) -> Result + 'a; scoped_thread_local!(static mut EVALUATOR: for<'a> &'a mut Evaluator<'a>); struct JsRegExp { source: String, flags: String, } impl RegExp for JsRegExp { fn syntax() -> RegexSyntax { RegexSyntax::EcmaScript } fn parse(source: &str, flags: &str, _force_eval: bool) -> Result { EVALUATOR.with(|evaluate| evaluate(source, flags, None))?; Ok(Self { source: source.into(), flags: flags.into(), }) } fn matches<'a>(&self, text: &'a str) -> Option>> { // During parsing, only the existence of a protocol match is used. EVALUATOR .with(|evaluate| evaluate(&self.source, &self.flags, Some(text))) .ok()? .then(Vec::new) } fn pattern_string(&self) -> &str { &self.source } } pub fn init() -> Extension { Extension { name: "ext:urlpattern", dependencies: &["ext:web"], ops: vec![ Op::with_scope( "op_urlpattern_parse", |scope, (input, base, ignore_case): ( quirks::StringOrInit<'static>, Option, bool, )| { let mut evaluate = |source: &str, flags: &str, text: Option<&str>| { v8::tc_scope!(let scope, scope); let source = v8::String::new(scope, source).ok_or(())?; let mut options = v8::RegExpCreationFlags::UNICODE_SETS; if flags.contains('i') { options |= v8::RegExpCreationFlags::IGNORE_CASE; } let regex = v8::RegExp::new(scope, source, options).ok_or(())?; // V8's Exec API otherwise dispatches through the public exec property. let null = v8::null(scope).into(); regex.set_prototype(scope, null).ok_or(())?; match text { Some(text) => { let text = v8::String::new(scope, text).ok_or(())?; Ok(regex .exec(scope, text) .is_some_and(|result| !result.is_null())) } None => Ok(true), } }; let parsed = EVALUATOR.set(&mut evaluate, || { let init = match input { quirks::StringOrInit::String(input) => { let base = base .as_deref() .map(url::Url::parse) .transpose() .map_err(urlpattern::Error::Url)?; urlpattern::UrlPatternInit::parse_constructor_string::( &input, base, )? } input => { quirks::process_construct_pattern_input(input, base.as_deref())? } }; urlpattern::UrlPattern::::parse( init, UrlPatternOptions { regex_syntax: RegexSyntax::EcmaScript, ignore_case, }, ) }); let parsed = parsed.and_then(|pattern| { let has_regexp_groups = pattern.has_regexp_groups(); let components = [ pattern.protocol, pattern.username, pattern.password, pattern.hostname, pattern.port, pattern.pathname, pattern.search, pattern.hash, ] .into_iter() .map(|c| { let regex = c.regexp?; let flags = if regex.flags.contains('i') { "vi" } else { "v" }; Ok((c.pattern_string, regex.source, c.group_name_list, flags)) }) .collect::, urlpattern::Error>>()?; Ok((components, has_regexp_groups)) }); let (pattern, error) = match parsed { Ok(pattern) => (Some(pattern), None), Err(error) => (None, Some(error.to_string())), }; Ok::<_, Infallible>((pattern, error)) }, ), Op::new( "op_urlpattern_match_input", |(input, base): (quirks::StringOrInit<'static>, Option)| { let parsed = quirks::process_match_input(input, base.as_deref()); let (input, error) = match parsed { Ok(input) => ( input.and_then(|(input, _)| quirks::parse_match_input(input)), None, ), Err(error) => (None, Some(error.to_string())), }; Ok::<_, Infallible>((input, error)) }, ), ], modules: &[( "ext:urlpattern/main.js", include_str!(concat!(env!("OUT_DIR"), "/urlpattern.js")), )], entry_point: Some("ext:urlpattern/main.js"), } }