lightningcss/
context.rs

1use std::collections::HashSet;
2
3use crate::compat::Feature;
4use crate::declaration::DeclarationBlock;
5use crate::media_query::{
6  MediaCondition, MediaFeatureId, MediaFeatureName, MediaFeatureValue, MediaList, MediaQuery, MediaType,
7  QueryFeature,
8};
9use crate::properties::custom::UnparsedProperty;
10use crate::properties::Property;
11use crate::rules::media::MediaRule;
12use crate::rules::supports::{SupportsCondition, SupportsRule};
13use crate::rules::{style::StyleRule, CssRule, CssRuleList};
14use crate::selector::{Direction, PseudoClass};
15use crate::targets::Targets;
16use crate::values::ident::Ident;
17use crate::vendor_prefix::VendorPrefix;
18use parcel_selectors::parser::Component;
19
20#[derive(Debug)]
21pub(crate) struct SupportsEntry<'i> {
22  pub condition: SupportsCondition<'i>,
23  pub declarations: Vec<Property<'i>>,
24  pub important_declarations: Vec<Property<'i>>,
25}
26
27#[derive(Debug, PartialEq)]
28pub(crate) enum DeclarationContext {
29  None,
30  StyleRule,
31  Keyframes,
32  StyleAttribute,
33}
34
35#[derive(Debug)]
36pub(crate) struct PropertyHandlerContext<'i, 'o> {
37  pub targets: Targets,
38  pub is_important: bool,
39  supports: Vec<SupportsEntry<'i>>,
40  ltr: Vec<Property<'i>>,
41  rtl: Vec<Property<'i>>,
42  dark: Vec<Property<'i>>,
43  pub context: DeclarationContext,
44  pub unused_symbols: &'o HashSet<String>,
45}
46
47impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
48  pub fn new(targets: Targets, unused_symbols: &'o HashSet<String>) -> Self {
49    PropertyHandlerContext {
50      targets,
51      is_important: false,
52      supports: Vec::new(),
53      ltr: Vec::new(),
54      rtl: Vec::new(),
55      dark: Vec::new(),
56      context: DeclarationContext::None,
57      unused_symbols,
58    }
59  }
60
61  pub fn child(&self, context: DeclarationContext) -> Self {
62    PropertyHandlerContext {
63      targets: self.targets,
64      is_important: false,
65      supports: Vec::new(),
66      ltr: Vec::new(),
67      rtl: Vec::new(),
68      dark: Vec::new(),
69      context,
70      unused_symbols: self.unused_symbols,
71    }
72  }
73
74  pub fn should_compile_logical(&self, feature: Feature) -> bool {
75    // Don't convert logical properties in style attributes because
76    // our fallbacks rely on extra rules to define --ltr and --rtl.
77    if self.context == DeclarationContext::StyleAttribute {
78      return false;
79    }
80
81    self.targets.should_compile_logical(feature)
82  }
83
84  pub fn add_logical_rule(&mut self, ltr: Property<'i>, rtl: Property<'i>) {
85    self.ltr.push(ltr);
86    self.rtl.push(rtl);
87  }
88
89  pub fn add_dark_rule(&mut self, property: Property<'i>) {
90    self.dark.push(property);
91  }
92
93  pub fn get_additional_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {
94    // TODO: :dir/:lang raises the specificity of the selector. Use :where to lower it?
95    let mut dest = Vec::new();
96
97    macro_rules! rule {
98      ($dir: ident, $decls: ident) => {
99        let mut selectors = style_rule.selectors.clone();
100        for selector in &mut selectors.0 {
101          selector.append(Component::NonTSPseudoClass(PseudoClass::Dir {
102            direction: Direction::$dir,
103          }));
104        }
105
106        let rule = StyleRule {
107          selectors,
108          vendor_prefix: VendorPrefix::None,
109          declarations: DeclarationBlock {
110            declarations: self.$decls.clone(),
111            important_declarations: vec![],
112          },
113          rules: CssRuleList(vec![]),
114          loc: style_rule.loc.clone(),
115        };
116
117        dest.push(CssRule::Style(rule));
118      };
119    }
120
121    if !self.ltr.is_empty() {
122      rule!(Ltr, ltr);
123    }
124
125    if !self.rtl.is_empty() {
126      rule!(Rtl, rtl);
127    }
128
129    if !self.dark.is_empty() {
130      dest.push(CssRule::Media(MediaRule {
131        query: MediaList {
132          media_queries: vec![MediaQuery {
133            qualifier: None,
134            media_type: MediaType::All,
135            condition: Some(MediaCondition::Feature(QueryFeature::Plain {
136              name: MediaFeatureName::Standard(MediaFeatureId::PrefersColorScheme),
137              value: MediaFeatureValue::Ident(Ident("dark".into())),
138            })),
139          }],
140        },
141        rules: CssRuleList(vec![CssRule::Style(StyleRule {
142          selectors: style_rule.selectors.clone(),
143          vendor_prefix: VendorPrefix::None,
144          declarations: DeclarationBlock {
145            declarations: self.dark.clone(),
146            important_declarations: vec![],
147          },
148          rules: CssRuleList(vec![]),
149          loc: style_rule.loc.clone(),
150        })]),
151        loc: style_rule.loc.clone(),
152      }))
153    }
154
155    dest
156  }
157
158  pub fn add_conditional_property(&mut self, condition: SupportsCondition<'i>, property: Property<'i>) {
159    if self.context != DeclarationContext::StyleRule {
160      return;
161    }
162
163    if let Some(entry) = self.supports.iter_mut().find(|supports| condition == supports.condition) {
164      if self.is_important {
165        entry.important_declarations.push(property);
166      } else {
167        entry.declarations.push(property);
168      }
169    } else {
170      let mut important_declarations = Vec::new();
171      let mut declarations = Vec::new();
172      if self.is_important {
173        important_declarations.push(property);
174      } else {
175        declarations.push(property);
176      }
177      self.supports.push(SupportsEntry {
178        condition,
179        important_declarations,
180        declarations,
181      });
182    }
183  }
184
185  pub fn add_unparsed_fallbacks(&mut self, unparsed: &mut UnparsedProperty<'i>) {
186    if self.context != DeclarationContext::StyleRule && self.context != DeclarationContext::StyleAttribute {
187      return;
188    }
189
190    let fallbacks = unparsed.value.get_fallbacks(self.targets);
191    for (condition, fallback) in fallbacks {
192      self.add_conditional_property(
193        condition,
194        Property::Unparsed(UnparsedProperty {
195          property_id: unparsed.property_id.clone(),
196          value: fallback,
197        }),
198      );
199    }
200  }
201
202  pub fn get_supports_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {
203    if self.supports.is_empty() {
204      return Vec::new();
205    }
206
207    let mut dest = Vec::new();
208    for entry in &self.supports {
209      dest.push(CssRule::Supports(SupportsRule {
210        condition: entry.condition.clone(),
211        rules: CssRuleList(vec![CssRule::Style(StyleRule {
212          selectors: style_rule.selectors.clone(),
213          vendor_prefix: VendorPrefix::None,
214          declarations: DeclarationBlock {
215            declarations: entry.declarations.clone(),
216            important_declarations: entry.important_declarations.clone(),
217          },
218          rules: CssRuleList(vec![]),
219          loc: style_rule.loc.clone(),
220        })]),
221        loc: style_rule.loc.clone(),
222      }));
223    }
224
225    dest
226  }
227
228  pub fn reset(&mut self) {
229    self.supports.clear();
230    self.ltr.clear();
231    self.rtl.clear();
232    self.dark.clear();
233  }
234}