tor-browser

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

media_list.rs (5848B)


      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 list:
      6 //!
      7 //! https://drafts.csswg.org/mediaqueries/#typedef-media-query-list
      8 
      9 use super::{Device, MediaQuery, Qualifier};
     10 use crate::context::QuirksMode;
     11 use crate::derives::*;
     12 use crate::error_reporting::ContextualParseError;
     13 use crate::parser::ParserContext;
     14 use crate::stylesheets::CustomMediaEvaluator;
     15 use crate::values::computed;
     16 use cssparser::{Delimiter, Parser};
     17 use cssparser::{ParserInput, Token};
     18 use selectors::kleene_value::KleeneValue;
     19 
     20 /// A type that encapsulates a media query list.
     21 #[derive(Clone, MallocSizeOf, ToCss, ToShmem)]
     22 #[css(comma, derive_debug)]
     23 pub struct MediaList {
     24    /// The list of media queries.
     25    #[css(iterable)]
     26    pub media_queries: Vec<MediaQuery>,
     27 }
     28 
     29 impl MediaList {
     30    /// Parse a media query list from CSS.
     31    ///
     32    /// Always returns a media query list. If any invalid media query is
     33    /// found, the media query list is only filled with the equivalent of
     34    /// "not all", see:
     35    ///
     36    /// <https://drafts.csswg.org/mediaqueries/#error-handling>
     37    pub fn parse(context: &ParserContext, input: &mut Parser) -> Self {
     38        if input.is_exhausted() {
     39            return Self::empty();
     40        }
     41 
     42        let mut media_queries = vec![];
     43        loop {
     44            let start_position = input.position();
     45            match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) {
     46                Ok(mq) => {
     47                    media_queries.push(mq);
     48                },
     49                Err(err) => {
     50                    media_queries.push(MediaQuery::never_matching());
     51                    let location = err.location;
     52                    let error = ContextualParseError::InvalidMediaRule(
     53                        input.slice_from(start_position),
     54                        err,
     55                    );
     56                    context.log_css_error(location, error);
     57                },
     58            }
     59 
     60            match input.next() {
     61                Ok(&Token::Comma) => {},
     62                Ok(_) => unreachable!(),
     63                Err(_) => break,
     64            }
     65        }
     66 
     67        MediaList { media_queries }
     68    }
     69 
     70    /// Create an empty MediaList.
     71    pub fn empty() -> Self {
     72        MediaList {
     73            media_queries: vec![],
     74        }
     75    }
     76 
     77    /// Evaluate a whole `MediaList` against `Device`.
     78    pub fn evaluate(
     79        &self,
     80        device: &Device,
     81        quirks_mode: QuirksMode,
     82        custom: &mut CustomMediaEvaluator,
     83    ) -> bool {
     84        computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
     85            self.matches(context, custom).to_bool(/* unknown = */ false)
     86        })
     87    }
     88 
     89    /// Evaluate the current `MediaList` with a pre-existing context and custom-media evaluator.
     90    pub fn matches(
     91        &self,
     92        context: &computed::Context,
     93        custom: &mut CustomMediaEvaluator,
     94    ) -> KleeneValue {
     95        // Check if it is an empty media query list or any queries match.
     96        // https://drafts.csswg.org/mediaqueries-4/#mq-list
     97        if self.media_queries.is_empty() {
     98            return KleeneValue::True;
     99        }
    100        KleeneValue::any(self.media_queries.iter(), |mq| {
    101            let mut query_match = if mq.media_type.matches(context.device().media_type()) {
    102                mq.condition
    103                    .as_ref()
    104                    .map_or(KleeneValue::True, |c| c.matches(context, custom))
    105            } else {
    106                KleeneValue::False
    107            };
    108            // Apply the logical NOT qualifier to the result
    109            if matches!(mq.qualifier, Some(Qualifier::Not)) {
    110                query_match = !query_match;
    111            }
    112            query_match
    113        })
    114    }
    115 
    116    /// Whether this `MediaList` contains no media queries.
    117    pub fn is_empty(&self) -> bool {
    118        self.media_queries.is_empty()
    119    }
    120 
    121    /// Whether this `MediaList` depends on the viewport size.
    122    pub fn is_viewport_dependent(&self) -> bool {
    123        self.media_queries.iter().any(|q| q.is_viewport_dependent())
    124    }
    125 
    126    /// Append a new media query item to the media list.
    127    /// <https://drafts.csswg.org/cssom/#dom-medialist-appendmedium>
    128    ///
    129    /// Returns true if added, false if fail to parse the medium string.
    130    pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool {
    131        let mut input = ParserInput::new(new_medium);
    132        let mut parser = Parser::new(&mut input);
    133        let new_query = match MediaQuery::parse(&context, &mut parser) {
    134            Ok(query) => query,
    135            Err(_) => {
    136                return false;
    137            },
    138        };
    139        // This algorithm doesn't actually matches the current spec,
    140        // but it matches the behavior of Gecko and Edge.
    141        // See https://github.com/w3c/csswg-drafts/issues/697
    142        self.media_queries.retain(|query| query != &new_query);
    143        self.media_queries.push(new_query);
    144        true
    145    }
    146 
    147    /// Delete a media query from the media list.
    148    /// <https://drafts.csswg.org/cssom/#dom-medialist-deletemedium>
    149    ///
    150    /// Returns true if found and deleted, false otherwise.
    151    pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool {
    152        let mut input = ParserInput::new(old_medium);
    153        let mut parser = Parser::new(&mut input);
    154        let old_query = match MediaQuery::parse(context, &mut parser) {
    155            Ok(query) => query,
    156            Err(_) => {
    157                return false;
    158            },
    159        };
    160        let old_len = self.media_queries.len();
    161        self.media_queries.retain(|query| query != &old_query);
    162        old_len != self.media_queries.len()
    163    }
    164 }