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 }