tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

supports_rule.rs (15068B)


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