style/stylesheets/
supports_rule.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports)
6
7use crate::font_face::{FontFaceSourceFormatKeyword, FontFaceSourceTechFlags};
8use crate::parser::ParserContext;
9use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
10use crate::selector_parser::{SelectorImpl, SelectorParser};
11use crate::shared_lock::{DeepCloneWithLock, Locked};
12use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
13use crate::stylesheets::{CssRuleType, CssRules};
14use cssparser::parse_important;
15use cssparser::{Delimiter, Parser, SourceLocation, Token};
16use cssparser::{ParseError as CssParseError, ParserInput};
17#[cfg(feature = "gecko")]
18use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
19use selectors::parser::{Selector, SelectorParseErrorKind};
20use servo_arc::Arc;
21use std::fmt::{self, Write};
22use std::str;
23use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
24
25/// An [`@supports`][supports] rule.
26///
27/// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports
28#[derive(Debug, ToShmem)]
29pub struct SupportsRule {
30    /// The parsed condition
31    pub condition: SupportsCondition,
32    /// Child rules
33    pub rules: Arc<Locked<CssRules>>,
34    /// The result of evaluating the condition
35    pub enabled: bool,
36    /// The line and column of the rule's source code.
37    pub source_location: SourceLocation,
38}
39
40impl SupportsRule {
41    /// Measure heap usage.
42    #[cfg(feature = "gecko")]
43    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
44        // Measurement of other fields may be added later.
45        self.rules.unconditional_shallow_size_of(ops)
46            + self.rules.read_with(guard).size_of(guard, ops)
47    }
48}
49
50impl ToCssWithGuard for SupportsRule {
51    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
52        dest.write_str("@supports ")?;
53        self.condition.to_css(&mut CssWriter::new(dest))?;
54        self.rules.read_with(guard).to_css_block(guard, dest)
55    }
56}
57
58impl DeepCloneWithLock for SupportsRule {
59    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
60        let rules = self.rules.read_with(guard);
61        SupportsRule {
62            condition: self.condition.clone(),
63            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
64            enabled: self.enabled,
65            source_location: self.source_location.clone(),
66        }
67    }
68}
69
70/// An @supports condition
71///
72/// <https://drafts.csswg.org/css-conditional-3/#at-supports>
73#[derive(Clone, Debug, ToShmem)]
74pub enum SupportsCondition {
75    /// `not (condition)`
76    Not(Box<SupportsCondition>),
77    /// `(condition)`
78    Parenthesized(Box<SupportsCondition>),
79    /// `(condition) and (condition) and (condition) ..`
80    And(Vec<SupportsCondition>),
81    /// `(condition) or (condition) or (condition) ..`
82    Or(Vec<SupportsCondition>),
83    /// `property-ident: value` (value can be any tokens)
84    Declaration(Declaration),
85    /// A `selector()` function.
86    Selector(RawSelector),
87    /// `font-format(<font-format>)`
88    FontFormat(FontFaceSourceFormatKeyword),
89    /// `font-tech(<font-tech>)`
90    FontTech(FontFaceSourceTechFlags),
91    /// `(any tokens)` or `func(any tokens)`
92    FutureSyntax(String),
93}
94
95impl SupportsCondition {
96    /// Parse a condition
97    ///
98    /// <https://drafts.csswg.org/css-conditional/#supports_condition>
99    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
100        if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
101            let inner = SupportsCondition::parse_in_parens(input)?;
102            return Ok(SupportsCondition::Not(Box::new(inner)));
103        }
104
105        let in_parens = SupportsCondition::parse_in_parens(input)?;
106
107        let location = input.current_source_location();
108        let (keyword, wrapper) = match input.next() {
109            // End of input
110            Err(..) => return Ok(in_parens),
111            Ok(&Token::Ident(ref ident)) => {
112                match_ignore_ascii_case! { &ident,
113                    "and" => ("and", SupportsCondition::And as fn(_) -> _),
114                    "or" => ("or", SupportsCondition::Or as fn(_) -> _),
115                    _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
116                }
117            },
118            Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
119        };
120
121        let mut conditions = Vec::with_capacity(2);
122        conditions.push(in_parens);
123        loop {
124            conditions.push(SupportsCondition::parse_in_parens(input)?);
125            if input
126                .try_parse(|input| input.expect_ident_matching(keyword))
127                .is_err()
128            {
129                // Did not find the expected keyword.
130                // If we found some other token, it will be rejected by
131                // `Parser::parse_entirely` somewhere up the stack.
132                return Ok(wrapper(conditions));
133            }
134        }
135    }
136
137    /// Parses a functional supports condition.
138    fn parse_functional<'i, 't>(
139        function: &str,
140        input: &mut Parser<'i, 't>,
141    ) -> Result<Self, ParseError<'i>> {
142        match_ignore_ascii_case! { function,
143            "selector" => {
144                let pos = input.position();
145                consume_any_value(input)?;
146                Ok(SupportsCondition::Selector(RawSelector(
147                    input.slice_from(pos).to_owned()
148                )))
149            },
150            "font-format" if static_prefs::pref!("layout.css.font-tech.enabled") => {
151                let kw = FontFaceSourceFormatKeyword::parse(input)?;
152                Ok(SupportsCondition::FontFormat(kw))
153            },
154            "font-tech" if static_prefs::pref!("layout.css.font-tech.enabled") => {
155                let flag = FontFaceSourceTechFlags::parse_one(input)?;
156                Ok(SupportsCondition::FontTech(flag))
157            },
158            _ => {
159                Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
160            },
161        }
162    }
163
164    /// Parses an `@import` condition as per
165    /// https://drafts.csswg.org/css-cascade-5/#typedef-import-conditions
166    pub fn parse_for_import<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
167        input.expect_function_matching("supports")?;
168        input.parse_nested_block(parse_condition_or_declaration)
169    }
170
171    /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens>
172    fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
173        // Whitespace is normally taken care of in `Parser::next`, but we want to not include it in
174        // `pos` for the SupportsCondition::FutureSyntax cases.
175        input.skip_whitespace();
176        let pos = input.position();
177        let location = input.current_source_location();
178        match *input.next()? {
179            Token::ParenthesisBlock => {
180                let nested = input
181                    .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration));
182                if let Ok(nested) = nested {
183                    return Ok(Self::Parenthesized(Box::new(nested)));
184                }
185            },
186            Token::Function(ref ident) => {
187                let ident = ident.clone();
188                let nested = input.try_parse(|input| {
189                    input.parse_nested_block(|input| {
190                        SupportsCondition::parse_functional(&ident, input)
191                    })
192                });
193                if nested.is_ok() {
194                    return nested;
195                }
196            },
197            ref t => return Err(location.new_unexpected_token_error(t.clone())),
198        }
199        input.parse_nested_block(consume_any_value)?;
200        Ok(SupportsCondition::FutureSyntax(
201            input.slice_from(pos).to_owned(),
202        ))
203    }
204
205    /// Evaluate a supports condition
206    pub fn eval(&self, cx: &ParserContext) -> bool {
207        match *self {
208            SupportsCondition::Not(ref cond) => !cond.eval(cx),
209            SupportsCondition::Parenthesized(ref cond) => cond.eval(cx),
210            SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx)),
211            SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx)),
212            SupportsCondition::Declaration(ref decl) => decl.eval(cx),
213            SupportsCondition::Selector(ref selector) => selector.eval(cx),
214            SupportsCondition::FontFormat(ref format) => eval_font_format(format),
215            SupportsCondition::FontTech(ref tech) => eval_font_tech(tech),
216            SupportsCondition::FutureSyntax(_) => false,
217        }
218    }
219}
220
221#[cfg(feature = "gecko")]
222fn eval_font_format(kw: &FontFaceSourceFormatKeyword) -> bool {
223    use crate::gecko_bindings::bindings;
224    unsafe { bindings::Gecko_IsFontFormatSupported(*kw) }
225}
226
227#[cfg(feature = "gecko")]
228fn eval_font_tech(flag: &FontFaceSourceTechFlags) -> bool {
229    use crate::gecko_bindings::bindings;
230    unsafe { bindings::Gecko_IsFontTechSupported(*flag) }
231}
232
233#[cfg(feature = "servo")]
234fn eval_font_format(_: &FontFaceSourceFormatKeyword) -> bool {
235    false
236}
237
238#[cfg(feature = "servo")]
239fn eval_font_tech(_: &FontFaceSourceTechFlags) -> bool {
240    false
241}
242
243/// supports_condition | declaration
244/// <https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext>
245pub fn parse_condition_or_declaration<'i, 't>(
246    input: &mut Parser<'i, 't>,
247) -> Result<SupportsCondition, ParseError<'i>> {
248    if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
249        Ok(condition)
250    } else {
251        Declaration::parse(input).map(SupportsCondition::Declaration)
252    }
253}
254
255impl ToCss for SupportsCondition {
256    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
257    where
258        W: Write,
259    {
260        match *self {
261            SupportsCondition::Not(ref cond) => {
262                dest.write_str("not ")?;
263                cond.to_css(dest)
264            },
265            SupportsCondition::Parenthesized(ref cond) => {
266                dest.write_char('(')?;
267                cond.to_css(dest)?;
268                dest.write_char(')')
269            },
270            SupportsCondition::And(ref vec) => {
271                let mut first = true;
272                for cond in vec {
273                    if !first {
274                        dest.write_str(" and ")?;
275                    }
276                    first = false;
277                    cond.to_css(dest)?;
278                }
279                Ok(())
280            },
281            SupportsCondition::Or(ref vec) => {
282                let mut first = true;
283                for cond in vec {
284                    if !first {
285                        dest.write_str(" or ")?;
286                    }
287                    first = false;
288                    cond.to_css(dest)?;
289                }
290                Ok(())
291            },
292            SupportsCondition::Declaration(ref decl) => decl.to_css(dest),
293            SupportsCondition::Selector(ref selector) => {
294                dest.write_str("selector(")?;
295                selector.to_css(dest)?;
296                dest.write_char(')')
297            },
298            SupportsCondition::FontFormat(ref kw) => {
299                dest.write_str("font-format(")?;
300                kw.to_css(dest)?;
301                dest.write_char(')')
302            },
303            SupportsCondition::FontTech(ref flag) => {
304                dest.write_str("font-tech(")?;
305                flag.to_css(dest)?;
306                dest.write_char(')')
307            },
308            SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
309        }
310    }
311}
312
313#[derive(Clone, Debug, ToShmem)]
314/// A possibly-invalid CSS selector.
315pub struct RawSelector(pub String);
316
317impl ToCss for RawSelector {
318    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
319    where
320        W: Write,
321    {
322        dest.write_str(&self.0)
323    }
324}
325
326impl RawSelector {
327    /// Tries to evaluate a `selector()` function.
328    pub fn eval(&self, context: &ParserContext) -> bool {
329        let mut input = ParserInput::new(&self.0);
330        let mut input = Parser::new(&mut input);
331        input
332            .parse_entirely(|input| -> Result<(), CssParseError<()>> {
333                let parser = SelectorParser {
334                    namespaces: &context.namespaces,
335                    stylesheet_origin: context.stylesheet_origin,
336                    url_data: context.url_data,
337                    for_supports_rule: true,
338                };
339
340                Selector::<SelectorImpl>::parse(&parser, input)
341                    .map_err(|_| input.new_custom_error(()))?;
342
343                Ok(())
344            })
345            .is_ok()
346    }
347}
348
349#[derive(Clone, Debug, ToShmem)]
350/// A possibly-invalid property declaration
351pub struct Declaration(pub String);
352
353impl ToCss for Declaration {
354    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
355    where
356        W: Write,
357    {
358        dest.write_str(&self.0)
359    }
360}
361
362/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value>
363fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
364    input.expect_no_error_token().map_err(|err| err.into())
365}
366
367impl Declaration {
368    /// Parse a declaration
369    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> {
370        let pos = input.position();
371        input.expect_ident()?;
372        input.expect_colon()?;
373        consume_any_value(input)?;
374        Ok(Declaration(input.slice_from(pos).to_owned()))
375    }
376
377    /// Determine if a declaration parses
378    ///
379    /// <https://drafts.csswg.org/css-conditional-3/#support-definition>
380    pub fn eval(&self, context: &ParserContext) -> bool {
381        debug_assert!(context.rule_types().contains(CssRuleType::Style));
382
383        let mut input = ParserInput::new(&self.0);
384        let mut input = Parser::new(&mut input);
385        input
386            .parse_entirely(|input| -> Result<(), CssParseError<()>> {
387                let prop = input.expect_ident_cloned().unwrap();
388                input.expect_colon().unwrap();
389
390                let id =
391                    PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
392
393                let mut declarations = SourcePropertyDeclaration::default();
394                input.parse_until_before(Delimiter::Bang, |input| {
395                    PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
396                        .map_err(|_| input.new_custom_error(()))
397                })?;
398                let _ = input.try_parse(parse_important);
399                Ok(())
400            })
401            .is_ok()
402    }
403}