tor-browser

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

rule_cache.rs (8138B)


      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 cache from rule node to computed values, in order to cache reset
      6 //! properties.
      7 
      8 use crate::logical_geometry::WritingMode;
      9 use crate::properties::{ComputedValues, StyleBuilder};
     10 use crate::rule_tree::StrongRuleNode;
     11 use crate::selector_parser::PseudoElement;
     12 use crate::shared_lock::StylesheetGuards;
     13 use crate::values::computed::{NonNegativeLength, Zoom};
     14 use crate::values::specified::color::ColorSchemeFlags;
     15 use rustc_hash::FxHashMap;
     16 use servo_arc::Arc;
     17 use smallvec::SmallVec;
     18 
     19 /// The conditions for caching and matching a style in the rule cache.
     20 #[derive(Clone, Debug, Default)]
     21 pub struct RuleCacheConditions {
     22    uncacheable: bool,
     23    font_size: Option<NonNegativeLength>,
     24    line_height: Option<NonNegativeLength>,
     25    writing_mode: Option<WritingMode>,
     26    color_scheme: Option<ColorSchemeFlags>,
     27 }
     28 
     29 impl RuleCacheConditions {
     30    /// Sets the style as depending in the font-size value.
     31    pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
     32        debug_assert!(self.font_size.map_or(true, |f| f == font_size));
     33        self.font_size = Some(font_size);
     34    }
     35 
     36    /// Sets the style as depending in the line-height value.
     37    pub fn set_line_height_dependency(&mut self, line_height: NonNegativeLength) {
     38        debug_assert!(self.line_height.map_or(true, |l| l == line_height));
     39        self.line_height = Some(line_height);
     40    }
     41 
     42    /// Sets the style as depending in the color-scheme property value.
     43    pub fn set_color_scheme_dependency(&mut self, color_scheme: ColorSchemeFlags) {
     44        debug_assert!(self.color_scheme.map_or(true, |cs| cs == color_scheme));
     45        self.color_scheme = Some(color_scheme);
     46    }
     47 
     48    /// Sets the style as uncacheable.
     49    pub fn set_uncacheable(&mut self) {
     50        self.uncacheable = true;
     51    }
     52 
     53    /// Sets the style as depending in the writing-mode value `writing_mode`.
     54    pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
     55        debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
     56        self.writing_mode = Some(writing_mode);
     57    }
     58 
     59    /// Returns whether the current style's reset properties are cacheable.
     60    fn cacheable(&self) -> bool {
     61        !self.uncacheable
     62    }
     63 }
     64 
     65 #[derive(Debug)]
     66 struct CachedConditions {
     67    font_size: Option<NonNegativeLength>,
     68    line_height: Option<NonNegativeLength>,
     69    color_scheme: Option<ColorSchemeFlags>,
     70    writing_mode: Option<WritingMode>,
     71    zoom: Zoom,
     72 }
     73 
     74 impl CachedConditions {
     75    /// Returns whether `style` matches the conditions.
     76    fn matches(&self, style: &StyleBuilder) -> bool {
     77        if style.effective_zoom != self.zoom {
     78            return false;
     79        }
     80 
     81        if let Some(fs) = self.font_size {
     82            if style.get_font().clone_font_size().computed_size != fs {
     83                return false;
     84            }
     85        }
     86 
     87        if let Some(lh) = self.line_height {
     88            let new_line_height =
     89                style
     90                    .device
     91                    .calc_line_height(&style.get_font(), style.writing_mode, None);
     92            if new_line_height != lh {
     93                return false;
     94            }
     95        }
     96 
     97        if let Some(cs) = self.color_scheme {
     98            if style.get_inherited_ui().color_scheme_bits() != cs {
     99                return false;
    100            }
    101        }
    102 
    103        if let Some(wm) = self.writing_mode {
    104            if style.writing_mode != wm {
    105                return false;
    106            }
    107        }
    108 
    109        true
    110    }
    111 }
    112 
    113 /// A TLS cache from rules matched to computed values.
    114 pub struct RuleCache {
    115    // FIXME(emilio): Consider using LRUCache or something like that?
    116    map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>,
    117 }
    118 
    119 impl RuleCache {
    120    /// Creates an empty `RuleCache`.
    121    pub fn new() -> Self {
    122        Self {
    123            map: FxHashMap::default(),
    124        }
    125    }
    126 
    127    /// Walk the rule tree and return a rule node for using as the key
    128    /// for rule cache.
    129    ///
    130    /// It currently skips animation / style attribute / preshint rules when they don't contain any
    131    /// declaration of a reset property. We don't skip other levels because walking the whole
    132    /// parent chain can be expensive.
    133    ///
    134    /// TODO(emilio): Measure this, this was not super-well measured for performance (this was done
    135    /// for memory in bug 1427681)... Walking the rule tree might be worth it if we hit the cache
    136    /// enough?
    137    fn get_rule_node_for_cache<'r>(
    138        guards: &StylesheetGuards,
    139        mut rule_node: Option<&'r StrongRuleNode>,
    140    ) -> Option<&'r StrongRuleNode> {
    141        use crate::rule_tree::CascadeLevel;
    142        while let Some(node) = rule_node {
    143            let priority = node.cascade_priority();
    144            let cascade_level = priority.cascade_level();
    145            let should_try_to_skip = cascade_level.is_animation()
    146                || matches!(cascade_level, CascadeLevel::PresHints)
    147                || priority.layer_order().is_style_attribute_layer();
    148            if !should_try_to_skip {
    149                break;
    150            }
    151            if let Some(source) = node.style_source() {
    152                let decls = source.get().read_with(cascade_level.guard(guards));
    153                if decls.contains_any_reset() {
    154                    break;
    155                }
    156            }
    157            rule_node = node.parent();
    158        }
    159        rule_node
    160    }
    161 
    162    /// Finds a node in the properties matched cache.
    163    ///
    164    /// This needs to receive a `StyleBuilder` with the `early` properties
    165    /// already applied.
    166    pub fn find(
    167        &self,
    168        guards: &StylesheetGuards,
    169        builder_with_early_props: &StyleBuilder,
    170    ) -> Option<&ComputedValues> {
    171        // A pseudo-element with property restrictions can result in different
    172        // computed values if it's also used for a non-pseudo.
    173        if builder_with_early_props
    174            .pseudo
    175            .and_then(|p| p.property_restriction())
    176            .is_some()
    177        {
    178            return None;
    179        }
    180 
    181        let rules = builder_with_early_props.rules.as_ref();
    182        let rules = Self::get_rule_node_for_cache(guards, rules)?;
    183        let cached_values = self.map.get(rules)?;
    184 
    185        for &(ref conditions, ref values) in cached_values.iter() {
    186            if conditions.matches(builder_with_early_props) {
    187                debug!("Using cached reset style with conditions {:?}", conditions);
    188                return Some(&**values);
    189            }
    190        }
    191        None
    192    }
    193 
    194    /// Inserts a node into the rules cache if possible.
    195    ///
    196    /// Returns whether the style was inserted into the cache.
    197    pub fn insert_if_possible(
    198        &mut self,
    199        guards: &StylesheetGuards,
    200        style: &Arc<ComputedValues>,
    201        pseudo: Option<&PseudoElement>,
    202        conditions: &RuleCacheConditions,
    203    ) -> bool {
    204        if !conditions.cacheable() {
    205            return false;
    206        }
    207 
    208        // A pseudo-element with property restrictions can result in different
    209        // computed values if it's also used for a non-pseudo.
    210        if pseudo.and_then(|p| p.property_restriction()).is_some() {
    211            return false;
    212        }
    213 
    214        let rules = style.rules.as_ref();
    215        let rules = match Self::get_rule_node_for_cache(guards, rules) {
    216            Some(r) => r.clone(),
    217            None => return false,
    218        };
    219 
    220        debug!(
    221            "Inserting cached reset style with conditions {:?}",
    222            conditions
    223        );
    224        let cached_conditions = CachedConditions {
    225            writing_mode: conditions.writing_mode,
    226            font_size: conditions.font_size,
    227            line_height: conditions.line_height,
    228            color_scheme: conditions.color_scheme,
    229            zoom: style.effective_zoom,
    230        };
    231        self.map
    232            .entry(rules)
    233            .or_default()
    234            .push((cached_conditions, style.clone()));
    235        true
    236    }
    237 }