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