mirror of
https://github.com/lexmount/moli.git
synced 2026-10-08 08:01:01 +00:00
fix(selectors): accept webkit compatibility pseudos
This commit is contained in:
@@ -2549,7 +2549,6 @@ css/selectors/parsing/parse-is-where.html
|
||||
css/selectors/parsing/parse-part.html
|
||||
css/selectors/parsing/parse-slotted.html
|
||||
css/selectors/selectors-4/lang-singleton-subtag-matching.html
|
||||
css/selectors/webkit-pseudo-element.html
|
||||
custom-elements/parser/parser-uses-registry-of-owner-document.html
|
||||
custom-elements/perform-microtask-checkpoint-before-construction.html
|
||||
custom-elements/reactions/Document.html
|
||||
@@ -3233,7 +3232,6 @@ html/semantics/scripting-1/the-template-element/template-element/node-document-c
|
||||
html/semantics/scripting-1/the-template-element/template-element/template-content-hierarcy.html
|
||||
html/semantics/scripting-1/the-template-element/template-element/template-content-node-document.html
|
||||
html/semantics/sections/headingoffset-and-headingreset.html
|
||||
html/semantics/selectors/pseudo-classes/autofill.html
|
||||
html/semantics/selectors/pseudo-classes/dir.html
|
||||
html/semantics/selectors/pseudo-classes/readwrite-readonly.html
|
||||
html/semantics/selectors/pseudo-classes/valid-invalid.html
|
||||
|
||||
@@ -3303,6 +3303,7 @@ css/selectors/selector-after-font-family.html
|
||||
css/selectors/selector-placeholder-shown-emptify-placeholder.html
|
||||
css/selectors/selectors-attr-many-2.html
|
||||
css/selectors/selectors-case-sensitive-001.html
|
||||
css/selectors/webkit-pseudo-element.html
|
||||
css/selectors/x-pseudo-element.html
|
||||
custom-elements/CustomElementRegistry-constructor-and-callbacks-are-held-strongly.html
|
||||
custom-elements/CustomElementRegistry-getName.html
|
||||
@@ -6254,6 +6255,7 @@ html/semantics/scripting-1/the-template-element/template-element/template-descen
|
||||
html/semantics/scripting-1/the-template-element/template-element/template-descendant-frameset.html
|
||||
html/semantics/scripting-1/the-template-element/template-element/template-descendant-head.html
|
||||
html/semantics/sections/headingoffset-and-headingreset.html
|
||||
html/semantics/selectors/pseudo-classes/autofill.html
|
||||
html/semantics/selectors/pseudo-classes/checked-type-change.html
|
||||
html/semantics/selectors/pseudo-classes/checked.html
|
||||
html/semantics/selectors/pseudo-classes/default.html
|
||||
|
||||
@@ -668,6 +668,43 @@ pub(crate) fn dom_api_selector_list_contains_known_pseudo_element(selector_text:
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn webkit_compat_pseudo_element_validation_selector(
|
||||
selector_text: &str,
|
||||
) -> Option<String> {
|
||||
let mut input = ParserInput::new(selector_text);
|
||||
let mut parser = Parser::new(&mut input);
|
||||
let mut replacements = Vec::new();
|
||||
|
||||
while !parser.is_exhausted() {
|
||||
let Ok(token) = parser.next_including_whitespace_and_comments().cloned() else {
|
||||
return None;
|
||||
};
|
||||
if !matches!(token, Token::Colon) {
|
||||
continue;
|
||||
}
|
||||
let Ok(Token::Colon) = parser.next_including_whitespace_and_comments().cloned() else {
|
||||
continue;
|
||||
};
|
||||
let name_start = parser.position().byte_index();
|
||||
let Ok(Token::Ident(name)) = parser.next_including_whitespace_and_comments().cloned()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if is_unknown_webkit_pseudo_element_name(&name) {
|
||||
replacements.push((name_start, parser.position().byte_index()));
|
||||
}
|
||||
}
|
||||
|
||||
if replacements.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut selector = selector_text.to_owned();
|
||||
for (start, end) in replacements.into_iter().rev() {
|
||||
selector.replace_range(start..end, "part(webkit-compat)");
|
||||
}
|
||||
Some(selector)
|
||||
}
|
||||
|
||||
pub(crate) fn selector_list_has_invalid_terminal_pseudo_element_chain(selector_text: &str) -> bool {
|
||||
let mut input = ParserInput::new(selector_text);
|
||||
let mut parser = Parser::new(&mut input);
|
||||
@@ -721,22 +758,23 @@ fn parse_known_pseudo_element_after_colon(
|
||||
parser: &mut Parser<'_, '_>,
|
||||
) -> Option<KnownPseudoElementKind> {
|
||||
let state = parser.state();
|
||||
if !matches!(
|
||||
let has_double_colon = matches!(
|
||||
parser
|
||||
.next_including_whitespace_and_comments()
|
||||
.cloned()
|
||||
.ok()?,
|
||||
Token::Colon
|
||||
) {
|
||||
);
|
||||
if !has_double_colon {
|
||||
parser.reset(&state);
|
||||
}
|
||||
let Ok(token) = parser.next_including_whitespace_and_comments().cloned() else {
|
||||
return None;
|
||||
};
|
||||
match token {
|
||||
Token::Ident(name) => {
|
||||
is_known_terminal_pseudo_element_name(&name).then_some(KnownPseudoElementKind::Terminal)
|
||||
}
|
||||
Token::Ident(name) => (is_known_terminal_pseudo_element_name(&name)
|
||||
|| (has_double_colon && is_unknown_webkit_pseudo_element_name(&name)))
|
||||
.then_some(KnownPseudoElementKind::Terminal),
|
||||
Token::Function(name) => {
|
||||
let kind = match name.to_ascii_lowercase().as_str() {
|
||||
"cue" | "highlight" => KnownPseudoElementKind::Terminal,
|
||||
@@ -750,6 +788,12 @@ fn parse_known_pseudo_element_after_colon(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_unknown_webkit_pseudo_element_name(name: &str) -> bool {
|
||||
const WEBKIT_PREFIX: &str = "-webkit-";
|
||||
name.get(..WEBKIT_PREFIX.len())
|
||||
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(WEBKIT_PREFIX))
|
||||
}
|
||||
|
||||
fn is_known_terminal_pseudo_element_name(name: &str) -> bool {
|
||||
matches!(
|
||||
name.to_ascii_lowercase().as_str(),
|
||||
@@ -1157,6 +1201,12 @@ mod tests {
|
||||
assert!(dom_api_selector_list_has_only_known_pseudo_elements(
|
||||
"#target::highlight(foo)"
|
||||
));
|
||||
assert!(dom_api_selector_list_has_only_known_pseudo_elements(
|
||||
"span::-WeBkIt-something-invalid"
|
||||
));
|
||||
assert!(!dom_api_selector_list_has_only_known_pseudo_elements(
|
||||
"input:-webkit-autofill"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1178,6 +1228,23 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrites_webkit_compat_pseudo_elements_only_for_stylo_validation() {
|
||||
assert_eq!(
|
||||
webkit_compat_pseudo_element_validation_selector(
|
||||
"span::-WeBkIt-something-invalid:active"
|
||||
)
|
||||
.as_deref(),
|
||||
Some("span::part(webkit-compat):active")
|
||||
);
|
||||
assert_eq!(
|
||||
webkit_compat_pseudo_element_validation_selector(
|
||||
r#"[data-value='::-webkit-something-invalid']"#
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_invalid_terminal_pseudo_element_chains() {
|
||||
assert!(selector_list_has_invalid_terminal_pseudo_element_chain(
|
||||
@@ -1189,6 +1256,15 @@ mod tests {
|
||||
assert!(selector_list_has_invalid_terminal_pseudo_element_chain(
|
||||
"::highlight(foo)::part(label)"
|
||||
));
|
||||
assert!(selector_list_has_invalid_terminal_pseudo_element_chain(
|
||||
"span::-webkit-something-invalid::before"
|
||||
));
|
||||
assert!(!selector_list_has_invalid_terminal_pseudo_element_chain(
|
||||
"span::-webkit-something-invalid:active"
|
||||
));
|
||||
assert!(!selector_list_has_invalid_terminal_pseudo_element_chain(
|
||||
"input:-webkit-autofill::before"
|
||||
));
|
||||
assert!(!selector_list_has_invalid_terminal_pseudo_element_chain(
|
||||
"::part(label)::highlight(foo)"
|
||||
));
|
||||
|
||||
@@ -406,6 +406,12 @@ mod tests {
|
||||
assert!(validate_style_rule_selector_list(".one, main > .two").is_ok());
|
||||
assert!(validate_style_rule_selector_list("::part(mypart):lang(en)").is_ok());
|
||||
assert!(validate_style_rule_selector_list("::part(mypart):dir(ltr)").is_ok());
|
||||
assert!(
|
||||
validate_style_rule_selector_list("span::-webkit-something-invalid:active").is_ok()
|
||||
);
|
||||
assert!(
|
||||
validate_style_rule_selector_list("span::-webkit-something-invalid::before").is_err()
|
||||
);
|
||||
assert!(validate_style_rule_selector_list("").is_err());
|
||||
assert!(validate_style_rule_selector_list("div[").is_err());
|
||||
assert!(validate_style_rule_selector_list(".one,").is_err());
|
||||
@@ -419,6 +425,7 @@ mod tests {
|
||||
assert!(validate_supports_selector_list("::part(mypart):is(:hover)").is_ok());
|
||||
assert!(validate_supports_selector_list("::part(mypart):is(:first-child)").is_err());
|
||||
assert!(validate_supports_selector_list("::part(mypart):where(:first-child)").is_err());
|
||||
assert!(validate_supports_selector_list("span::-webkit-something-invalid").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1276,6 +1283,23 @@ mod tests {
|
||||
.query_selector_host(&host, "invalid# ::before")
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
engine
|
||||
.query_selector_host(&host, "span::-webkit-something-invalid")
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
engine
|
||||
.query_selector_host(&host, "span::-webkit-something-invalid:active")
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
assert!(
|
||||
engine
|
||||
.query_selector_host(&host, "span::-webkit-something-invalid::before")
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::cssom_selector::{
|
||||
dom_api_selector_list_has_only_known_pseudo_elements,
|
||||
dom_api_selector_text_with_trailing_attribute_recovery,
|
||||
selector_list_has_invalid_terminal_pseudo_element_chain,
|
||||
webkit_compat_pseudo_element_validation_selector,
|
||||
};
|
||||
use cssparser::ToCss;
|
||||
use selectors::parser::ParseRelative;
|
||||
@@ -83,12 +84,20 @@ fn parse_dom_api_selector_list_text_for_url(
|
||||
}
|
||||
|
||||
pub(crate) fn validate_style_rule_selector_list(selector: &str) -> Result<(), SelectorError> {
|
||||
parse_style_rule_selector_list_for_url(
|
||||
selector,
|
||||
Url::parse("about:blank").expect("about:blank is valid"),
|
||||
false,
|
||||
)
|
||||
.map(|_| ())
|
||||
if selector_list_has_invalid_terminal_pseudo_element_chain(selector) {
|
||||
return Err(SelectorError::syntax(
|
||||
"terminal pseudo-elements cannot be chained",
|
||||
));
|
||||
}
|
||||
let url = Url::parse("about:blank").expect("about:blank is valid");
|
||||
parse_style_rule_selector_list_for_url(selector, url.clone(), false)
|
||||
.or_else(|error| {
|
||||
let Some(selector) = webkit_compat_pseudo_element_validation_selector(selector) else {
|
||||
return Err(error);
|
||||
};
|
||||
parse_style_rule_selector_list_for_url(&selector, url, false)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_supports_selector_list(selector: &str) -> Result<(), SelectorError> {
|
||||
@@ -121,12 +130,29 @@ pub(crate) fn validate_style_rule_selector_list_with_namespaces(
|
||||
selector: &str,
|
||||
namespace_context: &StyleRuleNamespaceContext,
|
||||
) -> Result<(), SelectorError> {
|
||||
if selector_list_has_invalid_terminal_pseudo_element_chain(selector) {
|
||||
return Err(SelectorError::syntax(
|
||||
"terminal pseudo-elements cannot be chained",
|
||||
));
|
||||
}
|
||||
let url = Url::parse("about:blank").expect("about:blank is valid");
|
||||
parse_style_rule_selector_list_for_url_and_namespaces(
|
||||
selector,
|
||||
Url::parse("about:blank").expect("about:blank is valid"),
|
||||
url.clone(),
|
||||
namespace_context,
|
||||
false,
|
||||
)
|
||||
.or_else(|error| {
|
||||
let Some(selector) = webkit_compat_pseudo_element_validation_selector(selector) else {
|
||||
return Err(error);
|
||||
};
|
||||
parse_style_rule_selector_list_for_url_and_namespaces(
|
||||
&selector,
|
||||
url,
|
||||
namespace_context,
|
||||
false,
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user