Batch fix: svg: a selector's class checks count against the CSS matching budget (#2159)

This commit is contained in:
aiongg
2026-10-24 00:37:13 +00:00
parent b6c1cff444
commit 5e0dfcfd41
2 changed files with 23 additions and 5 deletions
+7 -5
View File
@@ -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));
+16
View File
@@ -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##"<use href="#r" x="{i}"/>"##)).collect();
let svg = |uses: &str| {
format!(
r##"<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><style>{rule}{{fill:red}}</style><defs><rect id="r" width="1" height="1" class="z"/></defs>{uses}</svg>"##
)
};
let e = parse(svg(&uses).as_bytes()).unwrap_err();
assert!(e.to_string().contains("<style> rules"), "{e}");
let few: String = uses.split_inclusive("/>").take(10).collect();
assert!(parse(svg(&few).as_bytes()).is_ok());
}
#[test]
fn matching_work_past_the_budget_errors_without_doing_it() {
// Within both sheet caps, yet one rectangle would apply 10 000 x 5 001 declarations.