From f07b2abff25786b9945e2b2be125e2c440f7b35e Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 17 Jul 2026 10:22:35 +0800 Subject: [PATCH] fix(selectors): accept webkit compatibility pseudos --- .../wpt-cross-current/failed-cases.txt | 26 ++++++ .../wpt-cross-current/passed-cases.txt | 26 ------ moli-selector/src/cssom_selector.rs | 86 +++++++++++++++++-- moli-selector/src/lib.rs | 24 ++++++ moli-selector/src/stylo/selector_parse.rs | 40 +++++++-- 5 files changed, 164 insertions(+), 38 deletions(-) diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 19d83ea51..00aece726 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -30,6 +30,7 @@ content-security-policy/resource-hints/prefetch-generate-directives.html content-security-policy/resource-hints/prefetch-no-csp.html content-security-policy/sandbox/autoplay-disabled-by-csp.html content-security-policy/sandbox/window-reuse-sandboxed.html +content-security-policy/script-src/non-nonceable-elements.html content-security-policy/script-src/nonce-enforce-blocked.html content-security-policy/script-src/script-src-trusted_types_eval_DedicatedWorker.html content-security-policy/securitypolicyviolation/blockeduri-inline.html @@ -2552,6 +2553,7 @@ dom/ranges/tentative/OpaqueRange-range-updates.html dom/ranges/tentative/OpaqueRange-supported-elements.html dom/ranges/tentative/OpaqueRange-unsupported-elements.html dom/ranges/tentative/OpaqueRange-validation.html +domparsing/DOMParser-parseFromString-html.html domparsing/DOMParser-parseFromString-url-base-pushstate.html domparsing/DOMParser-parseFromString-url-base.html domparsing/DOMParser-parseFromString-url-moretests.html @@ -3607,15 +3609,24 @@ svg/types/scripted/SVGLengthList-basics.html svg/types/scripted/SVGList-parse-invalid-clears-items.html svg/types/scripted/SVGMatrix-tentative.html svg/types/scripted/SVGPoint.html +trusted-types/Document-write-appending-line-feed.html trusted-types/Document-write.html trusted-types/Element-setAttribute-respects-Elements-node-documents-globals-CSP-after-adoption-from-non-TT-realm.html +trusted-types/GlobalEventHandlers-onclick.html trusted-types/HTMLElement-generic.html trusted-types/ServiceWorkerContainer-register-from-DedicatedWorker.https.html trusted-types/ServiceWorkerContainer-register-from-ServiceWorker.https.html trusted-types/ServiceWorkerContainer-register-from-SharedWorker.https.html trusted-types/TrustedType-AttributeNodes.html +trusted-types/TrustedTypePolicyFactory-constants.html +trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none.html +trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.html +trusted-types/TrustedTypePolicyFactory-defaultPolicy.html +trusted-types/TrustedTypePolicyFactory-getAttributeType-namespace.html +trusted-types/TrustedTypePolicyFactory-getAttributeType-svg.html trusted-types/TrustedTypePolicyFactory-getAttributeType.html trusted-types/Window-TrustedTypes.html +trusted-types/block-Document-execCommand.html trusted-types/block-string-assignment-to-Document-write.html trusted-types/block-string-assignment-to-Element-outerHTML.html trusted-types/block-string-assignment-to-Element-setAttribute.html @@ -3627,8 +3638,13 @@ trusted-types/block-string-assignment-to-attribute-via-attribute-node.html trusted-types/eval-function-constructor-untrusted-arguments-and-applying-default-policy.html trusted-types/eval-function-constructor.html trusted-types/inheriting-csp-for-local-schemes.html +trusted-types/legacy-trusted-script-urls.html trusted-types/modify-attributes-in-callback.html trusted-types/require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html +trusted-types/script-enforcement-006.html +trusted-types/script-enforcement-007.html +trusted-types/script-enforcement-010.html +trusted-types/script-enforcement-011.html trusted-types/set-attributes-require-trusted-types-default-policy.html trusted-types/set-attributes-require-trusted-types-no-default-policy-error-cases.html trusted-types/set-attributes-require-trusted-types-no-default-policy.html @@ -3637,17 +3653,27 @@ trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002.html trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-003.html trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html +trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html trusted-types/trusted-types-createHTMLDocument.html +trusted-types/trusted-types-duplicate-names-list.html +trusted-types/trusted-types-duplicate-names-without-enforcement.html +trusted-types/trusted-types-duplicate-names.html +trusted-types/trusted-types-eval-reporting-report-only.html trusted-types/trusted-types-event-handlers.html trusted-types/trusted-types-report-only.html +trusted-types/trusted-types-reporting-check-report-DedicatedWorker-create-policy.html trusted-types/trusted-types-reporting-check-report-DedicatedWorker-sink-mismatch.html +trusted-types/trusted-types-reporting-clipping-of-sample.html trusted-types/trusted-types-reporting-for-DedicatedWorker-ServiceWorkerContainer-register.https.html +trusted-types/trusted-types-reporting-for-Document-execCommand.html trusted-types/trusted-types-reporting-for-Document-write.html trusted-types/trusted-types-reporting-for-Element-setAttribute.html trusted-types/trusted-types-reporting-for-HTMLIFrameElement-srcdoc.html trusted-types/trusted-types-reporting-for-ServiceWorker-ServiceWorkerContainer-register.https.html trusted-types/trusted-types-reporting-for-SharedWorker-ServiceWorkerContainer-register.https.html +trusted-types/trusted-types-reporting.html trusted-types/trusted-types-secondary-document.html +trusted-types/trusted-types-svg-script-set-href.html uievents/interface/click-event.htm uievents/order-of-events/focus-events/focus-automated-blink-webkit.html url/a-element.html?exclude=(file|javascript|mailto) diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 150988538..3976a2be7 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -211,7 +211,6 @@ content-security-policy/script-src/hash-always-converted-to-utf-8/iso-8859-9.htm content-security-policy/script-src/hash-always-converted-to-utf-8/utf-8-lone-surrogate.html content-security-policy/script-src/hash-always-converted-to-utf-8/utf-8.html content-security-policy/script-src/javascript-window-open-blocked.html -content-security-policy/script-src/non-nonceable-elements.html content-security-policy/script-src/script-src-1_1.html content-security-policy/script-src/script-src-1_10_1.html content-security-policy/script-src/script-src-1_2.html @@ -4098,7 +4097,6 @@ dom/traversal/TreeWalker.html dom/window-extends-event-target.html dom/xpath-result-single-node-value-nullable.html domparsing/DOMParser-parseFromString-encoding.html -domparsing/DOMParser-parseFromString-html.html domparsing/DOMParser-parseFromString-stylesheets.html domparsing/DOMParser-parseFromString-xml-parsererror.html domparsing/createContextualFragment-in-detached-xml-document-crash.html @@ -8604,14 +8602,12 @@ trusted-types/DedicatedWorker-eval.html trusted-types/DedicatedWorker-importScripts.html trusted-types/DedicatedWorker-setTimeout-setInterval.html trusted-types/Document-execCommand.html -trusted-types/Document-write-appending-line-feed.html trusted-types/Element-insertAdjacentHTML.html trusted-types/Element-outerHTML.html trusted-types/Element-setAttribute-respects-Elements-node-documents-globals-CSP-after-adoption-from-TT-realm.html trusted-types/Element-setAttribute.html trusted-types/Element-setAttributeNS.html trusted-types/Element-toggleAttribute.html -trusted-types/GlobalEventHandlers-onclick.html trusted-types/HTMLScriptElement-internal-slot.html trusted-types/Node-multiple-arguments-tt-enforced.html trusted-types/Node-multiple-arguments.html @@ -8628,7 +8624,6 @@ trusted-types/SharedWorker-setTimeout-setInterval.html trusted-types/TrustedTypePolicy-CSP-no-name.html trusted-types/TrustedTypePolicy-CSP-wildcard.html trusted-types/TrustedTypePolicy-createXXX.html -trusted-types/TrustedTypePolicyFactory-constants.html trusted-types/TrustedTypePolicyFactory-createPolicy-createXYZTests.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-case.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-case2.html @@ -8636,18 +8631,12 @@ trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-noNamesGiven.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none-none-name.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none-none.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none-skip.html -trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-wildcard.html -trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.html trusted-types/TrustedTypePolicyFactory-createPolicy-non-tt-policy-name.html trusted-types/TrustedTypePolicyFactory-createPolicy-unenforced.html -trusted-types/TrustedTypePolicyFactory-defaultPolicy.html -trusted-types/TrustedTypePolicyFactory-getAttributeType-namespace.html -trusted-types/TrustedTypePolicyFactory-getAttributeType-svg.html trusted-types/TrustedTypePolicyFactory-isXXX.html trusted-types/Window-block-eval-function-constructor.html trusted-types/Window-setTimeout-setInterval.html -trusted-types/block-Document-execCommand.html trusted-types/block-string-assignment-to-DOMParser-parseFromString.html trusted-types/block-string-assignment-to-DedicatedWorker-setTimeout-setInterval.html trusted-types/block-string-assignment-to-Document-parseHTMLUnsafe.html @@ -8676,7 +8665,6 @@ trusted-types/eval-no-csp-no-tt.html trusted-types/eval-with-non-trusted-script-object.html trusted-types/eval-with-permissive-csp.html trusted-types/get-trusted-types-compliant-attribute-value.html -trusted-types/legacy-trusted-script-urls.html trusted-types/legacy-trusted-scripts.html trusted-types/navigate-to-javascript-url-001.html trusted-types/navigate-to-javascript-url-002.html @@ -8695,10 +8683,6 @@ trusted-types/script-enforcement-002.html trusted-types/script-enforcement-003.html trusted-types/script-enforcement-004.html trusted-types/script-enforcement-005.html -trusted-types/script-enforcement-006.html -trusted-types/script-enforcement-007.html -trusted-types/script-enforcement-010.html -trusted-types/script-enforcement-011.html trusted-types/script-enforcement-012.html trusted-types/script-enforcement-013.html trusted-types/script-enforcement-014.html @@ -8708,22 +8692,14 @@ trusted-types/script-enforcement-017.html trusted-types/set-attributes-no-require-trusted-types.html trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-003.html -trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html trusted-types/trusted-types-duplicate-names-list-report-only.html -trusted-types/trusted-types-duplicate-names-list.html -trusted-types/trusted-types-duplicate-names-without-enforcement.html -trusted-types/trusted-types-duplicate-names.html trusted-types/trusted-types-eval-reporting-no-unsafe-eval.html -trusted-types/trusted-types-eval-reporting-report-only.html -trusted-types/trusted-types-reporting-check-report-DedicatedWorker-create-policy.html -trusted-types/trusted-types-reporting-clipping-of-sample.html trusted-types/trusted-types-reporting-for-DOMParser-parseFromString.html trusted-types/trusted-types-reporting-for-DedicatedWorker-DedicatedWorker-constructor.html trusted-types/trusted-types-reporting-for-DedicatedWorker-eval.html trusted-types/trusted-types-reporting-for-DedicatedWorker-function-constructor.html trusted-types/trusted-types-reporting-for-DedicatedWorker-importScripts.html trusted-types/trusted-types-reporting-for-DedicatedWorker-setTimeout-setInterval.html -trusted-types/trusted-types-reporting-for-Document-execCommand.html trusted-types/trusted-types-reporting-for-Document-parseHTMLUnsafe.html trusted-types/trusted-types-reporting-for-Element-innerHTML.html trusted-types/trusted-types-reporting-for-Element-insertAdjacentHTML.html @@ -8751,11 +8727,9 @@ trusted-types/trusted-types-reporting-for-Window-SharedWorker-constructor.html trusted-types/trusted-types-reporting-for-Window-eval.html trusted-types/trusted-types-reporting-for-Window-function-constructor.html trusted-types/trusted-types-reporting-for-Window-setTimeout-setInterval.html -trusted-types/trusted-types-reporting.html trusted-types/trusted-types-sandbox-allow-scripts.html trusted-types/trusted-types-sandbox-no-allow-scripts.html trusted-types/trusted-types-source-file-path.html -trusted-types/trusted-types-svg-script-set-href.html trusted-types/trusted-types-tojson.html trusted-types/tt-block-eval.html uievents/constructors/inputevent-constructor.html diff --git a/moli-selector/src/cssom_selector.rs b/moli-selector/src/cssom_selector.rs index e7b8d108a..4eee47261 100644 --- a/moli-selector/src/cssom_selector.rs +++ b/moli-selector/src/cssom_selector.rs @@ -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 { + 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 { 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)" )); diff --git a/moli-selector/src/lib.rs b/moli-selector/src/lib.rs index 825a6e13c..cce110505 100644 --- a/moli-selector/src/lib.rs +++ b/moli-selector/src/lib.rs @@ -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] diff --git a/moli-selector/src/stylo/selector_parse.rs b/moli-selector/src/stylo/selector_parse.rs index 3cfa6863b..3feec410c 100644 --- a/moli-selector/src/stylo/selector_parse.rs +++ b/moli-selector/src/stylo/selector_parse.rs @@ -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(|_| ()) }