From 5e0dfcfd41eb158cd9502b2d73a8943701533850 Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 24 Oct 2026 00:37:13 +0000 Subject: [PATCH] Batch fix: svg: a selector's class checks count against the CSS matching budget (#2159) --- crates/svg/src/lib.rs | 12 +++++++----- crates/svg/src/tests.rs | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/svg/src/lib.rs b/crates/svg/src/lib.rs index efa6aee7..60f39adc 100644 --- a/crates/svg/src/lib.rs +++ b/crates/svg/src/lib.rs @@ -635,14 +635,16 @@ impl<'a, 'i> Parser<'a, 'i> { if !self.charge_css(buckets.iter().map(|b| b.len()).fold(0, usize::saturating_add)) { return; } - let mut matching: Vec<&Rule> = buckets - .iter() - .flat_map(|b| b.iter()) - .filter_map(|&i| css.rules.get(i)) + let candidates = || buckets.iter().flat_map(|b| b.iter()).filter_map(|&i| css.rules.get(i)); + // A selector like `.z.z.z…` checks each of its classes: charge them too. + if !self.charge_css(candidates().map(|r| r.classes.len()).fold(0, usize::saturating_add)) { + return; + } + let mut matching: Vec<&Rule> = candidates() .filter(|r| { r.tag.as_deref().is_none_or(|t| t == tag) && r.id.as_deref().is_none_or(|i| Some(i) == id) - && r.classes.iter().all(|c| classes.contains(&c.as_str())) + && r.classes.iter().all(|c| classes.binary_search(&c.as_str()).is_ok()) }) .collect(); matching.sort_by_key(|r| (r.specificity, r.order)); diff --git a/crates/svg/src/tests.rs b/crates/svg/src/tests.rs index 5fbc47c6..466c9053 100644 --- a/crates/svg/src/tests.rs +++ b/crates/svg/src/tests.rs @@ -379,6 +379,22 @@ fn huge_class_lists_count_against_the_matching_budget() { assert!(parse(svg(&few).as_bytes()).is_ok()); } +#[test] +fn a_selector_with_many_classes_counts_against_the_matching_budget() { + // `.z.z.z…` lands in one class bucket, but each match checks every class it lists. + let rule = ".z".repeat(300_000); + let uses: String = (0..200).map(|i| format!(r##""##)).collect(); + let svg = |uses: &str| { + format!( + r##"{uses}"## + ) + }; + let e = parse(svg(&uses).as_bytes()).unwrap_err(); + assert!(e.to_string().contains("