1#![deny(missing_docs)]
38
39pub mod container;
40pub mod counter_style;
41pub mod custom_media;
42pub mod document;
43pub mod font_face;
44pub mod font_feature_values;
45pub mod font_palette_values;
46pub mod import;
47pub mod keyframes;
48pub mod layer;
49pub mod media;
50pub mod namespace;
51pub mod nesting;
52pub mod page;
53pub mod position_try;
54pub mod property;
55pub mod scope;
56pub mod starting_style;
57pub mod style;
58pub mod supports;
59pub mod unknown;
60pub mod view_transition;
61pub mod viewport;
62
63use self::font_feature_values::FontFeatureValuesRule;
64use self::font_palette_values::FontPaletteValuesRule;
65use self::layer::{LayerBlockRule, LayerStatementRule};
66use self::property::PropertyRule;
67use crate::context::PropertyHandlerContext;
68use crate::declaration::{DeclarationBlock, DeclarationHandler};
69use crate::dependencies::{Dependency, ImportDependency};
70use crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind};
71use crate::parser::{parse_rule_list, parse_style_block, DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};
72use crate::prefixes::Feature;
73use crate::printer::Printer;
74use crate::rules::keyframes::KeyframesName;
75use crate::selector::{is_compatible, is_equivalent, Component, Selector, SelectorList};
76use crate::stylesheet::ParserOptions;
77use crate::targets::{should_compile, TargetsWithSupportsScope};
78use crate::traits::{AtRuleParser, ToCss};
79use crate::values::string::CowArcStr;
80use crate::vendor_prefix::VendorPrefix;
81#[cfg(feature = "visitor")]
82use crate::visitor::{Visit, VisitTypes, Visitor};
83use container::ContainerRule;
84use counter_style::CounterStyleRule;
85use cssparser::{parse_one_rule, ParseError, Parser, ParserInput};
86use custom_media::CustomMediaRule;
87use document::MozDocumentRule;
88use font_face::FontFaceRule;
89use import::ImportRule;
90use itertools::Itertools;
91use keyframes::KeyframesRule;
92use media::MediaRule;
93use namespace::NamespaceRule;
94use nesting::{NestedDeclarationsRule, NestingRule};
95use page::PageRule;
96use position_try::PositionTryRule;
97use scope::ScopeRule;
98use smallvec::{smallvec, SmallVec};
99use starting_style::StartingStyleRule;
100use std::collections::{HashMap, HashSet};
101use std::hash::{BuildHasherDefault, Hasher};
102use style::StyleRule;
103use supports::SupportsRule;
104use unknown::UnknownAtRule;
105use view_transition::ViewTransitionRule;
106use viewport::ViewportRule;
107
108#[derive(Clone)]
109pub(crate) struct StyleContext<'a, 'i> {
110 pub selectors: &'a SelectorList<'i>,
111 pub parent: Option<&'a StyleContext<'a, 'i>>,
112}
113
114#[derive(PartialEq, Eq, Debug, Clone, Copy)]
116#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))]
117#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
118#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
119#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
120pub struct Location {
121 pub source_index: u32,
123 pub line: u32,
125 pub column: u32,
128}
129
130#[derive(Debug, PartialEq, Clone)]
132#[cfg_attr(feature = "visitor", derive(Visit))]
133#[cfg_attr(feature = "visitor", visit(visit_rule, RULES))]
134#[cfg_attr(
135 feature = "serde",
136 derive(serde::Serialize),
137 serde(tag = "type", content = "value", rename_all = "kebab-case")
138)]
139#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema), schemars(rename = "Rule"))]
140#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
141pub enum CssRule<'i, R = DefaultAtRule> {
142 #[cfg_attr(feature = "serde", serde(borrow))]
144 Media(MediaRule<'i, R>),
145 Import(ImportRule<'i>),
147 Style(StyleRule<'i, R>),
149 Keyframes(KeyframesRule<'i>),
151 FontFace(FontFaceRule<'i>),
153 FontPaletteValues(FontPaletteValuesRule<'i>),
155 FontFeatureValues(FontFeatureValuesRule<'i>),
157 Page(PageRule<'i>),
159 Supports(SupportsRule<'i, R>),
161 CounterStyle(CounterStyleRule<'i>),
163 Namespace(NamespaceRule<'i>),
165 MozDocument(MozDocumentRule<'i, R>),
167 Nesting(NestingRule<'i, R>),
169 NestedDeclarations(NestedDeclarationsRule<'i>),
171 Viewport(ViewportRule<'i>),
173 CustomMedia(CustomMediaRule<'i>),
175 LayerStatement(LayerStatementRule<'i>),
177 LayerBlock(LayerBlockRule<'i, R>),
179 Property(PropertyRule<'i>),
181 Container(ContainerRule<'i, R>),
183 Scope(ScopeRule<'i, R>),
185 StartingStyle(StartingStyleRule<'i, R>),
187 ViewTransition(ViewTransitionRule<'i>),
189 PositionTry(PositionTryRule<'i>),
191 Ignored,
193 Unknown(UnknownAtRule<'i>),
195 Custom(R),
197}
198
199#[cfg(feature = "serde")]
201#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
202impl<'i, 'de: 'i, R: serde::Deserialize<'de>> serde::Deserialize<'de> for CssRule<'i, R> {
203 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204 where
205 D: serde::Deserializer<'de>,
206 {
207 #[derive(serde::Deserialize)]
208 #[serde(field_identifier, rename_all = "snake_case")]
209 enum Field {
210 Type,
211 Value,
212 }
213
214 struct PartialRule<'de> {
215 rule_type: CowArcStr<'de>,
216 content: serde_content::Value<'de>,
217 }
218
219 struct CssRuleVisitor;
220
221 impl<'de> serde::de::Visitor<'de> for CssRuleVisitor {
222 type Value = PartialRule<'de>;
223
224 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
225 formatter.write_str("a CssRule")
226 }
227
228 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
229 where
230 A: serde::de::MapAccess<'de>,
231 {
232 let mut rule_type: Option<CowArcStr<'de>> = None;
233 let mut value: Option<serde_content::Value> = None;
234 while let Some(key) = map.next_key()? {
235 match key {
236 Field::Type => {
237 rule_type = Some(map.next_value()?);
238 }
239 Field::Value => {
240 value = Some(map.next_value()?);
241 }
242 }
243 }
244
245 let rule_type = rule_type.ok_or_else(|| serde::de::Error::missing_field("type"))?;
246 let content = value.ok_or_else(|| serde::de::Error::missing_field("value"))?;
247 Ok(PartialRule { rule_type, content })
248 }
249 }
250
251 let partial = deserializer.deserialize_map(CssRuleVisitor)?;
252 let deserializer = serde_content::Deserializer::new(partial.content).coerce_numbers();
253
254 match partial.rule_type.as_ref() {
255 "media" => {
256 let rule = MediaRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
257 Ok(CssRule::Media(rule))
258 }
259 "import" => {
260 let rule = ImportRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
261 Ok(CssRule::Import(rule))
262 }
263 "style" => {
264 let rule = StyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
265 Ok(CssRule::Style(rule))
266 }
267 "keyframes" => {
268 let rule =
269 KeyframesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
270 Ok(CssRule::Keyframes(rule))
271 }
272 "font-face" => {
273 let rule = FontFaceRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
274 Ok(CssRule::FontFace(rule))
275 }
276 "font-palette-values" => {
277 let rule =
278 FontPaletteValuesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
279 Ok(CssRule::FontPaletteValues(rule))
280 }
281 "font-feature-values" => {
282 let rule =
283 FontFeatureValuesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
284 Ok(CssRule::FontFeatureValues(rule))
285 }
286 "page" => {
287 let rule = PageRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
288 Ok(CssRule::Page(rule))
289 }
290 "supports" => {
291 let rule = SupportsRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
292 Ok(CssRule::Supports(rule))
293 }
294 "counter-style" => {
295 let rule =
296 CounterStyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
297 Ok(CssRule::CounterStyle(rule))
298 }
299 "namespace" => {
300 let rule =
301 NamespaceRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
302 Ok(CssRule::Namespace(rule))
303 }
304 "moz-document" => {
305 let rule =
306 MozDocumentRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
307 Ok(CssRule::MozDocument(rule))
308 }
309 "nesting" => {
310 let rule = NestingRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
311 Ok(CssRule::Nesting(rule))
312 }
313 "nested-declarations" => {
314 let rule = NestedDeclarationsRule::deserialize(deserializer)
315 .map_err(|e| serde::de::Error::custom(e.to_string()))?;
316 Ok(CssRule::NestedDeclarations(rule))
317 }
318 "viewport" => {
319 let rule = ViewportRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
320 Ok(CssRule::Viewport(rule))
321 }
322 "custom-media" => {
323 let rule =
324 CustomMediaRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
325 Ok(CssRule::CustomMedia(rule))
326 }
327 "layer-statement" => {
328 let rule =
329 LayerStatementRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
330 Ok(CssRule::LayerStatement(rule))
331 }
332 "layer-block" => {
333 let rule =
334 LayerBlockRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
335 Ok(CssRule::LayerBlock(rule))
336 }
337 "property" => {
338 let rule = PropertyRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
339 Ok(CssRule::Property(rule))
340 }
341 "container" => {
342 let rule =
343 ContainerRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
344 Ok(CssRule::Container(rule))
345 }
346 "scope" => {
347 let rule = ScopeRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
348 Ok(CssRule::Scope(rule))
349 }
350 "starting-style" => {
351 let rule =
352 StartingStyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
353 Ok(CssRule::StartingStyle(rule))
354 }
355 "view-transition" => {
356 let rule =
357 ViewTransitionRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
358 Ok(CssRule::ViewTransition(rule))
359 }
360 "position-try" => {
361 let rule =
362 PositionTryRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
363 Ok(CssRule::PositionTry(rule))
364 }
365 "ignored" => Ok(CssRule::Ignored),
366 "unknown" => {
367 let rule =
368 UnknownAtRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
369 Ok(CssRule::Unknown(rule))
370 }
371 "custom" => {
372 let rule = R::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
373 Ok(CssRule::Custom(rule))
374 }
375 t => Err(serde::de::Error::unknown_variant(t, &[])),
376 }
377 }
378}
379
380impl<'a, 'i, T: ToCss> ToCss for CssRule<'i, T> {
381 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
382 where
383 W: std::fmt::Write,
384 {
385 match self {
386 CssRule::Media(media) => media.to_css(dest),
387 CssRule::Import(import) => import.to_css(dest),
388 CssRule::Style(style) => style.to_css(dest),
389 CssRule::Keyframes(keyframes) => keyframes.to_css(dest),
390 CssRule::FontFace(font_face) => font_face.to_css(dest),
391 CssRule::FontPaletteValues(f) => f.to_css(dest),
392 CssRule::FontFeatureValues(font_feature_values) => font_feature_values.to_css(dest),
393 CssRule::Page(font_face) => font_face.to_css(dest),
394 CssRule::Supports(supports) => supports.to_css(dest),
395 CssRule::CounterStyle(counter_style) => counter_style.to_css(dest),
396 CssRule::Namespace(namespace) => namespace.to_css(dest),
397 CssRule::MozDocument(document) => document.to_css(dest),
398 CssRule::Nesting(nesting) => nesting.to_css(dest),
399 CssRule::NestedDeclarations(nested) => nested.to_css(dest),
400 CssRule::Viewport(viewport) => viewport.to_css(dest),
401 CssRule::CustomMedia(custom_media) => custom_media.to_css(dest),
402 CssRule::LayerStatement(layer) => layer.to_css(dest),
403 CssRule::LayerBlock(layer) => layer.to_css(dest),
404 CssRule::Property(property) => property.to_css(dest),
405 CssRule::StartingStyle(rule) => rule.to_css(dest),
406 CssRule::Container(container) => container.to_css(dest),
407 CssRule::Scope(scope) => scope.to_css(dest),
408 CssRule::ViewTransition(rule) => rule.to_css(dest),
409 CssRule::PositionTry(rule) => rule.to_css(dest),
410 CssRule::Unknown(unknown) => unknown.to_css(dest),
411 CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {
412 kind: PrinterErrorKind::FmtError,
413 loc: None,
414 }),
415 CssRule::Ignored => Ok(()),
416 }
417 }
418}
419
420impl<'i> CssRule<'i, DefaultAtRule> {
421 pub fn parse<'t>(
423 input: &mut Parser<'i, 't>,
424 options: &ParserOptions<'i>,
425 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
426 Self::parse_with(input, options, &mut DefaultAtRuleParser)
427 }
428
429 pub fn parse_string(
431 input: &'i str,
432 options: ParserOptions<'i>,
433 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
434 Self::parse_string_with(input, options, &mut DefaultAtRuleParser)
435 }
436}
437
438impl<'i, T> CssRule<'i, T> {
439 pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(
441 input: &mut Parser<'i, 't>,
442 options: &ParserOptions<'i>,
443 at_rule_parser: &mut P,
444 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
445 let mut rules = CssRuleList(Vec::new());
446 parse_one_rule(input, &mut TopLevelRuleParser::new(options, at_rule_parser, &mut rules))?;
447 Ok(rules.0.pop().unwrap())
448 }
449
450 pub fn parse_string_with<P: AtRuleParser<'i, AtRule = T>>(
452 input: &'i str,
453 options: ParserOptions<'i>,
454 at_rule_parser: &mut P,
455 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
456 let mut input = ParserInput::new(input);
457 let mut parser = Parser::new(&mut input);
458 Self::parse_with(&mut parser, &options, at_rule_parser)
459 }
460}
461
462#[derive(Debug, PartialEq, Clone, Default)]
464#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
465#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
466#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
467pub struct CssRuleList<'i, R = DefaultAtRule>(
468 #[cfg_attr(feature = "serde", serde(borrow))] pub Vec<CssRule<'i, R>>,
469);
470
471impl<'i> CssRuleList<'i, DefaultAtRule> {
472 pub fn parse<'t>(
474 input: &mut Parser<'i, 't>,
475 options: &ParserOptions<'i>,
476 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
477 Self::parse_with(input, options, &mut DefaultAtRuleParser)
478 }
479
480 pub fn parse_style_block<'t>(
483 input: &mut Parser<'i, 't>,
484 options: &ParserOptions<'i>,
485 is_nested: bool,
486 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
487 Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested)
488 }
489}
490
491impl<'i, T> CssRuleList<'i, T> {
492 pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(
494 input: &mut Parser<'i, 't>,
495 options: &ParserOptions<'i>,
496 at_rule_parser: &mut P,
497 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
498 parse_rule_list(input, options, at_rule_parser)
499 }
500
501 pub fn parse_style_block_with<'t, P: AtRuleParser<'i, AtRule = T>>(
504 input: &mut Parser<'i, 't>,
505 options: &ParserOptions<'i>,
506 at_rule_parser: &mut P,
507 is_nested: bool,
508 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
509 parse_style_block(input, options, at_rule_parser, is_nested)
510 }
511}
512
513#[cfg(feature = "visitor")]
515#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
516impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for CssRuleList<'i, T> {
517 const CHILD_TYPES: VisitTypes = VisitTypes::all();
518
519 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
520 if visitor.visit_types().contains(VisitTypes::RULES) {
521 visitor.visit_rule_list(self)
522 } else {
523 self.0.visit(visitor)
524 }
525 }
526
527 fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
528 self.0.visit(visitor)
529 }
530}
531
532pub(crate) struct MinifyContext<'a, 'i> {
533 pub targets: TargetsWithSupportsScope,
534 pub handler: &'a mut DeclarationHandler<'i>,
535 pub important_handler: &'a mut DeclarationHandler<'i>,
536 pub handler_context: PropertyHandlerContext<'i, 'a>,
537 pub unused_symbols: &'a HashSet<String>,
538 pub custom_media: Option<HashMap<CowArcStr<'i>, CustomMediaRule<'i>>>,
539 pub css_modules: bool,
540 pub pure_css_modules: bool,
541}
542
543impl<'i, T: Clone> CssRuleList<'i, T> {
544 pub(crate) fn minify(
545 &mut self,
546 context: &mut MinifyContext<'_, 'i>,
547 parent_is_unused: bool,
548 ) -> Result<(), MinifyError> {
549 let mut keyframe_rules = HashMap::new();
550 let mut layer_rules = HashMap::new();
551 let mut has_layers = false;
552 let mut property_rules = HashMap::new();
553 let mut font_feature_values_rules = Vec::new();
554 let mut style_rules =
555 HashMap::with_capacity_and_hasher(self.0.len(), BuildHasherDefault::<PrecomputedHasher>::default());
556 let mut rules = Vec::new();
557 for mut rule in self.0.drain(..) {
558 match &mut rule {
559 CssRule::Keyframes(keyframes) => {
560 if context.unused_symbols.contains(match &keyframes.name {
561 KeyframesName::Ident(ident) => ident.0.as_ref(),
562 KeyframesName::Custom(string) => string.as_ref(),
563 }) {
564 continue;
565 }
566 keyframes.minify(context);
567
568 macro_rules! set_prefix {
569 ($keyframes: ident) => {
570 $keyframes.vendor_prefix =
571 context.targets.current.prefixes($keyframes.vendor_prefix, Feature::AtKeyframes);
572 };
573 }
574
575 if let Some(existing_idx) = keyframe_rules.get(&keyframes.name) {
577 if let Some(CssRule::Keyframes(existing)) = &mut rules.get_mut(*existing_idx) {
578 if existing.vendor_prefix == keyframes.vendor_prefix {
580 *existing = keyframes.clone();
581 continue;
582 }
583 if existing.keyframes == keyframes.keyframes {
585 existing.vendor_prefix |= keyframes.vendor_prefix;
586 set_prefix!(existing);
587 continue;
588 }
589 }
590 }
591
592 set_prefix!(keyframes);
593 keyframe_rules.insert(keyframes.name.clone(), rules.len());
594
595 let fallbacks = keyframes.get_fallbacks(&context.targets.current);
596 rules.push(rule);
597 rules.extend(fallbacks);
598 continue;
599 }
600 CssRule::CustomMedia(_) => {
601 if context.custom_media.is_some() {
602 continue;
603 }
604 }
605 CssRule::Media(media) => {
606 if let Some(CssRule::Media(last_rule)) = rules.last_mut() {
607 if last_rule.query == media.query {
608 last_rule.rules.0.extend(media.rules.0.drain(..));
609 last_rule.minify(context, parent_is_unused)?;
610 continue;
611 }
612 }
613
614 if media.minify(context, parent_is_unused)? {
615 continue;
616 }
617 }
618 CssRule::Supports(supports) => {
619 if let Some(CssRule::Supports(last_rule)) = rules.last_mut() {
620 if last_rule.condition == supports.condition {
621 last_rule.rules.0.extend(supports.rules.0.drain(..));
622 last_rule.minify(context, parent_is_unused)?;
623 continue;
624 }
625 }
626
627 supports.minify(context, parent_is_unused)?;
628 if supports.rules.0.is_empty() {
629 continue;
630 }
631 }
632 CssRule::Container(container) => {
633 if let Some(CssRule::Container(last_rule)) = rules.last_mut() {
634 if last_rule.name == container.name && last_rule.condition == container.condition {
635 last_rule.rules.0.extend(container.rules.0.drain(..));
636 last_rule.minify(context, parent_is_unused)?;
637 continue;
638 }
639 }
640
641 if container.minify(context, parent_is_unused)? {
642 continue;
643 }
644 }
645 CssRule::LayerBlock(layer) => {
646 if let Some(name) = &layer.name {
649 if let Some(idx) = layer_rules.get(name) {
650 if let Some(CssRule::LayerBlock(last_rule)) = rules.get_mut(*idx) {
651 last_rule.rules.0.extend(layer.rules.0.drain(..));
652 continue;
653 }
654 }
655
656 layer_rules.insert(name.clone(), rules.len());
657 has_layers = true;
658 }
659 }
660 CssRule::LayerStatement(layer) => {
661 for name in &layer.names {
664 if !layer_rules.contains_key(name) {
665 layer_rules.insert(name.clone(), rules.len());
666 has_layers = true;
667 rules.push(CssRule::LayerBlock(LayerBlockRule {
668 name: Some(name.clone()),
669 rules: CssRuleList(vec![]),
670 loc: layer.loc.clone(),
671 }));
672 }
673 }
674 continue;
675 }
676 CssRule::MozDocument(document) => document.minify(context)?,
677 CssRule::Style(style) => {
678 if parent_is_unused || style.minify(context, parent_is_unused)? {
679 continue;
680 }
681
682 let incompatible = if style.selectors.0.len() > 1
685 && context.targets.current.should_compile_selectors()
686 && !style.is_compatible(context.targets.current)
687 {
688 if context.targets.current.is_compatible(crate::compat::Feature::IsSelector)
693 && !style.selectors.0.iter().any(|selector| selector.has_pseudo_element())
694 && style.selectors.0.iter().map(|selector| selector.specificity()).all_equal()
695 {
696 style.selectors =
697 SelectorList::new(smallvec![
698 Component::Is(style.selectors.0.clone().into_boxed_slice()).into()
699 ]);
700 smallvec![]
701 } else {
702 let (compatible, incompatible) = style
705 .selectors
706 .0
707 .iter()
708 .cloned()
709 .partition::<SmallVec<[Selector; 1]>, _>(|selector| {
710 let list = SelectorList::new(smallvec![selector.clone()]);
711 is_compatible(&list.0, context.targets.current)
712 });
713 style.selectors = SelectorList::new(compatible);
714 incompatible
715 }
716 } else {
717 smallvec![]
718 };
719
720 style.update_prefix(context);
721
722 let mut merged = false;
724 if let Some(CssRule::Style(last_style_rule)) = rules.last_mut() {
725 if merge_style_rules(style, last_style_rule, context) {
726 while rules.len() >= 2 {
730 let len = rules.len();
731 let (a, b) = rules.split_at_mut(len - 1);
732 if let (CssRule::Style(last), CssRule::Style(prev)) = (&mut b[0], &mut a[len - 2]) {
733 if merge_style_rules(last, prev, context) {
734 rules.pop();
736 continue;
737 }
738 }
739 break;
741 }
742 merged = true;
743 }
744 }
745
746 let supports = context.handler_context.get_supports_rules(&style);
748 let logical = context.handler_context.get_additional_rules(&style);
749
750 let incompatible_rules = incompatible
751 .into_iter()
752 .map(|selector| {
753 let list = SelectorList::new(smallvec![selector]);
755 let mut clone = style.clone();
756 clone.selectors = list;
757 clone.update_prefix(context);
758
759 let supports = context.handler_context.get_supports_rules(&clone);
761 let logical = context.handler_context.get_additional_rules(&clone);
762 (clone, logical, supports)
763 })
764 .collect::<Vec<_>>();
765
766 context.handler_context.reset();
767
768 let nested_rule = if !style.rules.0.is_empty()
772 && !style.selectors.0.is_empty()
774 && (!logical.is_empty() || !supports.is_empty() || !incompatible_rules.is_empty())
775 {
776 let mut rules = CssRuleList(vec![]);
777 std::mem::swap(&mut style.rules, &mut rules);
778 Some(StyleRule {
779 selectors: style.selectors.clone(),
780 declarations: DeclarationBlock::default(),
781 rules,
782 vendor_prefix: style.vendor_prefix,
783 loc: style.loc,
784 })
785 } else {
786 None
787 };
788
789 if !merged && !style.is_empty() {
790 let source_index = style.loc.source_index;
791 let has_no_rules = style.rules.0.is_empty();
792 let idx = rules.len();
793 rules.push(rule);
794
795 if has_no_rules {
799 let key = StyleRuleKey::new(unsafe { &*(&rules as *const _) }, idx);
801 if idx > 0 {
802 if let Some(i) = style_rules.remove(&key) {
803 if let CssRule::Style(other) = &rules[i] {
804 if !context.css_modules || source_index == other.loc.source_index {
806 rules[i] = CssRule::Ignored;
808 }
809 }
810 }
811 }
812
813 style_rules.insert(key, idx);
814 }
815 }
816
817 if !logical.is_empty() {
818 let mut logical = CssRuleList(logical);
819 logical.minify(context, parent_is_unused)?;
820 rules.extend(logical.0)
821 }
822
823 rules.extend(supports);
824 for (rule, logical, supports) in incompatible_rules {
825 if !rule.is_empty() {
826 rules.push(CssRule::Style(rule));
827 }
828 if !logical.is_empty() {
829 let mut logical = CssRuleList(logical);
830 logical.minify(context, parent_is_unused)?;
831 rules.extend(logical.0)
832 }
833 rules.extend(supports);
834 }
835
836 if let Some(nested_rule) = nested_rule {
837 rules.push(CssRule::Style(nested_rule));
838 }
839
840 continue;
841 }
842 CssRule::CounterStyle(counter_style) => {
843 if context.unused_symbols.contains(counter_style.name.0.as_ref()) {
844 continue;
845 }
846 }
847 CssRule::Scope(scope) => scope.minify(context)?,
848 CssRule::Nesting(nesting) => {
849 if nesting.minify(context, parent_is_unused)? {
850 continue;
851 }
852 }
853 CssRule::NestedDeclarations(nested) => {
854 if nested.minify(context, parent_is_unused) {
855 continue;
856 }
857 }
858 CssRule::StartingStyle(rule) => {
859 if rule.minify(context, parent_is_unused)? {
860 continue;
861 }
862 }
863 CssRule::FontPaletteValues(f) => {
864 if context.unused_symbols.contains(f.name.0.as_ref()) {
865 continue;
866 }
867
868 f.minify(context, parent_is_unused);
869
870 let fallbacks = f.get_fallbacks(context.targets.current);
871 rules.push(rule);
872 rules.extend(fallbacks);
873 continue;
874 }
875 CssRule::FontFeatureValues(rule) => {
876 if let Some(index) = font_feature_values_rules
877 .iter()
878 .find(|index| matches!(&rules[**index], CssRule::FontFeatureValues(r) if r.name == rule.name))
879 {
880 if let CssRule::FontFeatureValues(existing) = &mut rules[*index] {
881 existing.merge(rule);
882 }
883 continue;
884 } else {
885 font_feature_values_rules.push(rules.len());
886 }
887 }
888 CssRule::PositionTry(rule) => {
889 if context.unused_symbols.contains(rule.name.0.as_ref()) {
890 continue;
891 }
892 }
893 CssRule::Property(property) => {
894 if context.unused_symbols.contains(property.name.0.as_ref()) {
895 continue;
896 }
897
898 if let Some(index) = property_rules.get(&property.name) {
899 rules[*index] = rule;
900 continue;
901 } else {
902 property_rules.insert(property.name.clone(), rules.len());
903 }
904 }
905 CssRule::Import(_) => {
906 layer_rules.clear();
908 }
909 _ => {}
910 }
911
912 rules.push(rule)
913 }
914
915 if has_layers {
918 let mut declared_layers = HashSet::new();
919 let mut layer_statement = None;
920 for index in 0..rules.len() {
921 match &mut rules[index] {
922 CssRule::LayerBlock(layer) => {
923 if layer.minify(context, parent_is_unused)? {
924 if let Some(name) = &layer.name {
925 if declared_layers.contains(name) {
926 rules[index] = CssRule::Ignored;
928 continue;
929 }
930
931 let name = name.clone();
932 declared_layers.insert(name.clone());
933
934 if let Some(layer_index) = layer_statement {
935 if let CssRule::LayerStatement(layer) = &mut rules[layer_index] {
936 layer.names.push(name);
938 rules[index] = CssRule::Ignored;
939 }
940 } else {
941 rules[index] = CssRule::LayerStatement(LayerStatementRule {
943 names: vec![name],
944 loc: layer.loc,
945 });
946 layer_statement = Some(index);
947 }
948 } else {
949 rules[index] = CssRule::Ignored;
951 }
952 } else {
953 layer_statement = None;
955 }
956 }
957 CssRule::Import(import) => {
958 if let Some(layer) = &import.layer {
959 layer_statement = None;
961 if let Some(name) = layer {
962 declared_layers.insert(name.clone());
963 }
964 }
965 }
966 _ => {}
967 }
968 }
969 }
970
971 self.0 = rules;
972 Ok(())
973 }
974}
975
976fn merge_style_rules<'i, T>(
977 style: &mut StyleRule<'i, T>,
978 last_style_rule: &mut StyleRule<'i, T>,
979 context: &mut MinifyContext<'_, 'i>,
980) -> bool {
981 if style.selectors == last_style_rule.selectors
983 && style.is_compatible(context.targets.current)
984 && last_style_rule.is_compatible(context.targets.current)
985 && style.rules.0.is_empty()
986 && last_style_rule.rules.0.is_empty()
987 && (!context.css_modules || style.loc.source_index == last_style_rule.loc.source_index)
988 {
989 last_style_rule
990 .declarations
991 .declarations
992 .extend(style.declarations.declarations.drain(..));
993 last_style_rule
994 .declarations
995 .important_declarations
996 .extend(style.declarations.important_declarations.drain(..));
997 last_style_rule
998 .declarations
999 .minify(context.handler, context.important_handler, &mut context.handler_context);
1000 return true;
1001 } else if style.declarations == last_style_rule.declarations
1002 && style.rules.0.is_empty()
1003 && last_style_rule.rules.0.is_empty()
1004 {
1005 if !style.vendor_prefix.is_empty()
1008 && !last_style_rule.vendor_prefix.is_empty()
1009 && is_equivalent(&style.selectors.0, &last_style_rule.selectors.0)
1010 {
1011 if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {
1014 last_style_rule.vendor_prefix = style.vendor_prefix;
1015 } else {
1016 last_style_rule.vendor_prefix |= style.vendor_prefix;
1017 }
1018 return true;
1019 }
1020
1021 if style.is_compatible(context.targets.current) && last_style_rule.is_compatible(context.targets.current) {
1023 last_style_rule.selectors.0.extend(style.selectors.0.drain(..));
1024 if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {
1025 last_style_rule.vendor_prefix = style.vendor_prefix;
1026 } else {
1027 last_style_rule.vendor_prefix |= style.vendor_prefix;
1028 }
1029 return true;
1030 }
1031 }
1032 false
1033}
1034
1035impl<'a, 'i, T: ToCss> ToCss for CssRuleList<'i, T> {
1036 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1037 where
1038 W: std::fmt::Write,
1039 {
1040 let mut first = true;
1041 let mut last_without_block = false;
1042
1043 for (i, rule) in self.0.iter().enumerate() {
1044 if let CssRule::Ignored = &rule {
1045 continue;
1046 }
1047
1048 if let CssRule::Import(rule) = &rule {
1050 if dest.remove_imports {
1051 let dep = if dest.dependencies.is_some() {
1052 Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))
1053 } else {
1054 None
1055 };
1056
1057 if let Some(dependencies) = &mut dest.dependencies {
1058 dependencies.push(dep.unwrap());
1059 continue;
1060 }
1061 }
1062 }
1063
1064 if first {
1065 first = false;
1066 } else {
1067 if !dest.minify
1068 && !(last_without_block
1069 && matches!(
1070 rule,
1071 CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
1072 ))
1073 {
1074 dest.write_char('\n')?;
1075 }
1076 dest.newline()?;
1077 }
1078 rule.to_css(dest)?;
1079
1080 if dest.minify
1082 && !should_compile!(dest.targets.current, Nesting)
1083 && matches!(rule, CssRule::NestedDeclarations(_))
1084 && i != self.0.len() - 1
1085 {
1086 dest.write_char(';')?;
1087 }
1088
1089 last_without_block = matches!(
1090 rule,
1091 CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
1092 );
1093 }
1094
1095 Ok(())
1096 }
1097}
1098
1099impl<'i, T> std::ops::Index<usize> for CssRuleList<'i, T> {
1100 type Output = CssRule<'i, T>;
1101
1102 fn index(&self, index: usize) -> &Self::Output {
1103 &self.0[index]
1104 }
1105}
1106
1107impl<'i, T> std::ops::IndexMut<usize> for CssRuleList<'i, T> {
1108 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1109 &mut self.0[index]
1110 }
1111}
1112
1113#[derive(Default)]
1115struct PrecomputedHasher {
1116 hash: Option<u64>,
1117}
1118
1119impl Hasher for PrecomputedHasher {
1120 #[inline]
1121 fn write(&mut self, _: &[u8]) {
1122 unreachable!()
1123 }
1124
1125 #[inline]
1126 fn write_u64(&mut self, i: u64) {
1127 debug_assert!(self.hash.is_none());
1128 self.hash = Some(i);
1129 }
1130
1131 #[inline]
1132 fn finish(&self) -> u64 {
1133 self.hash.unwrap()
1134 }
1135}
1136
1137#[derive(Clone)]
1141pub(crate) struct StyleRuleKey<'a, 'i, R> {
1142 list: &'a Vec<CssRule<'i, R>>,
1143 index: usize,
1144 hash: u64,
1145}
1146
1147impl<'a, 'i, R> StyleRuleKey<'a, 'i, R> {
1148 fn new(list: &'a Vec<CssRule<'i, R>>, index: usize) -> Self {
1149 let rule = match &list[index] {
1150 CssRule::Style(style) => style,
1151 _ => unreachable!(),
1152 };
1153
1154 Self {
1155 list,
1156 index,
1157 hash: rule.hash_key(),
1158 }
1159 }
1160}
1161
1162impl<'a, 'i, R> PartialEq for StyleRuleKey<'a, 'i, R> {
1163 fn eq(&self, other: &Self) -> bool {
1164 let rule = match self.list.get(self.index) {
1165 Some(CssRule::Style(style)) => style,
1166 _ => return false,
1167 };
1168
1169 let other_rule = match other.list.get(other.index) {
1170 Some(CssRule::Style(style)) => style,
1171 _ => return false,
1172 };
1173
1174 rule.is_duplicate(other_rule)
1175 }
1176}
1177
1178impl<'a, 'i, R> Eq for StyleRuleKey<'a, 'i, R> {}
1179
1180impl<'a, 'i, R> std::hash::Hash for StyleRuleKey<'a, 'i, R> {
1181 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1182 state.write_u64(self.hash);
1183 }
1184}