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