tor-browser

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

page_rule.rs (12792B)


      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 [`@page`][page] rule.
      6 //!
      7 //! [page]: https://drafts.csswg.org/css2/page.html#page-box
      8 
      9 use crate::derives::*;
     10 use crate::parser::{Parse, ParserContext};
     11 use crate::properties::PropertyDeclarationBlock;
     12 use crate::shared_lock::{
     13    DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
     14 };
     15 use crate::stylesheets::{style_or_page_rule_to_css, CssRules};
     16 use crate::values::{AtomIdent, CustomIdent};
     17 use cssparser::{match_ignore_ascii_case, Parser, SourceLocation, Token};
     18 #[cfg(feature = "gecko")]
     19 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
     20 use servo_arc::Arc;
     21 use smallvec::SmallVec;
     22 use std::fmt::{self, Write};
     23 use style_traits::{CssStringWriter, CssWriter, ParseError, ToCss};
     24 
     25 macro_rules! page_pseudo_classes {
     26    ($($(#[$($meta:tt)+])* $id:ident => $val:literal,)+) => {
     27        /// [`@page`][page] rule pseudo-classes.
     28        ///
     29        /// https://drafts.csswg.org/css-page-3/#page-selectors
     30        #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
     31        #[repr(u8)]
     32        pub enum PagePseudoClass {
     33            $($(#[$($meta)+])* $id,)+
     34        }
     35        impl PagePseudoClass {
     36            fn parse<'i, 't>(
     37                input: &mut Parser<'i, 't>,
     38            ) -> Result<Self, ParseError<'i>> {
     39                let loc = input.current_source_location();
     40                let colon = input.next_including_whitespace()?;
     41                if *colon != Token::Colon {
     42                    return Err(loc.new_unexpected_token_error(colon.clone()));
     43                }
     44 
     45                let ident = input.next_including_whitespace()?;
     46                if let Token::Ident(s) = ident {
     47                    return match_ignore_ascii_case! { &**s,
     48                        $($val => Ok(PagePseudoClass::$id),)+
     49                        _ => Err(loc.new_unexpected_token_error(Token::Ident(s.clone()))),
     50                    };
     51                }
     52                Err(loc.new_unexpected_token_error(ident.clone()))
     53            }
     54            #[inline]
     55            fn to_str(&self) -> &'static str {
     56                match *self {
     57                    $(PagePseudoClass::$id => concat!(':', $val),)+
     58                }
     59            }
     60        }
     61    }
     62 }
     63 
     64 page_pseudo_classes! {
     65    /// [`:first`][first] pseudo-class
     66    ///
     67    /// [first] https://drafts.csswg.org/css-page-3/#first-pseudo
     68    First => "first",
     69    /// [`:blank`][blank] pseudo-class
     70    ///
     71    /// [blank] https://drafts.csswg.org/css-page-3/#blank-pseudo
     72    Blank => "blank",
     73    /// [`:left`][left] pseudo-class
     74    ///
     75    /// [left]: https://drafts.csswg.org/css-page-3/#spread-pseudos
     76    Left => "left",
     77    /// [`:right`][right] pseudo-class
     78    ///
     79    /// [right]: https://drafts.csswg.org/css-page-3/#spread-pseudos
     80    Right => "right",
     81 }
     82 
     83 bitflags! {
     84    /// Bit-flags for pseudo-class. This should only be used for querying if a
     85    /// page-rule applies.
     86    ///
     87    /// https://drafts.csswg.org/css-page-3/#page-selectors
     88    #[derive(Clone, Copy)]
     89    #[repr(C)]
     90    pub struct PagePseudoClassFlags : u8 {
     91        /// No pseudo-classes
     92        const NONE = 0;
     93        /// Flag for PagePseudoClass::First
     94        const FIRST = 1 << 0;
     95        /// Flag for PagePseudoClass::Blank
     96        const BLANK = 1 << 1;
     97        /// Flag for PagePseudoClass::Left
     98        const LEFT = 1 << 2;
     99        /// Flag for PagePseudoClass::Right
    100        const RIGHT = 1 << 3;
    101    }
    102 }
    103 
    104 impl PagePseudoClassFlags {
    105    /// Creates a pseudo-class flags object with a single pseudo-class.
    106    #[inline]
    107    pub fn new(other: &PagePseudoClass) -> Self {
    108        match *other {
    109            PagePseudoClass::First => PagePseudoClassFlags::FIRST,
    110            PagePseudoClass::Blank => PagePseudoClassFlags::BLANK,
    111            PagePseudoClass::Left => PagePseudoClassFlags::LEFT,
    112            PagePseudoClass::Right => PagePseudoClassFlags::RIGHT,
    113        }
    114    }
    115    /// Checks if the given pseudo class applies to this set of flags.
    116    #[inline]
    117    pub fn contains_class(self, other: &PagePseudoClass) -> bool {
    118        self.intersects(PagePseudoClassFlags::new(other))
    119    }
    120 }
    121 
    122 type PagePseudoClasses = SmallVec<[PagePseudoClass; 4]>;
    123 
    124 /// Type of a single [`@page`][page selector]
    125 ///
    126 /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
    127 #[derive(Clone, Debug, MallocSizeOf, ToShmem)]
    128 pub struct PageSelector {
    129    /// Page name
    130    ///
    131    /// https://drafts.csswg.org/css-page-3/#page-type-selector
    132    pub name: AtomIdent,
    133    /// Pseudo-classes for [`@page`][page-selectors]
    134    ///
    135    /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
    136    pub pseudos: PagePseudoClasses,
    137 }
    138 
    139 /// Computes the [specificity] given the g, h, and f values as in the spec.
    140 ///
    141 /// g is number of `:first` or `:blank`, h is number of `:left` or `:right`,
    142 /// f is if the selector includes a page-name (selectors can only include one
    143 /// or zero page-names).
    144 ///
    145 /// This places hard limits of 65535 on h and 32767 on g, at which point all
    146 /// higher values are treated as those limits respectively.
    147 ///
    148 /// [specificity]: https://drafts.csswg.org/css-page/#specificity
    149 #[inline]
    150 fn selector_specificity(g: usize, h: usize, f: bool) -> u32 {
    151    let h = h.min(0xFFFF) as u32;
    152    let g = (g.min(0x7FFF) as u32) << 16;
    153    let f = if f { 0x80000000 } else { 0 };
    154    h + g + f
    155 }
    156 
    157 impl PageSelector {
    158    /// Checks if the ident matches a page-name's ident.
    159    ///
    160    /// This does not take pseudo selectors into account.
    161    #[inline]
    162    pub fn ident_matches(&self, other: &CustomIdent) -> bool {
    163        self.name.0 == other.0
    164    }
    165 
    166    /// Checks that this selector matches the ident and all pseudo classes are
    167    /// present in the provided flags.
    168    #[inline]
    169    pub fn matches(&self, name: &CustomIdent, flags: PagePseudoClassFlags) -> bool {
    170        self.ident_matches(name) && self.flags_match(flags)
    171    }
    172 
    173    /// Checks that all pseudo classes in this selector are present in the
    174    /// provided flags.
    175    ///
    176    /// Equivalent to, but may be more efficient than:
    177    ///
    178    /// ```
    179    /// match_specificity(flags).is_some()
    180    /// ```
    181    pub fn flags_match(&self, flags: PagePseudoClassFlags) -> bool {
    182        self.pseudos.iter().all(|pc| flags.contains_class(pc))
    183    }
    184 
    185    /// Implements specificity calculation for a page selector given a set of
    186    /// page pseudo-classes to match with.
    187    /// If this selector includes any pseudo-classes that are not in the flags,
    188    /// then this will return None.
    189    ///
    190    /// To fit the specificity calculation into a 32-bit value, this limits the
    191    /// maximum count of :first and :blank to 32767, and the maximum count of
    192    /// :left and :right to 65535.
    193    ///
    194    /// https://drafts.csswg.org/css-page-3/#cascading-and-page-context
    195    pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option<u32> {
    196        let mut g: usize = 0;
    197        let mut h: usize = 0;
    198        for pc in self.pseudos.iter() {
    199            if !flags.contains_class(pc) {
    200                return None;
    201            }
    202            match pc {
    203                PagePseudoClass::First | PagePseudoClass::Blank => g += 1,
    204                PagePseudoClass::Left | PagePseudoClass::Right => h += 1,
    205            }
    206        }
    207        Some(selector_specificity(g, h, !self.name.0.is_empty()))
    208    }
    209 }
    210 
    211 impl ToCss for PageSelector {
    212    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    213    where
    214        W: Write,
    215    {
    216        self.name.to_css(dest)?;
    217        for pc in self.pseudos.iter() {
    218            dest.write_str(pc.to_str())?;
    219        }
    220        Ok(())
    221    }
    222 }
    223 
    224 fn parse_page_name<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AtomIdent, ParseError<'i>> {
    225    let s = input.expect_ident()?;
    226    Ok(AtomIdent::from(&**s))
    227 }
    228 
    229 impl Parse for PageSelector {
    230    fn parse<'i, 't>(
    231        _context: &ParserContext,
    232        input: &mut Parser<'i, 't>,
    233    ) -> Result<Self, ParseError<'i>> {
    234        let name = input.try_parse(parse_page_name);
    235        let mut pseudos = PagePseudoClasses::default();
    236        while let Ok(pc) = input.try_parse(PagePseudoClass::parse) {
    237            pseudos.push(pc);
    238        }
    239        // If the result was empty, then we didn't get a selector.
    240        let name = match name {
    241            Ok(name) => name,
    242            Err(..) if !pseudos.is_empty() => AtomIdent::new(atom!("")),
    243            Err(err) => return Err(err),
    244        };
    245        Ok(PageSelector { name, pseudos })
    246    }
    247 }
    248 
    249 /// A list of [`@page`][page selectors]
    250 ///
    251 /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
    252 #[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)]
    253 #[css(comma)]
    254 pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>);
    255 
    256 impl PageSelectors {
    257    /// Creates a new PageSelectors from a Vec, as from parse_comma_separated
    258    #[inline]
    259    pub fn new(s: Vec<PageSelector>) -> Self {
    260        PageSelectors(s.into())
    261    }
    262    /// Returns true iff there are any page selectors
    263    #[inline]
    264    pub fn is_empty(&self) -> bool {
    265        self.as_slice().is_empty()
    266    }
    267    /// Get the underlying PageSelector data as a slice
    268    #[inline]
    269    pub fn as_slice(&self) -> &[PageSelector] {
    270        &*self.0
    271    }
    272 }
    273 
    274 impl Parse for PageSelectors {
    275    fn parse<'i, 't>(
    276        context: &ParserContext,
    277        input: &mut Parser<'i, 't>,
    278    ) -> Result<Self, ParseError<'i>> {
    279        Ok(PageSelectors::new(input.parse_comma_separated(|i| {
    280            PageSelector::parse(context, i)
    281        })?))
    282    }
    283 }
    284 
    285 /// A [`@page`][page] rule.
    286 ///
    287 /// This implements only a limited subset of the CSS
    288 /// 2.2 syntax.
    289 ///
    290 /// [page]: https://drafts.csswg.org/css2/page.html#page-box
    291 /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
    292 #[derive(Clone, Debug, ToShmem)]
    293 pub struct PageRule {
    294    /// Selectors of the page-rule
    295    pub selectors: PageSelectors,
    296    /// Nested rules.
    297    pub rules: Arc<Locked<CssRules>>,
    298    /// The declaration block this page rule contains.
    299    pub block: Arc<Locked<PropertyDeclarationBlock>>,
    300    /// The source position this rule was found at.
    301    pub source_location: SourceLocation,
    302 }
    303 
    304 impl PageRule {
    305    /// Measure heap usage.
    306    #[cfg(feature = "gecko")]
    307    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
    308        // Measurement of other fields may be added later.
    309        self.rules.unconditional_shallow_size_of(ops)
    310            + self.rules.read_with(guard).size_of(guard, ops)
    311            + self.block.unconditional_shallow_size_of(ops)
    312            + self.block.read_with(guard).size_of(ops)
    313            + self.selectors.size_of(ops)
    314    }
    315    /// Computes the specificity of this page rule when matched with flags.
    316    ///
    317    /// Computing this value has linear-complexity with the size of the
    318    /// selectors, so the caller should usually call this once and cache the
    319    /// result.
    320    ///
    321    /// Returns None if the flags do not match this page rule.
    322    ///
    323    /// The return type is ordered by page-rule specificity.
    324    pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option<u32> {
    325        if self.selectors.is_empty() {
    326            // A page-rule with no selectors matches all pages, but with the
    327            // lowest possible specificity.
    328            return Some(selector_specificity(0, 0, false));
    329        }
    330        let mut specificity = None;
    331        for s in self.selectors.0.iter().map(|s| s.match_specificity(flags)) {
    332            specificity = s.max(specificity);
    333        }
    334        specificity
    335    }
    336 }
    337 
    338 impl ToCssWithGuard for PageRule {
    339    /// Serialization of PageRule is not specced, adapted from steps for StyleRule.
    340    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
    341        // https://drafts.csswg.org/cssom/#serialize-a-css-rule
    342        dest.write_str("@page ")?;
    343        if !self.selectors.is_empty() {
    344            self.selectors.to_css(&mut CssWriter::new(dest))?;
    345            dest.write_char(' ')?;
    346        }
    347        style_or_page_rule_to_css(Some(&self.rules), &self.block, guard, dest)
    348    }
    349 }
    350 
    351 impl DeepCloneWithLock for PageRule {
    352    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
    353        let rules = self.rules.read_with(&guard);
    354        PageRule {
    355            selectors: self.selectors.clone(),
    356            block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
    357            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
    358            source_location: self.source_location.clone(),
    359        }
    360    }
    361 }