diff options
Diffstat (limited to '')
-rw-r--r-- | servo/components/style/rule_cache.rs | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/servo/components/style/rule_cache.rs b/servo/components/style/rule_cache.rs new file mode 100644 index 0000000000..70c5b79731 --- /dev/null +++ b/servo/components/style/rule_cache.rs @@ -0,0 +1,219 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A cache from rule node to computed values, in order to cache reset +//! properties. + +use crate::logical_geometry::WritingMode; +use crate::properties::{ComputedValues, StyleBuilder}; +use crate::rule_tree::StrongRuleNode; +use crate::selector_parser::PseudoElement; +use crate::shared_lock::StylesheetGuards; +use crate::values::computed::{NonNegativeLength, Zoom}; +use fxhash::FxHashMap; +use servo_arc::Arc; +use smallvec::SmallVec; + +/// The conditions for caching and matching a style in the rule cache. +#[derive(Clone, Debug, Default)] +pub struct RuleCacheConditions { + uncacheable: bool, + font_size: Option<NonNegativeLength>, + line_height: Option<NonNegativeLength>, + writing_mode: Option<WritingMode>, +} + +impl RuleCacheConditions { + /// Sets the style as depending in the font-size value. + pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) { + debug_assert!(self.font_size.map_or(true, |f| f == font_size)); + self.font_size = Some(font_size); + } + + /// Sets the style as depending in the line-height value. + pub fn set_line_height_dependency(&mut self, line_height: NonNegativeLength) { + debug_assert!(self.line_height.map_or(true, |l| l == line_height)); + self.line_height = Some(line_height); + } + + /// Sets the style as uncacheable. + pub fn set_uncacheable(&mut self) { + self.uncacheable = true; + } + + /// Sets the style as depending in the writing-mode value `writing_mode`. + pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) { + debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode)); + self.writing_mode = Some(writing_mode); + } + + /// Returns whether the current style's reset properties are cacheable. + fn cacheable(&self) -> bool { + !self.uncacheable + } +} + +#[derive(Debug)] +struct CachedConditions { + font_size: Option<NonNegativeLength>, + line_height: Option<NonNegativeLength>, + writing_mode: Option<WritingMode>, + zoom: Zoom, +} + +impl CachedConditions { + /// Returns whether `style` matches the conditions. + fn matches(&self, style: &StyleBuilder) -> bool { + if style.effective_zoom != self.zoom { + return false; + } + + if let Some(fs) = self.font_size { + if style.get_font().clone_font_size().computed_size != fs { + return false; + } + } + + if let Some(lh) = self.line_height { + let new_line_height = + style + .device + .calc_line_height(&style.get_font(), style.writing_mode, None); + if new_line_height != lh { + return false; + } + } + + if let Some(wm) = self.writing_mode { + if style.writing_mode != wm { + return false; + } + } + + true + } +} + +/// A TLS cache from rules matched to computed values. +pub struct RuleCache { + // FIXME(emilio): Consider using LRUCache or something like that? + map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>, +} + +impl RuleCache { + /// Creates an empty `RuleCache`. + pub fn new() -> Self { + Self { + map: FxHashMap::default(), + } + } + + /// Walk the rule tree and return a rule node for using as the key + /// for rule cache. + /// + /// It currently skips a rule node when it is neither from a style + /// rule, nor containing any declaration of reset property. We don't + /// skip style rule so that we don't need to walk a long way in the + /// worst case. Skipping declarations rule nodes should be enough + /// to address common cases that rule cache would fail to share + /// when using the rule node directly, like preshint, style attrs, + /// and animations. + fn get_rule_node_for_cache<'r>( + guards: &StylesheetGuards, + mut rule_node: Option<&'r StrongRuleNode>, + ) -> Option<&'r StrongRuleNode> { + while let Some(node) = rule_node { + match node.style_source() { + Some(s) => match s.as_declarations() { + Some(decls) => { + let cascade_level = node.cascade_level(); + let decls = decls.read_with(cascade_level.guard(guards)); + if decls.contains_any_reset() { + break; + } + }, + None => break, + }, + None => {}, + } + rule_node = node.parent(); + } + rule_node + } + + /// Finds a node in the properties matched cache. + /// + /// This needs to receive a `StyleBuilder` with the `early` properties + /// already applied. + pub fn find( + &self, + guards: &StylesheetGuards, + builder_with_early_props: &StyleBuilder, + ) -> Option<&ComputedValues> { + // A pseudo-element with property restrictions can result in different + // computed values if it's also used for a non-pseudo. + if builder_with_early_props + .pseudo + .and_then(|p| p.property_restriction()) + .is_some() + { + return None; + } + + let rules = builder_with_early_props.rules.as_ref(); + let rules = Self::get_rule_node_for_cache(guards, rules)?; + let cached_values = self.map.get(rules)?; + + for &(ref conditions, ref values) in cached_values.iter() { + if conditions.matches(builder_with_early_props) { + debug!("Using cached reset style with conditions {:?}", conditions); + return Some(&**values); + } + } + None + } + + /// Inserts a node into the rules cache if possible. + /// + /// Returns whether the style was inserted into the cache. + pub fn insert_if_possible( + &mut self, + guards: &StylesheetGuards, + style: &Arc<ComputedValues>, + pseudo: Option<&PseudoElement>, + conditions: &RuleCacheConditions, + ) -> bool { + if !conditions.cacheable() { + return false; + } + + // A pseudo-element with property restrictions can result in different + // computed values if it's also used for a non-pseudo. + if pseudo.and_then(|p| p.property_restriction()).is_some() { + return false; + } + + let rules = style.rules.as_ref(); + let rules = match Self::get_rule_node_for_cache(guards, rules) { + Some(r) => r.clone(), + None => return false, + }; + + debug!( + "Inserting cached reset style with conditions {:?}", + conditions + ); + let cached_conditions = CachedConditions { + writing_mode: conditions.writing_mode, + font_size: conditions.font_size, + line_height: conditions.line_height, + zoom: style.effective_zoom, + }; + self.map + .entry(rules) + .or_default() + .push((cached_conditions, style.clone())); + true + } +} |