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