Skip to main content

style/invalidation/element/
state_and_attributes.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//! An invalidation processor for style changes due to state and attribute
6//! changes.
7
8use crate::context::SharedStyleContext;
9use crate::data::ElementData;
10use crate::dom::{TElement, TNode};
11use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper, Snapshots};
12use crate::invalidation::element::invalidation_map::*;
13use crate::invalidation::element::invalidator::{
14    DescendantInvalidationLists, InvalidationVector, SiblingTraversalMap,
15    any_next_has_scope_in_negation, note_scope_dependency_force_at_subject,
16};
17use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
18use crate::invalidation::element::restyle_hints::RestyleHint;
19use crate::selector_map::SelectorMap;
20use crate::selector_parser::Snapshot;
21use crate::stylesheets::origin::OriginSet;
22use crate::values::AtomIdent;
23use crate::{Atom, WeakAtom};
24use dom::ElementState;
25use selectors::OpaqueElement;
26use selectors::attr::CaseSensitivity;
27use selectors::kleene_value::KleeneValue;
28use selectors::matching::{
29    MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, SelectorCaches,
30    VisitedHandlingMode, matches_selector_kleene,
31};
32use smallvec::SmallVec;
33
34/// The collector implementation.
35struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
36where
37    E: TElement,
38{
39    element: E,
40    wrapper: ElementWrapper<'a, 'b, E>,
41    snapshot: &'a Snapshot,
42    matching_context: &'a mut MatchingContext<'b, E::Impl>,
43    lookup_element: E,
44    removed_id: Option<&'a WeakAtom>,
45    added_id: Option<&'a WeakAtom>,
46    classes_removed: &'a SmallVec<[Atom; 8]>,
47    classes_added: &'a SmallVec<[Atom; 8]>,
48    custom_states_removed: &'a SmallVec<[AtomIdent; 8]>,
49    custom_states_added: &'a SmallVec<[AtomIdent; 8]>,
50    state_changes: ElementState,
51    descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
52    sibling_invalidations: &'a mut InvalidationVector<'selectors>,
53    invalidates_self: bool,
54}
55
56/// An invalidation processor for style changes due to state and attribute
57/// changes.
58pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> {
59    shared_context: &'a SharedStyleContext<'b>,
60    snapshots: Snapshots<'a>,
61    element: E,
62    data: &'a mut ElementData,
63    matching_context: MatchingContext<'a, E::Impl>,
64    traversal_map: SiblingTraversalMap<E>,
65}
66
67impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> {
68    /// Creates a new StateAndAttrInvalidationProcessor.
69    pub fn new(
70        shared_context: &'a SharedStyleContext<'b>,
71        element: E,
72        data: &'a mut ElementData,
73        selector_caches: &'a mut SelectorCaches,
74    ) -> Self {
75        let matching_context = MatchingContext::new_for_visited(
76            MatchingMode::Normal,
77            None,
78            selector_caches,
79            VisitedHandlingMode::AllLinksVisitedAndUnvisited,
80            shared_context.quirks_mode(),
81            NeedsSelectorFlags::No,
82            MatchingForInvalidation::Yes,
83        );
84
85        Self {
86            shared_context,
87            snapshots: Snapshots::new(shared_context.snapshot_map),
88            element,
89            data,
90            matching_context,
91            traversal_map: SiblingTraversalMap::default(),
92        }
93    }
94}
95
96/// Checks a dependency against a given element and wrapper, to see if something
97/// changed.
98pub fn check_dependency<E, W>(
99    dependency: &Dependency,
100    element: E,
101    wrapper: W,
102    context: &mut MatchingContext<'_, E::Impl>,
103    scope: Option<OpaqueElement>,
104) -> bool
105where
106    E: TElement,
107    W: selectors::Element<Impl = E::Impl>,
108{
109    context.for_invalidation_comparison(|context| {
110        context.nest_for_scope_condition(scope, |context| {
111            let matches_now = matches_selector_kleene(
112                &dependency.selector,
113                dependency.selector_offset,
114                None,
115                element,
116                context,
117            );
118
119            // If the previous dependency was a scope dependency (i.e. by `scope` is set),
120            // possible change in scope element is encapsulated in `:scope`, whose
121            // matching value will not change. We instead check that the change in scope
122            // element can propagate (i.e. This selector matches).
123            if scope.is_some() && matches_now != KleeneValue::False {
124                return true;
125            }
126
127            let matched_then = matches_selector_kleene(
128                &dependency.selector,
129                dependency.selector_offset,
130                None,
131                wrapper,
132                context,
133            );
134
135            matched_then != matches_now || matches_now == KleeneValue::Unknown
136        })
137    })
138}
139
140/// Whether we should process the descendants of a given element for style
141/// invalidation.
142pub fn should_process_descendants(data: &ElementData) -> bool {
143    !data.styles.is_display_none() && !data.hint.contains(RestyleHint::RESTYLE_DESCENDANTS)
144}
145
146/// Propagates the bits after invalidating a descendant child.
147pub fn propagate_dirty_bit_up_to<E>(ancestor: E, child: E)
148where
149    E: TElement,
150{
151    // The child may not be a flattened tree child of the current element,
152    // but may be arbitrarily deep.
153    //
154    // Since we keep the traversal flags in terms of the flattened tree,
155    // we need to propagate it as appropriate.
156    let mut current = child.traversal_parent();
157    while let Some(parent) = current.take() {
158        unsafe { parent.set_dirty_descendants() };
159        current = parent.traversal_parent();
160
161        if parent == ancestor {
162            return;
163        }
164    }
165    debug_assert!(
166        false,
167        "Should've found {:?} as an ancestor of {:?}",
168        ancestor, child
169    );
170}
171
172/// Propagates the bits after invalidating a descendant child, if needed.
173pub fn invalidated_descendants<E>(element: E, child: E)
174where
175    E: TElement,
176{
177    if !child.has_data() {
178        return;
179    }
180    propagate_dirty_bit_up_to(element, child)
181}
182
183/// Sets the appropriate restyle hint after invalidating the style of a given
184/// element.
185pub fn invalidated_self<E>(element: E) -> bool
186where
187    E: TElement,
188{
189    let mut data = match element.mutate_data() {
190        Some(data) => data,
191        None => return false,
192    };
193    data.hint.insert(RestyleHint::RESTYLE_SELF);
194    true
195}
196
197/// Sets the appropriate hint after invalidating the style of a sibling.
198pub fn invalidated_sibling<E>(element: E, of: E)
199where
200    E: TElement,
201{
202    debug_assert_eq!(
203        element.as_node().parent_node(),
204        of.as_node().parent_node(),
205        "Should be siblings"
206    );
207    if !invalidated_self(element) {
208        return;
209    }
210    if element.traversal_parent() != of.traversal_parent() {
211        let parent = element.as_node().parent_element_or_host();
212        debug_assert!(
213            parent.is_some(),
214            "How can we have siblings without parent nodes?"
215        );
216        if let Some(e) = parent {
217            propagate_dirty_bit_up_to(e, element)
218        }
219    }
220}
221
222impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'a, 'a, E>
223    for StateAndAttrInvalidationProcessor<'a, 'b, E>
224where
225    E: TElement,
226{
227    /// We need to invalidate style on pseudo-elements, in order to process
228    /// changes that could otherwise end up in ::before or ::after content being
229    /// generated, and invalidate lazy pseudo caches.
230    fn invalidates_on_pseudo_element(&self) -> bool {
231        true
232    }
233
234    fn check_outer_dependency(
235        &mut self,
236        dependency: &Dependency,
237        element: E,
238        scope: Option<OpaqueElement>,
239    ) -> bool {
240        // We cannot assert about `element` having a snapshot here (in fact it most likely won't),
241        // because it may be an arbitrary descendant or later-sibling of the element we started
242        // invalidating with.
243        let wrapper = ElementWrapper::new(element, &self.snapshots);
244        check_dependency(
245            dependency,
246            element,
247            wrapper,
248            &mut self.matching_context,
249            scope,
250        )
251    }
252
253    fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
254        &mut self.matching_context
255    }
256
257    fn sibling_traversal_map(&self) -> &SiblingTraversalMap<E> {
258        &self.traversal_map
259    }
260
261    fn collect_invalidations(
262        &mut self,
263        element: E,
264        _self_invalidations: &mut InvalidationVector<'a>,
265        descendant_invalidations: &mut DescendantInvalidationLists<'a>,
266        sibling_invalidations: &mut InvalidationVector<'a>,
267    ) -> bool {
268        debug_assert_eq!(element, self.element);
269        debug_assert!(element.has_snapshot(), "Why bothering?");
270
271        let wrapper = ElementWrapper::new(element, &self.snapshots);
272
273        let state_changes = wrapper.state_changes();
274        let Some(snapshot) = wrapper.snapshot() else {
275            return false;
276        };
277
278        if !snapshot.has_attrs() && !snapshot.has_custom_states() && state_changes.is_empty() {
279            return false;
280        }
281
282        let mut classes_removed = SmallVec::<[Atom; 8]>::new();
283        let mut classes_added = SmallVec::<[Atom; 8]>::new();
284        if snapshot.class_changed() {
285            // TODO(emilio): Do this more efficiently!
286            snapshot.each_class(|c| {
287                if !element.has_class(c, CaseSensitivity::CaseSensitive) {
288                    classes_removed.push(c.0.clone())
289                }
290            });
291
292            element.each_class(|c| {
293                if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
294                    classes_added.push(c.0.clone())
295                }
296            })
297        }
298
299        let mut custom_states_removed = SmallVec::<[AtomIdent; 8]>::new();
300        let mut custom_states_added = SmallVec::<[AtomIdent; 8]>::new();
301        if snapshot.has_custom_states() {
302            snapshot.each_custom_state(|s| {
303                if !element.has_custom_state(s) {
304                    custom_states_removed.push(s.clone())
305                }
306            });
307            element.each_custom_state(|s| {
308                if !snapshot.has_custom_state(s) {
309                    custom_states_added.push(s.clone())
310                }
311            })
312        }
313
314        let mut id_removed = None;
315        let mut id_added = None;
316        if snapshot.id_changed() {
317            let old_id = snapshot.id_attr();
318            let current_id = element.id();
319
320            if old_id != current_id {
321                id_removed = old_id;
322                id_added = current_id;
323            }
324        }
325
326        if log_enabled!(::log::Level::Debug) {
327            debug!("Collecting changes for: {:?}", element);
328            if !state_changes.is_empty() {
329                debug!(" > state: {:?}", state_changes);
330            }
331            if snapshot.id_changed() {
332                debug!(" > id changed: +{:?} -{:?}", id_added, id_removed);
333            }
334            if snapshot.class_changed() {
335                debug!(
336                    " > class changed: +{:?} -{:?}",
337                    classes_added, classes_removed
338                );
339            }
340            let mut attributes_changed = false;
341            snapshot.each_attr_changed(|_| {
342                attributes_changed = true;
343            });
344            if attributes_changed {
345                debug!(
346                    " > attributes changed, old: {}",
347                    snapshot.debug_list_attributes()
348                )
349            }
350        }
351
352        let lookup_element = if element.implemented_pseudo_element().is_some() {
353            element.pseudo_element_originating_element().unwrap()
354        } else {
355            element
356        };
357
358        let mut shadow_rule_datas = SmallVec::<[_; 3]>::new();
359        let matches_document_author_rules =
360            element.each_applicable_non_document_style_rule_data(|data, host| {
361                shadow_rule_datas.push((data, host.opaque()))
362            });
363
364        let invalidated_self = {
365            let mut collector = Collector {
366                wrapper,
367                lookup_element,
368                state_changes,
369                element,
370                snapshot,
371                matching_context: &mut self.matching_context,
372                removed_id: id_removed,
373                added_id: id_added,
374                classes_removed: &classes_removed,
375                classes_added: &classes_added,
376                custom_states_removed: &custom_states_removed,
377                custom_states_added: &custom_states_added,
378                descendant_invalidations,
379                sibling_invalidations,
380                invalidates_self: false,
381            };
382
383            let document_origins = if !matches_document_author_rules {
384                OriginSet::ORIGIN_USER_AGENT | OriginSet::ORIGIN_USER
385            } else {
386                OriginSet::all()
387            };
388
389            for (cascade_data, origin) in self.shared_context.stylist.iter_origins() {
390                if document_origins.contains(origin.into()) {
391                    collector
392                        .collect_dependencies_in_invalidation_map(cascade_data.invalidation_map());
393                }
394            }
395
396            for &(data, ref host) in &shadow_rule_datas {
397                collector.matching_context.current_host = Some(*host);
398                collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
399            }
400
401            collector.invalidates_self
402        };
403
404        // If we generated a ton of descendant invalidations, it's probably not
405        // worth to go ahead and try to process them.
406        //
407        // Just restyle the descendants directly.
408        //
409        // This number is completely made-up, but the page that made us add this
410        // code generated 1960+ invalidations (bug 1420741).
411        //
412        // We don't look at slotted_descendants because those don't propagate
413        // down more than one level anyway.
414        if descendant_invalidations.dom_descendants.len() > 150 {
415            self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
416        }
417
418        if invalidated_self {
419            self.data.hint.insert(RestyleHint::RESTYLE_SELF);
420        }
421
422        invalidated_self
423    }
424
425    fn should_process_descendants(&mut self, element: E) -> bool {
426        if element == self.element {
427            return should_process_descendants(self.data);
428        }
429
430        match element.borrow_data() {
431            Some(d) => should_process_descendants(&d),
432            None => false,
433        }
434    }
435
436    fn recursion_limit_exceeded(&mut self, element: E) {
437        if element == self.element {
438            self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
439            return;
440        }
441
442        if let Some(mut data) = element.mutate_data() {
443            data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
444        }
445    }
446
447    fn invalidated_descendants(&mut self, element: E, child: E) {
448        invalidated_descendants(element, child)
449    }
450
451    fn invalidated_self(&mut self, element: E) {
452        debug_assert_ne!(element, self.element);
453        invalidated_self(element);
454    }
455
456    fn invalidated_sibling(&mut self, element: E, of: E) {
457        debug_assert_ne!(element, self.element);
458        invalidated_sibling(element, of);
459    }
460
461    fn invalidated_highlight_pseudo(&mut self, element: E) {
462        element.note_highlight_pseudo_style_invalidated();
463    }
464}
465
466impl<'a, 'b, 'selectors, E> Collector<'a, 'b, 'selectors, E>
467where
468    E: TElement,
469    'selectors: 'a,
470{
471    fn collect_dependencies_in_invalidation_map(&mut self, map: &'selectors InvalidationMap) {
472        let quirks_mode = self.matching_context.quirks_mode();
473        let removed_id = self.removed_id;
474        if let Some(id) = removed_id
475            && let Some(deps) = map.id_to_selector.get(id, quirks_mode)
476        {
477            for dep in deps {
478                self.scan_dependency(dep, false);
479            }
480        }
481
482        let added_id = self.added_id;
483        if let Some(id) = added_id
484            && let Some(deps) = map.id_to_selector.get(id, quirks_mode)
485        {
486            for dep in deps {
487                self.scan_dependency(dep, false);
488            }
489        }
490
491        for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
492            if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
493                for dep in deps {
494                    self.scan_dependency(dep, false);
495                }
496            }
497        }
498
499        for state in self
500            .custom_states_added
501            .iter()
502            .chain(self.custom_states_removed.iter())
503        {
504            if let Some(deps) = map.custom_state_affecting_selectors.get(state) {
505                for dep in deps {
506                    self.scan_dependency(dep, false);
507                }
508            }
509        }
510
511        self.snapshot.each_attr_changed(|attribute| {
512            if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) {
513                for dep in deps {
514                    self.scan_dependency(dep, false);
515                }
516            }
517        });
518
519        self.collect_state_dependencies(&map.state_affecting_selectors)
520    }
521
522    fn collect_state_dependencies(&mut self, map: &'selectors SelectorMap<StateDependency>) {
523        if self.state_changes.is_empty() {
524            return;
525        }
526        map.lookup_with_additional(
527            self.lookup_element,
528            self.matching_context.quirks_mode(),
529            self.removed_id,
530            self.classes_removed,
531            self.state_changes,
532            |dependency| {
533                if !dependency.state.intersects(self.state_changes) {
534                    return true;
535                }
536                self.scan_dependency(&dependency.dep, false);
537                true
538            },
539        );
540    }
541
542    /// Check whether a dependency should be taken into account.
543    #[inline]
544    fn check_dependency(&mut self, dependency: &Dependency, set_scope: bool) -> bool {
545        check_dependency(
546            dependency,
547            self.element,
548            self.wrapper,
549            self.matching_context,
550            set_scope.then(|| self.element.opaque()),
551        )
552    }
553
554    fn scan_dependency(&mut self, dependency: &'selectors Dependency, set_scope: bool) {
555        debug_assert!(
556            matches!(
557                dependency.invalidation_kind(),
558                DependencyInvalidationKind::Normal(_) | DependencyInvalidationKind::Scope(_)
559            ),
560            "Found unexpected dependency invalidation kind"
561        );
562        debug!(
563            "TreeStyleInvalidator::scan_dependency({:?}, {:?})",
564            self.element, dependency
565        );
566
567        if !self.dependency_may_be_relevant(dependency) {
568            return;
569        }
570
571        if self.check_dependency(dependency, set_scope) {
572            self.note_dependency(dependency, set_scope)
573        }
574    }
575
576    fn note_dependency(&mut self, dependency: &'selectors Dependency, set_scope: bool) {
577        debug_assert!(self.dependency_may_be_relevant(dependency));
578        let invalidation_kind = dependency.invalidation_kind();
579        if matches!(
580            invalidation_kind,
581            DependencyInvalidationKind::Normal(NormalDependencyInvalidationKind::Element)
582        ) {
583            if let Some(ref next) = dependency.next {
584                // We know something changed in the inner selector, go outwards
585                // now.
586                self.scan_dependency(&next.as_ref().slice()[0], set_scope);
587            } else {
588                self.invalidates_self = true;
589            }
590            return;
591        }
592
593        if let DependencyInvalidationKind::Scope(scope_kind) = invalidation_kind {
594            if scope_kind == ScopeDependencyInvalidationKind::ImplicitScope
595                && let Some(ref next) = dependency.next
596            {
597                // When we reach an implicit scope dependency, we know there's an
598                // element matching that implicit scope somewhere in the descendant.
599                // We need to go find it so that we can continue the invalidation from
600                // its next dependencies.
601                for dep in next.as_ref().slice() {
602                    let invalidation = Invalidation::new_always_effective_for_next_descendant(
603                        dep,
604                        self.matching_context.current_host,
605                        self.matching_context.scope_element,
606                    );
607
608                    self.descendant_invalidations
609                        .dom_descendants
610                        .push(invalidation);
611                }
612                return;
613            }
614
615            if dependency.selector.is_rightmost(dependency.selector_offset) {
616                let force_add = any_next_has_scope_in_negation(dependency);
617                if scope_kind == ScopeDependencyInvalidationKind::ScopeEnd || force_add {
618                    let invalidations = note_scope_dependency_force_at_subject(
619                        dependency,
620                        self.matching_context.current_host,
621                        self.matching_context.scope_element,
622                        force_add,
623                    );
624                    for invalidation in invalidations {
625                        self.descendant_invalidations
626                            .dom_descendants
627                            .push(invalidation);
628                    }
629                    self.invalidates_self = true;
630                } else if let Some(ref next) = dependency.next {
631                    for dep in next.as_ref().slice() {
632                        self.scan_dependency(dep, true);
633                    }
634                }
635                return;
636            }
637        }
638
639        debug_assert_ne!(dependency.selector_offset, 0);
640        debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
641
642        let invalidation = Invalidation::new(
643            dependency,
644            self.matching_context.current_host,
645            self.matching_context.scope_element,
646        );
647
648        let invalidated_self = push_invalidation(
649            invalidation,
650            invalidation_kind,
651            self.descendant_invalidations,
652            self.sibling_invalidations,
653        );
654
655        // For highlight pseudos (::selection, ::highlight, ::target-text), we need
656        // to trigger a repaint since their styles are resolved lazily during
657        // painting rather than during the restyle traversal.
658        if invalidated_self
659            && dependency
660                .selector
661                .pseudo_element()
662                .is_some_and(|p| p.is_lazy_painted_highlight_pseudo())
663        {
664            self.element.note_highlight_pseudo_style_invalidated();
665        }
666
667        self.invalidates_self |= invalidated_self;
668    }
669
670    /// Returns whether `dependency` may cause us to invalidate the style of
671    /// more elements than what we've already invalidated.
672    fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool {
673        match dependency.invalidation_kind() {
674            DependencyInvalidationKind::FullSelector | DependencyInvalidationKind::Relative(_) => {
675                unreachable!()
676            },
677            DependencyInvalidationKind::Scope(_) => true,
678            DependencyInvalidationKind::Normal(kind) => match kind {
679                NormalDependencyInvalidationKind::Element => !self.invalidates_self,
680                NormalDependencyInvalidationKind::SlottedElements => {
681                    self.element.is_html_slot_element()
682                },
683                NormalDependencyInvalidationKind::Parts => self.element.shadow_root().is_some(),
684                NormalDependencyInvalidationKind::ElementAndDescendants
685                | NormalDependencyInvalidationKind::Siblings
686                | NormalDependencyInvalidationKind::Descendants => true,
687            },
688        }
689    }
690}
691
692pub(crate) fn push_invalidation<'a>(
693    invalidation: Invalidation<'a>,
694    invalidation_kind: DependencyInvalidationKind,
695    descendant_invalidations: &mut DescendantInvalidationLists<'a>,
696    sibling_invalidations: &mut InvalidationVector<'a>,
697) -> bool {
698    match invalidation_kind {
699        DependencyInvalidationKind::FullSelector => unreachable!(),
700        DependencyInvalidationKind::Relative(_) => unreachable!(),
701        DependencyInvalidationKind::Scope(_) => {
702            // Scope invalidation kind matters only upon reaching the subject.
703            // Examine the combinator to the right of the compound.
704            let combinator = invalidation.combinator_to_right();
705            if combinator.is_sibling() {
706                sibling_invalidations.push(invalidation);
707            } else {
708                descendant_invalidations.dom_descendants.push(invalidation);
709            }
710            true
711        },
712        DependencyInvalidationKind::Normal(kind) => match kind {
713            NormalDependencyInvalidationKind::Element => unreachable!(),
714            NormalDependencyInvalidationKind::ElementAndDescendants => {
715                descendant_invalidations.dom_descendants.push(invalidation);
716                true
717            },
718            NormalDependencyInvalidationKind::Descendants => {
719                descendant_invalidations.dom_descendants.push(invalidation);
720                false
721            },
722            NormalDependencyInvalidationKind::Siblings => {
723                sibling_invalidations.push(invalidation);
724                false
725            },
726            NormalDependencyInvalidationKind::Parts => {
727                descendant_invalidations.parts.push(invalidation);
728                false
729            },
730            NormalDependencyInvalidationKind::SlottedElements => {
731                descendant_invalidations
732                    .slotted_descendants
733                    .push(invalidation);
734                false
735            },
736        },
737    }
738}
739
740pub(crate) fn dependency_may_be_relevant<E: TElement>(
741    dependency: &Dependency,
742    element: &E,
743    already_invalidated_self: bool,
744) -> bool {
745    match dependency.invalidation_kind() {
746        DependencyInvalidationKind::FullSelector => unreachable!(),
747        DependencyInvalidationKind::Relative(_) => unreachable!(),
748        DependencyInvalidationKind::Scope(_) => true,
749        DependencyInvalidationKind::Normal(kind) => match kind {
750            NormalDependencyInvalidationKind::Element => !already_invalidated_self,
751            NormalDependencyInvalidationKind::SlottedElements => element.is_html_slot_element(),
752            NormalDependencyInvalidationKind::Parts => element.shadow_root().is_some(),
753            NormalDependencyInvalidationKind::ElementAndDescendants
754            | NormalDependencyInvalidationKind::Siblings
755            | NormalDependencyInvalidationKind::Descendants => true,
756        },
757    }
758}