1use std::collections::HashMap;
4
5use super::Location;
6use super::{CssRuleList, MinifyContext};
7use crate::error::{MinifyError, ParserError, PrinterError};
8use crate::parser::DefaultAtRule;
9use crate::printer::Printer;
10use crate::properties::custom::TokenList;
11use crate::properties::PropertyId;
12use crate::targets::{Features, FeaturesIterator, Targets};
13use crate::traits::{Parse, ToCss};
14use crate::values::string::CowArcStr;
15use crate::vendor_prefix::VendorPrefix;
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use cssparser::*;
19
20#[cfg(feature = "serde")]
21use crate::serialization::ValueWrapper;
22
23#[derive(Debug, PartialEq, Clone)]
25#[cfg_attr(feature = "visitor", derive(Visit))]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
29pub struct SupportsRule<'i, R = DefaultAtRule> {
30 #[cfg_attr(feature = "serde", serde(borrow))]
32 pub condition: SupportsCondition<'i>,
33 pub rules: CssRuleList<'i, R>,
35 #[cfg_attr(feature = "visitor", skip_visit)]
37 pub loc: Location,
38}
39
40impl<'i, T: Clone> SupportsRule<'i, T> {
41 pub(crate) fn minify(
42 &mut self,
43 context: &mut MinifyContext<'_, 'i>,
44 parent_is_unused: bool,
45 ) -> Result<(), MinifyError> {
46 let inserted = context.targets.enter_supports(self.condition.get_supported_features());
47 if inserted {
48 context.handler_context.targets = context.targets.current;
49 }
50
51 self.condition.set_prefixes_for_targets(&context.targets.current);
52 let result = self.rules.minify(context, parent_is_unused);
53
54 if inserted {
55 context.targets.exit_supports();
56 context.handler_context.targets = context.targets.current;
57 }
58 result
59 }
60}
61
62impl<'a, 'i, T: ToCss> ToCss for SupportsRule<'i, T> {
63 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
64 where
65 W: std::fmt::Write,
66 {
67 #[cfg(feature = "sourcemap")]
68 dest.add_mapping(self.loc);
69 dest.write_str("@supports ")?;
70 self.condition.to_css(dest)?;
71 dest.whitespace()?;
72 dest.write_char('{')?;
73 dest.indent();
74 dest.newline()?;
75
76 let inserted = dest.targets.enter_supports(self.condition.get_supported_features());
77 self.rules.to_css(dest)?;
78 if inserted {
79 dest.targets.exit_supports();
80 }
81
82 dest.dedent();
83 dest.newline()?;
84 dest.write_char('}')
85 }
86}
87
88#[derive(Debug, PartialEq, Clone)]
91#[cfg_attr(feature = "visitor", derive(Visit))]
92#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
93#[cfg_attr(feature = "visitor", visit(visit_supports_condition, SUPPORTS_CONDITIONS))]
94#[cfg_attr(
95 feature = "serde",
96 derive(serde::Serialize, serde::Deserialize),
97 serde(tag = "type", rename_all = "kebab-case")
98)]
99#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
100pub enum SupportsCondition<'i> {
101 #[cfg_attr(feature = "visitor", skip_type)]
103 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Box<SupportsCondition>>"))]
104 Not(Box<SupportsCondition<'i>>),
105 #[cfg_attr(feature = "visitor", skip_type)]
107 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Vec<SupportsCondition>>"))]
108 And(Vec<SupportsCondition<'i>>),
109 #[cfg_attr(feature = "visitor", skip_type)]
111 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Vec<SupportsCondition>>"))]
112 Or(Vec<SupportsCondition<'i>>),
113 Declaration {
115 #[cfg_attr(feature = "serde", serde(borrow, rename = "propertyId"))]
117 property_id: PropertyId<'i>,
118 value: CowArcStr<'i>,
120 },
121 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
123 Selector(CowArcStr<'i>),
124 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
127 Unknown(CowArcStr<'i>),
128}
129
130impl<'i> SupportsCondition<'i> {
131 pub fn and(&mut self, b: &SupportsCondition<'i>) {
133 if let SupportsCondition::And(a) = self {
134 if !a.contains(&b) {
135 a.push(b.clone());
136 }
137 } else if self != b {
138 *self = SupportsCondition::And(vec![self.clone(), b.clone()])
139 }
140 }
141
142 pub fn or(&mut self, b: &SupportsCondition<'i>) {
144 if let SupportsCondition::Or(a) = self {
145 if !a.contains(&b) {
146 a.push(b.clone());
147 }
148 } else if self != b {
149 *self = SupportsCondition::Or(vec![self.clone(), b.clone()])
150 }
151 }
152
153 fn set_prefixes_for_targets(&mut self, targets: &Targets) {
154 match self {
155 SupportsCondition::Not(cond) => cond.set_prefixes_for_targets(targets),
156 SupportsCondition::And(items) | SupportsCondition::Or(items) => {
157 for item in items {
158 item.set_prefixes_for_targets(targets);
159 }
160 }
161 SupportsCondition::Declaration { property_id, .. } => {
162 let prefix = property_id.prefix();
163 if prefix.is_empty() || prefix.contains(VendorPrefix::None) {
164 property_id.set_prefixes_for_targets(*targets);
165 }
166 }
167 _ => {}
168 }
169 }
170
171 fn get_supported_features(&self) -> Features {
172 fn get_supported_features_internal(value: &SupportsCondition) -> Option<Features> {
173 match value {
174 SupportsCondition::And(list) => list.iter().map(|c| get_supported_features_internal(c)).try_union_all(),
175 SupportsCondition::Declaration { value, .. } => {
176 let mut input = ParserInput::new(&value);
177 let mut parser = Parser::new(&mut input);
178 if let Ok(tokens) = TokenList::parse(&mut parser, &Default::default(), 0) {
179 Some(tokens.get_features())
180 } else {
181 Some(Features::empty())
182 }
183 }
184 SupportsCondition::Not(_) | SupportsCondition::Or(_) => None,
186 SupportsCondition::Selector(_) | SupportsCondition::Unknown(_) => Some(Features::empty()),
187 }
188 }
189
190 get_supported_features_internal(self).unwrap_or(Features::empty())
191 }
192}
193
194impl<'i> Parse<'i> for SupportsCondition<'i> {
195 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
196 if input.try_parse(|input| input.expect_ident_matching("not")).is_ok() {
197 let in_parens = Self::parse_in_parens(input)?;
198 return Ok(SupportsCondition::Not(Box::new(in_parens)));
199 }
200
201 let in_parens = Self::parse_in_parens(input)?;
202 let mut expected_type = None;
203 let mut conditions = Vec::new();
204 let mut seen_declarations = HashMap::new();
205
206 loop {
207 let condition = input.try_parse(|input| {
208 let location = input.current_source_location();
209 let s = input.expect_ident()?;
210 let found_type = match_ignore_ascii_case! { &s,
211 "and" => 1,
212 "or" => 2,
213 _ => return Err(location.new_unexpected_token_error(
214 cssparser::Token::Ident(s.clone())
215 ))
216 };
217
218 if let Some(expected) = expected_type {
219 if found_type != expected {
220 return Err(location.new_unexpected_token_error(cssparser::Token::Ident(s.clone())));
221 }
222 } else {
223 expected_type = Some(found_type);
224 }
225
226 Self::parse_in_parens(input)
227 });
228
229 if let Ok(condition) = condition {
230 if conditions.is_empty() {
231 conditions.push(in_parens.clone());
232 if let SupportsCondition::Declaration { property_id, value } = &in_parens {
233 seen_declarations.insert((property_id.with_prefix(VendorPrefix::None), value.clone()), 0);
234 }
235 }
236
237 if let SupportsCondition::Declaration { property_id, value } = condition {
238 let property_id = property_id.with_prefix(VendorPrefix::None);
240 let key = (property_id.clone(), value.clone());
241 if let Some(index) = seen_declarations.get(&key) {
242 if let SupportsCondition::Declaration {
243 property_id: cur_property,
244 ..
245 } = &mut conditions[*index]
246 {
247 cur_property.add_prefix(property_id.prefix());
248 }
249 } else {
250 seen_declarations.insert(key, conditions.len());
251 conditions.push(SupportsCondition::Declaration { property_id, value });
252 }
253 } else {
254 conditions.push(condition);
255 }
256 } else {
257 break;
258 }
259 }
260
261 if conditions.len() == 1 {
262 return Ok(conditions.pop().unwrap());
263 }
264
265 match expected_type {
266 Some(1) => Ok(SupportsCondition::And(conditions)),
267 Some(2) => Ok(SupportsCondition::Or(conditions)),
268 _ => Ok(in_parens),
269 }
270 }
271}
272
273impl<'i> SupportsCondition<'i> {
274 fn parse_in_parens<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
275 input.skip_whitespace();
276 let location = input.current_source_location();
277 let pos = input.position();
278 match input.next()? {
279 Token::Function(ref f) => {
280 match_ignore_ascii_case! { &*f,
281 "selector" => {
282 let res = input.try_parse(|input| {
283 input.parse_nested_block(|input| {
284 let pos = input.position();
285 input.expect_no_error_token()?;
286 Ok(SupportsCondition::Selector(input.slice_from(pos).into()))
287 })
288 });
289 if res.is_ok() {
290 return res
291 }
292 },
293 _ => {}
294 }
295 }
296 Token::ParenthesisBlock => {
297 let res = input.try_parse(|input| {
298 input.parse_nested_block(|input| {
299 if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
300 return Ok(condition);
301 }
302
303 Self::parse_declaration(input)
304 })
305 });
306 if res.is_ok() {
307 return res;
308 }
309 }
310 t => return Err(location.new_unexpected_token_error(t.clone())),
311 };
312
313 input.parse_nested_block(|input| input.expect_no_error_token().map_err(|err| err.into()))?;
314 Ok(SupportsCondition::Unknown(input.slice_from(pos).into()))
315 }
316
317 pub(crate) fn parse_declaration<'t>(
318 input: &mut Parser<'i, 't>,
319 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
320 let property_id = PropertyId::parse(input)?;
321 input.expect_colon()?;
322 input.skip_whitespace();
323 let pos = input.position();
324 input.expect_no_error_token()?;
325 Ok(SupportsCondition::Declaration {
326 property_id,
327 value: input.slice_from(pos).into(),
328 })
329 }
330
331 fn needs_parens(&self, parent: &SupportsCondition) -> bool {
332 match self {
333 SupportsCondition::Not(_) => true,
334 SupportsCondition::And(_) => !matches!(parent, SupportsCondition::And(_)),
335 SupportsCondition::Or(_) => !matches!(parent, SupportsCondition::Or(_)),
336 _ => false,
337 }
338 }
339
340 fn to_css_with_parens_if_needed<W>(&self, dest: &mut Printer<W>, needs_parens: bool) -> Result<(), PrinterError>
341 where
342 W: std::fmt::Write,
343 {
344 if needs_parens {
345 dest.write_char('(')?;
346 }
347 self.to_css(dest)?;
348 if needs_parens {
349 dest.write_char(')')?;
350 }
351 Ok(())
352 }
353}
354
355impl<'i> ToCss for SupportsCondition<'i> {
356 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
357 where
358 W: std::fmt::Write,
359 {
360 match self {
361 SupportsCondition::Not(condition) => {
362 dest.write_str("not ")?;
363 condition.to_css_with_parens_if_needed(dest, condition.needs_parens(self))
364 }
365 SupportsCondition::And(conditions) => {
366 let mut first = true;
367 for condition in conditions {
368 if first {
369 first = false;
370 } else {
371 dest.write_str(" and ")?;
372 }
373 condition.to_css_with_parens_if_needed(dest, condition.needs_parens(self))?;
374 }
375 Ok(())
376 }
377 SupportsCondition::Or(conditions) => {
378 let mut first = true;
379 for condition in conditions {
380 if first {
381 first = false;
382 } else {
383 dest.write_str(" or ")?;
384 }
385 condition.to_css_with_parens_if_needed(dest, condition.needs_parens(self))?;
386 }
387 Ok(())
388 }
389 SupportsCondition::Declaration { property_id, value } => {
390 dest.write_char('(')?;
391
392 let prefix = property_id.prefix().or_none();
393 if prefix != VendorPrefix::None {
394 dest.write_char('(')?;
395 }
396
397 let name = property_id.name();
398 let mut first = true;
399 for p in prefix {
400 if first {
401 first = false;
402 } else {
403 dest.write_str(") or (")?;
404 }
405
406 p.to_css(dest)?;
407 serialize_name(name, dest)?;
408 dest.delim(':', false)?;
409 dest.write_str(value)?;
410 }
411
412 if prefix != VendorPrefix::None {
413 dest.write_char(')')?;
414 }
415
416 dest.write_char(')')
417 }
418 SupportsCondition::Selector(sel) => {
419 dest.write_str("selector(")?;
420 dest.write_str(sel)?;
421 dest.write_char(')')
422 }
423 SupportsCondition::Unknown(unknown) => dest.write_str(&unknown),
424 }
425 }
426}