Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
//! A cache from rule node to computed values, in order to cache reset
6
//! properties.
7
8
use crate::logical_geometry::WritingMode;
9
use crate::properties::{ComputedValues, StyleBuilder};
10
use crate::rule_tree::StrongRuleNode;
11
use crate::selector_parser::PseudoElement;
12
use crate::shared_lock::StylesheetGuards;
13
use crate::values::computed::NonNegativeLength;
14
use fxhash::FxHashMap;
15
use servo_arc::Arc;
16
use smallvec::SmallVec;
17
18
/// The conditions for caching and matching a style in the rule cache.
19
#[derive(Clone, Debug, Default)]
20
pub struct RuleCacheConditions {
21
uncacheable: bool,
22
font_size: Option<NonNegativeLength>,
23
writing_mode: Option<WritingMode>,
24
}
25
26
impl RuleCacheConditions {
27
/// Sets the style as depending in the font-size value.
28
pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
29
debug_assert!(self.font_size.map_or(true, |f| f == font_size));
30
self.font_size = Some(font_size);
31
}
32
33
/// Sets the style as uncacheable.
34
pub fn set_uncacheable(&mut self) {
35
self.uncacheable = true;
36
}
37
38
/// Sets the style as depending in the writing-mode value `writing_mode`.
39
pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
40
debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
41
self.writing_mode = Some(writing_mode);
42
}
43
44
/// Returns whether the current style's reset properties are cacheable.
45
fn cacheable(&self) -> bool {
46
!self.uncacheable
47
}
48
49
/// Returns whether `style` matches the conditions.
50
fn matches(&self, style: &StyleBuilder) -> bool {
51
if self.uncacheable {
52
return false;
53
}
54
55
if let Some(fs) = self.font_size {
56
if style.get_font().clone_font_size().size != fs {
57
return false;
58
}
59
}
60
61
if let Some(wm) = self.writing_mode {
62
if style.writing_mode != wm {
63
return false;
64
}
65
}
66
67
true
68
}
69
}
70
71
/// A TLS cache from rules matched to computed values.
72
pub struct RuleCache {
73
// FIXME(emilio): Consider using LRUCache or something like that?
74
map: FxHashMap<StrongRuleNode, SmallVec<[(RuleCacheConditions, Arc<ComputedValues>); 1]>>,
75
}
76
77
impl RuleCache {
78
/// Creates an empty `RuleCache`.
79
pub fn new() -> Self {
80
Self {
81
map: FxHashMap::default(),
82
}
83
}
84
85
/// Walk the rule tree and return a rule node for using as the key
86
/// for rule cache.
87
///
88
/// It currently skips a rule node when it is neither from a style
89
/// rule, nor containing any declaration of reset property. We don't
90
/// skip style rule so that we don't need to walk a long way in the
91
/// worst case. Skipping declarations rule nodes should be enough
92
/// to address common cases that rule cache would fail to share
93
/// when using the rule node directly, like preshint, style attrs,
94
/// and animations.
95
fn get_rule_node_for_cache<'r>(
96
guards: &StylesheetGuards,
97
mut rule_node: Option<&'r StrongRuleNode>,
98
) -> Option<&'r StrongRuleNode> {
99
while let Some(node) = rule_node {
100
match node.style_source() {
101
Some(s) => match s.as_declarations() {
102
Some(decls) => {
103
let cascade_level = node.cascade_level();
104
let decls = decls.read_with(cascade_level.guard(guards));
105
if decls.contains_any_reset() {
106
break;
107
}
108
},
109
None => break,
110
},
111
None => {},
112
}
113
rule_node = node.parent();
114
}
115
rule_node
116
}
117
118
/// Finds a node in the properties matched cache.
119
///
120
/// This needs to receive a `StyleBuilder` with the `early` properties
121
/// already applied.
122
pub fn find(
123
&self,
124
guards: &StylesheetGuards,
125
builder_with_early_props: &StyleBuilder,
126
) -> Option<&ComputedValues> {
127
// A pseudo-element with property restrictions can result in different
128
// computed values if it's also used for a non-pseudo.
129
if builder_with_early_props
130
.pseudo
131
.and_then(|p| p.property_restriction())
132
.is_some()
133
{
134
return None;
135
}
136
137
let rules = builder_with_early_props.rules.as_ref();
138
let rules = Self::get_rule_node_for_cache(guards, rules)?;
139
let cached_values = self.map.get(rules)?;
140
141
for &(ref conditions, ref values) in cached_values.iter() {
142
if conditions.matches(builder_with_early_props) {
143
debug!("Using cached reset style with conditions {:?}", conditions);
144
return Some(&**values);
145
}
146
}
147
None
148
}
149
150
/// Inserts a node into the rules cache if possible.
151
///
152
/// Returns whether the style was inserted into the cache.
153
pub fn insert_if_possible(
154
&mut self,
155
guards: &StylesheetGuards,
156
style: &Arc<ComputedValues>,
157
pseudo: Option<&PseudoElement>,
158
conditions: &RuleCacheConditions,
159
) -> bool {
160
if !conditions.cacheable() {
161
return false;
162
}
163
164
// A pseudo-element with property restrictions can result in different
165
// computed values if it's also used for a non-pseudo.
166
if pseudo.and_then(|p| p.property_restriction()).is_some() {
167
return false;
168
}
169
170
let rules = style.rules.as_ref();
171
let rules = match Self::get_rule_node_for_cache(guards, rules) {
172
Some(r) => r.clone(),
173
None => return false,
174
};
175
176
debug!(
177
"Inserting cached reset style with conditions {:?}",
178
conditions
179
);
180
self.map
181
.entry(rules)
182
.or_insert_with(SmallVec::new)
183
.push((conditions.clone(), style.clone()));
184
185
true
186
}
187
}