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;
14 use fxhash::FxHashMap;
15 use servo_arc::Arc;
16 use smallvec::SmallVec;
17 
18 /// The conditions for caching and matching a style in the rule cache.
19 #[derive(Clone, Debug, Default)]
20 pub struct RuleCacheConditions {
21     uncacheable: bool,
22     font_size: Option<NonNegativeLength>,
23     writing_mode: Option<WritingMode>,
24 }
25 
26 impl RuleCacheConditions {
27     /// Sets the style as depending in the font-size value.
set_font_size_dependency(&mut self, font_size: NonNegativeLength)28     pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
29         debug_assert!(self.font_size.map_or(true, |f| f == font_size));
30         self.font_size = Some(font_size);
31     }
32 
33     /// Sets the style as uncacheable.
set_uncacheable(&mut self)34     pub fn set_uncacheable(&mut self) {
35         self.uncacheable = true;
36     }
37 
38     /// Sets the style as depending in the writing-mode value `writing_mode`.
set_writing_mode_dependency(&mut self, writing_mode: WritingMode)39     pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
40         debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
41         self.writing_mode = Some(writing_mode);
42     }
43 
44     /// Returns whether the current style's reset properties are cacheable.
cacheable(&self) -> bool45     fn cacheable(&self) -> bool {
46         !self.uncacheable
47     }
48 
49     /// Returns whether `style` matches the conditions.
matches(&self, style: &StyleBuilder) -> bool50     fn matches(&self, style: &StyleBuilder) -> bool {
51         if self.uncacheable {
52             return false;
53         }
54 
55         if let Some(fs) = self.font_size {
56             if style.get_font().clone_font_size().size != fs {
57                 return false;
58             }
59         }
60 
61         if let Some(wm) = self.writing_mode {
62             if style.writing_mode != wm {
63                 return false;
64             }
65         }
66 
67         true
68     }
69 }
70 
71 /// A TLS cache from rules matched to computed values.
72 pub struct RuleCache {
73     // FIXME(emilio): Consider using LRUCache or something like that?
74     map: FxHashMap<StrongRuleNode, SmallVec<[(RuleCacheConditions, Arc<ComputedValues>); 1]>>,
75 }
76 
77 impl RuleCache {
78     /// Creates an empty `RuleCache`.
new() -> Self79     pub fn new() -> Self {
80         Self {
81             map: FxHashMap::default(),
82         }
83     }
84 
85     /// Walk the rule tree and return a rule node for using as the key
86     /// for rule cache.
87     ///
88     /// It currently skips a rule node when it is neither from a style
89     /// rule, nor containing any declaration of reset property. We don't
90     /// skip style rule so that we don't need to walk a long way in the
91     /// worst case. Skipping declarations rule nodes should be enough
92     /// to address common cases that rule cache would fail to share
93     /// when using the rule node directly, like preshint, style attrs,
94     /// and animations.
get_rule_node_for_cache<'r>( guards: &StylesheetGuards, mut rule_node: Option<&'r StrongRuleNode>, ) -> Option<&'r StrongRuleNode>95     fn get_rule_node_for_cache<'r>(
96         guards: &StylesheetGuards,
97         mut rule_node: Option<&'r StrongRuleNode>,
98     ) -> Option<&'r StrongRuleNode> {
99         while let Some(node) = rule_node {
100             match node.style_source() {
101                 Some(s) => match s.as_declarations() {
102                     Some(decls) => {
103                         let cascade_level = node.cascade_level();
104                         let decls = decls.read_with(cascade_level.guard(guards));
105                         if decls.contains_any_reset() {
106                             break;
107                         }
108                     },
109                     None => break,
110                 },
111                 None => {},
112             }
113             rule_node = node.parent();
114         }
115         rule_node
116     }
117 
118     /// Finds a node in the properties matched cache.
119     ///
120     /// This needs to receive a `StyleBuilder` with the `early` properties
121     /// already applied.
find( &self, guards: &StylesheetGuards, builder_with_early_props: &StyleBuilder, ) -> Option<&ComputedValues>122     pub fn find(
123         &self,
124         guards: &StylesheetGuards,
125         builder_with_early_props: &StyleBuilder,
126     ) -> Option<&ComputedValues> {
127         // A pseudo-element with property restrictions can result in different
128         // computed values if it's also used for a non-pseudo.
129         if builder_with_early_props
130             .pseudo
131             .and_then(|p| p.property_restriction())
132             .is_some()
133         {
134             return None;
135         }
136 
137         let rules = builder_with_early_props.rules.as_ref();
138         let rules = Self::get_rule_node_for_cache(guards, rules)?;
139         let cached_values = self.map.get(rules)?;
140 
141         for &(ref conditions, ref values) in cached_values.iter() {
142             if conditions.matches(builder_with_early_props) {
143                 debug!("Using cached reset style with conditions {:?}", conditions);
144                 return Some(&**values);
145             }
146         }
147         None
148     }
149 
150     /// Inserts a node into the rules cache if possible.
151     ///
152     /// Returns whether the style was inserted into the cache.
insert_if_possible( &mut self, guards: &StylesheetGuards, style: &Arc<ComputedValues>, pseudo: Option<&PseudoElement>, conditions: &RuleCacheConditions, ) -> bool153     pub fn insert_if_possible(
154         &mut self,
155         guards: &StylesheetGuards,
156         style: &Arc<ComputedValues>,
157         pseudo: Option<&PseudoElement>,
158         conditions: &RuleCacheConditions,
159     ) -> bool {
160         if !conditions.cacheable() {
161             return false;
162         }
163 
164         // A pseudo-element with property restrictions can result in different
165         // computed values if it's also used for a non-pseudo.
166         if pseudo.and_then(|p| p.property_restriction()).is_some() {
167             return false;
168         }
169 
170         let rules = style.rules.as_ref();
171         let rules = match Self::get_rule_node_for_cache(guards, rules) {
172             Some(r) => r.clone(),
173             None => return false,
174         };
175 
176         debug!(
177             "Inserting cached reset style with conditions {:?}",
178             conditions
179         );
180         self.map
181             .entry(rules)
182             .or_insert_with(SmallVec::new)
183             .push((conditions.clone(), style.clone()));
184 
185         true
186     }
187 }
188