tor-browser

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

document_rule.rs (11156B)


      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 //! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document)
      6 //! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4.
      7 //! We implement the prefixed `@-moz-document`.
      8 
      9 use crate::derives::*;
     10 use crate::media_queries::Device;
     11 use crate::parser::{Parse, ParserContext};
     12 use crate::shared_lock::{DeepCloneWithLock, Locked};
     13 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
     14 use crate::stylesheets::CssRules;
     15 use crate::values::CssUrl;
     16 use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, Parser, SourceLocation};
     17 #[cfg(feature = "gecko")]
     18 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
     19 use servo_arc::Arc;
     20 use std::fmt::{self, Write};
     21 use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
     22 
     23 #[derive(Debug, ToShmem)]
     24 /// A @-moz-document rule
     25 pub struct DocumentRule {
     26    /// The parsed condition
     27    pub condition: DocumentCondition,
     28    /// Child rules
     29    pub rules: Arc<Locked<CssRules>>,
     30    /// The line and column of the rule's source code.
     31    pub source_location: SourceLocation,
     32 }
     33 
     34 impl DocumentRule {
     35    /// Measure heap usage.
     36    #[cfg(feature = "gecko")]
     37    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
     38        // Measurement of other fields may be added later.
     39        self.rules.unconditional_shallow_size_of(ops)
     40            + self.rules.read_with(guard).size_of(guard, ops)
     41    }
     42 }
     43 
     44 impl ToCssWithGuard for DocumentRule {
     45    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
     46        dest.write_str("@-moz-document ")?;
     47        self.condition.to_css(&mut CssWriter::new(dest))?;
     48        dest.write_str(" {")?;
     49        for rule in self.rules.read_with(guard).0.iter() {
     50            dest.write_char(' ')?;
     51            rule.to_css(guard, dest)?;
     52        }
     53        dest.write_str(" }")
     54    }
     55 }
     56 
     57 impl DeepCloneWithLock for DocumentRule {
     58    /// Deep clones this DocumentRule.
     59    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
     60        let rules = self.rules.read_with(guard);
     61        DocumentRule {
     62            condition: self.condition.clone(),
     63            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
     64            source_location: self.source_location.clone(),
     65        }
     66    }
     67 }
     68 
     69 /// The kind of media document that the rule will match.
     70 #[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)]
     71 #[allow(missing_docs)]
     72 pub enum MediaDocumentKind {
     73    All,
     74    Image,
     75    Video,
     76 }
     77 
     78 /// A matching function for a `@document` rule's condition.
     79 #[derive(Clone, Debug, ToCss, ToShmem)]
     80 pub enum DocumentMatchingFunction {
     81    /// Exact URL matching function. It evaluates to true whenever the
     82    /// URL of the document being styled is exactly the URL given.
     83    Url(CssUrl),
     84    /// URL prefix matching function. It evaluates to true whenever the
     85    /// URL of the document being styled has the argument to the
     86    /// function as an initial substring (which is true when the two
     87    /// strings are equal). When the argument is the empty string,
     88    /// it evaluates to true for all documents.
     89    #[css(function)]
     90    UrlPrefix(String),
     91    /// Domain matching function. It evaluates to true whenever the URL
     92    /// of the document being styled has a host subcomponent and that
     93    /// host subcomponent is exactly the argument to the ‘domain()’
     94    /// function or a final substring of the host component is a
     95    /// period (U+002E) immediately followed by the argument to the
     96    /// ‘domain()’ function.
     97    #[css(function)]
     98    Domain(String),
     99    /// Regular expression matching function. It evaluates to true
    100    /// whenever the regular expression matches the entirety of the URL
    101    /// of the document being styled.
    102    #[css(function)]
    103    Regexp(String),
    104    /// Matching function for a media document.
    105    #[css(function)]
    106    MediaDocument(MediaDocumentKind),
    107    /// Matching function for a plain-text document.
    108    #[css(function)]
    109    PlainTextDocument(()),
    110    /// Matching function for a document that can be observed by other content
    111    /// documents.
    112    #[css(function)]
    113    UnobservableDocument(()),
    114 }
    115 
    116 macro_rules! parse_quoted_or_unquoted_string {
    117    ($input:ident, $url_matching_function:expr) => {
    118        $input.parse_nested_block(|input| {
    119            let start = input.position();
    120            input
    121                .parse_entirely(|input| {
    122                    let string = input.expect_string()?;
    123                    Ok($url_matching_function(string.as_ref().to_owned()))
    124                })
    125                .or_else(|_: ParseError| {
    126                    while let Ok(_) = input.next() {}
    127                    Ok($url_matching_function(input.slice_from(start).to_string()))
    128                })
    129        })
    130    };
    131 }
    132 
    133 impl DocumentMatchingFunction {
    134    /// Parse a URL matching function for a`@document` rule's condition.
    135    pub fn parse<'i, 't>(
    136        context: &ParserContext,
    137        input: &mut Parser<'i, 't>,
    138    ) -> Result<Self, ParseError<'i>> {
    139        if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) {
    140            return Ok(DocumentMatchingFunction::Url(url));
    141        }
    142 
    143        let location = input.current_source_location();
    144        let function = input.expect_function()?.clone();
    145        match_ignore_ascii_case! { &function,
    146            "url-prefix" => {
    147                parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
    148            },
    149            "domain" => {
    150                parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
    151            },
    152            "regexp" => {
    153                input.parse_nested_block(|input| {
    154                    Ok(DocumentMatchingFunction::Regexp(
    155                        input.expect_string()?.as_ref().to_owned(),
    156                    ))
    157                })
    158            },
    159            "media-document" => {
    160                input.parse_nested_block(|input| {
    161                    let kind = MediaDocumentKind::parse(input)?;
    162                    Ok(DocumentMatchingFunction::MediaDocument(kind))
    163                })
    164            },
    165 
    166            "plain-text-document" => {
    167                input.parse_nested_block(|input| {
    168                    input.expect_exhausted()?;
    169                    Ok(DocumentMatchingFunction::PlainTextDocument(()))
    170                })
    171            },
    172 
    173            "unobservable-document" => {
    174                input.parse_nested_block(|input| {
    175                    input.expect_exhausted()?;
    176                    Ok(DocumentMatchingFunction::UnobservableDocument(()))
    177                })
    178            },
    179 
    180            _ => {
    181                Err(location.new_custom_error(
    182                    StyleParseErrorKind::UnexpectedFunction(function.clone())
    183                ))
    184            },
    185        }
    186    }
    187 
    188    #[cfg(feature = "gecko")]
    189    /// Evaluate a URL matching function.
    190    pub fn evaluate(&self, device: &Device) -> bool {
    191        use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
    192        use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
    193        use nsstring::nsCStr;
    194 
    195        let func = match *self {
    196            DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
    197            DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
    198            DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
    199            DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
    200            DocumentMatchingFunction::MediaDocument(_) => {
    201                GeckoDocumentMatchingFunction::MediaDocument
    202            },
    203            DocumentMatchingFunction::PlainTextDocument(..) => {
    204                GeckoDocumentMatchingFunction::PlainTextDocument
    205            },
    206            DocumentMatchingFunction::UnobservableDocument(..) => {
    207                GeckoDocumentMatchingFunction::UnobservableDocument
    208            },
    209        };
    210 
    211        let pattern = nsCStr::from(match *self {
    212            DocumentMatchingFunction::Url(ref url) => url.as_str(),
    213            DocumentMatchingFunction::UrlPrefix(ref pat)
    214            | DocumentMatchingFunction::Domain(ref pat)
    215            | DocumentMatchingFunction::Regexp(ref pat) => pat,
    216            DocumentMatchingFunction::MediaDocument(kind) => match kind {
    217                MediaDocumentKind::All => "all",
    218                MediaDocumentKind::Image => "image",
    219                MediaDocumentKind::Video => "video",
    220            },
    221            DocumentMatchingFunction::PlainTextDocument(())
    222            | DocumentMatchingFunction::UnobservableDocument(()) => "",
    223        });
    224        unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) }
    225    }
    226 
    227    #[cfg(not(feature = "gecko"))]
    228    /// Evaluate a URL matching function.
    229    pub fn evaluate(&self, _: &Device) -> bool {
    230        false
    231    }
    232 }
    233 
    234 /// A `@document` rule's condition.
    235 ///
    236 /// <https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document>
    237 ///
    238 /// The `@document` rule's condition is written as a comma-separated list of
    239 /// URL matching functions, and the condition evaluates to true whenever any
    240 /// one of those functions evaluates to true.
    241 #[derive(Clone, Debug, ToCss, ToShmem)]
    242 #[css(comma)]
    243 pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
    244 
    245 impl DocumentCondition {
    246    /// Parse a document condition.
    247    pub fn parse<'i, 't>(
    248        context: &ParserContext,
    249        input: &mut Parser<'i, 't>,
    250    ) -> Result<Self, ParseError<'i>> {
    251        let conditions =
    252            input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
    253 
    254        let condition = DocumentCondition(conditions);
    255        if !condition.allowed_in(context) {
    256            return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid("-moz-document".into())));
    257        }
    258        Ok(condition)
    259    }
    260 
    261    /// Evaluate a document condition.
    262    pub fn evaluate(&self, device: &Device) -> bool {
    263        self.0
    264            .iter()
    265            .any(|url_matching_function| url_matching_function.evaluate(device))
    266    }
    267 
    268    #[cfg(feature = "servo")]
    269    fn allowed_in(&self, _: &ParserContext) -> bool {
    270        false
    271    }
    272 
    273    #[cfg(feature = "gecko")]
    274    fn allowed_in(&self, context: &ParserContext) -> bool {
    275        if context.chrome_rules_enabled() {
    276            return true;
    277        }
    278 
    279        // Allow a single url-prefix() for compatibility.
    280        //
    281        // See bug 1446470 and dependencies.
    282        if self.0.len() != 1 {
    283            return false;
    284        }
    285 
    286        // NOTE(emilio): This technically allows url-prefix("") too, but...
    287        match self.0[0] {
    288            DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
    289            _ => false,
    290        }
    291    }
    292 }