Merge pull request #7745 from dotCooCoo/fix/svg-css-not-rule-budget

SVG import: :not() rules test only the rules indexed for the element
This commit is contained in:
BFlat
2026-11-15 12:37:19 +07:00
committed by GitHub
2 changed files with 23 additions and 1 deletions
+3 -1
View File
@@ -556,10 +556,12 @@ impl Styles {
}
/// The properties rules with a `:not()` set on `n`, each with its value after the cascade.
/// Only the rules [`Index::candidates`] gives for `n` are tested, as in [`Styles::matched_bytes`].
pub(super) fn set_by_not_rules(&self, n: XNode) -> Vec<(String, String)> {
let declared = self.declared(n);
let mut out: Vec<(String, String)> = Vec::new();
for r in self.rules.iter().filter(|r| r.selector.has_not() && r.selector.matches(n)) {
let rules = self.index.candidates(n).into_iter().filter_map(|i| self.rules.get(i));
for r in rules.filter(|r| r.selector.has_not() && r.selector.matches(n)) {
for (k, ..) in r.decls.iter() {
if !out.iter().any(|(o, _)| o == k)
&& let Some(v) = declared.get(k)
+20
View File
@@ -334,6 +334,26 @@ fn not_selectors() {
);
}
/// The `:not()` rules filed under classes an element lacks are not tested against it, so
/// [`Styles::set_by_not_rules`] tests no more rules than [`Styles::matched_bytes`] counts.
#[test]
fn not_rules_test_only_the_indexed_candidates() {
const N: usize = 2_000;
let rules: String = (0..N).map(|i| format!(".c{i}:not(.skip) {{ fill: #{i:06x} }}\n")).collect();
let rects: String = (0..N).map(|i| format!(r#"<rect id="r{i}" class="c{i}"/>"#)).collect();
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><style>{rules}</style>{rects}<rect id="skip" class="c0 skip"/></svg>"#);
with_doc(&svg, |doc, st| {
let before = selector_tests();
for i in 0..N {
assert_eq!(st.set_by_not_rules(by_id(doc, &format!("r{i}"))), [("fill".to_string(), format!("#{i:06x}"))]);
}
assert!(st.set_by_not_rules(by_id(doc, "skip")).is_empty());
// Testing every `:not()` rule against every element would test N × N selectors.
let tests = selector_tests() - before;
assert!(tests <= 3 * N as u64, "finding the :not() rules of {N} elements tested {tests} selectors");
});
}
#[test]
fn decl_comments_are_stripped() {
let d = |k: &str, v: &str| (k.to_string(), v.to_string(), false);