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.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 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 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 .and_then(|p| p.property_restriction())
189 .is_some()
190 {
191 return None;
192 }
193
194 if context
195 .included_cascade_flags
196 .contains(RuleCascadeFlags::STARTING_STYLE)
197 {
198 return None;
200 }
201
202 let rules = context.builder.rules.as_ref();
203 let rules = Self::get_rule_node_for_cache(guards, rules)?;
204 let cached_values = self.map.get(rules)?;
205
206 for (conditions, values) in cached_values.iter() {
207 if conditions.matches(values, &context.builder) {
208 debug!("Using cached reset style with conditions {:?}", conditions);
209 return Some(&**values);
210 }
211 }
212 None
213 }
214
215 pub fn insert_if_possible(
219 &mut self,
220 guards: &StylesheetGuards,
221 style: &Arc<ComputedValues>,
222 pseudo: Option<&PseudoElement>,
223 inputs: &CascadeInputs,
224 conditions: &RuleCacheConditions,
225 ) -> bool {
226 if !conditions.cacheable() {
227 return false;
228 }
229
230 if pseudo.and_then(|p| p.property_restriction()).is_some() {
234 return false;
235 }
236
237 if inputs
239 .included_cascade_flags
240 .contains(RuleCascadeFlags::STARTING_STYLE)
241 {
242 return false;
243 }
244
245 let rules = style.rules.as_ref();
246 let rules = match Self::get_rule_node_for_cache(guards, rules) {
247 Some(r) => r.clone(),
248 None => return false,
249 };
250
251 debug!(
252 "Inserting cached reset style with conditions {:?}",
253 conditions
254 );
255 let cached_conditions = CachedConditions {
256 writing_mode: conditions.writing_mode,
257 font_size: conditions.font_size,
258 line_height: conditions.line_height,
259 color_scheme: conditions.color_scheme,
260 };
261 self.map
262 .entry(rules)
263 .or_default()
264 .push((cached_conditions, style.clone()));
265 true
266 }
267}