tor-browser

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

media_query.rs (6424B)


      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 //! A media query:
      6 //!
      7 //! https://drafts.csswg.org/mediaqueries/#typedef-media-query
      8 
      9 use crate::derives::*;
     10 use crate::parser::ParserContext;
     11 use crate::queries::{FeatureFlags, FeatureType, QueryCondition};
     12 use crate::str::string_as_ascii_lowercase;
     13 use crate::values::CustomIdent;
     14 use crate::Atom;
     15 use cssparser::{match_ignore_ascii_case, Parser};
     16 use std::fmt::{self, Write};
     17 use style_traits::{CssWriter, ParseError, ToCss};
     18 
     19 /// <https://drafts.csswg.org/mediaqueries/#mq-prefix>
     20 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
     21 pub enum Qualifier {
     22    /// Hide a media query from legacy UAs:
     23    /// <https://drafts.csswg.org/mediaqueries/#mq-only>
     24    Only,
     25    /// Negate a media query:
     26    /// <https://drafts.csswg.org/mediaqueries/#mq-not>
     27    Not,
     28 }
     29 
     30 /// <https://drafts.csswg.org/mediaqueries/#media-types>
     31 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
     32 pub struct MediaType(pub CustomIdent);
     33 
     34 impl MediaType {
     35    /// The `screen` media type.
     36    pub fn screen() -> Self {
     37        MediaType(CustomIdent(atom!("screen")))
     38    }
     39 
     40    /// The `print` media type.
     41    pub fn print() -> Self {
     42        MediaType(CustomIdent(atom!("print")))
     43    }
     44 
     45    fn parse(name: &str) -> Result<Self, ()> {
     46        // From https://drafts.csswg.org/mediaqueries/#mq-syntax:
     47        //
     48        //   The <media-type> production does not include the keywords only, not, and, or, and layer.
     49        //
     50        // Here we also perform the to-ascii-lowercase part of the serialization
     51        // algorithm: https://drafts.csswg.org/cssom/#serializing-media-queries
     52        match_ignore_ascii_case! { name,
     53            "not" | "or" | "and" | "only" | "layer" => Err(()),
     54            _ => Ok(MediaType(CustomIdent(Atom::from(string_as_ascii_lowercase(name))))),
     55        }
     56    }
     57 }
     58 
     59 /// A [media query][mq].
     60 ///
     61 /// [mq]: https://drafts.csswg.org/mediaqueries/
     62 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
     63 pub struct MediaQuery {
     64    /// The qualifier for this query.
     65    pub qualifier: Option<Qualifier>,
     66    /// The media type for this query, that can be known, unknown, or "all".
     67    pub media_type: MediaQueryType,
     68    /// The condition that this media query contains. This cannot have `or`
     69    /// in the first level.
     70    pub condition: Option<QueryCondition>,
     71 }
     72 
     73 impl ToCss for MediaQuery {
     74    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     75    where
     76        W: Write,
     77    {
     78        if let Some(qual) = self.qualifier {
     79            qual.to_css(dest)?;
     80            dest.write_char(' ')?;
     81        }
     82 
     83        match self.media_type {
     84            MediaQueryType::All => {
     85                // We need to print "all" if there's a qualifier, or there's
     86                // just an empty list of expressions.
     87                //
     88                // Otherwise, we'd serialize media queries like "(min-width:
     89                // 40px)" in "all (min-width: 40px)", which is unexpected.
     90                if self.qualifier.is_some() || self.condition.is_none() {
     91                    dest.write_str("all")?;
     92                }
     93            },
     94            MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?,
     95        }
     96 
     97        let condition = match self.condition {
     98            Some(ref c) => c,
     99            None => return Ok(()),
    100        };
    101 
    102        if self.media_type != MediaQueryType::All || self.qualifier.is_some() {
    103            dest.write_str(" and ")?;
    104        }
    105 
    106        condition.to_css(dest)
    107    }
    108 }
    109 
    110 impl MediaQuery {
    111    /// Return a media query that never matches, used for when we fail to parse
    112    /// a given media query.
    113    pub fn never_matching() -> Self {
    114        Self {
    115            qualifier: Some(Qualifier::Not),
    116            media_type: MediaQueryType::All,
    117            condition: None,
    118        }
    119    }
    120 
    121    /// Returns whether this media query depends on the viewport.
    122    pub fn is_viewport_dependent(&self) -> bool {
    123        self.condition.as_ref().map_or(false, |c| {
    124            return c
    125                .cumulative_flags()
    126                .contains(FeatureFlags::VIEWPORT_DEPENDENT);
    127        })
    128    }
    129 
    130    /// Parse a media query given css input.
    131    ///
    132    /// Returns an error if any of the expressions is unknown.
    133    pub fn parse<'i, 't>(
    134        context: &ParserContext,
    135        input: &mut Parser<'i, 't>,
    136    ) -> Result<Self, ParseError<'i>> {
    137        let (qualifier, explicit_media_type) = input
    138            .try_parse(|input| -> Result<_, ()> {
    139                let qualifier = input.try_parse(Qualifier::parse).ok();
    140                let ident = input.expect_ident().map_err(|_| ())?;
    141                let media_type = MediaQueryType::parse(&ident)?;
    142                Ok((qualifier, Some(media_type)))
    143            })
    144            .unwrap_or_default();
    145 
    146        let condition = if explicit_media_type.is_none() {
    147            Some(QueryCondition::parse(context, input, FeatureType::Media)?)
    148        } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
    149            Some(QueryCondition::parse_disallow_or(
    150                context,
    151                input,
    152                FeatureType::Media,
    153            )?)
    154        } else {
    155            None
    156        };
    157 
    158        let media_type = explicit_media_type.unwrap_or(MediaQueryType::All);
    159        Ok(Self {
    160            qualifier,
    161            media_type,
    162            condition,
    163        })
    164    }
    165 }
    166 
    167 /// <http://dev.w3.org/csswg/mediaqueries-3/#media0>
    168 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
    169 pub enum MediaQueryType {
    170    /// A media type that matches every device.
    171    All,
    172    /// A specific media type.
    173    Concrete(MediaType),
    174 }
    175 
    176 impl MediaQueryType {
    177    fn parse(ident: &str) -> Result<Self, ()> {
    178        match_ignore_ascii_case! { ident,
    179            "all" => return Ok(MediaQueryType::All),
    180            _ => (),
    181        };
    182 
    183        // If parseable, accept this type as a concrete type.
    184        MediaType::parse(ident).map(MediaQueryType::Concrete)
    185    }
    186 
    187    /// Returns whether this media query type matches a MediaType.
    188    pub fn matches(&self, other: MediaType) -> bool {
    189        match *self {
    190            MediaQueryType::All => true,
    191            MediaQueryType::Concrete(ref known_type) => *known_type == other,
    192        }
    193    }
    194 }