1use 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#[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 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 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 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 pub fn set_uncacheable(&mut self) {
50 self.uncacheable = true;
51 }
52
53 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 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 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
113pub struct RuleCache {
115 map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>,
117}
118
119impl RuleCache {
120 pub fn new() -> Self {
122 Self {
123 map: FxHashMap::default(),
124 }
125 }
126
127 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 pub fn find(
167 &self,
168 guards: &StylesheetGuards,
169 builder_with_early_props: &StyleBuilder,
170 ) -> Option<&ComputedValues> {
171 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 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 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}