mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
svg: allocate a CSS block only for accepted selectors; charge class tokens (#2140)
This commit is contained in:
+15
-4
@@ -483,13 +483,14 @@ fn parse_css(css: &str, sheet: &mut StyleSheet) -> Result<(), Error> {
|
||||
if sel.starts_with('@') {
|
||||
continue;
|
||||
}
|
||||
let decls = parse_decls(body);
|
||||
let mut decls = parse_decls(body);
|
||||
sheet.decls = sheet.decls.saturating_add(decls.len());
|
||||
if sheet.decls > MAX_CSS_DECLARATIONS {
|
||||
return Err(Error::Xml(format!("its <style> sheets hold more than {MAX_CSS_DECLARATIONS} declarations")));
|
||||
}
|
||||
let block = sheet.blocks.len();
|
||||
sheet.blocks.push(decls);
|
||||
// Stored with the rule's first accepted selector: a rule of unsupported selectors only
|
||||
// (`a b{}`, repeated) adds no block.
|
||||
let mut block = None;
|
||||
for one in sel.split(',') {
|
||||
let one = one.trim();
|
||||
// Only simple selectors (no combinators / pseudo-classes / attributes).
|
||||
@@ -499,6 +500,11 @@ fn parse_css(css: &str, sheet: &mut StyleSheet) -> Result<(), Error> {
|
||||
if sheet.rules.len() >= MAX_CSS_SELECTORS {
|
||||
return Err(Error::Xml(format!("its <style> sheets hold more than {MAX_CSS_SELECTORS} selectors")));
|
||||
}
|
||||
let block = *block.get_or_insert_with(|| {
|
||||
let i = sheet.blocks.len();
|
||||
sheet.blocks.push(std::mem::take(&mut decls));
|
||||
i
|
||||
});
|
||||
let mut tag = None;
|
||||
let mut classes = vec![];
|
||||
let mut id = None;
|
||||
@@ -611,8 +617,13 @@ impl<'a, 'i> Parser<'a, 'i> {
|
||||
if css.rules.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Splitting and sorting the class list is work too: charge its length first.
|
||||
let class = n.attribute("class").unwrap_or_default();
|
||||
if !self.charge_css(class.len()) {
|
||||
return;
|
||||
}
|
||||
let tag = n.tag_name().name();
|
||||
let mut classes: Vec<&str> = n.attribute("class").map(|c| c.split_whitespace().collect()).unwrap_or_default();
|
||||
let mut classes: Vec<&str> = class.split_whitespace().collect();
|
||||
classes.sort_unstable();
|
||||
classes.dedup();
|
||||
let id = n.attribute("id");
|
||||
|
||||
@@ -334,6 +334,32 @@ fn a_comma_list_stores_its_declarations_once() {
|
||||
assert_eq!(sheet.decls, 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rules_of_unsupported_selectors_store_no_blocks() {
|
||||
let mut sheet = StyleSheet::default();
|
||||
parse_css(&"a b{}".repeat(100_000), &mut sheet).unwrap();
|
||||
parse_css("a b{fill:red} a>b, rect{fill:blue} c:hover, d{fill:red}", &mut sheet).unwrap();
|
||||
assert_eq!(sheet.rules.len(), 2);
|
||||
assert_eq!(sheet.blocks.len(), 2);
|
||||
assert!(sheet.rules.iter().all(|r| sheet.blocks.get(r.block).is_some_and(|b| b.len() == 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_class_lists_count_against_the_matching_budget() {
|
||||
// One rule, but a 1 MB class attribute re-split on each of 100 `<use>` copies (#2140).
|
||||
let class = "a ".repeat(500_000);
|
||||
let uses: String = (0..100).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>.z{{fill:red}}</style><defs><rect id="r" width="1" height="1" class="{class}"/></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.
|
||||
|
||||
Reference in New Issue
Block a user