1use crate::computed_value_flags::ComputedValueFlags;
9use crate::context::CascadeInputs;
10use crate::logical_geometry::WritingMode;
11use crate::properties::{ComputedValues, StyleBuilder};
12use crate::rule_tree::{RuleCascadeFlags, StrongRuleNode};
13use crate::selector_parser::PseudoElement;
14use crate::shared_lock::StylesheetGuards;
15use crate::values::computed::{Context, NonNegativeLength};
16use crate::values::specified::color::ColorSchemeFlags;
17use rustc_hash::FxHashMap;
18use servo_arc::Arc;
19use smallvec::SmallVec;
20
21#[derive(Clone, Debug, Default)]
23pub struct RuleCacheConditions {
24 uncacheable: bool,
25 font_size: Option<NonNegativeLength>,
26 line_height: Option<NonNegativeLength>,
27 writing_mode: Option<WritingMode>,
28 color_scheme: Option<ColorSchemeFlags>,
29}
30
31impl RuleCacheConditions {
32 pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
34 debug_assert!(self.font_size.map_or(true, |f| f == font_size));
35 self.font_size = Some(font_size);
36 }
37
38 pub fn set_line_height_dependency(&mut self, line_height: NonNegativeLength) {
40 debug_assert!(self.line_height.map_or(true, |l| l == line_height));
41 self.line_height = Some(line_height);
42 }
43
44 pub fn set_color_scheme_dependency(&mut self, color_scheme: ColorSchemeFlags) {
46 debug_assert!(self.color_scheme.map_or(true, |cs| cs == color_scheme));
47 self.color_scheme = Some(color_scheme);
48 }
49
50 pub fn set_uncacheable(&mut self) {
52 self.uncacheable = true;
53 }
54
55 pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
57 debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
58 self.writing_mode = Some(writing_mode);
59 }
60
61 fn cacheable(&self) -> bool {
63 !self.uncacheable
64 }
65}
66
67#[derive(Debug)]
68struct CachedConditions {
69 font_size: Option<NonNegativeLength>,
70 line_height: Option<NonNegativeLength>,
71 color_scheme: Option<ColorSchemeFlags>,
72 writing_mode: Option<WritingMode>,
73}
74
75impl CachedConditions {
76 fn matches(&self, cached_style: &ComputedValues, style: &StyleBuilder) -> bool {
78 if cached_style.effective_zoom != style.effective_zoom {
79 return false;
80 }
81
82 if cached_style
83 .flags
84 .intersects(ComputedValueFlags::IS_IN_APPEARANCE_BASE_SUBTREE)
85 != style
86 .flags()
87 .intersects(ComputedValueFlags::IS_IN_APPEARANCE_BASE_SUBTREE)
88 {
89 return false;
90 }
91
92 if let Some(fs) = self.font_size {
93 if style.get_font().clone_font_size().computed_size != fs {
94 return false;
95 }
96 }
97
98 if let Some(lh) = self.line_height {
99 let new_line_height =
100 style
101 .device
102 .calc_line_height(&style.get_font(), style.writing_mode, None);
103 if new_line_height != lh {
104 return false;
105 }
106 }
107
108 if self
109 .color_scheme
110 .is_some_and(|cs| style.get_inherited_ui().color_scheme_bits() != cs)
111 {
112 return false;
113 }
114
115 if self.writing_mode.is_some_and(|wm| style.writing_mode != wm) {
116 return false;
117 }
118
119 true
120 }
121}
122
123pub struct RuleCache {
125 map: FxHashMap<StrongRuleNode, SmallVec<[(CachedConditions, Arc<ComputedValues>); 1]>>,
127}
128
129impl RuleCache {
130 pub fn new() -> Self {
132 Self {
133 map: FxHashMap::default(),
134 }
135 }
136
137 fn get_rule_node_for_cache<'r>(
148 guards: &StylesheetGuards,
149 mut rule_node: Option<&'r StrongRuleNode>,
150 ) -> Option<&'r StrongRuleNode> {
151 use crate::rule_tree::CascadeOrigin;
152 while let Some(node) = rule_node {
153 let priority = node.cascade_priority();
154 let cascade_level = priority.cascade_level();
155 let should_try_to_skip = cascade_level.is_animation()
156 || cascade_level.origin() == CascadeOrigin::PresHints
157 || priority.layer_order().is_style_attribute_layer();
158 if !should_try_to_skip {
159 break;
160 }
161 if let Some(source) = node.style_source() {
162 let decls = source.get().read_with(cascade_level.guard(guards));
163 if decls.contains_any_reset() {
164 break;
165 }
166 }
167 rule_node = node.parent();
168 }
169 rule_node
170 }
171
172 pub fn find(&self, guards: &StylesheetGuards, context: &Context) -> Option<&ComputedValues> {
177 if context
180 .builder
181 .pseudo
182 .and_then(|p| p.property_restriction())
183 .is_some()
184 {
185 return None;
186 }
187
188 if context
189 .included_cascade_flags
190 .contains(RuleCascadeFlags::STARTING_STYLE)
191 {
192 return None;
194 }
195
196 let rules = context.builder.rules.as_ref();
197 let rules = Self::get_rule_node_for_cache(guards, rules)?;
198 let cached_values = self.map.get(rules)?;
199
200 for &(ref conditions, ref values) in cached_values.iter() {
201 if conditions.matches(values, &context.builder) {
202 debug!("Using cached reset style with conditions {:?}", conditions);
203 return Some(&**values);
204 }
205 }
206 None
207 }
208
209 pub fn insert_if_possible(
213 &mut self,
214 guards: &StylesheetGuards,
215 style: &Arc<ComputedValues>,
216 pseudo: Option<&PseudoElement>,
217 inputs: &CascadeInputs,
218 conditions: &RuleCacheConditions,
219 ) -> bool {
220 if !conditions.cacheable() {
221 return false;
222 }
223
224 if pseudo.and_then(|p| p.property_restriction()).is_some() {
228 return false;
229 }
230
231 if inputs
233 .included_cascade_flags
234 .contains(RuleCascadeFlags::STARTING_STYLE)
235 {
236 return false;
237 }
238
239 let rules = style.rules.as_ref();
240 let rules = match Self::get_rule_node_for_cache(guards, rules) {
241 Some(r) => r.clone(),
242 None => return false,
243 };
244
245 debug!(
246 "Inserting cached reset style with conditions {:?}",
247 conditions
248 );
249 let cached_conditions = CachedConditions {
250 writing_mode: conditions.writing_mode,
251 font_size: conditions.font_size,
252 line_height: conditions.line_height,
253 color_scheme: conditions.color_scheme,
254 };
255 self.map
256 .entry(rules)
257 .or_default()
258 .push((cached_conditions, style.clone()));
259 true
260 }
261}