tor-browser

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

stylesheet.rs (19077B)


      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 use crate::context::QuirksMode;
      6 use crate::derives::*;
      7 use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
      8 use crate::media_queries::{Device, MediaList};
      9 use crate::parser::ParserContext;
     10 use crate::shared_lock::{DeepCloneWithLock, Locked};
     11 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
     12 use crate::stylesheets::loader::StylesheetLoader;
     13 use crate::stylesheets::rule_parser::{State, TopLevelRuleParser};
     14 use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator};
     15 use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator};
     16 use crate::stylesheets::{
     17    CssRule, CssRules, CustomMediaEvaluator, CustomMediaMap, Origin, UrlExtraData,
     18 };
     19 use crate::use_counters::UseCounters;
     20 use crate::{Namespace, Prefix};
     21 use cssparser::{Parser, ParserInput, StyleSheetParser};
     22 #[cfg(feature = "gecko")]
     23 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
     24 use rustc_hash::FxHashMap;
     25 use servo_arc::Arc;
     26 use std::ops::Deref;
     27 use std::sync::atomic::{AtomicBool, Ordering};
     28 use style_traits::ParsingMode;
     29 
     30 use super::scope_rule::ImplicitScopeRoot;
     31 
     32 /// This structure holds the user-agent and user stylesheets.
     33 pub struct UserAgentStylesheets {
     34    /// The lock used for user-agent stylesheets.
     35    pub shared_lock: SharedRwLock,
     36    /// The user or user agent stylesheets.
     37    pub user_or_user_agent_stylesheets: Vec<DocumentStyleSheet>,
     38    /// The quirks mode stylesheet.
     39    pub quirks_mode_stylesheet: DocumentStyleSheet,
     40 }
     41 
     42 /// A set of namespaces applying to a given stylesheet.
     43 ///
     44 /// The namespace id is used in gecko
     45 #[derive(Clone, Debug, Default, MallocSizeOf)]
     46 #[allow(missing_docs)]
     47 pub struct Namespaces {
     48    pub default: Option<Namespace>,
     49    pub prefixes: FxHashMap<Prefix, Namespace>,
     50 }
     51 
     52 /// The contents of a given stylesheet. This effectively maps to a
     53 /// StyleSheetInner in Gecko.
     54 #[derive(Debug)]
     55 pub struct StylesheetContents {
     56    /// List of rules in the order they were found (important for
     57    /// cascading order)
     58    pub rules: Arc<Locked<CssRules>>,
     59    /// The origin of this stylesheet.
     60    pub origin: Origin,
     61    /// The url data this stylesheet should use.
     62    pub url_data: UrlExtraData,
     63    /// The namespaces that apply to this stylesheet.
     64    pub namespaces: Namespaces,
     65    /// The quirks mode of this stylesheet.
     66    pub quirks_mode: QuirksMode,
     67    /// This stylesheet's source map URL.
     68    pub source_map_url: Option<String>,
     69    /// This stylesheet's source URL.
     70    pub source_url: Option<String>,
     71    /// The use counters of the original stylesheet.
     72    pub use_counters: UseCounters,
     73 
     74    /// We don't want to allow construction outside of this file, to guarantee
     75    /// that all contents are created with Arc<>.
     76    _forbid_construction: (),
     77 }
     78 
     79 impl StylesheetContents {
     80    /// Parse a given CSS string, with a given url-data, origin, and
     81    /// quirks mode.
     82    pub fn from_str(
     83        css: &str,
     84        url_data: UrlExtraData,
     85        origin: Origin,
     86        shared_lock: &SharedRwLock,
     87        stylesheet_loader: Option<&dyn StylesheetLoader>,
     88        error_reporter: Option<&dyn ParseErrorReporter>,
     89        quirks_mode: QuirksMode,
     90        allow_import_rules: AllowImportRules,
     91        sanitization_data: Option<&mut SanitizationData>,
     92    ) -> Arc<Self> {
     93        let use_counters = UseCounters::default();
     94        let (namespaces, rules, source_map_url, source_url) = Stylesheet::parse_rules(
     95            css,
     96            &url_data,
     97            origin,
     98            &shared_lock,
     99            stylesheet_loader,
    100            error_reporter,
    101            quirks_mode,
    102            Some(&use_counters),
    103            allow_import_rules,
    104            sanitization_data,
    105        );
    106 
    107        Arc::new(Self {
    108            rules: CssRules::new(rules, &shared_lock),
    109            origin,
    110            url_data,
    111            namespaces,
    112            quirks_mode,
    113            source_map_url,
    114            source_url,
    115            use_counters,
    116            _forbid_construction: (),
    117        })
    118    }
    119 
    120    /// Creates a new StylesheetContents with the specified pre-parsed rules,
    121    /// origin, URL data, and quirks mode.
    122    ///
    123    /// Since the rules have already been parsed, and the intention is that
    124    /// this function is used for read only User Agent style sheets, an empty
    125    /// namespace map is used, and the source map and source URLs are set to
    126    /// None.
    127    ///
    128    /// An empty namespace map should be fine, as it is only used for parsing,
    129    /// not serialization of existing selectors.  Since UA sheets are read only,
    130    /// we should never need the namespace map.
    131    pub fn from_shared_data(
    132        rules: Arc<Locked<CssRules>>,
    133        origin: Origin,
    134        url_data: UrlExtraData,
    135        quirks_mode: QuirksMode,
    136    ) -> Arc<Self> {
    137        debug_assert!(rules.is_static());
    138        Arc::new(Self {
    139            rules,
    140            origin,
    141            url_data,
    142            namespaces: Namespaces::default(),
    143            quirks_mode,
    144            source_map_url: None,
    145            source_url: None,
    146            use_counters: UseCounters::default(),
    147            _forbid_construction: (),
    148        })
    149    }
    150 
    151    /// Returns a reference to the list of rules.
    152    #[inline]
    153    pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] {
    154        &self.rules.read_with(guard).0
    155    }
    156 
    157    /// Measure heap usage.
    158    #[cfg(feature = "gecko")]
    159    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
    160        if self.rules.is_static() {
    161            return 0;
    162        }
    163        // Measurement of other fields may be added later.
    164        self.rules.unconditional_shallow_size_of(ops)
    165            + self.rules.read_with(guard).size_of(guard, ops)
    166    }
    167 
    168    /// Return an iterator using the condition `C`.
    169    #[inline]
    170    pub fn iter_rules<'a, 'b, C, CMM>(
    171        &'a self,
    172        device: &'a Device,
    173        custom_media: CMM,
    174        guard: &'a SharedRwLockReadGuard<'b>,
    175    ) -> RulesIterator<'a, 'b, C, CMM>
    176    where
    177        C: NestedRuleIterationCondition,
    178        CMM: Deref<Target = CustomMediaMap>,
    179    {
    180        RulesIterator::new(
    181            device,
    182            self.quirks_mode,
    183            custom_media,
    184            guard,
    185            self.rules(guard).iter(),
    186        )
    187    }
    188 
    189    /// Return an iterator over the effective rules within the style-sheet, as
    190    /// according to the supplied `Device`.
    191    #[inline]
    192    pub fn effective_rules<'a, 'b, CMM: Deref<Target = CustomMediaMap>>(
    193        &'a self,
    194        device: &'a Device,
    195        custom_media: CMM,
    196        guard: &'a SharedRwLockReadGuard<'b>,
    197    ) -> EffectiveRulesIterator<'a, 'b, CMM> {
    198        self.iter_rules::<EffectiveRules, CMM>(device, custom_media, guard)
    199    }
    200 
    201    /// Perform a deep clone, of this stylesheet, with an explicit URL data if needed.
    202    pub fn deep_clone(
    203        &self,
    204        lock: &SharedRwLock,
    205        url_data: Option<&UrlExtraData>,
    206        guard: &SharedRwLockReadGuard,
    207    ) -> Arc<Self> {
    208        // Make a deep clone of the rules, using the new lock.
    209        let rules = self
    210            .rules
    211            .read_with(guard)
    212            .deep_clone_with_lock(lock, guard);
    213 
    214        let url_data = url_data.cloned().unwrap_or_else(|| self.url_data.clone());
    215 
    216        Arc::new(Self {
    217            rules: Arc::new(lock.wrap(rules)),
    218            quirks_mode: self.quirks_mode,
    219            origin: self.origin,
    220            url_data,
    221            namespaces: self.namespaces.clone(),
    222            source_map_url: self.source_map_url.clone(),
    223            source_url: self.source_url.clone(),
    224            use_counters: self.use_counters.clone(),
    225            _forbid_construction: (),
    226        })
    227    }
    228 }
    229 
    230 /// The structure servo uses to represent a stylesheet.
    231 #[derive(Debug)]
    232 pub struct Stylesheet {
    233    /// The contents of this stylesheet.
    234    pub contents: Locked<Arc<StylesheetContents>>,
    235    /// The lock used for objects inside this stylesheet
    236    pub shared_lock: SharedRwLock,
    237    /// List of media associated with the Stylesheet.
    238    pub media: Arc<Locked<MediaList>>,
    239    /// Whether this stylesheet should be disabled.
    240    pub disabled: AtomicBool,
    241 }
    242 
    243 /// A trait to represent a given stylesheet in a document.
    244 pub trait StylesheetInDocument: ::std::fmt::Debug {
    245    /// Get whether this stylesheet is enabled.
    246    fn enabled(&self) -> bool;
    247 
    248    /// Get the media associated with this stylesheet.
    249    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>;
    250 
    251    /// Returns a reference to the contents of the stylesheet.
    252    fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents;
    253 
    254    /// Returns whether the style-sheet applies for the current device.
    255    fn is_effective_for_device(
    256        &self,
    257        device: &Device,
    258        custom_media: &CustomMediaMap,
    259        guard: &SharedRwLockReadGuard,
    260    ) -> bool {
    261        let media = match self.media(guard) {
    262            Some(m) => m,
    263            None => return true,
    264        };
    265        media.evaluate(
    266            device,
    267            self.contents(guard).quirks_mode,
    268            &mut CustomMediaEvaluator::new(custom_media, guard),
    269        )
    270    }
    271 
    272    /// Return the implicit scope root for this stylesheet, if one exists.
    273    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot>;
    274 }
    275 
    276 impl StylesheetInDocument for Stylesheet {
    277    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
    278        Some(self.media.read_with(guard))
    279    }
    280 
    281    fn enabled(&self) -> bool {
    282        !self.disabled()
    283    }
    284 
    285    #[inline]
    286    fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents {
    287        self.contents.read_with(guard)
    288    }
    289 
    290    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
    291        None
    292    }
    293 }
    294 
    295 /// A simple wrapper over an `Arc<Stylesheet>`, with pointer comparison, and
    296 /// suitable for its use in a `StylesheetSet`.
    297 #[derive(Clone, Debug)]
    298 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
    299 pub struct DocumentStyleSheet(
    300    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub Arc<Stylesheet>,
    301 );
    302 
    303 impl PartialEq for DocumentStyleSheet {
    304    fn eq(&self, other: &Self) -> bool {
    305        Arc::ptr_eq(&self.0, &other.0)
    306    }
    307 }
    308 
    309 impl StylesheetInDocument for DocumentStyleSheet {
    310    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
    311        self.0.media(guard)
    312    }
    313 
    314    fn enabled(&self) -> bool {
    315        self.0.enabled()
    316    }
    317 
    318    #[inline]
    319    fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents {
    320        self.0.contents(guard)
    321    }
    322 
    323    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
    324        None
    325    }
    326 }
    327 
    328 /// The kind of sanitization to use when parsing a stylesheet.
    329 #[repr(u8)]
    330 #[derive(Clone, Copy, Debug, PartialEq)]
    331 pub enum SanitizationKind {
    332    /// Perform no sanitization.
    333    None,
    334    /// Allow only @font-face, style rules, and @namespace.
    335    Standard,
    336    /// Allow everything but conditional rules.
    337    NoConditionalRules,
    338 }
    339 
    340 /// Whether @import rules are allowed.
    341 #[repr(u8)]
    342 #[derive(Clone, Copy, Debug, PartialEq)]
    343 pub enum AllowImportRules {
    344    /// @import rules will be parsed.
    345    Yes,
    346    /// @import rules will not be parsed.
    347    No,
    348 }
    349 
    350 impl SanitizationKind {
    351    fn allows(self, rule: &CssRule) -> bool {
    352        debug_assert_ne!(self, SanitizationKind::None);
    353        // NOTE(emilio): If this becomes more complex (not filtering just by
    354        // top-level rules), we should thread all the data through nested rules
    355        // and such. But this doesn't seem necessary at the moment.
    356        let is_standard = matches!(self, SanitizationKind::Standard);
    357        match *rule {
    358            CssRule::Document(..) |
    359            CssRule::Media(..) |
    360            CssRule::CustomMedia(..) |
    361            CssRule::Supports(..) |
    362            CssRule::Import(..) |
    363            CssRule::Container(..) |
    364            // TODO(emilio): Perhaps Layer should not be always sanitized? But
    365            // we sanitize @media and co, so this seems safer for now.
    366            CssRule::LayerStatement(..) |
    367            CssRule::LayerBlock(..) |
    368            // TODO(dshin): Same comment as Layer applies - shouldn't give away
    369            // something like display size - erring on the side of "safe" for now.
    370            CssRule::Scope(..) |
    371            CssRule::StartingStyle(..) => false,
    372 
    373            CssRule::FontFace(..) |
    374            CssRule::Namespace(..) |
    375            CssRule::Style(..) |
    376            CssRule::NestedDeclarations(..) |
    377            CssRule::PositionTry(..) => true,
    378 
    379            CssRule::Keyframes(..) |
    380            CssRule::Page(..) |
    381            CssRule::Margin(..) |
    382            CssRule::Property(..) |
    383            CssRule::FontFeatureValues(..) |
    384            CssRule::FontPaletteValues(..) |
    385            CssRule::CounterStyle(..) => !is_standard,
    386        }
    387    }
    388 }
    389 
    390 /// A struct to hold the data relevant to style sheet sanitization.
    391 #[derive(Debug)]
    392 pub struct SanitizationData {
    393    kind: SanitizationKind,
    394    output: String,
    395 }
    396 
    397 impl SanitizationData {
    398    /// Create a new input for sanitization.
    399    #[inline]
    400    pub fn new(kind: SanitizationKind) -> Option<Self> {
    401        if matches!(kind, SanitizationKind::None) {
    402            return None;
    403        }
    404        Some(Self {
    405            kind,
    406            output: String::new(),
    407        })
    408    }
    409 
    410    /// Take the sanitized output.
    411    #[inline]
    412    pub fn take(self) -> String {
    413        self.output
    414    }
    415 }
    416 
    417 impl Stylesheet {
    418    fn parse_rules(
    419        css: &str,
    420        url_data: &UrlExtraData,
    421        origin: Origin,
    422        shared_lock: &SharedRwLock,
    423        stylesheet_loader: Option<&dyn StylesheetLoader>,
    424        error_reporter: Option<&dyn ParseErrorReporter>,
    425        quirks_mode: QuirksMode,
    426        use_counters: Option<&UseCounters>,
    427        allow_import_rules: AllowImportRules,
    428        mut sanitization_data: Option<&mut SanitizationData>,
    429    ) -> (Namespaces, Vec<CssRule>, Option<String>, Option<String>) {
    430        let mut input = ParserInput::new(css);
    431        let mut input = Parser::new(&mut input);
    432 
    433        let context = ParserContext::new(
    434            origin,
    435            url_data,
    436            None,
    437            ParsingMode::DEFAULT,
    438            quirks_mode,
    439            /* namespaces = */ Default::default(),
    440            error_reporter,
    441            use_counters,
    442        );
    443 
    444        let mut rule_parser = TopLevelRuleParser {
    445            shared_lock,
    446            loader: stylesheet_loader,
    447            context,
    448            state: State::Start,
    449            dom_error: None,
    450            insert_rule_context: None,
    451            allow_import_rules,
    452            declaration_parser_state: Default::default(),
    453            first_declaration_block: Default::default(),
    454            wants_first_declaration_block: false,
    455            error_reporting_state: Default::default(),
    456            rules: Vec::new(),
    457        };
    458 
    459        {
    460            let mut iter = StyleSheetParser::new(&mut input, &mut rule_parser);
    461            while let Some(result) = iter.next() {
    462                match result {
    463                    Ok(rule_start) => {
    464                        // TODO(emilio, nesting): sanitize nested CSS rules, probably?
    465                        if let Some(ref mut data) = sanitization_data {
    466                            if let Some(ref rule) = iter.parser.rules.last() {
    467                                if !data.kind.allows(rule) {
    468                                    iter.parser.rules.pop();
    469                                    continue;
    470                                }
    471                            }
    472                            let end = iter.input.position().byte_index();
    473                            data.output.push_str(&css[rule_start.byte_index()..end]);
    474                        }
    475                    },
    476                    Err((error, slice)) => {
    477                        let location = error.location;
    478                        let error = ContextualParseError::InvalidRule(slice, error);
    479                        iter.parser.context.log_css_error(location, error);
    480                    },
    481                }
    482            }
    483        }
    484 
    485        let source_map_url = input.current_source_map_url().map(String::from);
    486        let source_url = input.current_source_url().map(String::from);
    487        (
    488            rule_parser.context.namespaces.into_owned(),
    489            rule_parser.rules,
    490            source_map_url,
    491            source_url,
    492        )
    493    }
    494 
    495    /// Creates an empty stylesheet and parses it with a given base url, origin and media.
    496    pub fn from_str(
    497        css: &str,
    498        url_data: UrlExtraData,
    499        origin: Origin,
    500        media: Arc<Locked<MediaList>>,
    501        shared_lock: SharedRwLock,
    502        stylesheet_loader: Option<&dyn StylesheetLoader>,
    503        error_reporter: Option<&dyn ParseErrorReporter>,
    504        quirks_mode: QuirksMode,
    505        allow_import_rules: AllowImportRules,
    506    ) -> Self {
    507        // FIXME: Consider adding use counters to Servo?
    508        let contents = StylesheetContents::from_str(
    509            css,
    510            url_data,
    511            origin,
    512            &shared_lock,
    513            stylesheet_loader,
    514            error_reporter,
    515            quirks_mode,
    516            allow_import_rules,
    517            /* sanitized_output = */ None,
    518        );
    519 
    520        Stylesheet {
    521            contents: shared_lock.wrap(contents),
    522            shared_lock,
    523            media,
    524            disabled: AtomicBool::new(false),
    525        }
    526    }
    527 
    528    /// Returns whether the stylesheet has been explicitly disabled through the
    529    /// CSSOM.
    530    pub fn disabled(&self) -> bool {
    531        self.disabled.load(Ordering::SeqCst)
    532    }
    533 
    534    /// Records that the stylesheet has been explicitly disabled through the
    535    /// CSSOM.
    536    ///
    537    /// Returns whether the the call resulted in a change in disabled state.
    538    ///
    539    /// Disabled stylesheets remain in the document, but their rules are not
    540    /// added to the Stylist.
    541    pub fn set_disabled(&self, disabled: bool) -> bool {
    542        self.disabled.swap(disabled, Ordering::SeqCst) != disabled
    543    }
    544 }
    545 
    546 #[cfg(feature = "servo")]
    547 impl Clone for Stylesheet {
    548    fn clone(&self) -> Self {
    549        // Create a new lock for our clone.
    550        let lock = self.shared_lock.clone();
    551        let guard = self.shared_lock.read();
    552 
    553        // Make a deep clone of the media, using the new lock.
    554        let media = self.media.read_with(&guard).clone();
    555        let media = Arc::new(lock.wrap(media));
    556        let contents = lock.wrap(Arc::new(
    557            self.contents
    558                .read_with(&guard)
    559                .deep_clone_with_lock(&lock, &guard),
    560        ));
    561 
    562        Stylesheet {
    563            contents,
    564            media,
    565            shared_lock: lock,
    566            disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)),
    567        }
    568    }
    569 }