Merge a new set of styles into an current one, considering the rules of CSS precedence. The merge process maintains the order of specificity and respects the `!important` rule in CSS.
(
writer: &mut Wr,
current_style: &StrTendril,
new_styles: &ElementStyleMap<'_>,
declarations_buffer: &mut SmallVec<[Vec<u8>; 8]>,
minify_css: bool,
)
| 697 | /// |
| 698 | /// The merge process maintains the order of specificity and respects the `!important` rule in CSS. |
| 699 | fn merge_styles<Wr: Write>( |
| 700 | writer: &mut Wr, |
| 701 | current_style: &StrTendril, |
| 702 | new_styles: &ElementStyleMap<'_>, |
| 703 | declarations_buffer: &mut SmallVec<[Vec<u8>; 8]>, |
| 704 | minify_css: bool, |
| 705 | ) -> Result<(), InlineError> { |
| 706 | // This function is designed with a focus on reusing existing allocations where possible |
| 707 | // We start by parsing the current declarations in the "style" attribute |
| 708 | let mut parser_input = cssparser::ParserInput::new(current_style); |
| 709 | let mut parser = cssparser::Parser::new(&mut parser_input); |
| 710 | let mut declaration_parser = parser::CSSDeclarationListParser; |
| 711 | let current_declarations = cssparser::RuleBodyParser::new(&mut parser, &mut declaration_parser); |
| 712 | // We manually manage the length of our buffer. The buffer may contain slots used |
| 713 | // in previous runs, and we want to access only the portion that we build in this iteration |
| 714 | let mut parsed_declarations_count: usize = 0; |
| 715 | for (idx, declaration) in current_declarations.enumerate() { |
| 716 | parsed_declarations_count = parsed_declarations_count.saturating_add(1); |
| 717 | let (property, value) = declaration?; |
| 718 | let estimated_declaration_size = property |
| 719 | .len() |
| 720 | .saturating_add(STYLE_SEPARATOR.len()) |
| 721 | .saturating_add(value.len()); |
| 722 | // We store the existing style declarations in the buffer for later merging with new styles |
| 723 | // If possible, we reuse existing slots in the buffer to avoid additional allocations |
| 724 | if let Some(buffer) = declarations_buffer.get_mut(idx) { |
| 725 | buffer.clear(); |
| 726 | buffer.reserve(estimated_declaration_size); |
| 727 | write_declaration(buffer, &property, value, minify_css)?; |
| 728 | } else { |
| 729 | let mut buffer = Vec::with_capacity(estimated_declaration_size); |
| 730 | write_declaration(&mut buffer, &property, value, minify_css)?; |
| 731 | declarations_buffer.push(buffer); |
| 732 | } |
| 733 | } |
| 734 | // Keep the number of current declarations to write them last as they have the precedence |
| 735 | let current_declarations_count = parsed_declarations_count; |
| 736 | // Next, we iterate over the new styles and merge them into our existing set |
| 737 | // New rules will not override old ones unless they are marked as `!important` |
| 738 | let sep = if minify_css { |
| 739 | STYLE_SEPARATOR_MIN |
| 740 | } else { |
| 741 | STYLE_SEPARATOR |
| 742 | }; |
| 743 | for (property, _, value) in new_styles { |
| 744 | match ( |
| 745 | value.trim_end().strip_suffix("!important"), |
| 746 | declarations_buffer |
| 747 | .iter_mut() |
| 748 | .take(parsed_declarations_count) |
| 749 | .find(|style| { |
| 750 | style.starts_with(property.as_bytes()) |
| 751 | && style.get(property.len()..property.len().saturating_add(sep.len())) |
| 752 | == Some(sep) |
| 753 | }), |
| 754 | ) { |
| 755 | // The new rule is `!important` and there's an existing rule with the same name |
| 756 | // Only override if the existing inline rule is NOT `!important`. |
no test coverage detected