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##""##
+ )
+ };
+ let e = parse(svg(&uses).as_bytes()).unwrap_err();
+ assert!(e.to_string().contains("