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
//! High-level interface to CSS selector matching.
6
7
#![allow(unsafe_code)]
8
#![deny(missing_docs)]
9
10
use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
11
use crate::context::{SharedStyleContext, StyleContext};
12
use crate::data::ElementData;
13
use crate::dom::TElement;
14
use crate::invalidation::element::restyle_hints::RestyleHint;
15
use crate::properties::longhands::display::computed_value::T as Display;
16
use crate::properties::ComputedValues;
17
use crate::rule_tree::{CascadeLevel, StrongRuleNode};
18
use crate::selector_parser::{PseudoElement, RestyleDamage};
19
use crate::style_resolver::ResolvedElementStyles;
20
use crate::traversal_flags::TraversalFlags;
21
use selectors::matching::ElementSelectorFlags;
22
use servo_arc::{Arc, ArcBorrow};
23
24
/// Represents the result of comparing an element's old and new style.
25
#[derive(Debug)]
26
pub struct StyleDifference {
27
/// The resulting damage.
28
pub damage: RestyleDamage,
29
30
/// Whether any styles changed.
31
pub change: StyleChange,
32
}
33
34
/// Represents whether or not the style of an element has changed.
35
#[derive(Clone, Copy, Debug)]
36
pub enum StyleChange {
37
/// The style hasn't changed.
38
Unchanged,
39
/// The style has changed.
40
Changed {
41
/// Whether only reset structs changed.
42
reset_only: bool,
43
},
44
}
45
46
/// Whether or not newly computed values for an element need to be cascade
47
/// to children.
48
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
49
pub enum ChildCascadeRequirement {
50
/// Old and new computed values were the same, or we otherwise know that
51
/// we won't bother recomputing style for children, so we can skip cascading
52
/// the new values into child elements.
53
CanSkipCascade = 0,
54
/// The same as `MustCascadeChildren`, but we only need to actually
55
/// recascade if the child inherits any explicit reset style.
56
MustCascadeChildrenIfInheritResetStyle = 1,
57
/// Old and new computed values were different, so we must cascade the
58
/// new values to children.
59
MustCascadeChildren = 2,
60
/// The same as `MustCascadeChildren`, but for the entire subtree. This is
61
/// used to handle root font-size updates needing to recascade the whole
62
/// document.
63
MustCascadeDescendants = 3,
64
}
65
66
impl ChildCascadeRequirement {
67
/// Whether we can unconditionally skip the cascade.
68
pub fn can_skip_cascade(&self) -> bool {
69
matches!(*self, ChildCascadeRequirement::CanSkipCascade)
70
}
71
}
72
73
/// Determines which styles are being cascaded currently.
74
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
75
enum CascadeVisitedMode {
76
/// Cascade the regular, unvisited styles.
77
Unvisited,
78
/// Cascade the styles used when an element's relevant link is visited. A
79
/// "relevant link" is the element being matched if it is a link or the
80
/// nearest ancestor link.
81
Visited,
82
}
83
84
trait PrivateMatchMethods: TElement {
85
/// Updates the rule nodes without re-running selector matching, using just
86
/// the rule tree, for a specific visited mode.
87
///
88
/// Returns true if an !important rule was replaced.
89
fn replace_rules_internal(
90
&self,
91
replacements: RestyleHint,
92
context: &mut StyleContext<Self>,
93
cascade_visited: CascadeVisitedMode,
94
cascade_inputs: &mut ElementCascadeInputs,
95
) -> bool {
96
use crate::properties::PropertyDeclarationBlock;
97
use crate::shared_lock::Locked;
98
99
debug_assert!(
100
replacements.intersects(RestyleHint::replacements()) &&
101
(replacements & !RestyleHint::replacements()).is_empty()
102
);
103
104
let stylist = &context.shared.stylist;
105
let guards = &context.shared.guards;
106
107
let primary_rules = match cascade_visited {
108
CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(),
109
CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(),
110
};
111
112
let primary_rules = match primary_rules {
113
Some(r) => r,
114
None => return false,
115
};
116
117
let replace_rule_node = |level: CascadeLevel,
118
pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
119
path: &mut StrongRuleNode|
120
-> bool {
121
let mut important_rules_changed = false;
122
let new_node = stylist.rule_tree().update_rule_at_level(
123
level,
124
pdb,
125
path,
126
guards,
127
&mut important_rules_changed,
128
);
129
if let Some(n) = new_node {
130
*path = n;
131
}
132
important_rules_changed
133
};
134
135
if !context.shared.traversal_flags.for_animation_only() {
136
let mut result = false;
137
if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) {
138
let style_attribute = self.style_attribute();
139
result |= replace_rule_node(
140
CascadeLevel::StyleAttributeNormal,
141
style_attribute,
142
primary_rules,
143
);
144
result |= replace_rule_node(
145
CascadeLevel::StyleAttributeImportant,
146
style_attribute,
147
primary_rules,
148
);
149
// FIXME(emilio): Still a hack!
150
self.unset_dirty_style_attribute();
151
}
152
return result;
153
}
154
155
// Animation restyle hints are processed prior to other restyle
156
// hints in the animation-only traversal.
157
//
158
// Non-animation restyle hints will be processed in a subsequent
159
// normal traversal.
160
if replacements.intersects(RestyleHint::for_animations()) {
161
debug_assert!(context.shared.traversal_flags.for_animation_only());
162
163
if replacements.contains(RestyleHint::RESTYLE_SMIL) {
164
replace_rule_node(
165
CascadeLevel::SMILOverride,
166
self.smil_override(),
167
primary_rules,
168
);
169
}
170
171
if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) {
172
replace_rule_node(
173
CascadeLevel::Transitions,
174
self.transition_rule().as_ref().map(|a| a.borrow_arc()),
175
primary_rules,
176
);
177
}
178
179
if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) {
180
replace_rule_node(
181
CascadeLevel::Animations,
182
self.animation_rule().as_ref().map(|a| a.borrow_arc()),
183
primary_rules,
184
);
185
}
186
}
187
188
false
189
}
190
191
/// If there is no transition rule in the ComputedValues, it returns None.
192
#[cfg(feature = "gecko")]
193
fn after_change_style(
194
&self,
195
context: &mut StyleContext<Self>,
196
primary_style: &Arc<ComputedValues>,
197
) -> Option<Arc<ComputedValues>> {
198
use crate::context::CascadeInputs;
199
use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
200
use crate::stylist::RuleInclusion;
201
202
let rule_node = primary_style.rules();
203
let without_transition_rules = context
204
.shared
205
.stylist
206
.rule_tree()
207
.remove_transition_rule_if_applicable(rule_node);
208
if without_transition_rules == *rule_node {
209
// We don't have transition rule in this case, so return None to let
210
// the caller use the original ComputedValues.
211
return None;
212
}
213
214
// FIXME(bug 868975): We probably need to transition visited style as
215
// well.
216
let inputs = CascadeInputs {
217
rules: Some(without_transition_rules),
218
visited_rules: primary_style.visited_rules().cloned(),
219
};
220
221
// Actually `PseudoElementResolution` doesn't really matter.
222
let style = StyleResolverForElement::new(
223
*self,
224
context,
225
RuleInclusion::All,
226
PseudoElementResolution::IfApplicable,
227
)
228
.cascade_style_and_visited_with_default_parents(inputs);
229
230
Some(style.0)
231
}
232
233
#[cfg(feature = "gecko")]
234
fn needs_animations_update(
235
&self,
236
context: &mut StyleContext<Self>,
237
old_style: Option<&ComputedValues>,
238
new_style: &ComputedValues,
239
) -> bool {
240
let new_box_style = new_style.get_box();
241
let new_style_specifies_animations = new_box_style.specifies_animations();
242
243
let has_animations = self.has_css_animations();
244
if !new_style_specifies_animations && !has_animations {
245
return false;
246
}
247
248
let old_style = match old_style {
249
Some(old) => old,
250
// If we have no old style but have animations, we may be a
251
// pseudo-element which was re-created without style changes.
252
//
253
// This can happen when we reframe the pseudo-element without
254
// restyling it (due to content insertion on a flex container or
255
// such, for example). See bug 1564366.
256
//
257
// FIXME(emilio): The really right fix for this is keeping the
258
// pseudo-element itself around on reframes, but that's a bit
259
// harder. If we do that we can probably remove quite a lot of the
260
// EffectSet complexity though, since right now it's stored on the
261
// parent element for pseudo-elements given we need to keep it
262
// around...
263
None => {
264
return new_style_specifies_animations || new_style.is_pseudo_style();
265
},
266
};
267
268
let old_box_style = old_style.get_box();
269
270
let keyframes_could_have_changed = context
271
.shared
272
.traversal_flags
273
.contains(TraversalFlags::ForCSSRuleChanges);
274
275
// If the traversal is triggered due to changes in CSS rules changes, we
276
// need to try to update all CSS animations on the element if the
277
// element has or will have CSS animation style regardless of whether
278
// the animation is running or not.
279
//
280
// TODO: We should check which @keyframes were added/changed/deleted and
281
// update only animations corresponding to those @keyframes.
282
if keyframes_could_have_changed {
283
return true;
284
}
285
286
// If the animations changed, well...
287
if !old_box_style.animations_equals(new_box_style) {
288
return true;
289
}
290
291
let old_display = old_box_style.clone_display();
292
let new_display = new_box_style.clone_display();
293
294
// If we were display: none, we may need to trigger animations.
295
if old_display == Display::None && new_display != Display::None {
296
return new_style_specifies_animations;
297
}
298
299
// If we are becoming display: none, we may need to stop animations.
300
if old_display != Display::None && new_display == Display::None {
301
return has_animations;
302
}
303
304
// We might need to update animations if writing-mode or direction
305
// changed, and any of the animations contained logical properties.
306
//
307
// We may want to be more granular, but it's probably not worth it.
308
if new_style.writing_mode != old_style.writing_mode {
309
return has_animations;
310
}
311
312
false
313
}
314
315
/// Create a SequentialTask for resolving descendants in a SMIL display
316
/// property animation if the display property changed from none.
317
#[cfg(feature = "gecko")]
318
fn handle_display_change_for_smil_if_needed(
319
&self,
320
context: &mut StyleContext<Self>,
321
old_values: Option<&ComputedValues>,
322
new_values: &ComputedValues,
323
restyle_hints: RestyleHint,
324
) {
325
use crate::context::PostAnimationTasks;
326
327
if !restyle_hints.intersects(RestyleHint::RESTYLE_SMIL) {
328
return;
329
}
330
331
if new_values.is_display_property_changed_from_none(old_values) {
332
// When display value is changed from none to other, we need to
333
// traverse descendant elements in a subsequent normal
334
// traversal (we can't traverse them in this animation-only restyle
335
// since we have no way to know whether the decendants
336
// need to be traversed at the beginning of the animation-only
337
// restyle).
338
let task = ::context::SequentialTask::process_post_animation(
339
*self,
340
PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL,
341
);
342
context.thread_local.tasks.push(task);
343
}
344
}
345
346
#[cfg(feature = "gecko")]
347
fn process_animations(
348
&self,
349
context: &mut StyleContext<Self>,
350
old_values: &mut Option<Arc<ComputedValues>>,
351
new_values: &mut Arc<ComputedValues>,
352
restyle_hint: RestyleHint,
353
important_rules_changed: bool,
354
) {
355
use crate::context::UpdateAnimationsTasks;
356
357
if context.shared.traversal_flags.for_animation_only() {
358
self.handle_display_change_for_smil_if_needed(
359
context,
360
old_values.as_ref().map(|v| &**v),
361
new_values,
362
restyle_hint,
363
);
364
return;
365
}
366
367
// Bug 868975: These steps should examine and update the visited styles
368
// in addition to the unvisited styles.
369
370
let mut tasks = UpdateAnimationsTasks::empty();
371
if self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values) {
372
tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS);
373
}
374
375
let before_change_style = if self
376
.might_need_transitions_update(old_values.as_ref().map(|s| &**s), new_values)
377
{
378
let after_change_style = if self.has_css_transitions() {
379
self.after_change_style(context, new_values)
380
} else {
381
None
382
};
383
384
// In order to avoid creating a SequentialTask for transitions which
385
// may not be updated, we check it per property to make sure Gecko
386
// side will really update transition.
387
let needs_transitions_update = {
388
// We borrow new_values here, so need to add a scope to make
389
// sure we release it before assigning a new value to it.
390
let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values);
391
392
self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref)
393
};
394
395
if needs_transitions_update {
396
if let Some(values_without_transitions) = after_change_style {
397
*new_values = values_without_transitions;
398
}
399
tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS);
400
401
// We need to clone old_values into SequentialTask, so we can
402
// use it later.
403
old_values.clone()
404
} else {
405
None
406
}
407
} else {
408
None
409
};
410
411
if self.has_animations() {
412
tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES);
413
if important_rules_changed {
414
tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS);
415
}
416
if new_values.is_display_property_changed_from_none(old_values.as_ref().map(|s| &**s)) {
417
tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE);
418
}
419
}
420
421
if !tasks.is_empty() {
422
let task =
423
::context::SequentialTask::update_animations(*self, before_change_style, tasks);
424
context.thread_local.tasks.push(task);
425
}
426
}
427
428
#[cfg(feature = "servo")]
429
fn process_animations(
430
&self,
431
context: &mut StyleContext<Self>,
432
old_values: &mut Option<Arc<ComputedValues>>,
433
new_values: &mut Arc<ComputedValues>,
434
_restyle_hint: RestyleHint,
435
_important_rules_changed: bool,
436
) {
437
use crate::animation;
438
use crate::dom::TNode;
439
440
let mut possibly_expired_animations = vec![];
441
let shared_context = context.shared;
442
if let Some(ref mut old) = *old_values {
443
// FIXME(emilio, #20116): This makes no sense.
444
self.update_animations_for_cascade(
445
shared_context,
446
old,
447
&mut possibly_expired_animations,
448
&context.thread_local.font_metrics_provider,
449
);
450
}
451
452
let new_animations_sender = &context.thread_local.new_animations_sender;
453
let this_opaque = self.as_node().opaque();
454
// Trigger any present animations if necessary.
455
animation::maybe_start_animations(
456
*self,
457
&shared_context,
458
new_animations_sender,
459
this_opaque,
460
&new_values,
461
);
462
463
// Trigger transitions if necessary. This will reset `new_values` back
464
// to its old value if it did trigger a transition.
465
if let Some(ref values) = *old_values {
466
animation::start_transitions_if_applicable(
467
new_animations_sender,
468
this_opaque,
469
&values,
470
new_values,
471
&shared_context.timer,
472
&possibly_expired_animations,
473
);
474
}
475
}
476
477
/// Computes and applies non-redundant damage.
478
fn accumulate_damage_for(
479
&self,
480
shared_context: &SharedStyleContext,
481
damage: &mut RestyleDamage,
482
old_values: &ComputedValues,
483
new_values: &ComputedValues,
484
pseudo: Option<&PseudoElement>,
485
) -> ChildCascadeRequirement {
486
debug!("accumulate_damage_for: {:?}", self);
487
debug_assert!(!shared_context
488
.traversal_flags
489
.contains(TraversalFlags::FinalAnimationTraversal));
490
491
let difference = self.compute_style_difference(old_values, new_values, pseudo);
492
493
*damage |= difference.damage;
494
495
debug!(" > style difference: {:?}", difference);
496
497
// We need to cascade the children in order to ensure the correct
498
// propagation of inherited computed value flags.
499
if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() {
500
debug!(
501
" > flags changed: {:?} != {:?}",
502
old_values.flags, new_values.flags
503
);
504
return ChildCascadeRequirement::MustCascadeChildren;
505
}
506
507
match difference.change {
508
StyleChange::Unchanged => return ChildCascadeRequirement::CanSkipCascade,
509
StyleChange::Changed { reset_only } => {
510
// If inherited properties changed, the best we can do is
511
// cascade the children.
512
if !reset_only {
513
return ChildCascadeRequirement::MustCascadeChildren;
514
}
515
},
516
}
517
518
let old_display = old_values.get_box().clone_display();
519
let new_display = new_values.get_box().clone_display();
520
521
if old_display != new_display {
522
// If we used to be a display: none element, and no longer are, our
523
// children need to be restyled because they're unstyled.
524
if old_display == Display::None {
525
return ChildCascadeRequirement::MustCascadeChildren;
526
}
527
// Blockification of children may depend on our display value,
528
// so we need to actually do the recascade. We could potentially
529
// do better, but it doesn't seem worth it.
530
if old_display.is_item_container() != new_display.is_item_container() {
531
return ChildCascadeRequirement::MustCascadeChildren;
532
}
533
// We may also need to blockify and un-blockify descendants if our
534
// display goes from / to display: contents, since the "layout
535
// parent style" changes.
536
if old_display.is_contents() || new_display.is_contents() {
537
return ChildCascadeRequirement::MustCascadeChildren;
538
}
539
// Line break suppression may also be affected if the display
540
// type changes from ruby to non-ruby.
541
#[cfg(feature = "gecko")]
542
{
543
if old_display.is_ruby_type() != new_display.is_ruby_type() {
544
return ChildCascadeRequirement::MustCascadeChildren;
545
}
546
}
547
}
548
549
// Children with justify-items: auto may depend on our
550
// justify-items property value.
551
//
552
// Similarly, we could potentially do better, but this really
553
// seems not common enough to care about.
554
#[cfg(feature = "gecko")]
555
{
556
use crate::values::specified::align::AlignFlags;
557
558
let old_justify_items = old_values.get_position().clone_justify_items();
559
let new_justify_items = new_values.get_position().clone_justify_items();
560
561
let was_legacy_justify_items =
562
old_justify_items.computed.0.contains(AlignFlags::LEGACY);
563
564
let is_legacy_justify_items = new_justify_items.computed.0.contains(AlignFlags::LEGACY);
565
566
if is_legacy_justify_items != was_legacy_justify_items {
567
return ChildCascadeRequirement::MustCascadeChildren;
568
}
569
570
if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed
571
{
572
return ChildCascadeRequirement::MustCascadeChildren;
573
}
574
}
575
576
#[cfg(feature = "servo")]
577
{
578
// We may need to set or propagate the CAN_BE_FRAGMENTED bit
579
// on our children.
580
if old_values.is_multicol() != new_values.is_multicol() {
581
return ChildCascadeRequirement::MustCascadeChildren;
582
}
583
}
584
585
// We could prove that, if our children don't inherit reset
586
// properties, we can stop the cascade.
587
ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle
588
}
589
590
// FIXME(emilio, #20116): It's not clear to me that the name of this method
591
// represents anything of what it does.
592
//
593
// Also, this function gets the old style, for some reason I don't really
594
// get, but the functions called (mainly update_style_for_animation) expects
595
// the new style, wtf?
596
#[cfg(feature = "servo")]
597
fn update_animations_for_cascade(
598
&self,
599
context: &SharedStyleContext,
600
style: &mut Arc<ComputedValues>,
601
possibly_expired_animations: &mut Vec<crate::animation::PropertyAnimation>,
602
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
603
) {
604
use crate::animation::{self, Animation, AnimationUpdate};
605
use crate::dom::TNode;
606
607
// Finish any expired transitions.
608
let this_opaque = self.as_node().opaque();
609
animation::complete_expired_transitions(this_opaque, style, context);
610
611
// Merge any running animations into the current style, and cancel them.
612
let had_running_animations = context
613
.running_animations
614
.read()
615
.get(&this_opaque)
616
.is_some();
617
if !had_running_animations {
618
return;
619
}
620
621
let mut all_running_animations = context.running_animations.write();
622
for mut running_animation in all_running_animations.get_mut(&this_opaque).unwrap() {
623
if let Animation::Transition(_, _, ref frame) = *running_animation {
624
possibly_expired_animations.push(frame.property_animation.clone());
625
continue;
626
}
627
628
let update = animation::update_style_for_animation::<Self>(
629
context,
630
&mut running_animation,
631
style,
632
font_metrics,
633
);
634
635
match *running_animation {
636
Animation::Transition(..) => unreachable!(),
637
Animation::Keyframes(_, _, _, ref mut state) => match update {
638
AnimationUpdate::Regular => {},
639
AnimationUpdate::AnimationCanceled => {
640
state.expired = true;
641
},
642
},
643
}
644
}
645
}
646
}
647
648
impl<E: TElement> PrivateMatchMethods for E {}
649
650
/// The public API that elements expose for selector matching.
651
pub trait MatchMethods: TElement {
652
/// Returns the closest parent element that doesn't have a display: contents
653
/// style (and thus generates a box).
654
///
655
/// This is needed to correctly handle blockification of flex and grid
656
/// items.
657
///
658
/// Returns itself if the element has no parent. In practice this doesn't
659
/// happen because the root element is blockified per spec, but it could
660
/// happen if we decide to not blockify for roots of disconnected subtrees,
661
/// which is a kind of dubious behavior.
662
fn layout_parent(&self) -> Self {
663
let mut current = self.clone();
664
loop {
665
current = match current.traversal_parent() {
666
Some(el) => el,
667
None => return current,
668
};
669
670
let is_display_contents = current
671
.borrow_data()
672
.unwrap()
673
.styles
674
.primary()
675
.is_display_contents();
676
677
if !is_display_contents {
678
return current;
679
}
680
}
681
}
682
683
/// Updates the styles with the new ones, diffs them, and stores the restyle
684
/// damage.
685
fn finish_restyle(
686
&self,
687
context: &mut StyleContext<Self>,
688
data: &mut ElementData,
689
mut new_styles: ResolvedElementStyles,
690
important_rules_changed: bool,
691
) -> ChildCascadeRequirement {
692
use std::cmp;
693
694
self.process_animations(
695
context,
696
&mut data.styles.primary,
697
&mut new_styles.primary.style.0,
698
data.hint,
699
important_rules_changed,
700
);
701
702
// First of all, update the styles.
703
let old_styles = data.set_styles(new_styles);
704
705
let new_primary_style = data.styles.primary.as_ref().unwrap();
706
707
let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
708
if self.is_root() && !self.is_in_native_anonymous_subtree() {
709
let device = context.shared.stylist.device();
710
let new_font_size = new_primary_style.get_font().clone_font_size();
711
712
if old_styles
713
.primary
714
.as_ref()
715
.map_or(true, |s| s.get_font().clone_font_size() != new_font_size)
716
{
717
debug_assert!(self.owner_doc_matches_for_testing(device));
718
device.set_root_font_size(new_font_size.size());
719
// If the root font-size changed since last time, and something
720
// in the document did use rem units, ensure we recascade the
721
// entire tree.
722
if device.used_root_font_size() {
723
cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
724
}
725
}
726
}
727
728
if context.shared.stylist.quirks_mode() == QuirksMode::Quirks {
729
if self.is_html_document_body_element() {
730
// NOTE(emilio): We _could_ handle dynamic changes to it if it
731
// changes and before we reach our children the cascade stops,
732
// but we don't track right now whether we use the document body
733
// color, and nobody else handles that properly anyway.
734
735
let device = context.shared.stylist.device();
736
737
// Needed for the "inherit from body" quirk.
738
let text_color = new_primary_style.get_inherited_text().clone_color();
739
device.set_body_text_color(text_color);
740
}
741
}
742
743
// Don't accumulate damage if we're in the final animation traversal.
744
if context
745
.shared
746
.traversal_flags
747
.contains(TraversalFlags::FinalAnimationTraversal)
748
{
749
return ChildCascadeRequirement::MustCascadeChildren;
750
}
751
752
// Also, don't do anything if there was no style.
753
let old_primary_style = match old_styles.primary {
754
Some(s) => s,
755
None => return ChildCascadeRequirement::MustCascadeChildren,
756
};
757
758
cascade_requirement = cmp::max(
759
cascade_requirement,
760
self.accumulate_damage_for(
761
context.shared,
762
&mut data.damage,
763
&old_primary_style,
764
new_primary_style,
765
None,
766
),
767
);
768
769
if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() {
770
// This is the common case; no need to examine pseudos here.
771
return cascade_requirement;
772
}
773
774
let pseudo_styles = old_styles
775
.pseudos
776
.as_array()
777
.iter()
778
.zip(data.styles.pseudos.as_array().iter());
779
780
for (i, (old, new)) in pseudo_styles.enumerate() {
781
match (old, new) {
782
(&Some(ref old), &Some(ref new)) => {
783
self.accumulate_damage_for(
784
context.shared,
785
&mut data.damage,
786
old,
787
new,
788
Some(&PseudoElement::from_eager_index(i)),
789
);
790
},
791
(&None, &None) => {},
792
_ => {
793
// It's possible that we're switching from not having
794
// ::before/::after at all to having styles for them but not
795
// actually having a useful pseudo-element. Check for that
796
// case.
797
let pseudo = PseudoElement::from_eager_index(i);
798
let new_pseudo_should_exist =
799
new.as_ref().map_or(false, |s| pseudo.should_exist(s));
800
let old_pseudo_should_exist =
801
old.as_ref().map_or(false, |s| pseudo.should_exist(s));
802
if new_pseudo_should_exist != old_pseudo_should_exist {
803
data.damage |= RestyleDamage::reconstruct();
804
return cascade_requirement;
805
}
806
},
807
}
808
}
809
810
cascade_requirement
811
}
812
813
/// Applies selector flags to an element, deferring mutations of the parent
814
/// until after the traversal.
815
///
816
/// TODO(emilio): This is somewhat inefficient, because it doesn't take
817
/// advantage of us knowing that the traversal is sequential.
818
fn apply_selector_flags(
819
&self,
820
map: &mut SelectorFlagsMap<Self>,
821
element: &Self,
822
flags: ElementSelectorFlags,
823
) {
824
// Handle flags that apply to the element.
825
let self_flags = flags.for_self();
826
if !self_flags.is_empty() {
827
if element == self {
828
// If this is the element we're styling, we have exclusive
829
// access to the element, and thus it's fine inserting them,
830
// even from the worker.
831
unsafe {
832
element.set_selector_flags(self_flags);
833
}
834
} else {
835
// Otherwise, this element is an ancestor of the current element
836
// we're styling, and thus multiple children could write to it
837
// if we did from here.
838
//
839
// Instead, we can read them, and post them if necessary as a
840
// sequential task in order for them to be processed later.
841
if !element.has_selector_flags(self_flags) {
842
map.insert_flags(*element, self_flags);
843
}
844
}
845
}
846
847
// Handle flags that apply to the parent.
848
let parent_flags = flags.for_parent();
849
if !parent_flags.is_empty() {
850
if let Some(p) = element.parent_element() {
851
if !p.has_selector_flags(parent_flags) {
852
map.insert_flags(p, parent_flags);
853
}
854
}
855
}
856
}
857
858
/// Updates the rule nodes without re-running selector matching, using just
859
/// the rule tree.
860
///
861
/// Returns true if an !important rule was replaced.
862
fn replace_rules(
863
&self,
864
replacements: RestyleHint,
865
context: &mut StyleContext<Self>,
866
cascade_inputs: &mut ElementCascadeInputs,
867
) -> bool {
868
let mut result = false;
869
result |= self.replace_rules_internal(
870
replacements,
871
context,
872
CascadeVisitedMode::Unvisited,
873
cascade_inputs,
874
);
875
result |= self.replace_rules_internal(
876
replacements,
877
context,
878
CascadeVisitedMode::Visited,
879
cascade_inputs,
880
);
881
result
882
}
883
884
/// Given the old and new style of this element, and whether it's a
885
/// pseudo-element, compute the restyle damage used to determine which
886
/// kind of layout or painting operations we'll need.
887
fn compute_style_difference(
888
&self,
889
old_values: &ComputedValues,
890
new_values: &ComputedValues,
891
pseudo: Option<&PseudoElement>,
892
) -> StyleDifference {
893
debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
894
RestyleDamage::compute_style_difference(old_values, new_values)
895
}
896
}
897
898
impl<E: TElement> MatchMethods for E {}