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