style/
rule_cache.rs

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
8use crate::logical_geometry::WritingMode;
9use crate::properties::{ComputedValues, StyleBuilder};
10use crate::rule_tree::StrongRuleNode;
11use crate::selector_parser::PseudoElement;
12use crate::shared_lock::StylesheetGuards;
13use crate::values::computed::{NonNegativeLength, Zoom};
14use crate::values::specified::color::ColorSchemeFlags;
15use fxhash::FxHashMap;
16use servo_arc::Arc;
17use smallvec::SmallVec;
18
19/// The conditions for caching and matching a style in the rule cache.
20#[derive(Clone, Debug, Default)]
21pub 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
29impl 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)]
66struct 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
74impl 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.
114pub struct RuleCache {
115    // FIXME(emilio): Consider using LRUCache or something like that?
116    map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>,
117}
118
119impl 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}