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
//! Per-node data used in style calculation.
6
7
use crate::context::{SharedStyleContext, StackLimitChecker};
8
use crate::dom::TElement;
9
use crate::invalidation::element::invalidator::InvalidationResult;
10
use crate::invalidation::element::restyle_hints::RestyleHint;
11
use crate::properties::ComputedValues;
12
use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT};
13
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle};
14
#[cfg(feature = "gecko")]
15
use malloc_size_of::MallocSizeOfOps;
16
use selectors::NthIndexCache;
17
use servo_arc::Arc;
18
use std::fmt;
19
use std::mem;
20
use std::ops::{Deref, DerefMut};
21
22
bitflags! {
23
/// Various flags stored on ElementData.
24
#[derive(Default)]
25
pub struct ElementDataFlags: u8 {
26
/// Whether the styles changed for this restyle.
27
const WAS_RESTYLED = 1 << 0;
28
/// Whether the last traversal of this element did not do
29
/// any style computation. This is not true during the initial
30
/// styling pass, nor is it true when we restyle (in which case
31
/// WAS_RESTYLED is set).
32
///
33
/// This bit always corresponds to the last time the element was
34
/// traversed, so each traversal simply updates it with the appropriate
35
/// value.
36
const TRAVERSED_WITHOUT_STYLING = 1 << 1;
37
38
/// Whether the primary style of this element data was reused from
39
/// another element via a rule node comparison. This allows us to
40
/// differentiate between elements that shared styles because they met
41
/// all the criteria of the style sharing cache, compared to elements
42
/// that reused style structs via rule node identity.
43
///
44
/// The former gives us stronger transitive guarantees that allows us to
45
/// apply the style sharing cache to cousins.
46
const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2;
47
}
48
}
49
50
/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements.
51
///
52
/// We use an Arc so that sharing these styles via the style sharing cache does
53
/// not require duplicate allocations. We leverage the copy-on-write semantics of
54
/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations)
55
/// in servo_arc.
56
#[derive(Clone, Debug, Default)]
57
pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>);
58
59
#[derive(Default)]
60
struct EagerPseudoArray(EagerPseudoArrayInner);
61
type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT];
62
63
impl Deref for EagerPseudoArray {
64
type Target = EagerPseudoArrayInner;
65
fn deref(&self) -> &Self::Target {
66
&self.0
67
}
68
}
69
70
impl DerefMut for EagerPseudoArray {
71
fn deref_mut(&mut self) -> &mut Self::Target {
72
&mut self.0
73
}
74
}
75
76
// Manually implement `Clone` here because the derived impl of `Clone` for
77
// array types assumes the value inside is `Copy`.
78
impl Clone for EagerPseudoArray {
79
fn clone(&self) -> Self {
80
let mut clone = Self::default();
81
for i in 0..EAGER_PSEUDO_COUNT {
82
clone[i] = self.0[i].clone();
83
}
84
clone
85
}
86
}
87
88
// Override Debug to print which pseudos we have, and substitute the rule node
89
// for the much-more-verbose ComputedValues stringification.
90
impl fmt::Debug for EagerPseudoArray {
91
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92
write!(f, "EagerPseudoArray {{ ")?;
93
for i in 0..EAGER_PSEUDO_COUNT {
94
if let Some(ref values) = self[i] {
95
write!(
96
f,
97
"{:?}: {:?}, ",
98
PseudoElement::from_eager_index(i),
99
&values.rules
100
)?;
101
}
102
}
103
write!(f, "}}")
104
}
105
}
106
107
// Can't use [None; EAGER_PSEUDO_COUNT] here because it complains
108
// about Copy not being implemented for our Arc type.
109
#[cfg(feature = "gecko")]
110
const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None];
111
#[cfg(feature = "servo")]
112
const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None];
113
114
impl EagerPseudoStyles {
115
/// Returns whether there are any pseudo styles.
116
pub fn is_empty(&self) -> bool {
117
self.0.is_none()
118
}
119
120
/// Grabs a reference to the list of styles, if they exist.
121
pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> {
122
match self.0 {
123
None => None,
124
Some(ref x) => Some(&x.0),
125
}
126
}
127
128
/// Grabs a reference to the list of styles or a list of None if
129
/// there are no styles to be had.
130
pub fn as_array(&self) -> &EagerPseudoArrayInner {
131
self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY)
132
}
133
134
/// Returns a reference to the style for a given eager pseudo, if it exists.
135
pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
136
debug_assert!(pseudo.is_eager());
137
self.0
138
.as_ref()
139
.and_then(|p| p[pseudo.eager_index()].as_ref())
140
}
141
142
/// Sets the style for the eager pseudo.
143
pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
144
if self.0.is_none() {
145
self.0 = Some(Arc::new(Default::default()));
146
}
147
let arr = Arc::make_mut(self.0.as_mut().unwrap());
148
arr[pseudo.eager_index()] = Some(value);
149
}
150
}
151
152
/// The styles associated with a node, including the styles for any
153
/// pseudo-elements.
154
#[derive(Clone, Default)]
155
pub struct ElementStyles {
156
/// The element's style.
157
pub primary: Option<Arc<ComputedValues>>,
158
/// A list of the styles for the element's eagerly-cascaded pseudo-elements.
159
pub pseudos: EagerPseudoStyles,
160
}
161
162
impl ElementStyles {
163
/// Returns the primary style.
164
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
165
self.primary.as_ref()
166
}
167
168
/// Returns the primary style. Panic if no style available.
169
pub fn primary(&self) -> &Arc<ComputedValues> {
170
self.primary.as_ref().unwrap()
171
}
172
173
/// Whether this element `display` value is `none`.
174
pub fn is_display_none(&self) -> bool {
175
self.primary().get_box().clone_display().is_none()
176
}
177
178
#[cfg(feature = "gecko")]
179
fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize {
180
// As the method name suggests, we don't measures the ComputedValues
181
// here, because they are measured on the C++ side.
182
183
// XXX: measure the EagerPseudoArray itself, but not the ComputedValues
184
// within it.
185
186
0
187
}
188
}
189
190
// We manually implement Debug for ElementStyles so that we can avoid the
191
// verbose stringification of every property in the ComputedValues. We
192
// substitute the rule node instead.
193
impl fmt::Debug for ElementStyles {
194
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195
write!(
196
f,
197
"ElementStyles {{ primary: {:?}, pseudos: {:?} }}",
198
self.primary.as_ref().map(|x| &x.rules),
199
self.pseudos
200
)
201
}
202
}
203
204
/// Style system data associated with an Element.
205
///
206
/// In Gecko, this hangs directly off the Element. Servo, this is embedded
207
/// inside of layout data, which itself hangs directly off the Element. In
208
/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
209
#[derive(Debug, Default)]
210
pub struct ElementData {
211
/// The styles for the element and its pseudo-elements.
212
pub styles: ElementStyles,
213
214
/// The restyle damage, indicating what kind of layout changes are required
215
/// afte restyling.
216
pub damage: RestyleDamage,
217
218
/// The restyle hint, which indicates whether selectors need to be rematched
219
/// for this element, its children, and its descendants.
220
pub hint: RestyleHint,
221
222
/// Flags.
223
pub flags: ElementDataFlags,
224
}
225
226
/// The kind of restyle that a single element should do.
227
#[derive(Debug)]
228
pub enum RestyleKind {
229
/// We need to run selector matching plus re-cascade, that is, a full
230
/// restyle.
231
MatchAndCascade,
232
/// We need to recascade with some replacement rule, such as the style
233
/// attribute, or animation rules.
234
CascadeWithReplacements(RestyleHint),
235
/// We only need to recascade, for example, because only inherited
236
/// properties in the parent changed.
237
CascadeOnly,
238
}
239
240
impl ElementData {
241
/// Invalidates style for this element, its descendants, and later siblings,
242
/// based on the snapshot of the element that we took when attributes or
243
/// state changed.
244
pub fn invalidate_style_if_needed<'a, E: TElement>(
245
&mut self,
246
element: E,
247
shared_context: &SharedStyleContext,
248
stack_limit_checker: Option<&StackLimitChecker>,
249
nth_index_cache: &mut NthIndexCache,
250
) -> InvalidationResult {
251
// In animation-only restyle we shouldn't touch snapshot at all.
252
if shared_context.traversal_flags.for_animation_only() {
253
return InvalidationResult::empty();
254
}
255
256
use crate::invalidation::element::invalidator::TreeStyleInvalidator;
257
use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor;
258
259
debug!(
260
"invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
261
handled_snapshot: {}, pseudo: {:?}",
262
element,
263
shared_context.traversal_flags,
264
element.has_snapshot(),
265
element.handled_snapshot(),
266
element.implemented_pseudo_element()
267
);
268
269
if !element.has_snapshot() || element.handled_snapshot() {
270
return InvalidationResult::empty();
271
}
272
273
let mut processor =
274
StateAndAttrInvalidationProcessor::new(shared_context, element, self, nth_index_cache);
275
276
let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor);
277
278
let result = invalidator.invalidate();
279
280
unsafe { element.set_handled_snapshot() }
281
debug_assert!(element.handled_snapshot());
282
283
result
284
}
285
286
/// Returns true if this element has styles.
287
#[inline]
288
pub fn has_styles(&self) -> bool {
289
self.styles.primary.is_some()
290
}
291
292
/// Returns this element's styles as resolved styles to use for sharing.
293
pub fn share_styles(&self) -> ResolvedElementStyles {
294
ResolvedElementStyles {
295
primary: self.share_primary_style(),
296
pseudos: self.styles.pseudos.clone(),
297
}
298
}
299
300
/// Returns this element's primary style as a resolved style to use for sharing.
301
pub fn share_primary_style(&self) -> PrimaryStyle {
302
let reused_via_rule_node = self
303
.flags
304
.contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
305
306
PrimaryStyle {
307
style: ResolvedStyle(self.styles.primary().clone()),
308
reused_via_rule_node,
309
}
310
}
311
312
/// Sets a new set of styles, returning the old ones.
313
pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
314
if new_styles.primary.reused_via_rule_node {
315
self.flags
316
.insert(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
317
} else {
318
self.flags
319
.remove(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
320
}
321
mem::replace(&mut self.styles, new_styles.into())
322
}
323
324
/// Returns the kind of restyling that we're going to need to do on this
325
/// element, based of the stored restyle hint.
326
pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> RestyleKind {
327
if shared_context.traversal_flags.for_animation_only() {
328
return self.restyle_kind_for_animation(shared_context);
329
}
330
331
if !self.has_styles() {
332
return RestyleKind::MatchAndCascade;
333
}
334
335
if self.hint.match_self() {
336
return RestyleKind::MatchAndCascade;
337
}
338
339
if self.hint.has_replacements() {
340
debug_assert!(
341
!self.hint.has_animation_hint(),
342
"Animation only restyle hint should have already processed"
343
);
344
return RestyleKind::CascadeWithReplacements(self.hint & RestyleHint::replacements());
345
}
346
347
debug_assert!(
348
self.hint.has_recascade_self(),
349
"We definitely need to do something: {:?}!",
350
self.hint
351
);
352
return RestyleKind::CascadeOnly;
353
}
354
355
/// Returns the kind of restyling for animation-only restyle.
356
fn restyle_kind_for_animation(&self, shared_context: &SharedStyleContext) -> RestyleKind {
357
debug_assert!(shared_context.traversal_flags.for_animation_only());
358
debug_assert!(
359
self.has_styles(),
360
"Unstyled element shouldn't be traversed during \
361
animation-only traversal"
362
);
363
364
// return either CascadeWithReplacements or CascadeOnly in case of
365
// animation-only restyle. I.e. animation-only restyle never does
366
// selector matching.
367
if self.hint.has_animation_hint() {
368
return RestyleKind::CascadeWithReplacements(self.hint & RestyleHint::for_animations());
369
}
370
371
return RestyleKind::CascadeOnly;
372
}
373
374
/// Drops any restyle state from the element.
375
///
376
/// FIXME(bholley): The only caller of this should probably just assert that
377
/// the hint is empty and call clear_flags_and_damage().
378
#[inline]
379
pub fn clear_restyle_state(&mut self) {
380
self.hint = RestyleHint::empty();
381
self.clear_restyle_flags_and_damage();
382
}
383
384
/// Drops restyle flags and damage from the element.
385
#[inline]
386
pub fn clear_restyle_flags_and_damage(&mut self) {
387
self.damage = RestyleDamage::empty();
388
self.flags.remove(ElementDataFlags::WAS_RESTYLED);
389
}
390
391
/// Mark this element as restyled, which is useful to know whether we need
392
/// to do a post-traversal.
393
pub fn set_restyled(&mut self) {
394
self.flags.insert(ElementDataFlags::WAS_RESTYLED);
395
self.flags
396
.remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
397
}
398
399
/// Returns true if this element was restyled.
400
#[inline]
401
pub fn is_restyle(&self) -> bool {
402
self.flags.contains(ElementDataFlags::WAS_RESTYLED)
403
}
404
405
/// Mark that we traversed this element without computing any style for it.
406
pub fn set_traversed_without_styling(&mut self) {
407
self.flags
408
.insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
409
}
410
411
/// Returns whether this element has been part of a restyle.
412
#[inline]
413
pub fn contains_restyle_data(&self) -> bool {
414
self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
415
}
416
417
/// Returns whether it is safe to perform cousin sharing based on the ComputedValues
418
/// identity of the primary style in this ElementData. There are a few subtle things
419
/// to check.
420
///
421
/// First, if a parent element was already styled and we traversed past it without
422
/// restyling it, that may be because our clever invalidation logic was able to prove
423
/// that the styles of that element would remain unchanged despite changes to the id
424
/// or class attributes. However, style sharing relies on the strong guarantee that all
425
/// the classes and ids up the respective parent chains are identical. As such, if we
426
/// skipped styling for one (or both) of the parents on this traversal, we can't share
427
/// styles across cousins. Note that this is a somewhat conservative check. We could
428
/// tighten it by having the invalidation logic explicitly flag elements for which it
429
/// ellided styling.
430
///
431
/// Second, we want to only consider elements whose ComputedValues match due to a hit
432
/// in the style sharing cache, rather than due to the rule-node-based reuse that
433
/// happens later in the styling pipeline. The former gives us the stronger guarantees
434
/// we need for style sharing, the latter does not.
435
pub fn safe_for_cousin_sharing(&self) -> bool {
436
!self.flags.intersects(
437
ElementDataFlags::TRAVERSED_WITHOUT_STYLING |
438
ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
439
)
440
}
441
442
/// Measures memory usage.
443
#[cfg(feature = "gecko")]
444
pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize {
445
let n = self.styles.size_of_excluding_cvs(ops);
446
447
// We may measure more fields in the future if DMD says it's worth it.
448
449
n
450
}
451
}