diff --git a/CHANGELOG.md b/CHANGELOG.md index 03172a61..bd6eaa3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- With `keep_at_rules` enabled, at-rules without a block (e.g. `@import`) lost their prelude and terminator, fusing with the following rule (`@import@media ...`) and producing a stylesheet browsers reject entirely. + ## [0.21.0] - 2026-06-16 ### Changed diff --git a/bindings/c/CHANGELOG.md b/bindings/c/CHANGELOG.md index 5d95d471..8aaa9180 100644 --- a/bindings/c/CHANGELOG.md +++ b/bindings/c/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- With `keep_at_rules` enabled, at-rules without a block (e.g. `@import`) lost their prelude and terminator, fusing with the following rule (`@import@media ...`) and producing a stylesheet browsers reject entirely. + ## [0.21.0] - 2026-06-16 ### Changed diff --git a/bindings/java/CHANGELOG.md b/bindings/java/CHANGELOG.md index 1f528e8d..895fd1d8 100644 --- a/bindings/java/CHANGELOG.md +++ b/bindings/java/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- With `keepAtRules` enabled, at-rules without a block (e.g. `@import`) lost their prelude and terminator, fusing with the following rule (`@import@media ...`) and producing a stylesheet browsers reject entirely. + ## [0.21.0] - 2026-06-16 ### Changed diff --git a/bindings/javascript/CHANGELOG.md b/bindings/javascript/CHANGELOG.md index 8176ccd0..1455ebab 100644 --- a/bindings/javascript/CHANGELOG.md +++ b/bindings/javascript/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- With `keepAtRules` enabled, at-rules without a block (e.g. `@import`) lost their prelude and terminator, fusing with the following rule (`@import@media ...`) and producing a stylesheet browsers reject entirely. + ## [0.21.0] - 2026-06-16 ### Changed diff --git a/bindings/javascript/wasm/index.html b/bindings/javascript/wasm/index.html index c0733191..981c4888 100644 --- a/bindings/javascript/wasm/index.html +++ b/bindings/javascript/wasm/index.html @@ -209,8 +209,7 @@

Input HTML

Big Text

- +
Element<'a> { fn previous_sibling_element(&self) -> Option> { let mut node = &self.document[self.node_id]; loop { - if let Some(previous_sibling_id) = node.previous_sibling { - let previous_sibling = &self.document[previous_sibling_id]; - if let NodeData::Element { element, .. } = &previous_sibling.data { - return Some(Element::new(self.document, previous_sibling_id, element)); - } - node = previous_sibling; - } else { - // Node has no previous sibling at all - return None; + // Returns `None` once the node has no previous sibling at all + let previous_sibling_id = node.previous_sibling?; + let previous_sibling = &self.document[previous_sibling_id]; + if let NodeData::Element { element, .. } = &previous_sibling.data { + return Some(Element::new(self.document, previous_sibling_id, element)); } + node = previous_sibling; } } fn next_sibling_element(&self) -> Option> { let mut node = &self.document[self.node_id]; loop { - if let Some(next_sibling_id) = node.next_sibling { - let next_sibling = &self.document[next_sibling_id]; - if let NodeData::Element { element, .. } = &next_sibling.data { - return Some(Element::new(self.document, next_sibling_id, element)); - } - node = next_sibling; - } else { - // Node has no next sibling at all - return None; + // Returns `None` once the node has no next sibling at all + let next_sibling_id = node.next_sibling?; + let next_sibling = &self.document[next_sibling_id]; + if let NodeData::Element { element, .. } = &next_sibling.data { + return Some(Element::new(self.document, next_sibling_id, element)); } + node = next_sibling; } } // NOTE: For large documents, it is called in a hot loop. diff --git a/css-inline/src/html/selectors/attr_value.rs b/css-inline/src/html/selectors/attr_value.rs index b348c685..9cd6edcd 100644 --- a/css-inline/src/html/selectors/attr_value.rs +++ b/css-inline/src/html/selectors/attr_value.rs @@ -11,7 +11,7 @@ impl ToCss for AttrValue { where W: Write, { - write!(cssparser::CssStringWriter::new(dest), "{}", &self.0) + write!(cssparser::CssStringWriter::new(dest), "{}", self.0) } } diff --git a/css-inline/src/parser.rs b/css-inline/src/parser.rs index f914ea2d..ec5f0865 100644 --- a/css-inline/src/parser.rs +++ b/css-inline/src/parser.rs @@ -161,6 +161,21 @@ impl<'i> cssparser::AtRuleParser<'i> for AtRuleFilteringParser<'_, 'i, '_> { self.at_rules.push(' '); Ok((prelude, (start, self.at_rules.len()))) } + + fn rule_without_block( + &mut self, + prelude: Self::Prelude, + _start: &ParserState, + ) -> Result { + // `parse_prelude` has already written `@` + the rule name; without this + // the name would fuse with the following rule (e.g. `@import@media ...`), + // producing a stylesheet browsers reject entirely. + let start = self.at_rules.len(); + self.at_rules.push_str(prelude); + self.at_rules.push(';'); + self.at_rules.push(' '); + Ok((prelude, (start, self.at_rules.len()))) + } } fn parse_declarations_into<'i>( diff --git a/css-inline/tests/test_inlining.rs b/css-inline/tests/test_inlining.rs index 4f916e30..766afcda 100644 --- a/css-inline/tests/test_inlining.rs +++ b/css-inline/tests/test_inlining.rs @@ -779,6 +779,44 @@ fn keep_multiple_at_rules() { ) } +#[test] +fn keep_at_rules_without_block() { + // At-rules terminated by a semicolon instead of a block (e.g. `@import`) + // must keep their prelude and terminator; otherwise the bare `@import` + // fuses with the following rule and browsers discard the whole sheet. + let html = r#" + + + + + +

Test

+ + + "#; + + let options = InlineOptions { + keep_at_rules: true, + ..Default::default() + }; + let inliner = CSSInliner::new(options); + let result = inliner.inline(html).unwrap(); + assert_eq!( + result, + r#" + + + +

Test

+ + + "# + ) +} + #[test] fn extra_css() { let html = html!("h1 {background-color: blue;}", "

Hello world!

"); diff --git a/profiler/src/main.rs b/profiler/src/main.rs index 79eac16b..5ac932bb 100644 --- a/profiler/src/main.rs +++ b/profiler/src/main.rs @@ -39,7 +39,7 @@ fn main() -> Result<(), Box> { output.clear(); } } else { - panic!("Can not find benchmark: {}", &args.benchmark) + panic!("Can not find benchmark: {}", args.benchmark) } Ok(())