1use crate::FxHashMap;
9use crate::computed_value_flags::ComputedValueFlags;
10use crate::context::CascadeInputs;
11use crate::logical_geometry::WritingMode;
12use crate::properties::{ComputedValues, StyleBuilder};
13use crate::rule_tree::{RuleCascadeFlags, StrongRuleNode};
14use crate::selector_parser::PseudoElement;
15use crate::shared_lock::StylesheetGuards;
16use crate::values::computed::{Context, NonNegativeLength};
17use crate::values::specified::color::ColorSchemeFlags;
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.is_none_or(|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.is_none_or(|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.is_none_or(|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.is_none_or(|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 && style.get_font().slow_clone_font_size().computed_size != fs
94 {
95 return false;
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 Default for RuleCache {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl RuleCache {
136 pub fn new() -> Self {
138 Self {
139 map: FxHashMap::default(),
140 }
141 }
142
143 fn get_rule_node_for_cache<'r>(
154 guards: &StylesheetGuards,
155 mut rule_node: Option<&'r StrongRuleNode>,
156 ) -> Option<&'r StrongRuleNode> {
157 use crate::rule_tree::CascadeOrigin;
158 while let Some(node) = rule_node {
159 let priority = node.cascade_priority();
160 let cascade_level = priority.cascade_level();
161 let should_try_to_skip = cascade_level.is_animation()
162 || cascade_level.origin() == CascadeOrigin::PresHints
163 || priority.layer_order().is_style_attribute_layer();
164 if !should_try_to_skip {
165 break;
166 }
167 if let Some(source) = node.style_source() {
168 let decls = source.get().read_with(cascade_level.guard(guards));
169 if decls.contains_any_reset() {
170 break;
171 }
172 }
173 rule_node = node.parent();
174 }
175 rule_node
176 }
177
178 pub fn find(&self, guards: &StylesheetGuards, context: &Context) -> Option<&ComputedValues> {
183 if context
186 .builder
187 .pseudo
188 .is_some_and(|p| !p.property_restriction().is_empty())
189 {
190 return None;
191 }
192
193 if context
194 .included_cascade_flags
195 .contains(RuleCascadeFlags::STARTING_STYLE)
196 {
197 return None;
199 }
200
201 let rules = context.builder.rules.as_ref();
202 let rules = Self::get_rule_node_for_cache(guards, rules)?;
203 let cached_values = self.map.get(rules)?;
204
205 for (conditions, values) in cached_values.iter() {
206 if conditions.matches(values, &context.builder) {
207 debug!("Using cached reset style with conditions {:?}", conditions);
208 return Some(&**values);
209 }
210 }
211 None
212 }
213
214 pub fn insert_if_possible(
218 &mut self,
219 guards: &StylesheetGuards,
220 style: &Arc<ComputedValues>,
221 pseudo: Option<&PseudoElement>,
222 inputs: &CascadeInputs,
223 conditions: &RuleCacheConditions,
224 ) -> bool {
225 if !conditions.cacheable() {
226 return false;
227 }
228
229 if pseudo.is_some_and(|p| !p.property_restriction().is_empty()) {
233 return false;
234 }
235
236 if inputs
238 .included_cascade_flags
239 .contains(RuleCascadeFlags::STARTING_STYLE)
240 {
241 return false;
242 }
243
244 let rules = style.rules.as_ref();
245 let rules = match Self::get_rule_node_for_cache(guards, rules) {
246 Some(r) => r.clone(),
247 None => return false,
248 };
249
250 debug!(
251 "Inserting cached reset style with conditions {:?}",
252 conditions
253 );
254 let cached_conditions = CachedConditions {
255 writing_mode: conditions.writing_mode,
256 font_size: conditions.font_size,
257 line_height: conditions.line_height,
258 color_scheme: conditions.color_scheme,
259 };
260 self.map
261 .entry(rules)
262 .or_default()
263 .push((cached_conditions, style.clone()));
264 true
265 }
266}