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