Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions bindings/c/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions bindings/java/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions bindings/javascript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions bindings/javascript/wasm/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,7 @@ <h2 class="text-lg font-semibold text-gray-900">Input HTML</h2>
<body>
<h1>Big Text</h1>
</body>
</html></textarea
>
</html></textarea>
</div>
<div
class="mt-auto border-t border-gray-100 pt-4 sm:flex sm:items-center sm:justify-between"
Expand Down
4 changes: 4 additions & 0 deletions bindings/php/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions bindings/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

### Removed
Expand Down
4 changes: 4 additions & 0 deletions bindings/ruby/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 12 additions & 18 deletions css-inline/src/html/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,31 +66,25 @@ impl<'a> Element<'a> {
fn previous_sibling_element(&self) -> Option<Element<'a>> {
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<Element<'a>> {
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.
Expand Down
2 changes: 1 addition & 1 deletion css-inline/src/html/selectors/attr_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
15 changes: 15 additions & 0 deletions css-inline/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self::AtRule, ()> {
// `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>(
Expand Down
38 changes: 38 additions & 0 deletions css-inline/tests/test_inlining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
<html>
<head>
<style>
@import url("print.css");
@media (max-width: 600px) { h1 { font-size: 18px; } }
</style>
</head>
<body>
<h1>Test</h1>
</body>
</html>
"#;

let options = InlineOptions {
keep_at_rules: true,
..Default::default()
};
let inliner = CSSInliner::new(options);
let result = inliner.inline(html).unwrap();
assert_eq!(
result,
r#"<html><head><style>@import url("print.css"); @media (max-width: 600px) { h1 { font-size: 18px; } } </style>

</head>
<body>
<h1>Test</h1>


</body></html>"#
)
}

#[test]
fn extra_css() {
let html = html!("h1 {background-color: blue;}", "<h1>Hello world!</h1>");
Expand Down
2 changes: 1 addition & 1 deletion profiler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
output.clear();
}
} else {
panic!("Can not find benchmark: {}", &args.benchmark)
panic!("Can not find benchmark: {}", args.benchmark)
}

Ok(())
Expand Down
Loading