Skip to main content

style/
matching.rs

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
10use crate::computed_value_flags::ComputedValueFlags;
11#[cfg(feature = "servo")]
12use crate::context::CascadeInputs;
13use crate::context::{ElementCascadeInputs, QuirksMode};
14use crate::context::{SharedStyleContext, StyleContext};
15use crate::data::{ElementData, ElementStyles};
16use crate::dom::TElement;
17#[cfg(feature = "servo")]
18use crate::dom::TNode;
19use crate::invalidation::element::restyle_hints::RestyleHint;
20use crate::properties::longhands::display::computed_value::T as Display;
21use crate::properties::ComputedValues;
22use crate::properties::PropertyDeclarationBlock;
23#[cfg(feature = "servo")]
24use crate::rule_tree::RuleCascadeFlags;
25use crate::rule_tree::{CascadeLevel, CascadeOrigin, StrongRuleNode};
26use crate::selector_parser::{PseudoElement, RestyleDamage};
27use crate::shared_lock::Locked;
28use crate::style_resolver::StyleResolverForElement;
29use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles};
30use crate::stylesheets::layer_rule::LayerOrder;
31use crate::stylist::RuleInclusion;
32use crate::traversal_flags::TraversalFlags;
33use crate::values::generics::animation::GenericAnimationTimeline;
34use crate::values::specified::animation::Scroller;
35use servo_arc::{Arc, ArcBorrow};
36
37/// Represents the result of comparing an element's old and new style.
38#[derive(Debug)]
39pub struct StyleDifference {
40    /// The resulting damage.
41    pub damage: RestyleDamage,
42    /// Whether any styles changed.
43    pub change: StyleChange,
44}
45
46/// Represents whether or not the style of an element has changed.
47#[derive(Clone, Copy, Debug)]
48pub enum StyleChange {
49    /// The style hasn't changed.
50    Unchanged,
51    /// The style has changed.
52    Changed {
53        /// Whether only reset properties have changed.
54        reset_only: bool,
55        /// Whether custom properties have changed.
56        custom_properties_changed: bool,
57    },
58}
59
60/// Determines which styles are being cascaded currently.
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62enum CascadeVisitedMode {
63    /// Cascade the regular, unvisited styles.
64    Unvisited,
65    /// Cascade the styles used when an element's relevant link is visited.  A
66    /// "relevant link" is the element being matched if it is a link or the
67    /// nearest ancestor link.
68    Visited,
69}
70
71trait PrivateMatchMethods: TElement {
72    fn replace_single_rule_node(
73        context: &SharedStyleContext,
74        level: CascadeLevel,
75        layer_order: LayerOrder,
76        pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
77        path: &mut StrongRuleNode,
78    ) -> bool {
79        let stylist = &context.stylist;
80        let guards = &context.guards;
81
82        let mut important_rules_changed = false;
83        let new_node = stylist.rule_tree().update_rule_at_level(
84            level,
85            layer_order,
86            pdb,
87            path,
88            guards,
89            &mut important_rules_changed,
90        );
91        if let Some(n) = new_node {
92            *path = n;
93        }
94        important_rules_changed
95    }
96
97    /// Updates the rule nodes without re-running selector matching, using just
98    /// the rule tree, for a specific visited mode.
99    ///
100    /// Returns true if an !important rule was replaced.
101    fn replace_rules_internal(
102        &self,
103        replacements: RestyleHint,
104        context: &mut StyleContext<Self>,
105        cascade_visited: CascadeVisitedMode,
106        cascade_inputs: &mut ElementCascadeInputs,
107    ) -> bool {
108        debug_assert!(
109            replacements.intersects(RestyleHint::replacements())
110                && (replacements & !RestyleHint::replacements()).is_empty()
111        );
112
113        let primary_rules = match cascade_visited {
114            CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(),
115            CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(),
116        };
117
118        let primary_rules = match primary_rules {
119            Some(r) => r,
120            None => return false,
121        };
122
123        if !context.shared.traversal_flags.for_animation_only() {
124            let mut result = false;
125            if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) {
126                let style_attribute = self.style_attribute();
127                result |= Self::replace_single_rule_node(
128                    context.shared,
129                    CascadeLevel::same_tree_author_normal(),
130                    LayerOrder::style_attribute(),
131                    style_attribute,
132                    primary_rules,
133                );
134                result |= Self::replace_single_rule_node(
135                    context.shared,
136                    CascadeLevel::same_tree_author_important(),
137                    LayerOrder::style_attribute(),
138                    style_attribute,
139                    primary_rules,
140                );
141                // FIXME(emilio): Still a hack!
142                self.unset_dirty_style_attribute();
143            }
144            return result;
145        }
146
147        // Animation restyle hints are processed prior to other restyle
148        // hints in the animation-only traversal.
149        //
150        // Non-animation restyle hints will be processed in a subsequent
151        // normal traversal.
152        if replacements.intersects(RestyleHint::for_animations()) {
153            debug_assert!(context.shared.traversal_flags.for_animation_only());
154
155            if replacements.contains(RestyleHint::RESTYLE_SMIL) {
156                Self::replace_single_rule_node(
157                    context.shared,
158                    CascadeLevel::new(CascadeOrigin::SMILOverride),
159                    LayerOrder::root(),
160                    self.smil_override(),
161                    primary_rules,
162                );
163            }
164
165            if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) {
166                Self::replace_single_rule_node(
167                    context.shared,
168                    CascadeLevel::new(CascadeOrigin::Transitions),
169                    LayerOrder::root(),
170                    self.transition_rule(&context.shared)
171                        .as_ref()
172                        .map(|a| a.borrow_arc()),
173                    primary_rules,
174                );
175            }
176
177            if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) {
178                Self::replace_single_rule_node(
179                    context.shared,
180                    CascadeLevel::new(CascadeOrigin::Animations),
181                    LayerOrder::root(),
182                    self.animation_rule(&context.shared)
183                        .as_ref()
184                        .map(|a| a.borrow_arc()),
185                    primary_rules,
186                );
187            }
188        }
189
190        false
191    }
192
193    #[inline]
194    fn requires_animation_update_for_scroll_self(
195        old: &ComputedValues,
196        new: &ComputedValues,
197    ) -> bool {
198        // Need to specifically take care of `animation-timeline: scroll(self)` - unlike other values, it can become inactive.
199        // When we switch in and out of being scrollable, we should make sure to perform the animation update.
200        // Specifying scroll in any axis makes the other axis scrollable [1], so we need to update on either axis changing.
201        // This does not apply to `scroll(root)`, since the viewport scroller is always available, or `scroll(nearest)`,
202        // which will go up to root.
203        // [1]: https://drafts.csswg.org/css-overflow/#propdef-overflow
204        let scrollable_changed = old.clone_overflow_x().is_scrollable()
205            != new.clone_overflow_x().is_scrollable()
206            || old.clone_overflow_y().is_scrollable() != new.clone_overflow_y().is_scrollable();
207        if !scrollable_changed {
208            return false;
209        }
210        new.get_ui().animation_timeline_iter().any(|timeline| {
211            let scroll_function = match timeline {
212                GenericAnimationTimeline::Scroll(ref sf) => sf,
213                _ => return false,
214            };
215            if scroll_function.scroller != Scroller::SelfElement {
216                return false;
217            }
218            true
219        })
220    }
221
222    /// If there is no transition rule in the ComputedValues, it returns None.
223    fn after_change_style(
224        &self,
225        context: &mut StyleContext<Self>,
226        primary_style: &Arc<ComputedValues>,
227    ) -> Option<Arc<ComputedValues>> {
228        // Actually `PseudoElementResolution` doesn't really matter.
229        StyleResolverForElement::new(
230            *self,
231            context,
232            RuleInclusion::All,
233            PseudoElementResolution::IfApplicable,
234        )
235        .after_change_style(primary_style)
236    }
237
238    fn needs_animations_update(
239        &self,
240        context: &mut StyleContext<Self>,
241        old_style: Option<&ComputedValues>,
242        new_style: &ComputedValues,
243        pseudo_element: Option<PseudoElement>,
244    ) -> bool {
245        let new_ui_style = new_style.get_ui();
246        let new_style_specifies_animations = new_ui_style.specifies_animations();
247
248        let has_animations = self.has_css_animations(&context.shared, pseudo_element);
249        if !new_style_specifies_animations && !has_animations {
250            return false;
251        }
252
253        let old_style = match old_style {
254            Some(old) => old,
255            // If we have no old style but have animations, we may be a
256            // pseudo-element which was re-created without style changes.
257            //
258            // This can happen when we reframe the pseudo-element without
259            // restyling it (due to content insertion on a flex container or
260            // such, for example). See bug 1564366.
261            //
262            // FIXME(emilio): The really right fix for this is keeping the
263            // pseudo-element itself around on reframes, but that's a bit
264            // harder. If we do that we can probably remove quite a lot of the
265            // EffectSet complexity though, since right now it's stored on the
266            // parent element for pseudo-elements given we need to keep it
267            // around...
268            None => {
269                return new_style_specifies_animations || new_style.is_pseudo_style();
270            },
271        };
272
273        let old_ui_style = old_style.get_ui();
274
275        let keyframes_could_have_changed = context
276            .shared
277            .traversal_flags
278            .contains(TraversalFlags::ForCSSRuleChanges);
279
280        // If the traversal is triggered due to changes in CSS rules changes, we
281        // need to try to update all CSS animations on the element if the
282        // element has or will have CSS animation style regardless of whether
283        // the animation is running or not.
284        //
285        // TODO: We should check which @keyframes were added/changed/deleted and
286        // update only animations corresponding to those @keyframes.
287        if keyframes_could_have_changed {
288            return true;
289        }
290
291        // If the animations changed, well...
292        if !old_ui_style.animations_equals(new_ui_style) {
293            return true;
294        }
295
296        let old_display = old_style.clone_display();
297        let new_display = new_style.clone_display();
298
299        // If we were display: none, we may need to trigger animations.
300        if old_display == Display::None && new_display != Display::None {
301            return new_style_specifies_animations;
302        }
303
304        // If we are becoming display: none, we may need to stop animations.
305        if old_display != Display::None && new_display == Display::None {
306            return has_animations;
307        }
308
309        // We might need to update animations if writing-mode or direction
310        // changed, and any of the animations contained logical properties.
311        //
312        // We may want to be more granular, but it's probably not worth it.
313        if new_style.writing_mode != old_style.writing_mode {
314            return has_animations;
315        }
316
317        if Self::requires_animation_update_for_scroll_self(old_style, new_style) {
318            return has_animations;
319        }
320
321        false
322    }
323
324    fn might_need_transitions_update(
325        &self,
326        context: &StyleContext<Self>,
327        old_style: Option<&ComputedValues>,
328        new_style: &ComputedValues,
329        pseudo_element: Option<PseudoElement>,
330    ) -> bool {
331        let old_style = match old_style {
332            Some(v) => v,
333            None => return false,
334        };
335
336        if !self.has_css_transitions(context.shared, pseudo_element)
337            && !new_style.get_ui().specifies_transitions()
338        {
339            return false;
340        }
341
342        if old_style.clone_display().is_none() {
343            return false;
344        }
345
346        return true;
347    }
348
349    #[cfg(feature = "gecko")]
350    fn maybe_resolve_starting_style(
351        &self,
352        context: &mut StyleContext<Self>,
353        old_values: Option<&Arc<ComputedValues>>,
354        new_styles: &ResolvedElementStyles,
355    ) -> Option<Arc<ComputedValues>> {
356        // For both cases:
357        // If there is no transitions specified we don't have to resolve starting style.
358        let new_primary = new_styles.primary_style();
359        if !new_primary.get_ui().specifies_transitions() {
360            return None;
361        }
362
363        // We resolve starting style only if we don't have before-change-style, or we change from
364        // display:none.
365        if old_values.is_some()
366            && !new_primary.is_display_property_changed_from_none(old_values.map(|s| &**s))
367        {
368            return None;
369        }
370
371        let mut resolver = StyleResolverForElement::new(
372            *self,
373            context,
374            RuleInclusion::All,
375            PseudoElementResolution::IfApplicable,
376        );
377
378        let starting_style = resolver.resolve_starting_style(new_primary)?;
379        if starting_style.style().clone_display().is_none() {
380            return None;
381        }
382
383        Some(starting_style.0)
384    }
385
386    /// Handle CSS Transitions. Returns None if we don't need to update transitions. And it returns
387    /// the before-change style per CSS Transitions spec.
388    ///
389    /// Note: The before-change style could be the computed values of all properties on the element
390    /// as of the previous style change event, or the starting style if we don't have the valid
391    /// before-change style there.
392    #[cfg(feature = "gecko")]
393    fn process_transitions(
394        &self,
395        context: &mut StyleContext<Self>,
396        old_values: Option<&Arc<ComputedValues>>,
397        new_styles: &mut ResolvedElementStyles,
398    ) -> Option<Arc<ComputedValues>> {
399        let starting_values = self.maybe_resolve_starting_style(context, old_values, new_styles);
400        let before_change_or_starting = starting_values.as_ref().or(old_values);
401        let new_values = new_styles.primary_style_mut();
402
403        if !self.might_need_transitions_update(
404            context,
405            before_change_or_starting.map(|s| &**s),
406            new_values,
407            /* pseudo_element = */ None,
408        ) {
409            return None;
410        }
411
412        let after_change_style =
413            if self.has_css_transitions(context.shared, /* pseudo_element = */ None) {
414                self.after_change_style(context, new_values)
415            } else {
416                None
417            };
418
419        // In order to avoid creating a SequentialTask for transitions which
420        // may not be updated, we check it per property to make sure Gecko
421        // side will really update transition.
422        if !self.needs_transitions_update(
423            before_change_or_starting.unwrap(),
424            after_change_style.as_ref().unwrap_or(&new_values),
425        ) {
426            return None;
427        }
428
429        if let Some(values_without_transitions) = after_change_style {
430            *new_values = values_without_transitions;
431        }
432
433        // Move the new-created starting style, or clone the old values.
434        if starting_values.is_some() {
435            starting_values
436        } else {
437            old_values.cloned()
438        }
439    }
440
441    #[cfg(feature = "gecko")]
442    fn process_animations(
443        &self,
444        context: &mut StyleContext<Self>,
445        old_styles: &mut ElementStyles,
446        new_styles: &mut ResolvedElementStyles,
447        important_rules_changed: bool,
448    ) {
449        use crate::context::UpdateAnimationsTasks;
450
451        let old_values = &old_styles.primary;
452        if context.shared.traversal_flags.for_animation_only() && old_values.is_some() {
453            return;
454        }
455
456        // Bug 868975: These steps should examine and update the visited styles
457        // in addition to the unvisited styles.
458
459        let mut tasks = UpdateAnimationsTasks::empty();
460
461        if old_values.as_deref().map_or_else(
462            || {
463                new_styles
464                    .primary_style()
465                    .get_ui()
466                    .specifies_timeline_scope()
467            },
468            |old| {
469                !old.get_ui()
470                    .timeline_scope_equals(new_styles.primary_style().get_ui())
471            },
472        ) {
473            tasks.insert(UpdateAnimationsTasks::TIMELINE_SCOPES);
474        }
475
476        if old_values.as_deref().map_or_else(
477            || {
478                new_styles
479                    .primary_style()
480                    .get_ui()
481                    .specifies_scroll_timelines()
482            },
483            |old| {
484                !old.get_ui()
485                    .scroll_timelines_equals(new_styles.primary_style().get_ui())
486            },
487        ) {
488            tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES);
489        }
490
491        if old_values.as_deref().map_or_else(
492            || {
493                new_styles
494                    .primary_style()
495                    .get_ui()
496                    .specifies_view_timelines()
497            },
498            |old| {
499                !old.get_ui()
500                    .view_timelines_equals(new_styles.primary_style().get_ui())
501            },
502        ) {
503            tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES);
504        }
505
506        if self.needs_animations_update(
507            context,
508            old_values.as_deref(),
509            new_styles.primary_style(),
510            /* pseudo_element = */ None,
511        ) {
512            tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS);
513        }
514
515        let before_change_style =
516            self.process_transitions(context, old_values.as_ref(), new_styles);
517        if before_change_style.is_some() {
518            tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS);
519        }
520
521        if self.has_animations(&context.shared) {
522            tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES);
523            if important_rules_changed {
524                tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS);
525            }
526            if new_styles
527                .primary_style()
528                .is_display_property_changed_from_none(old_values.as_deref())
529            {
530                tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE);
531            }
532        }
533
534        if !tasks.is_empty() {
535            let task = crate::context::SequentialTask::update_animations(
536                *self,
537                before_change_style,
538                tasks,
539            );
540            context.thread_local.tasks.push(task);
541        }
542    }
543
544    #[cfg(feature = "servo")]
545    fn process_animations(
546        &self,
547        context: &mut StyleContext<Self>,
548        old_styles: &mut ElementStyles,
549        new_resolved_styles: &mut ResolvedElementStyles,
550        _important_rules_changed: bool,
551    ) {
552        use crate::animation::AnimationSetKey;
553        use crate::dom::TDocument;
554
555        let style_changed = self.process_animations_for_style(
556            context,
557            &mut old_styles.primary,
558            new_resolved_styles.primary_style_mut(),
559            /* pseudo_element = */ None,
560        );
561
562        // If we have modified animation or transitions, we recascade style for this node.
563        if style_changed {
564            let primary_style = new_resolved_styles.primary_style();
565            let mut rule_node = primary_style.rules().clone();
566            let declarations = context.shared.animations.get_all_declarations(
567                &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()),
568                context.shared.current_time_for_animations,
569                self.as_node().owner_doc().shared_lock(),
570            );
571            Self::replace_single_rule_node(
572                &context.shared,
573                CascadeLevel::new(CascadeOrigin::Transitions),
574                LayerOrder::root(),
575                declarations.transitions.as_ref().map(|a| a.borrow_arc()),
576                &mut rule_node,
577            );
578            Self::replace_single_rule_node(
579                &context.shared,
580                CascadeLevel::new(CascadeOrigin::Animations),
581                LayerOrder::root(),
582                declarations.animations.as_ref().map(|a| a.borrow_arc()),
583                &mut rule_node,
584            );
585
586            if rule_node != *primary_style.rules() {
587                let inputs = CascadeInputs {
588                    rules: Some(rule_node),
589                    visited_rules: primary_style.visited_rules().cloned(),
590                    flags: primary_style.flags.for_cascade_inputs(),
591                    included_cascade_flags: RuleCascadeFlags::empty(),
592                };
593
594                new_resolved_styles.primary.style = StyleResolverForElement::new(
595                    *self,
596                    context,
597                    RuleInclusion::All,
598                    PseudoElementResolution::IfApplicable,
599                )
600                .cascade_style_and_visited_with_default_parents(inputs);
601            }
602        }
603
604        self.process_animations_for_pseudo(
605            context,
606            old_styles,
607            new_resolved_styles,
608            PseudoElement::Before,
609        );
610        self.process_animations_for_pseudo(
611            context,
612            old_styles,
613            new_resolved_styles,
614            PseudoElement::After,
615        );
616    }
617
618    #[cfg(feature = "servo")]
619    fn process_animations_for_pseudo(
620        &self,
621        context: &mut StyleContext<Self>,
622        old_styles: &ElementStyles,
623        new_resolved_styles: &mut ResolvedElementStyles,
624        pseudo_element: PseudoElement,
625    ) {
626        use crate::animation::AnimationSetKey;
627        use crate::dom::TDocument;
628
629        let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone());
630        let style = match new_resolved_styles.pseudos.get(&pseudo_element) {
631            Some(style) => Arc::clone(style),
632            None => {
633                context
634                    .shared
635                    .animations
636                    .cancel_all_animations_for_key(&key);
637                return;
638            },
639        };
640
641        let old_style = old_styles.pseudos.get(&pseudo_element).cloned();
642        self.process_animations_for_style(
643            context,
644            &old_style,
645            &style,
646            Some(pseudo_element.clone()),
647        );
648
649        let declarations = context.shared.animations.get_all_declarations(
650            &key,
651            context.shared.current_time_for_animations,
652            self.as_node().owner_doc().shared_lock(),
653        );
654        if declarations.is_empty() {
655            return;
656        }
657
658        let mut rule_node = style.rules().clone();
659        Self::replace_single_rule_node(
660            &context.shared,
661            CascadeLevel::new(CascadeOrigin::Transitions),
662            LayerOrder::root(),
663            declarations.transitions.as_ref().map(|a| a.borrow_arc()),
664            &mut rule_node,
665        );
666        Self::replace_single_rule_node(
667            &context.shared,
668            CascadeLevel::new(CascadeOrigin::Animations),
669            LayerOrder::root(),
670            declarations.animations.as_ref().map(|a| a.borrow_arc()),
671            &mut rule_node,
672        );
673        if rule_node == *style.rules() {
674            return;
675        }
676
677        let inputs = CascadeInputs {
678            rules: Some(rule_node),
679            visited_rules: style.visited_rules().cloned(),
680            flags: style.flags.for_cascade_inputs(),
681            included_cascade_flags: RuleCascadeFlags::empty(),
682        };
683
684        let new_style = StyleResolverForElement::new(
685            *self,
686            context,
687            RuleInclusion::All,
688            PseudoElementResolution::IfApplicable,
689        )
690        .cascade_style_and_visited_for_pseudo_with_default_parents(
691            inputs,
692            &pseudo_element,
693            &new_resolved_styles.primary,
694        );
695
696        new_resolved_styles
697            .pseudos
698            .set(&pseudo_element, new_style.0);
699    }
700
701    #[cfg(feature = "servo")]
702    fn process_animations_for_style(
703        &self,
704        context: &mut StyleContext<Self>,
705        old_values: &Option<Arc<ComputedValues>>,
706        new_values: &Arc<ComputedValues>,
707        pseudo_element: Option<PseudoElement>,
708    ) -> bool {
709        use crate::animation::{AnimationSetKey, AnimationState};
710
711        // We need to call this before accessing the `ElementAnimationSet` from the
712        // map because this call will do a RwLock::read().
713        let needs_animations_update = self.needs_animations_update(
714            context,
715            old_values.as_deref(),
716            new_values,
717            pseudo_element,
718        );
719
720        let might_need_transitions_update = self.might_need_transitions_update(
721            context,
722            old_values.as_deref(),
723            new_values,
724            pseudo_element,
725        );
726
727        let mut after_change_style = None;
728        if might_need_transitions_update {
729            after_change_style = self.after_change_style(context, new_values);
730        }
731
732        let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
733        let shared_context = context.shared;
734        let mut animation_set = shared_context
735            .animations
736            .sets
737            .write()
738            .remove(&key)
739            .unwrap_or_default();
740
741        // Starting animations is expensive, because we have to recalculate the style
742        // for all the keyframes. We only want to do this if we think that there's a
743        // chance that the animations really changed.
744        if needs_animations_update {
745            let mut resolver = StyleResolverForElement::new(
746                *self,
747                context,
748                RuleInclusion::All,
749                PseudoElementResolution::IfApplicable,
750            );
751
752            animation_set.update_animations_for_new_style::<Self>(
753                *self,
754                &shared_context,
755                &new_values,
756                &mut resolver,
757            );
758        }
759
760        animation_set.update_transitions_for_new_style(
761            might_need_transitions_update,
762            &shared_context,
763            old_values.as_ref(),
764            after_change_style.as_ref().unwrap_or(new_values),
765        );
766
767        // This should change the computed values in the style, so we don't need
768        // to mark this set as dirty.
769        animation_set
770            .transitions
771            .retain(|transition| transition.state != AnimationState::Finished);
772
773        animation_set
774            .animations
775            .retain(|animation| animation.state != AnimationState::Finished);
776
777        // If the ElementAnimationSet is empty, and don't store it in order to
778        // save memory and to avoid extra processing later.
779        let changed_animations = animation_set.dirty;
780        if !animation_set.is_empty() {
781            animation_set.dirty = false;
782            shared_context
783                .animations
784                .sets
785                .write()
786                .insert(key, animation_set);
787        }
788
789        changed_animations
790    }
791
792    /// Computes and applies non-redundant damage.
793    fn accumulate_damage_for(
794        &self,
795        shared_context: &SharedStyleContext,
796        damage: &mut RestyleDamage,
797        old_values: &ComputedValues,
798        new_values: &ComputedValues,
799        pseudo: Option<&PseudoElement>,
800    ) -> RestyleHint {
801        debug!("accumulate_damage_for: {:?}", self);
802        debug_assert!(!shared_context
803            .traversal_flags
804            .contains(TraversalFlags::FinalAnimationTraversal));
805
806        let difference = self.compute_style_difference(old_values, new_values, pseudo);
807
808        *damage |= difference.damage;
809
810        debug!(" > style difference: {:?}", difference);
811
812        let mut children_hint = RestyleHint::empty();
813        if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() {
814            // Even if the styles are otherwise equal, we need to cascade the children in order to
815            // ensure the correct propagation of inherited computed value flags.
816            debug!(
817                " > flags changed: {:?} != {:?}",
818                old_values.flags, new_values.flags
819            );
820            children_hint |= RestyleHint::RECASCADE_SELF;
821        } else if old_values.effective_zoom != new_values.effective_zoom {
822            // Similarly, even if styles are equal, we need to propagate zoom changes.
823            debug!(
824                " > zoom changed: {:?} != {:?}",
825                old_values.effective_zoom, new_values.effective_zoom
826            );
827            children_hint |= RestyleHint::RECASCADE_SELF;
828        }
829
830        let StyleChange::Changed {
831            reset_only,
832            custom_properties_changed,
833        } = difference.change
834        else {
835            return children_hint;
836        };
837
838        let new_container_name = new_values.clone_container_name();
839        if new_container_name != old_values.clone_container_name() {
840            // If we're becoming or stopped to become a named container, we need to potentially
841            // restyle children.
842            children_hint |= RestyleHint::RESTYLE_IF_AFFECTED_BY_NAMED_STYLE_CONTAINER;
843        } else if custom_properties_changed {
844            // Custom property changes affect style queries. How specifically depends on whether
845            // we're a named container (more expensive, need to check the subtree) or not.
846            children_hint |= if !new_container_name.is_none() {
847                RestyleHint::RESTYLE_IF_AFFECTED_BY_NAMED_STYLE_CONTAINER
848            } else {
849                RestyleHint::RESTYLE_IF_AFFECTED_BY_STYLE_QUERIES
850            };
851        }
852
853        if reset_only {
854            // If only reset properties changed, we _might_ need to unconditionally restyle, but
855            // most likely we can get away with stopping the cascade at the next level, if our
856            // children don't inherit reset properties.
857            children_hint |=
858                if need_to_unconditionally_recascade_for_reset_change(old_values, new_values) {
859                    RestyleHint::RECASCADE_SELF
860                } else {
861                    RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE
862                };
863        } else {
864            // If inherited properties changed, we need to cascade our children.
865            children_hint |= RestyleHint::RECASCADE_SELF;
866        }
867
868        children_hint
869    }
870}
871
872/// Whether we need to recascade children for a change in non-inherited properties.
873fn need_to_unconditionally_recascade_for_reset_change(
874    old_values: &ComputedValues,
875    new_values: &ComputedValues,
876) -> bool {
877    let old_display = old_values.clone_display();
878    let new_display = new_values.clone_display();
879
880    if old_display != new_display {
881        // If we used to be a display: none element, and no longer are, our
882        // children need to be restyled because they're unstyled.
883        if old_display == Display::None {
884            return true;
885        }
886        // Blockification of children may depend on our display value, so we need to actually do the
887        // recascade. We could potentially do better, but it doesn't seem worth it.
888        if old_display.is_item_container() != new_display.is_item_container() {
889            return true;
890        }
891        // We may also need to blockify and un-blockify descendants if our display goes from / to
892        // display: contents, since the "layout parent style" changes.
893        if old_display.is_contents() || new_display.is_contents() {
894            return true;
895        }
896        // Line break suppression may also be affected if the display
897        // type changes from ruby to non-ruby.
898        #[cfg(feature = "gecko")]
899        if old_display.is_ruby_type() != new_display.is_ruby_type() {
900            return true;
901        }
902    }
903
904    // Children with justify-items: auto may depend on our
905    // justify-items property value.
906    //
907    // Similarly, we could potentially do better, but this really
908    // seems not common enough to care about.
909    #[cfg(feature = "gecko")]
910    {
911        use crate::values::specified::align::AlignFlags;
912
913        let old_justify_items = old_values.get_position().clone_justify_items();
914        let new_justify_items = new_values.get_position().clone_justify_items();
915
916        let was_legacy_justify_items = old_justify_items.computed.contains(AlignFlags::LEGACY);
917
918        let is_legacy_justify_items = new_justify_items.computed.contains(AlignFlags::LEGACY);
919
920        if is_legacy_justify_items != was_legacy_justify_items {
921            return true;
922        }
923
924        if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed {
925            return true;
926        }
927    }
928
929    false
930}
931
932impl<E: TElement> PrivateMatchMethods for E {}
933
934/// The public API that elements expose for selector matching.
935pub trait MatchMethods: TElement {
936    /// Returns the closest parent element that doesn't have a display: contents
937    /// style (and thus generates a box).
938    ///
939    /// This is needed to correctly handle blockification of flex and grid
940    /// items.
941    ///
942    /// Returns itself if the element has no parent. In practice this doesn't
943    /// happen because the root element is blockified per spec, but it could
944    /// happen if we decide to not blockify for roots of disconnected subtrees,
945    /// which is a kind of dubious behavior.
946    fn layout_parent(&self) -> Self {
947        let mut current = self.clone();
948        loop {
949            current = match current.traversal_parent() {
950                Some(el) => el,
951                None => return current,
952            };
953
954            let is_display_contents = current
955                .borrow_data()
956                .unwrap()
957                .styles
958                .primary()
959                .is_display_contents();
960
961            if !is_display_contents {
962                return current;
963            }
964        }
965    }
966
967    /// Updates the styles with the new ones, diffs them, and stores the restyle
968    /// damage.
969    fn finish_restyle(
970        &self,
971        context: &mut StyleContext<Self>,
972        data: &mut ElementData,
973        mut new_styles: ResolvedElementStyles,
974        important_rules_changed: bool,
975    ) -> RestyleHint {
976        self.process_animations(
977            context,
978            &mut data.styles,
979            &mut new_styles,
980            important_rules_changed,
981        );
982
983        // First of all, update the styles.
984        let old_styles = data.set_styles(new_styles);
985
986        let new_primary_style = data.styles.primary.as_ref().unwrap();
987
988        let mut child_restyle_hint = RestyleHint::empty();
989        let is_root = new_primary_style
990            .flags
991            .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE);
992
993        let old_style = old_styles.primary.as_ref();
994        let old_font_size = old_style.map(|s| s.get_font().clone_font_size());
995        let device = context.shared.stylist.device();
996        let new_font_size = new_primary_style.get_font().clone_font_size();
997        // For line-height, we want the fully resolved value, as `normal` also depends on other
998        // font properties.
999        let new_line_height = device
1000            .calc_line_height(
1001                &new_primary_style.get_font(),
1002                new_primary_style.writing_mode,
1003                None,
1004            )
1005            .0;
1006        let old_line_height = old_style.map(|s| {
1007            device
1008                .calc_line_height(&s.get_font(), s.writing_mode, None)
1009                .0
1010        });
1011
1012        let font_size_changed = old_font_size != Some(new_font_size);
1013        let line_height_changed = old_line_height != Some(new_line_height);
1014
1015        // Update root font-relative units. If any of these unit values changed
1016        // since last time, ensure that we recascade the entire tree.
1017        if is_root {
1018            debug_assert!(self.owner_doc_matches_for_testing(device));
1019            device.set_root_style(new_primary_style);
1020
1021            // Update root font size for rem units
1022            if font_size_changed {
1023                let size = new_font_size.computed_size();
1024                device.set_root_font_size(new_primary_style.effective_zoom.unzoom(size.px()));
1025                if device.used_root_font_size() {
1026                    child_restyle_hint |= RestyleHint::recascade_subtree();
1027                }
1028            }
1029
1030            // Update root line height for rlh units
1031            if line_height_changed {
1032                device.set_root_line_height(
1033                    new_primary_style
1034                        .effective_zoom
1035                        .unzoom(new_line_height.px()),
1036                );
1037                if device.used_root_line_height() {
1038                    child_restyle_hint |= RestyleHint::recascade_subtree();
1039                }
1040            }
1041
1042            // Update root font metrics for rcap, rch, rex, ric units. Since querying
1043            // font metrics can be an expensive call, they are only updated if these
1044            // units are used in the document.
1045            if device.used_root_font_metrics() && device.update_root_font_metrics() {
1046                child_restyle_hint |= RestyleHint::recascade_subtree()
1047                    | RestyleHint::RESTYLE_IF_AFFECTED_BY_ANCESTOR_FONT_METRICS;
1048            }
1049        }
1050
1051        if font_size_changed || line_height_changed {
1052            child_restyle_hint |= RestyleHint::RESTYLE_IF_AFFECTED_BY_ANCESTOR_FONT_METRICS;
1053        }
1054
1055        if context.shared.stylist.quirks_mode() == QuirksMode::Quirks {
1056            if self.is_html_document_body_element() {
1057                // NOTE(emilio): We _could_ handle dynamic changes to it if it
1058                // changes and before we reach our children the cascade stops,
1059                // but we don't track right now whether we use the document body
1060                // color, and nobody else handles that properly anyway.
1061                let device = context.shared.stylist.device();
1062
1063                // Needed for the "inherit from body" quirk.
1064                let text_color = new_primary_style.get_inherited_text().clone_color();
1065                device.set_body_text_color(text_color);
1066            }
1067        }
1068
1069        // Don't accumulate damage if we're in the final animation traversal.
1070        if context
1071            .shared
1072            .traversal_flags
1073            .contains(TraversalFlags::FinalAnimationTraversal)
1074        {
1075            return RestyleHint::RECASCADE_SELF;
1076        }
1077
1078        // Also, don't do anything if there was no style.
1079        let old_primary_style = match old_styles.primary {
1080            Some(s) => s,
1081            None => return RestyleHint::RECASCADE_SELF,
1082        };
1083
1084        let old_container_type = old_primary_style.clone_container_type();
1085        let new_container_type = new_primary_style.clone_container_type();
1086        if old_container_type != new_container_type && !new_container_type.is_size_container_type()
1087        {
1088            // Stopped being a size container. Re-evaluate container queries and units on all our descendants.
1089            // Changes into and between different size containment is handled in `UpdateContainerQueryStyles`.
1090            child_restyle_hint |= RestyleHint::restyle_subtree();
1091        } else if old_container_type.is_size_container_type()
1092            && !old_primary_style.is_display_contents()
1093            && new_primary_style.is_display_contents()
1094        {
1095            // Also re-evaluate when a container gets 'display: contents', since size queries will now evaluate to unknown.
1096            // Other displays like 'inline' will keep generating a box, so they are handled in `UpdateContainerQueryStyles`.
1097            child_restyle_hint |= RestyleHint::restyle_subtree();
1098        }
1099
1100        child_restyle_hint |= self.accumulate_damage_for(
1101            context.shared,
1102            &mut data.damage,
1103            &old_primary_style,
1104            new_primary_style,
1105            None,
1106        );
1107
1108        if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() {
1109            // This is the common case; no need to examine pseudos here.
1110            return child_restyle_hint;
1111        }
1112
1113        let pseudo_styles = old_styles
1114            .pseudos
1115            .as_array()
1116            .iter()
1117            .zip(data.styles.pseudos.as_array().iter());
1118
1119        for (i, (old, new)) in pseudo_styles.enumerate() {
1120            match (old, new) {
1121                (&Some(ref old), &Some(ref new)) => {
1122                    self.accumulate_damage_for(
1123                        context.shared,
1124                        &mut data.damage,
1125                        old,
1126                        new,
1127                        Some(&PseudoElement::from_eager_index(i)),
1128                    );
1129                },
1130                (&None, &None) => {},
1131                _ => {
1132                    // It's possible that we're switching from not having
1133                    // ::before/::after at all to having styles for them but not
1134                    // actually having a useful pseudo-element.  Check for that
1135                    // case.
1136                    let pseudo = PseudoElement::from_eager_index(i);
1137                    let new_pseudo_should_exist =
1138                        new.as_ref().map_or(false, |s| pseudo.should_exist(s));
1139                    let old_pseudo_should_exist =
1140                        old.as_ref().map_or(false, |s| pseudo.should_exist(s));
1141                    if new_pseudo_should_exist != old_pseudo_should_exist {
1142                        data.damage |= RestyleDamage::reconstruct();
1143                        return child_restyle_hint;
1144                    }
1145                },
1146            }
1147        }
1148
1149        child_restyle_hint
1150    }
1151
1152    /// Updates the rule nodes without re-running selector matching, using just
1153    /// the rule tree.
1154    ///
1155    /// Returns true if an !important rule was replaced.
1156    fn replace_rules(
1157        &self,
1158        replacements: RestyleHint,
1159        context: &mut StyleContext<Self>,
1160        cascade_inputs: &mut ElementCascadeInputs,
1161    ) -> bool {
1162        let mut result = false;
1163        result |= self.replace_rules_internal(
1164            replacements,
1165            context,
1166            CascadeVisitedMode::Unvisited,
1167            cascade_inputs,
1168        );
1169        result |= self.replace_rules_internal(
1170            replacements,
1171            context,
1172            CascadeVisitedMode::Visited,
1173            cascade_inputs,
1174        );
1175        result
1176    }
1177
1178    /// Given the old and new style of this element, and whether it's a
1179    /// pseudo-element, compute the restyle damage used to determine which
1180    /// kind of layout or painting operations we'll need.
1181    fn compute_style_difference(
1182        &self,
1183        old_values: &ComputedValues,
1184        new_values: &ComputedValues,
1185        pseudo: Option<&PseudoElement>,
1186    ) -> StyleDifference {
1187        debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
1188        #[cfg(feature = "gecko")]
1189        {
1190            RestyleDamage::compute_style_difference(old_values, new_values)
1191        }
1192        #[cfg(feature = "servo")]
1193        {
1194            RestyleDamage::compute_style_difference::<Self>(old_values, new_values)
1195        }
1196    }
1197}
1198
1199impl<E: TElement> MatchMethods for E {}