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
//! Traversing the DOM tree; the bloom filter.
6
7
use crate::context::{ElementCascadeInputs, SharedStyleContext, StyleContext};
8
use crate::data::{ElementData, ElementStyles};
9
use crate::dom::{NodeInfo, OpaqueNode, TElement, TNode};
10
use crate::invalidation::element::restyle_hints::RestyleHint;
11
use crate::matching::{ChildCascadeRequirement, MatchMethods};
12
use crate::selector_parser::PseudoElement;
13
use crate::sharing::StyleSharingTarget;
14
use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
15
use crate::stylist::RuleInclusion;
16
use crate::traversal_flags::TraversalFlags;
17
use selectors::NthIndexCache;
18
use smallvec::SmallVec;
19
20
/// A per-traversal-level chunk of data. This is sent down by the traversal, and
21
/// currently only holds the dom depth for the bloom filter.
22
///
23
/// NB: Keep this as small as possible, please!
24
#[derive(Clone, Debug)]
25
pub struct PerLevelTraversalData {
26
/// The current dom depth.
27
///
28
/// This is kept with cooperation from the traversal code and the bloom
29
/// filter.
30
pub current_dom_depth: usize,
31
}
32
33
/// We use this structure, rather than just returning a boolean from pre_traverse,
34
/// to enfore that callers process root invalidations before starting the traversal.
35
pub struct PreTraverseToken<E: TElement>(Option<E>);
36
impl<E: TElement> PreTraverseToken<E> {
37
/// Whether we should traverse children.
38
pub fn should_traverse(&self) -> bool {
39
self.0.is_some()
40
}
41
42
/// Returns the traversal root for the current traversal.
43
pub(crate) fn traversal_root(self) -> Option<E> {
44
self.0
45
}
46
}
47
48
/// A global variable holding the state of
49
/// `is_servo_nonincremental_layout()`.
51
#[cfg(feature = "servo")]
52
pub static IS_SERVO_NONINCREMENTAL_LAYOUT: std::sync::atomic::AtomicBool =
53
std::sync::atomic::AtomicBool::new(false);
54
55
#[cfg(feature = "servo")]
56
#[inline]
57
fn is_servo_nonincremental_layout() -> bool {
58
use std::sync::atomic::Ordering;
59
60
IS_SERVO_NONINCREMENTAL_LAYOUT.load(Ordering::Relaxed)
61
}
62
63
#[cfg(not(feature = "servo"))]
64
#[inline]
65
fn is_servo_nonincremental_layout() -> bool {
66
false
67
}
68
69
/// A DOM Traversal trait, that is used to generically implement styling for
70
/// Gecko and Servo.
71
pub trait DomTraversal<E: TElement>: Sync {
72
/// Process `node` on the way down, before its children have been processed.
73
///
74
/// The callback is invoked for each child node that should be processed by
75
/// the traversal.
76
fn process_preorder<F>(
77
&self,
78
data: &PerLevelTraversalData,
79
context: &mut StyleContext<E>,
80
node: E::ConcreteNode,
81
note_child: F,
82
) where
83
F: FnMut(E::ConcreteNode);
84
85
/// Process `node` on the way up, after its children have been processed.
86
///
87
/// This is only executed if `needs_postorder_traversal` returns true.
88
fn process_postorder(&self, contect: &mut StyleContext<E>, node: E::ConcreteNode);
89
90
/// Boolean that specifies whether a bottom up traversal should be
91
/// performed.
92
///
93
/// If it's false, then process_postorder has no effect at all.
94
fn needs_postorder_traversal() -> bool {
95
true
96
}
97
98
/// Handles the postorder step of the traversal, if it exists, by bubbling
99
/// up the parent chain.
100
///
101
/// If we are the last child that finished processing, recursively process
102
/// our parent. Else, stop. Also, stop at the root.
103
///
104
/// Thus, if we start with all the leaves of a tree, we end up traversing
105
/// the whole tree bottom-up because each parent will be processed exactly
106
/// once (by the last child that finishes processing).
107
///
108
/// The only communication between siblings is that they both
109
/// fetch-and-subtract the parent's children count. This makes it safe to
110
/// call durign the parallel traversal.
111
fn handle_postorder_traversal(
112
&self,
113
context: &mut StyleContext<E>,
114
root: OpaqueNode,
115
mut node: E::ConcreteNode,
116
children_to_process: isize,
117
) {
118
// If the postorder step is a no-op, don't bother.
119
if !Self::needs_postorder_traversal() {
120
return;
121
}
122
123
if children_to_process == 0 {
124
// We are a leaf. Walk up the chain.
125
loop {
126
self.process_postorder(context, node);
127
if node.opaque() == root {
128
break;
129
}
130
let parent = node.traversal_parent().unwrap();
131
let remaining = parent.did_process_child();
132
if remaining != 0 {
133
// The parent has other unprocessed descendants. We only
134
// perform postorder processing after the last descendant
135
// has been processed.
136
break;
137
}
138
139
node = parent.as_node();
140
}
141
} else {
142
// Otherwise record the number of children to process when the time
143
// comes.
144
node.as_element()
145
.unwrap()
146
.store_children_to_process(children_to_process);
147
}
148
}
149
150
/// Style invalidations happen when traversing from a parent to its children.
151
/// However, this mechanism can't handle style invalidations on the root. As
152
/// such, we have a pre-traversal step to handle that part and determine whether
153
/// a full traversal is needed.
154
fn pre_traverse(root: E, shared_context: &SharedStyleContext) -> PreTraverseToken<E> {
155
let traversal_flags = shared_context.traversal_flags;
156
157
let mut data = root.mutate_data();
158
let mut data = data.as_mut().map(|d| &mut **d);
159
160
if let Some(ref mut data) = data {
161
if !traversal_flags.for_animation_only() {
162
// Invalidate our style, and that of our siblings and
163
// descendants as needed.
164
let invalidation_result = data.invalidate_style_if_needed(
165
root,
166
shared_context,
167
None,
168
&mut NthIndexCache::default(),
169
);
170
171
if invalidation_result.has_invalidated_siblings() {
172
let actual_root = root.traversal_parent().expect(
173
"How in the world can you invalidate \
174
siblings without a parent?",
175
);
176
unsafe { actual_root.set_dirty_descendants() }
177
return PreTraverseToken(Some(actual_root));
178
}
179
}
180
}
181
182
let should_traverse =
183
Self::element_needs_traversal(root, traversal_flags, data.as_mut().map(|d| &**d));
184
185
// If we're not going to traverse at all, we may need to clear some state
186
// off the root (which would normally be done at the end of recalc_style_at).
187
if !should_traverse && data.is_some() {
188
clear_state_after_traversing(root, data.unwrap(), traversal_flags);
189
}
190
191
PreTraverseToken(if should_traverse { Some(root) } else { None })
192
}
193
194
/// Returns true if traversal should visit a text node. The style system
195
/// never processes text nodes, but Servo overrides this to visit them for
196
/// flow construction when necessary.
197
fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool {
198
debug_assert!(node.is_text_node());
199
false
200
}
201
202
/// Returns true if traversal is needed for the given element and subtree.
203
fn element_needs_traversal(
204
el: E,
205
traversal_flags: TraversalFlags,
206
data: Option<&ElementData>,
207
) -> bool {
208
debug!(
209
"element_needs_traversal({:?}, {:?}, {:?})",
210
el, traversal_flags, data
211
);
212
213
// In case of animation-only traversal we need to traverse the element
214
// if the element has animation only dirty descendants bit,
215
// animation-only restyle hint or recascade.
216
if traversal_flags.for_animation_only() {
217
return data.map_or(false, |d| d.has_styles()) &&
218
(el.has_animation_only_dirty_descendants() ||
219
data.as_ref()
220
.unwrap()
221
.hint
222
.has_animation_hint_or_recascade());
223
}
224
225
// Non-incremental layout visits every node.
226
if is_servo_nonincremental_layout() {
227
return true;
228
}
229
230
// Unwrap the data.
231
let data = match data {
232
Some(d) if d.has_styles() => d,
233
_ => return true,
234
};
235
236
// If the dirty descendants bit is set, we need to traverse no matter
237
// what. Skip examining the ElementData.
238
if el.has_dirty_descendants() {
239
return true;
240
}
241
242
// If we have a restyle hint or need to recascade, we need to visit the
243
// element.
244
//
245
// Note that this is different than checking has_current_styles_for_traversal(),
246
// since that can return true even if we have a restyle hint indicating
247
// that the element's descendants (but not necessarily the element) need
248
// restyling.
249
if !data.hint.is_empty() {
250
return true;
251
}
252
253
// Servo uses the post-order traversal for flow construction, so we need
254
// to traverse any element with damage so that we can perform fixup /
255
// reconstruction on our way back up the tree.
256
if cfg!(feature = "servo") && !data.damage.is_empty() {
257
return true;
258
}
259
260
trace!("{:?} doesn't need traversal", el);
261
false
262
}
263
264
/// Returns true if we want to cull this subtree from the travesal.
265
fn should_cull_subtree(
266
&self,
267
context: &mut StyleContext<E>,
268
parent: E,
269
parent_data: &ElementData,
270
) -> bool {
271
debug_assert!(
272
parent.has_current_styles_for_traversal(parent_data, context.shared.traversal_flags)
273
);
274
275
// If the parent computed display:none, we don't style the subtree.
276
if parent_data.styles.is_display_none() {
277
debug!("Parent {:?} is display:none, culling traversal", parent);
278
return true;
279
}
280
281
return false;
282
}
283
284
/// Return the shared style context common to all worker threads.
285
fn shared_context(&self) -> &SharedStyleContext;
286
}
287
288
/// Manually resolve style by sequentially walking up the parent chain to the
289
/// first styled Element, ignoring pending restyles. The resolved style is made
290
/// available via a callback, and can be dropped by the time this function
291
/// returns in the display:none subtree case.
292
pub fn resolve_style<E>(
293
context: &mut StyleContext<E>,
294
element: E,
295
rule_inclusion: RuleInclusion,
296
pseudo: Option<&PseudoElement>,
297
) -> ElementStyles
298
where
299
E: TElement,
300
{
301
debug_assert!(
302
rule_inclusion == RuleInclusion::DefaultOnly ||
303
pseudo.map_or(false, |p| p.is_before_or_after()) ||
304
element.borrow_data().map_or(true, |d| !d.has_styles()),
305
"Why are we here?"
306
);
307
let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();
308
309
// Clear the bloom filter, just in case the caller is reusing TLS.
310
context.thread_local.bloom_filter.clear();
311
312
let mut style = None;
313
let mut ancestor = element.traversal_parent();
314
while let Some(current) = ancestor {
315
if rule_inclusion == RuleInclusion::All {
316
if let Some(data) = current.borrow_data() {
317
if let Some(ancestor_style) = data.styles.get_primary() {
318
style = Some(ancestor_style.clone());
319
break;
320
}
321
}
322
}
323
ancestors_requiring_style_resolution.push(current);
324
ancestor = current.traversal_parent();
325
}
326
327
if let Some(ancestor) = ancestor {
328
context.thread_local.bloom_filter.rebuild(ancestor);
329
context.thread_local.bloom_filter.push(ancestor);
330
}
331
332
let mut layout_parent_style = style.clone();
333
while let Some(style) = layout_parent_style.take() {
334
if !style.is_display_contents() {
335
layout_parent_style = Some(style);
336
break;
337
}
338
339
ancestor = ancestor.unwrap().traversal_parent();
340
layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone());
341
}
342
343
for ancestor in ancestors_requiring_style_resolution.iter().rev() {
344
context.thread_local.bloom_filter.assert_complete(*ancestor);
345
346
// Actually `PseudoElementResolution` doesn't really matter here.
347
// (but it does matter below!).
348
let primary_style = StyleResolverForElement::new(
349
*ancestor,
350
context,
351
rule_inclusion,
352
PseudoElementResolution::IfApplicable,
353
)
354
.resolve_primary_style(
355
style.as_ref().map(|s| &**s),
356
layout_parent_style.as_ref().map(|s| &**s),
357
);
358
359
let is_display_contents = primary_style.style().is_display_contents();
360
361
style = Some(primary_style.style.0);
362
if !is_display_contents {
363
layout_parent_style = style.clone();
364
}
365
366
context.thread_local.bloom_filter.push(*ancestor);
367
}
368
369
context.thread_local.bloom_filter.assert_complete(element);
370
StyleResolverForElement::new(
371
element,
372
context,
373
rule_inclusion,
374
PseudoElementResolution::Force,
375
)
376
.resolve_style(
377
style.as_ref().map(|s| &**s),
378
layout_parent_style.as_ref().map(|s| &**s),
379
)
380
.into()
381
}
382
383
/// Calculates the style for a single node.
384
#[inline]
385
#[allow(unsafe_code)]
386
pub fn recalc_style_at<E, D, F>(
387
traversal: &D,
388
traversal_data: &PerLevelTraversalData,
389
context: &mut StyleContext<E>,
390
element: E,
391
data: &mut ElementData,
392
note_child: F,
393
) where
394
E: TElement,
395
D: DomTraversal<E>,
396
F: FnMut(E::ConcreteNode),
397
{
398
use std::cmp;
399
400
let flags = context.shared.traversal_flags;
401
let is_initial_style = !data.has_styles();
402
403
context.thread_local.statistics.elements_traversed += 1;
404
debug_assert!(
405
flags.intersects(TraversalFlags::AnimationOnly) ||
406
!element.has_snapshot() ||
407
element.handled_snapshot(),
408
"Should've handled snapshots here already"
409
);
410
411
let compute_self = !element.has_current_styles_for_traversal(data, flags);
412
413
debug!(
414
"recalc_style_at: {:?} (compute_self={:?}, \
415
dirty_descendants={:?}, data={:?})",
416
element,
417
compute_self,
418
element.has_dirty_descendants(),
419
data
420
);
421
422
let mut child_cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
423
424
// Compute style for this element if necessary.
425
if compute_self {
426
child_cascade_requirement = compute_style(traversal_data, context, element, data);
427
428
if element.is_in_native_anonymous_subtree() {
429
// We must always cascade native anonymous subtrees, since they
430
// may have pseudo-elements underneath that would inherit from the
431
// closest non-NAC ancestor instead of us.
432
child_cascade_requirement = cmp::max(
433
child_cascade_requirement,
434
ChildCascadeRequirement::MustCascadeChildren,
435
);
436
}
437
438
// If we're restyling this element to display:none, throw away all style
439
// data in the subtree, notify the caller to early-return.
440
if data.styles.is_display_none() {
441
debug!(
442
"{:?} style is display:none - clearing data from descendants.",
443
element
444
);
445
unsafe {
446
clear_descendant_data(element);
447
}
448
}
449
450
// Inform any paint worklets of changed style, to speculatively
451
// evaluate the worklet code. In the case that the size hasn't changed,
452
// this will result in increased concurrency between script and layout.
453
notify_paint_worklet(context, data);
454
} else {
455
debug_assert!(data.has_styles());
456
data.set_traversed_without_styling();
457
}
458
459
// Now that matching and cascading is done, clear the bits corresponding to
460
// those operations and compute the propagated restyle hint (unless we're
461
// not processing invalidations, in which case don't need to propagate it
462
// and must avoid clearing it).
463
debug_assert!(
464
flags.for_animation_only() || !data.hint.has_animation_hint(),
465
"animation restyle hint should be handled during \
466
animation-only restyles"
467
);
468
let propagated_hint = data.hint.propagate(&flags);
469
470
trace!(
471
"propagated_hint={:?}, cascade_requirement={:?}, \
472
is_display_none={:?}, implementing_pseudo={:?}",
473
propagated_hint,
474
child_cascade_requirement,
475
data.styles.is_display_none(),
476
element.implemented_pseudo_element()
477
);
478
debug_assert!(
479
element.has_current_styles_for_traversal(data, flags),
480
"Should have computed style or haven't yet valid computed \
481
style in case of animation-only restyle"
482
);
483
484
let has_dirty_descendants_for_this_restyle = if flags.for_animation_only() {
485
element.has_animation_only_dirty_descendants()
486
} else {
487
element.has_dirty_descendants()
488
};
489
490
// Before examining each child individually, try to prove that our children
491
// don't need style processing. They need processing if any of the following
492
// conditions hold:
493
//
494
// * We have the dirty descendants bit.
495
// * We're propagating a restyle hint.
496
// * We can't skip the cascade.
497
// * This is a servo non-incremental traversal.
498
//
499
// Additionally, there are a few scenarios where we avoid traversing the
500
// subtree even if descendant styles are out of date. These cases are
501
// enumerated in should_cull_subtree().
502
let mut traverse_children = has_dirty_descendants_for_this_restyle ||
503
!propagated_hint.is_empty() ||
504
!child_cascade_requirement.can_skip_cascade() ||
505
is_servo_nonincremental_layout();
506
507
traverse_children =
508
traverse_children && !traversal.should_cull_subtree(context, element, &data);
509
510
// Examine our children, and enqueue the appropriate ones for traversal.
511
if traverse_children {
512
note_children::<E, D, F>(
513
context,
514
element,
515
data,
516
propagated_hint,
517
child_cascade_requirement,
518
is_initial_style,
519
note_child,
520
);
521
}
522
523
// FIXME(bholley): Make these assertions pass for servo.
524
if cfg!(feature = "gecko") && cfg!(debug_assertions) && data.styles.is_display_none() {
525
debug_assert!(!element.has_dirty_descendants());
526
debug_assert!(!element.has_animation_only_dirty_descendants());
527
}
528
529
clear_state_after_traversing(element, data, flags);
530
}
531
532
fn clear_state_after_traversing<E>(element: E, data: &mut ElementData, flags: TraversalFlags)
533
where
534
E: TElement,
535
{
536
if flags.intersects(TraversalFlags::FinalAnimationTraversal) {
537
debug_assert!(flags.for_animation_only());
538
data.clear_restyle_flags_and_damage();
539
unsafe {
540
element.unset_animation_only_dirty_descendants();
541
}
542
}
543
}
544
545
fn compute_style<E>(
546
traversal_data: &PerLevelTraversalData,
547
context: &mut StyleContext<E>,
548
element: E,
549
data: &mut ElementData,
550
) -> ChildCascadeRequirement
551
where
552
E: TElement,
553
{
554
use crate::data::RestyleKind::*;
555
556
context.thread_local.statistics.elements_styled += 1;
557
let kind = data.restyle_kind(context.shared);
558
559
debug!("compute_style: {:?} (kind={:?})", element, kind);
560
561
if data.has_styles() {
562
data.set_restyled();
563
}
564
565
let mut important_rules_changed = false;
566
let new_styles = match kind {
567
MatchAndCascade => {
568
debug_assert!(
569
!context.shared.traversal_flags.for_animation_only(),
570
"MatchAndCascade shouldn't be processed during \
571
animation-only traversal"
572
);
573
// Ensure the bloom filter is up to date.
574
context
575
.thread_local
576
.bloom_filter
577
.insert_parents_recovering(element, traversal_data.current_dom_depth);
578
579
context.thread_local.bloom_filter.assert_complete(element);
580
debug_assert_eq!(
581
context.thread_local.bloom_filter.matching_depth(),
582
traversal_data.current_dom_depth
583
);
584
585
// This is only relevant for animations as of right now.
586
important_rules_changed = true;
587
588
let mut target = StyleSharingTarget::new(element);
589
590
// Now that our bloom filter is set up, try the style sharing
591
// cache.
592
match target.share_style_if_possible(context) {
593
Some(shared_styles) => {
594
context.thread_local.statistics.styles_shared += 1;
595
shared_styles
596
},
597
None => {
598
context.thread_local.statistics.elements_matched += 1;
599
// Perform the matching and cascading.
600
let new_styles = {
601
let mut resolver = StyleResolverForElement::new(
602
element,
603
context,
604
RuleInclusion::All,
605
PseudoElementResolution::IfApplicable,
606
);
607
608
resolver.resolve_style_with_default_parents()
609
};
610
611
context.thread_local.sharing_cache.insert_if_possible(
612
&element,
613
&new_styles.primary,
614
Some(&mut target),
615
traversal_data.current_dom_depth,
616
);
617
618
new_styles
619
},
620
}
621
},
622
CascadeWithReplacements(flags) => {
623
// Skipping full matching, load cascade inputs from previous values.
624
let mut cascade_inputs = ElementCascadeInputs::new_from_element_data(data);
625
important_rules_changed = element.replace_rules(flags, context, &mut cascade_inputs);
626
627
let mut resolver = StyleResolverForElement::new(
628
element,
629
context,
630
RuleInclusion::All,
631
PseudoElementResolution::IfApplicable,
632
);
633
634
resolver.cascade_styles_with_default_parents(cascade_inputs)
635
},
636
CascadeOnly => {
637
// Skipping full matching, load cascade inputs from previous values.
638
let cascade_inputs = ElementCascadeInputs::new_from_element_data(data);
639
640
let new_styles = {
641
let mut resolver = StyleResolverForElement::new(
642
element,
643
context,
644
RuleInclusion::All,
645
PseudoElementResolution::IfApplicable,
646
);
647
648
resolver.cascade_styles_with_default_parents(cascade_inputs)
649
};
650
651
// Insert into the cache, but only if this style isn't reused from a
652
// sibling or cousin. Otherwise, recascading a bunch of identical
653
// elements would unnecessarily flood the cache with identical entries.
654
//
655
// This is analogous to the obvious "don't insert an element that just
656
// got a hit in the style sharing cache" behavior in the MatchAndCascade
657
// handling above.
658
//
659
// Note that, for the MatchAndCascade path, we still insert elements that
660
// shared styles via the rule node, because we know that there's something
661
// different about them that caused them to miss the sharing cache before
662
// selector matching. If we didn't, we would still end up with the same
663
// number of eventual styles, but would potentially miss out on various
664
// opportunities for skipping selector matching, which could hurt
665
// performance.
666
if !new_styles.primary.reused_via_rule_node {
667
context.thread_local.sharing_cache.insert_if_possible(
668
&element,
669
&new_styles.primary,
670
None,
671
traversal_data.current_dom_depth,
672
);
673
}
674
675
new_styles
676
},
677
};
678
679
element.finish_restyle(context, data, new_styles, important_rules_changed)
680
}
681
682
#[cfg(feature = "servo")]
683
fn notify_paint_worklet<E>(context: &StyleContext<E>, data: &ElementData)
684
where
685
E: TElement,
686
{
687
use crate::values::generics::image::{GenericImageLayer, Image};
688
use style_traits::ToCss;
689
690
// We speculatively evaluate any paint worklets during styling.
691
// This allows us to run paint worklets in parallel with style and layout.
692
// Note that this is wasted effort if the size of the node has
693
// changed, but in may cases it won't have.
694
if let Some(ref values) = data.styles.primary {
695
for image in &values.get_background().background_image.0 {
696
let (name, arguments) = match *image {
697
GenericImageLayer::Image(Image::PaintWorklet(ref worklet)) => {
698
(&worklet.name, &worklet.arguments)
699
},
700
_ => continue,
701
};
702
let painter = match context.shared.registered_speculative_painters.get(name) {
703
Some(painter) => painter,
704
None => continue,
705
};
706
let properties = painter
707
.properties()
708
.iter()
709
.filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
710
.map(|(name, id)| (name.clone(), values.computed_value_to_string(id)))
711
.collect();
712
let arguments = arguments
713
.iter()
714
.map(|argument| argument.to_css_string())
715
.collect();
716
debug!("Notifying paint worklet {}.", painter.name());
717
painter.speculatively_draw_a_paint_image(properties, arguments);
718
}
719
}
720
}
721
722
#[cfg(feature = "gecko")]
723
fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData)
724
where
725
E: TElement,
726
{
727
// The CSS paint API is Servo-only at the moment
728
}
729
730
fn note_children<E, D, F>(
731
context: &mut StyleContext<E>,
732
element: E,
733
data: &ElementData,
734
propagated_hint: RestyleHint,
735
cascade_requirement: ChildCascadeRequirement,
736
is_initial_style: bool,
737
mut note_child: F,
738
) where
739
E: TElement,
740
D: DomTraversal<E>,
741
F: FnMut(E::ConcreteNode),
742
{
743
trace!("note_children: {:?}", element);
744
let flags = context.shared.traversal_flags;
745
746
// Loop over all the traversal children.
747
for child_node in element.traversal_children() {
748
let child = match child_node.as_element() {
749
Some(el) => el,
750
None => {
751
if is_servo_nonincremental_layout() ||
752
D::text_node_needs_traversal(child_node, data)
753
{
754
note_child(child_node);
755
}
756
continue;
757
},
758
};
759
760
let mut child_data = child.mutate_data();
761
let mut child_data = child_data.as_mut().map(|d| &mut **d);
762
trace!(
763
" > {:?} -> {:?} + {:?}, pseudo: {:?}",
764
child,
765
child_data.as_ref().map(|d| d.hint),
766
propagated_hint,
767
child.implemented_pseudo_element()
768
);
769
770
if let Some(ref mut child_data) = child_data {
771
let mut child_hint = propagated_hint;
772
match cascade_requirement {
773
ChildCascadeRequirement::CanSkipCascade => {},
774
ChildCascadeRequirement::MustCascadeDescendants => {
775
child_hint |= RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS;
776
},
777
ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle => {
778
use crate::properties::computed_value_flags::ComputedValueFlags;
779
if child_data
780
.styles
781
.primary()
782
.flags
783
.contains(ComputedValueFlags::INHERITS_RESET_STYLE)
784
{
785
child_hint |= RestyleHint::RECASCADE_SELF;
786
}
787
},
788
ChildCascadeRequirement::MustCascadeChildren => {
789
child_hint |= RestyleHint::RECASCADE_SELF;
790
},
791
}
792
793
child_data.hint.insert(child_hint);
794
795
// Handle element snapshots and invalidation of descendants and siblings
796
// as needed.
797
//
798
// NB: This will be a no-op if there's no snapshot.
799
child_data.invalidate_style_if_needed(
800
child,
801
&context.shared,
802
Some(&context.thread_local.stack_limit_checker),
803
&mut context.thread_local.nth_index_cache,
804
);
805
}
806
807
if D::element_needs_traversal(child, flags, child_data.map(|d| &*d)) {
808
note_child(child_node);
809
810
// Set the dirty descendants bit on the parent as needed, so that we
811
// can find elements during the post-traversal.
812
//
813
// Note that these bits may be cleared again at the bottom of
814
// recalc_style_at if requested by the caller.
815
if !is_initial_style {
816
if flags.for_animation_only() {
817
unsafe {
818
element.set_animation_only_dirty_descendants();
819
}
820
} else {
821
unsafe {
822
element.set_dirty_descendants();
823
}
824
}
825
}
826
}
827
}
828
}
829
830
/// Clear style data for all the subtree under `root` (but not for root itself).
831
///
832
/// We use a list to avoid unbounded recursion, which we need to avoid in the
833
/// parallel traversal because the rayon stacks are small.
834
pub unsafe fn clear_descendant_data<E>(root: E)
835
where
836
E: TElement,
837
{
838
let mut parents = SmallVec::<[E; 32]>::new();
839
parents.push(root);
840
while let Some(p) = parents.pop() {
841
for kid in p.traversal_children() {
842
if let Some(kid) = kid.as_element() {
843
// We maintain an invariant that, if an element has data, all its
844
// ancestors have data as well.
845
//
846
// By consequence, any element without data has no descendants with
847
// data.
848
if kid.get_data().is_some() {
849
kid.clear_data();
850
parents.push(kid);
851
}
852
}
853
}
854
}
855
856
// Make sure not to clear NODE_NEEDS_FRAME on the root.
857
root.clear_descendant_bits();
858
}