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