1use crate::context::CascadeInputs;
9use crate::logical_geometry::WritingMode;
10use crate::properties::{ComputedValues, StyleBuilder};
11use crate::rule_tree::StrongRuleNode;
12use crate::selector_parser::PseudoElement;
13use crate::shared_lock::StylesheetGuards;
14use crate::values::computed::{Context, NonNegativeLength, Zoom};
15use crate::values::specified::color::ColorSchemeFlags;
16use rustc_hash::FxHashMap;
17use servo_arc::Arc;
18use smallvec::SmallVec;
19
20#[derive(Clone, Debug, Default)]
22pub struct RuleCacheConditions {
23 uncacheable: bool,
24 font_size: Option<NonNegativeLength>,
25 line_height: Option<NonNegativeLength>,
26 writing_mode: Option<WritingMode>,
27 color_scheme: Option<ColorSchemeFlags>,
28}
29
30impl RuleCacheConditions {
31 pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
33 debug_assert!(self.font_size.map_or(true, |f| f == font_size));
34 self.font_size = Some(font_size);
35 }
36
37 pub fn set_line_height_dependency(&mut self, line_height: NonNegativeLength) {
39 debug_assert!(self.line_height.map_or(true, |l| l == line_height));
40 self.line_height = Some(line_height);
41 }
42
43 pub fn set_color_scheme_dependency(&mut self, color_scheme: ColorSchemeFlags) {
45 debug_assert!(self.color_scheme.map_or(true, |cs| cs == color_scheme));
46 self.color_scheme = Some(color_scheme);
47 }
48
49 pub fn set_uncacheable(&mut self) {
51 self.uncacheable = true;
52 }
53
54 pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
56 debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
57 self.writing_mode = Some(writing_mode);
58 }
59
60 fn cacheable(&self) -> bool {
62 !self.uncacheable
63 }
64}
65
66#[derive(Debug)]
67struct CachedConditions {
68 font_size: Option<NonNegativeLength>,
69 line_height: Option<NonNegativeLength>,
70 color_scheme: Option<ColorSchemeFlags>,
71 writing_mode: Option<WritingMode>,
72 zoom: Zoom,
73}
74
75impl CachedConditions {
76 fn matches(&self, style: &StyleBuilder) -> bool {
78 if style.effective_zoom != self.zoom {
79 return false;
80 }
81
82 if let Some(fs) = self.font_size {
83 if style.get_font().clone_font_size().computed_size != fs {
84 return false;
85 }
86 }
87
88 if let Some(lh) = self.line_height {
89 let new_line_height =
90 style
91 .device
92 .calc_line_height(&style.get_font(), style.writing_mode, None);
93 if new_line_height != lh {
94 return false;
95 }
96 }
97
98 if let Some(cs) = self.color_scheme {
99 if style.get_inherited_ui().color_scheme_bits() != cs {
100 return false;
101 }
102 }
103
104 if let Some(wm) = self.writing_mode {
105 if style.writing_mode != wm {
106 return false;
107 }
108 }
109
110 true
111 }
112}
113
114pub struct RuleCache {
116 map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>,
118}
119
120impl RuleCache {
121 pub fn new() -> Self {
123 Self {
124 map: FxHashMap::default(),
125 }
126 }
127
128 fn get_rule_node_for_cache<'r>(
139 guards: &StylesheetGuards,
140 mut rule_node: Option<&'r StrongRuleNode>,
141 ) -> Option<&'r StrongRuleNode> {
142 use crate::rule_tree::CascadeOrigin;
143 while let Some(node) = rule_node {
144 let priority = node.cascade_priority();
145 let cascade_level = priority.cascade_level();
146 let should_try_to_skip = cascade_level.is_animation()
147 || cascade_level.origin() == CascadeOrigin::PresHints
148 || priority.layer_order().is_style_attribute_layer();
149 if !should_try_to_skip {
150 break;
151 }
152 if let Some(source) = node.style_source() {
153 let decls = source.get().read_with(cascade_level.guard(guards));
154 if decls.contains_any_reset() {
155 break;
156 }
157 }
158 rule_node = node.parent();
159 }
160 rule_node
161 }
162
163 pub fn find(&self, guards: &StylesheetGuards, context: &Context) -> Option<&ComputedValues> {
168 if context
171 .builder
172 .pseudo
173 .and_then(|p| p.property_restriction())
174 .is_some()
175 {
176 return None;
177 }
178
179 if !context.included_cascade_flags.is_empty() {
180 return None;
181 }
182
183 let rules = context.builder.rules.as_ref();
184 let rules = Self::get_rule_node_for_cache(guards, rules)?;
185 let cached_values = self.map.get(rules)?;
186
187 for &(ref conditions, ref values) in cached_values.iter() {
188 if conditions.matches(&context.builder) {
189 debug!("Using cached reset style with conditions {:?}", conditions);
190 return Some(&**values);
191 }
192 }
193 None
194 }
195
196 pub fn insert_if_possible(
200 &mut self,
201 guards: &StylesheetGuards,
202 style: &Arc<ComputedValues>,
203 pseudo: Option<&PseudoElement>,
204 inputs: &CascadeInputs,
205 conditions: &RuleCacheConditions,
206 ) -> bool {
207 if !conditions.cacheable() {
208 return false;
209 }
210
211 if pseudo.and_then(|p| p.property_restriction()).is_some() {
214 return false;
215 }
216
217 if !inputs.included_cascade_flags.is_empty() {
219 return false;
220 }
221
222 let rules = style.rules.as_ref();
223 let rules = match Self::get_rule_node_for_cache(guards, rules) {
224 Some(r) => r.clone(),
225 None => return false,
226 };
227
228 debug!(
229 "Inserting cached reset style with conditions {:?}",
230 conditions
231 );
232 let cached_conditions = CachedConditions {
233 writing_mode: conditions.writing_mode,
234 font_size: conditions.font_size,
235 line_height: conditions.line_height,
236 color_scheme: conditions.color_scheme,
237 zoom: style.effective_zoom,
238 };
239 self.map
240 .entry(rules)
241 .or_default()
242 .push((cached_conditions, style.clone()));
243 true
244 }
245}