Skip to main content

style/invalidation/element/
invalidator.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//! The struct that takes care of encapsulating all the logic on where and how
6//! element styles need to be invalidated.
7
8use crate::context::StackLimitChecker;
9use crate::dom::{TElement, TNode, TShadowRoot};
10use crate::invalidation::element::invalidation_map::{
11    Dependency, DependencyInvalidationKind, NormalDependencyInvalidationKind,
12    RelativeDependencyInvalidationKind, ScopeDependencyInvalidationKind,
13};
14use selectors::matching::matches_compound_selector_from;
15use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
16use selectors::parser::{Combinator, Component, Selector, SelectorVisitor};
17use selectors::{OpaqueElement, SelectorImpl};
18use smallvec::{smallvec, SmallVec};
19use std::fmt;
20use std::fmt::Write;
21
22struct SiblingInfo<E>
23where
24    E: TElement,
25{
26    affected: E,
27    prev_sibling: Option<E>,
28    next_sibling: Option<E>,
29}
30
31/// Traversal mapping for elements under consideration. It acts like a snapshot map,
32/// though this only "maps" one element at most.
33/// For general invalidations, this has no effect, especially since when
34/// DOM mutates, the mutation's effect should not escape the subtree being mutated.
35/// This is not the case for relative selectors, unfortunately, so we may end up
36/// traversing a portion of the DOM tree that mutated. In case the mutation is removal,
37/// its sibling relation is severed by the time the invalidation happens. This structure
38/// recovers that relation. Note - it assumes that there is only one element under this
39/// effect.
40pub struct SiblingTraversalMap<E>
41where
42    E: TElement,
43{
44    info: Option<SiblingInfo<E>>,
45}
46
47impl<E> Default for SiblingTraversalMap<E>
48where
49    E: TElement,
50{
51    fn default() -> Self {
52        Self { info: None }
53    }
54}
55
56impl<E> SiblingTraversalMap<E>
57where
58    E: TElement,
59{
60    /// Create a new traversal map with the affected element.
61    pub fn new(affected: E, prev_sibling: Option<E>, next_sibling: Option<E>) -> Self {
62        Self {
63            info: Some(SiblingInfo {
64                affected,
65                prev_sibling,
66                next_sibling,
67            }),
68        }
69    }
70
71    /// Get the element's previous sibling element.
72    pub fn next_sibling_for(&self, element: &E) -> Option<E> {
73        if let Some(ref info) = self.info {
74            if *element == info.affected {
75                return info.next_sibling;
76            }
77        }
78        element.next_sibling_element()
79    }
80
81    /// Get the element's previous sibling element.
82    pub fn prev_sibling_for(&self, element: &E) -> Option<E> {
83        if let Some(ref info) = self.info {
84            if *element == info.affected {
85                return info.prev_sibling;
86            }
87        }
88        element.prev_sibling_element()
89    }
90}
91
92/// A trait to abstract the collection of invalidations for a given pass.
93pub trait InvalidationProcessor<'a, 'b, E>
94where
95    E: TElement,
96{
97    /// Whether an invalidation that contains only a pseudo-element selector
98    /// like ::before or ::after triggers invalidation of the element that would
99    /// originate it.
100    fn invalidates_on_pseudo_element(&self) -> bool {
101        false
102    }
103
104    /// Whether the invalidation processor only cares about light-tree
105    /// descendants of a given element, that is, doesn't invalidate
106    /// pseudo-elements, NAC, shadow dom...
107    fn light_tree_only(&self) -> bool {
108        false
109    }
110
111    /// When a dependency from a :where or :is selector matches, it may still be
112    /// the case that we don't need to invalidate the full style. Consider the
113    /// case of:
114    ///
115    ///   div .foo:where(.bar *, .baz) .qux
116    ///
117    /// We can get to the `*` part after a .bar class change, but you only need
118    /// to restyle the element if it also matches .foo.
119    ///
120    /// Similarly, you only need to restyle .baz if the whole result of matching
121    /// the selector changes.
122    ///
123    /// This function is called to check the result of matching the "outer"
124    /// dependency that we generate for the parent of the `:where` selector,
125    /// that is, in the case above it should match
126    /// `div .foo:where(.bar *, .baz)`.
127    ///
128    /// `scope` is set to `Some()` if this dependency follows a scope invalidation
129    /// Matching context should be adjusted accordingly with `nest_for_scope`.
130    ///
131    /// Returning true unconditionally here is over-optimistic and may
132    /// over-invalidate.
133    fn check_outer_dependency(
134        &mut self,
135        dependency: &Dependency,
136        element: E,
137        scope: Option<OpaqueElement>,
138    ) -> bool;
139
140    /// The matching context that should be used to process invalidations.
141    fn matching_context(&mut self) -> &mut MatchingContext<'b, E::Impl>;
142
143    /// The traversal map that should be used to process invalidations.
144    fn sibling_traversal_map(&self) -> &SiblingTraversalMap<E>;
145
146    /// Collect invalidations for a given element's descendants and siblings.
147    ///
148    /// Returns whether the element itself was invalidated.
149    fn collect_invalidations(
150        &mut self,
151        element: E,
152        self_invalidations: &mut InvalidationVector<'a>,
153        descendant_invalidations: &mut DescendantInvalidationLists<'a>,
154        sibling_invalidations: &mut InvalidationVector<'a>,
155    ) -> bool;
156
157    /// Returns whether the invalidation process should process the descendants
158    /// of the given element.
159    fn should_process_descendants(&mut self, element: E) -> bool;
160
161    /// Executes an arbitrary action when the recursion limit is exceded (if
162    /// any).
163    fn recursion_limit_exceeded(&mut self, element: E);
164
165    /// Executes an action when `Self` is invalidated.
166    fn invalidated_self(&mut self, element: E);
167
168    /// Executes an action when `sibling` is invalidated as a sibling of
169    /// `of`.
170    fn invalidated_sibling(&mut self, sibling: E, of: E);
171
172    /// Called when a highlight pseudo-element (::selection, ::highlight,
173    /// ::target-text) style is invalidated. These pseudos have their styles
174    /// resolved lazily during painting rather than during the restyle traversal,
175    /// so style changes don't automatically trigger repaints.
176    fn invalidated_highlight_pseudo(&mut self, _element: E) {}
177
178    /// Executes an action when any descendant of `Self` is invalidated.
179    fn invalidated_descendants(&mut self, element: E, child: E);
180
181    /// Executes an action when an element in a relative selector is reached.
182    /// Lets the dependency to be borrowed for further processing out of the
183    /// invalidation traversal.
184    fn found_relative_selector_invalidation(
185        &mut self,
186        _element: E,
187        _kind: RelativeDependencyInvalidationKind,
188        _relative_dependency: &'a Dependency,
189    ) {
190        debug_assert!(false, "Reached relative selector dependency");
191    }
192}
193
194/// Different invalidation lists for descendants.
195#[derive(Debug, Default)]
196pub struct DescendantInvalidationLists<'a> {
197    /// Invalidations for normal DOM children and pseudo-elements.
198    ///
199    /// TODO(emilio): Having a list of invalidations just for pseudo-elements
200    /// may save some work here and there.
201    pub dom_descendants: InvalidationVector<'a>,
202    /// Invalidations for slotted children of an element.
203    pub slotted_descendants: InvalidationVector<'a>,
204    /// Invalidations for ::part()s of an element.
205    pub parts: InvalidationVector<'a>,
206}
207
208impl<'a> DescendantInvalidationLists<'a> {
209    fn is_empty(&self) -> bool {
210        self.dom_descendants.is_empty()
211            && self.slotted_descendants.is_empty()
212            && self.parts.is_empty()
213    }
214}
215
216/// The struct that takes care of encapsulating all the logic on where and how
217/// element styles need to be invalidated.
218pub struct TreeStyleInvalidator<'a, 'b, 'c, E, P: 'a>
219where
220    'b: 'a,
221    E: TElement,
222    P: InvalidationProcessor<'b, 'c, E>,
223{
224    element: E,
225    stack_limit_checker: Option<&'a StackLimitChecker>,
226    processor: &'a mut P,
227    _marker: std::marker::PhantomData<(&'b (), &'c ())>,
228}
229
230/// A vector of invalidations, optimized for small invalidation sets.
231pub type InvalidationVector<'a> = SmallVec<[Invalidation<'a>; 10]>;
232
233/// The kind of descendant invalidation we're processing.
234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235enum DescendantInvalidationKind {
236    /// A DOM descendant invalidation.
237    Dom,
238    /// A ::slotted() descendant invalidation.
239    Slotted,
240    /// A ::part() descendant invalidation.
241    Part,
242}
243
244/// The kind of invalidation we're processing.
245///
246/// We can use this to avoid pushing invalidations of the same kind to our
247/// descendants or siblings.
248#[derive(Clone, Copy, Debug, Eq, PartialEq)]
249enum InvalidationKind {
250    Descendant(DescendantInvalidationKind),
251    Sibling,
252}
253
254/// The kind of traversal an invalidation requires.
255pub enum InvalidationAddOverride {
256    /// This invalidation should be added to descendant invalidation
257    Descendant,
258    /// This invalidation should be added to sibling invalidations
259    Sibling,
260}
261
262/// An `Invalidation` is a complex selector that describes which elements,
263/// relative to a current element we are processing, must be restyled.
264#[derive(Clone)]
265pub struct Invalidation<'a> {
266    /// The dependency that generated this invalidation.
267    ///
268    /// Note that the offset inside the dependency is not really useful after
269    /// construction.
270    dependency: &'a Dependency,
271    /// The right shadow host from where the rule came from, if any.
272    ///
273    /// This is needed to ensure that we match the selector with the right
274    /// state, as whether some selectors like :host and ::part() match depends
275    /// on it.
276    host: Option<OpaqueElement>,
277    /// The scope element from which this rule comes from, if any.
278    scope: Option<OpaqueElement>,
279    /// The offset of the selector pointing to a compound selector.
280    ///
281    /// This order is a "parse order" offset, that is, zero is the leftmost part
282    /// of the selector written as parsed / serialized.
283    ///
284    /// It is initialized from the offset from `dependency`.
285    offset: usize,
286    /// Whether the invalidation was already matched by any previous sibling or
287    /// ancestor.
288    ///
289    /// If this is the case, we can avoid pushing invalidations generated by
290    /// this one if the generated invalidation is effective for all the siblings
291    /// or descendants after us.
292    matched_by_any_previous: bool,
293    /// Whether this incalidation should always be pushed to next invalidations.
294    ///
295    /// This is useful for overriding invalidations we would otherwise skip.
296    ///  e.g @scope(.a){:not(:scope)} where we would need the :not(:scope)
297    /// invalidation to traverse down for all children of the scope root
298    always_effective_for_next_descendant: bool,
299}
300
301impl<'a> Invalidation<'a> {
302    /// Create a new invalidation for matching a dependency.
303    pub fn new(
304        dependency: &'a Dependency,
305        host: Option<OpaqueElement>,
306        scope: Option<OpaqueElement>,
307    ) -> Self {
308        debug_assert!(
309            dependency.selector_offset == dependency.selector.len() + 1
310                || dependency.invalidation_kind()
311                    != DependencyInvalidationKind::Normal(
312                        NormalDependencyInvalidationKind::Element
313                    ),
314            "No point to this, if the dependency matched the element we should just invalidate it"
315        );
316        Self {
317            dependency,
318            host,
319            scope,
320            // + 1 to go past the combinator.
321            offset: dependency.selector.len() + 1 - dependency.selector_offset,
322            matched_by_any_previous: false,
323            always_effective_for_next_descendant: false,
324        }
325    }
326
327    /// Create a new invalidation for matching a dependency from the selector's subject.
328    /// Using this should be avoided whenever possible as it overinvalidates.
329    /// Only use it when it's not possible to match the selector in order due to
330    /// invalidations that don't necessarily start at the pointed compound, such as
331    /// what happens in note_scope_dependency_force_at_subject.
332    pub fn new_subject_invalidation(
333        dependency: &'a Dependency,
334        host: Option<OpaqueElement>,
335        scope: Option<OpaqueElement>,
336    ) -> Self {
337        let mut compound_offset = 0;
338        for s in dependency.selector.iter_raw_match_order() {
339            if s.is_combinator() {
340                break;
341            }
342            compound_offset += 1;
343        }
344
345        Self {
346            dependency,
347            host,
348            scope,
349            offset: dependency.selector.len() - compound_offset,
350            matched_by_any_previous: false,
351            always_effective_for_next_descendant: true,
352        }
353    }
354
355    /// Create a new invalidation for matching a dependency that should always check
356    /// its next descendants. It tends to overinvalidate less than new_subject_invalidation
357    /// but it should also be avoided whenever possible. Specifically used when crossing
358    /// into implicit scope invalidation.
359    pub fn new_always_effective_for_next_descendant(
360        dependency: &'a Dependency,
361        host: Option<OpaqueElement>,
362        scope: Option<OpaqueElement>,
363    ) -> Self {
364        if dependency.selector.is_rightmost(dependency.selector_offset) {
365            return Self::new_subject_invalidation(dependency, host, scope);
366        }
367
368        Self {
369            dependency,
370            host,
371            scope,
372            // + 1 to go past the combinator.
373            offset: dependency.selector.len() + 1 - dependency.selector_offset,
374            matched_by_any_previous: false,
375            always_effective_for_next_descendant: true,
376        }
377    }
378
379    /// Return the combinator to the right of the currently invalidating compound
380    /// Useful for determining whether this invalidation should be pushed to
381    /// sibling or descendant invalidations.
382    pub fn combinator_to_right(&self) -> Combinator {
383        debug_assert_ne!(self.dependency.selector_offset, 0);
384        self.dependency
385            .selector
386            .combinator_at_match_order(self.dependency.selector.len() - self.offset)
387    }
388
389    /// Whether this invalidation is effective for the next sibling or
390    /// descendant after us.
391    fn effective_for_next(&self) -> bool {
392        if self.offset == 0 || self.always_effective_for_next_descendant {
393            return true;
394        }
395
396        // TODO(emilio): For pseudo-elements this should be mostly false, except
397        // for the weird pseudos in <input type="number">.
398        //
399        // We should be able to do better here!
400        match self
401            .dependency
402            .selector
403            .combinator_at_parse_order(self.offset - 1)
404        {
405            Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
406            Combinator::Part
407            | Combinator::SlotAssignment
408            | Combinator::NextSibling
409            | Combinator::Child => false,
410        }
411    }
412
413    fn kind(&self) -> InvalidationKind {
414        if self.offset == 0 {
415            return InvalidationKind::Descendant(DescendantInvalidationKind::Dom);
416        }
417
418        match self
419            .dependency
420            .selector
421            .combinator_at_parse_order(self.offset - 1)
422        {
423            Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
424                InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
425            },
426            Combinator::Part => InvalidationKind::Descendant(DescendantInvalidationKind::Part),
427            Combinator::SlotAssignment => {
428                InvalidationKind::Descendant(DescendantInvalidationKind::Slotted)
429            },
430            Combinator::NextSibling | Combinator::LaterSibling => InvalidationKind::Sibling,
431        }
432    }
433}
434
435/// A struct that visits a selector and determines if there is a `:scope`
436/// component nested withing a negation. eg. :not(:scope)
437struct NegationScopeVisitor {
438    /// Have we found a negation list yet
439    in_negation: bool,
440    /// Have we found a :scope inside a negation yet
441    found_scope_in_negation: bool,
442}
443
444impl NegationScopeVisitor {
445    /// Create a new NegationScopeVisitor
446    fn new() -> Self {
447        Self {
448            in_negation: false,
449            found_scope_in_negation: false,
450        }
451    }
452
453    fn traverse_selector(
454        mut self,
455        selector: &Selector<<NegationScopeVisitor as SelectorVisitor>::Impl>,
456    ) -> bool {
457        selector.visit(&mut self);
458        self.found_scope_in_negation
459    }
460
461    /// Traverse all the next dependencies in an outer dependency until we reach
462    /// 1. :not(* :scope *)
463    /// 2. a scope or relative dependency
464    /// 3. the end of the chain of dependencies
465    /// Return whether or not we encountered :not(* :scope *)
466    fn traverse_dependency(mut self, dependency: &Dependency) -> bool {
467        if dependency.next.is_none()
468            || !matches!(
469                dependency.invalidation_kind(),
470                DependencyInvalidationKind::Normal(..)
471            )
472        {
473            dependency.selector.visit(&mut self);
474            return self.found_scope_in_negation;
475        }
476
477        let nested_visitor = Self {
478            in_negation: self.in_negation,
479            found_scope_in_negation: false,
480        };
481        dependency.selector.visit(&mut self);
482        // Has to be normal dependency and next.is_some()
483        nested_visitor.traverse_dependency(&dependency.next.as_ref().unwrap().slice()[0])
484    }
485}
486
487impl SelectorVisitor for NegationScopeVisitor {
488    type Impl = crate::selector_parser::SelectorImpl;
489
490    fn visit_attribute_selector(
491        &mut self,
492        _namespace: &selectors::attr::NamespaceConstraint<
493            &<Self::Impl as SelectorImpl>::NamespaceUrl,
494        >,
495        _local_name: &<Self::Impl as SelectorImpl>::LocalName,
496        _local_name_lower: &<Self::Impl as SelectorImpl>::LocalName,
497    ) -> bool {
498        true
499    }
500
501    fn visit_simple_selector(&mut self, component: &Component<Self::Impl>) -> bool {
502        if self.in_negation && component == &Component::Scope {
503            self.found_scope_in_negation = true;
504        }
505        true
506    }
507
508    fn visit_relative_selector_list(
509        &mut self,
510        _list: &[selectors::parser::RelativeSelector<Self::Impl>],
511    ) -> bool {
512        true
513    }
514
515    fn visit_selector_list(
516        &mut self,
517        list_kind: selectors::visitor::SelectorListKind,
518        list: &[selectors::parser::Selector<Self::Impl>],
519    ) -> bool {
520        for nested in list {
521            let nested_visitor = Self {
522                in_negation: list_kind.in_negation(),
523                found_scope_in_negation: false,
524            };
525
526            self.found_scope_in_negation |= nested_visitor.traverse_selector(nested);
527        }
528        true
529    }
530
531    fn visit_complex_selector(&mut self, _combinator_to_right: Option<Combinator>) -> bool {
532        true
533    }
534}
535
536/// Determines if we can find a selector in the form of :not(:scope)
537/// anywhere down the chain of dependencies.
538pub fn any_next_has_scope_in_negation(dependency: &Dependency) -> bool {
539    let next = match dependency.next.as_ref() {
540        None => return false,
541        Some(l) => l,
542    };
543
544    next.slice().iter().any(|dep| {
545        let visitor = NegationScopeVisitor::new();
546        visitor.traverse_dependency(dep)
547    })
548}
549
550impl<'a> fmt::Debug for Invalidation<'a> {
551    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
552        use cssparser::ToCss;
553
554        f.write_str("Invalidation(")?;
555        for component in self
556            .dependency
557            .selector
558            .iter_raw_parse_order_from(self.offset)
559        {
560            if matches!(*component, Component::Combinator(..)) {
561                break;
562            }
563            component.to_css(f)?;
564        }
565        f.write_char(')')
566    }
567}
568
569/// The result of processing a single invalidation for a given element.
570struct ProcessInvalidationResult {
571    /// Whether the element itself was invalidated.
572    invalidated_self: bool,
573    /// Whether the invalidation matched, either invalidating the element or
574    /// generating another invalidation.
575    matched: bool,
576}
577
578/// The result of a whole invalidation process for a given element.
579pub struct InvalidationResult {
580    /// Whether the element itself was invalidated.
581    invalidated_self: bool,
582    /// Whether the element's descendants were invalidated.
583    invalidated_descendants: bool,
584    /// Whether the element's siblings were invalidated.
585    invalidated_siblings: bool,
586}
587
588impl InvalidationResult {
589    /// Create an emtpy result.
590    pub fn empty() -> Self {
591        Self {
592            invalidated_self: false,
593            invalidated_descendants: false,
594            invalidated_siblings: false,
595        }
596    }
597
598    /// Whether the invalidation has invalidate the element itself.
599    pub fn has_invalidated_self(&self) -> bool {
600        self.invalidated_self
601    }
602
603    /// Whether the invalidation has invalidate desendants.
604    pub fn has_invalidated_descendants(&self) -> bool {
605        self.invalidated_descendants
606    }
607
608    /// Whether the invalidation has invalidate siblings.
609    pub fn has_invalidated_siblings(&self) -> bool {
610        self.invalidated_siblings
611    }
612}
613
614impl<'a, 'b, 'c, E, P: 'a> TreeStyleInvalidator<'a, 'b, 'c, E, P>
615where
616    'b: 'a,
617    E: TElement,
618    P: InvalidationProcessor<'b, 'c, E>,
619{
620    /// Trivially constructs a new `TreeStyleInvalidator`.
621    pub fn new(
622        element: E,
623        stack_limit_checker: Option<&'a StackLimitChecker>,
624        processor: &'a mut P,
625    ) -> Self {
626        Self {
627            element,
628            stack_limit_checker,
629            processor,
630            _marker: std::marker::PhantomData,
631        }
632    }
633
634    /// Perform the invalidation pass.
635    pub fn invalidate(mut self) -> InvalidationResult {
636        debug!("StyleTreeInvalidator::invalidate({:?})", self.element);
637
638        let mut self_invalidations = InvalidationVector::new();
639        let mut descendant_invalidations = DescendantInvalidationLists::default();
640        let mut sibling_invalidations = InvalidationVector::new();
641
642        let mut invalidated_self = self.processor.collect_invalidations(
643            self.element,
644            &mut self_invalidations,
645            &mut descendant_invalidations,
646            &mut sibling_invalidations,
647        );
648
649        debug!("Collected invalidations (self: {}): ", invalidated_self);
650        debug!(
651            " > self: {}, {:?}",
652            self_invalidations.len(),
653            self_invalidations
654        );
655        debug!(" > descendants: {:?}", descendant_invalidations);
656        debug!(
657            " > siblings: {}, {:?}",
658            sibling_invalidations.len(),
659            sibling_invalidations
660        );
661
662        let invalidated_self_from_collection = invalidated_self;
663
664        invalidated_self |= self.process_descendant_invalidations(
665            &self_invalidations,
666            &mut descendant_invalidations,
667            &mut sibling_invalidations,
668            DescendantInvalidationKind::Dom,
669        );
670
671        if invalidated_self && !invalidated_self_from_collection {
672            self.processor.invalidated_self(self.element);
673        }
674
675        let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations);
676        let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations);
677
678        InvalidationResult {
679            invalidated_self,
680            invalidated_descendants,
681            invalidated_siblings,
682        }
683    }
684
685    /// Go through later DOM siblings, invalidating style as needed using the
686    /// `sibling_invalidations` list.
687    ///
688    /// Returns whether any sibling's style or any sibling descendant's style
689    /// was invalidated.
690    fn invalidate_siblings(&mut self, sibling_invalidations: &mut InvalidationVector<'b>) -> bool {
691        if sibling_invalidations.is_empty() {
692            return false;
693        }
694
695        let mut current = self
696            .processor
697            .sibling_traversal_map()
698            .next_sibling_for(&self.element);
699        let mut any_invalidated = false;
700
701        while let Some(sibling) = current {
702            let mut sibling_invalidator =
703                TreeStyleInvalidator::new(sibling, self.stack_limit_checker, self.processor);
704
705            let mut invalidations_for_descendants = DescendantInvalidationLists::default();
706            let invalidated_sibling = sibling_invalidator.process_sibling_invalidations(
707                &mut invalidations_for_descendants,
708                sibling_invalidations,
709            );
710
711            if invalidated_sibling {
712                sibling_invalidator
713                    .processor
714                    .invalidated_sibling(sibling, self.element);
715            }
716
717            any_invalidated |= invalidated_sibling;
718
719            any_invalidated |=
720                sibling_invalidator.invalidate_descendants(&invalidations_for_descendants);
721
722            if sibling_invalidations.is_empty() {
723                break;
724            }
725
726            current = self
727                .processor
728                .sibling_traversal_map()
729                .next_sibling_for(&sibling);
730        }
731
732        any_invalidated
733    }
734
735    fn invalidate_pseudo_element_or_nac(
736        &mut self,
737        child: E,
738        invalidations: &[Invalidation<'b>],
739    ) -> bool {
740        let mut sibling_invalidations = InvalidationVector::new();
741
742        // Roots of NAC subtrees can indeed generate sibling invalidations, but
743        // they can be just ignored, since they have no siblings.
744        //
745        // Note that we can end up testing selectors that wouldn't end up
746        // matching due to this being NAC, like those coming from document
747        // rules, but we overinvalidate instead of checking this.
748
749        self.invalidate_child(
750            child,
751            invalidations,
752            &mut sibling_invalidations,
753            DescendantInvalidationKind::Dom,
754        )
755    }
756
757    /// Invalidate a child and recurse down invalidating its descendants if
758    /// needed.
759    fn invalidate_child(
760        &mut self,
761        child: E,
762        invalidations: &[Invalidation<'b>],
763        sibling_invalidations: &mut InvalidationVector<'b>,
764        descendant_invalidation_kind: DescendantInvalidationKind,
765    ) -> bool {
766        let mut invalidations_for_descendants = DescendantInvalidationLists::default();
767
768        let mut invalidated_child = false;
769        let invalidated_descendants = {
770            let mut child_invalidator =
771                TreeStyleInvalidator::new(child, self.stack_limit_checker, self.processor);
772
773            if !sibling_invalidations.is_empty() {
774                invalidated_child |= child_invalidator.process_sibling_invalidations(
775                    &mut invalidations_for_descendants,
776                    sibling_invalidations,
777                );
778            }
779
780            invalidated_child |= child_invalidator.process_descendant_invalidations(
781                invalidations,
782                &mut invalidations_for_descendants,
783                sibling_invalidations,
784                descendant_invalidation_kind,
785            );
786
787            if invalidated_child {
788                child_invalidator.processor.invalidated_self(child);
789            }
790
791            child_invalidator.invalidate_descendants(&invalidations_for_descendants)
792        };
793
794        // The child may not be a flattened tree child of the current element,
795        // but may be arbitrarily deep.
796        //
797        // Since we keep the traversal flags in terms of the flattened tree,
798        // we need to propagate it as appropriate.
799        if invalidated_child || invalidated_descendants {
800            self.processor.invalidated_descendants(self.element, child);
801        }
802
803        invalidated_child || invalidated_descendants
804    }
805
806    fn invalidate_nac(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
807        let mut any_nac_root = false;
808
809        let element = self.element;
810        element.each_anonymous_content_child(|nac| {
811            any_nac_root |= self.invalidate_pseudo_element_or_nac(nac, invalidations);
812        });
813
814        any_nac_root
815    }
816
817    // NB: It's important that this operates on DOM children, which is what
818    // selector-matching operates on.
819    fn invalidate_dom_descendants_of(
820        &mut self,
821        parent: E::ConcreteNode,
822        invalidations: &[Invalidation<'b>],
823    ) -> bool {
824        let mut any_descendant = false;
825
826        let mut sibling_invalidations = InvalidationVector::new();
827        for child in parent.dom_children() {
828            let child = match child.as_element() {
829                Some(e) => e,
830                None => continue,
831            };
832
833            any_descendant |= self.invalidate_child(
834                child,
835                invalidations,
836                &mut sibling_invalidations,
837                DescendantInvalidationKind::Dom,
838            );
839        }
840
841        any_descendant
842    }
843
844    fn invalidate_parts_in_shadow_tree(
845        &mut self,
846        shadow: <E::ConcreteNode as TNode>::ConcreteShadowRoot,
847        invalidations: &[Invalidation<'b>],
848    ) -> bool {
849        debug_assert!(!invalidations.is_empty());
850
851        let mut any = false;
852        let mut sibling_invalidations = InvalidationVector::new();
853
854        for node in shadow.as_node().dom_descendants() {
855            let element = match node.as_element() {
856                Some(e) => e,
857                None => continue,
858            };
859
860            if element.has_part_attr() {
861                any |= self.invalidate_child(
862                    element,
863                    invalidations,
864                    &mut sibling_invalidations,
865                    DescendantInvalidationKind::Part,
866                );
867                debug_assert!(
868                    sibling_invalidations.is_empty(),
869                    "::part() shouldn't have sibling combinators to the right, \
870                     this makes no sense! {:?}",
871                    sibling_invalidations
872                );
873            }
874
875            if let Some(shadow) = element.shadow_root() {
876                if element.exports_any_part() {
877                    any |= self.invalidate_parts_in_shadow_tree(shadow, invalidations)
878                }
879            }
880        }
881
882        any
883    }
884
885    fn invalidate_parts(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
886        if invalidations.is_empty() {
887            return false;
888        }
889
890        let shadow = match self.element.shadow_root() {
891            Some(s) => s,
892            None => return false,
893        };
894
895        self.invalidate_parts_in_shadow_tree(shadow, invalidations)
896    }
897
898    fn invalidate_slotted_elements(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
899        if invalidations.is_empty() {
900            return false;
901        }
902
903        let slot = self.element;
904        self.invalidate_slotted_elements_in_slot(slot, invalidations)
905    }
906
907    fn invalidate_slotted_elements_in_slot(
908        &mut self,
909        slot: E,
910        invalidations: &[Invalidation<'b>],
911    ) -> bool {
912        let mut any = false;
913
914        let mut sibling_invalidations = InvalidationVector::new();
915        for node in slot.slotted_nodes() {
916            let element = match node.as_element() {
917                Some(e) => e,
918                None => continue,
919            };
920
921            if element.is_html_slot_element() {
922                any |= self.invalidate_slotted_elements_in_slot(element, invalidations);
923            } else {
924                any |= self.invalidate_child(
925                    element,
926                    invalidations,
927                    &mut sibling_invalidations,
928                    DescendantInvalidationKind::Slotted,
929                );
930            }
931
932            debug_assert!(
933                sibling_invalidations.is_empty(),
934                "::slotted() shouldn't have sibling combinators to the right, \
935                 this makes no sense! {:?}",
936                sibling_invalidations
937            );
938        }
939
940        any
941    }
942
943    fn invalidate_non_slotted_descendants(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
944        if invalidations.is_empty() {
945            return false;
946        }
947
948        if self.processor.light_tree_only() {
949            let node = self.element.as_node();
950            return self.invalidate_dom_descendants_of(node, invalidations);
951        }
952
953        let mut any_descendant = false;
954
955        // NOTE(emilio): This is only needed for Shadow DOM to invalidate
956        // correctly on :host(..) changes. Instead of doing this, we could add
957        // a third kind of invalidation list that walks shadow root children,
958        // but it's not clear it's worth it.
959        //
960        // Also, it's needed as of right now for document state invalidation,
961        // where we rely on iterating every element that ends up in the composed
962        // doc, but we could fix that invalidating per subtree.
963        if let Some(root) = self.element.shadow_root() {
964            any_descendant |= self.invalidate_dom_descendants_of(root.as_node(), invalidations);
965        }
966
967        any_descendant |= self.invalidate_dom_descendants_of(self.element.as_node(), invalidations);
968
969        any_descendant |= self.invalidate_nac(invalidations);
970
971        any_descendant
972    }
973
974    /// Given the descendant invalidation lists, go through the current
975    /// element's descendants, and invalidate style on them.
976    fn invalidate_descendants(&mut self, invalidations: &DescendantInvalidationLists<'b>) -> bool {
977        if invalidations.is_empty() {
978            return false;
979        }
980
981        debug!(
982            "StyleTreeInvalidator::invalidate_descendants({:?})",
983            self.element
984        );
985        debug!(" > {:?}", invalidations);
986
987        let should_process = self.processor.should_process_descendants(self.element);
988
989        if !should_process {
990            return false;
991        }
992
993        if let Some(checker) = self.stack_limit_checker {
994            if checker.limit_exceeded() {
995                self.processor.recursion_limit_exceeded(self.element);
996                return true;
997            }
998        }
999
1000        let mut any_descendant = false;
1001
1002        any_descendant |= self.invalidate_non_slotted_descendants(&invalidations.dom_descendants);
1003        any_descendant |= self.invalidate_slotted_elements(&invalidations.slotted_descendants);
1004        any_descendant |= self.invalidate_parts(&invalidations.parts);
1005
1006        any_descendant
1007    }
1008
1009    /// Process the given sibling invalidations coming from our previous
1010    /// sibling.
1011    ///
1012    /// The sibling invalidations are somewhat special because they can be
1013    /// modified on the fly. New invalidations may be added and removed.
1014    ///
1015    /// In particular, all descendants get the same set of invalidations from
1016    /// the parent, but the invalidations from a given sibling depend on the
1017    /// ones we got from the previous one.
1018    ///
1019    /// Returns whether invalidated the current element's style.
1020    ///
1021    /// Callers should skip this when `sibling_invalidations` is empty, as it
1022    /// builds and drains a `SmallVec` to do nothing.
1023    fn process_sibling_invalidations(
1024        &mut self,
1025        descendant_invalidations: &mut DescendantInvalidationLists<'b>,
1026        sibling_invalidations: &mut InvalidationVector<'b>,
1027    ) -> bool {
1028        let mut i = 0;
1029        let mut new_sibling_invalidations = InvalidationVector::new();
1030        let mut invalidated_self = false;
1031
1032        while i < sibling_invalidations.len() {
1033            let result = self.process_invalidation(
1034                &sibling_invalidations[i],
1035                descendant_invalidations,
1036                &mut new_sibling_invalidations,
1037                InvalidationKind::Sibling,
1038            );
1039
1040            invalidated_self |= result.invalidated_self;
1041            sibling_invalidations[i].matched_by_any_previous |= result.matched;
1042            if sibling_invalidations[i].effective_for_next() {
1043                i += 1;
1044            } else {
1045                sibling_invalidations.remove(i);
1046            }
1047        }
1048
1049        sibling_invalidations.extend(new_sibling_invalidations.drain(..));
1050        invalidated_self
1051    }
1052
1053    /// Process a given invalidation list coming from our parent,
1054    /// adding to `descendant_invalidations` and `sibling_invalidations` as
1055    /// needed.
1056    ///
1057    /// Returns whether our style was invalidated as a result.
1058    fn process_descendant_invalidations(
1059        &mut self,
1060        invalidations: &[Invalidation<'b>],
1061        descendant_invalidations: &mut DescendantInvalidationLists<'b>,
1062        sibling_invalidations: &mut InvalidationVector<'b>,
1063        descendant_invalidation_kind: DescendantInvalidationKind,
1064    ) -> bool {
1065        let mut invalidated = false;
1066
1067        for invalidation in invalidations {
1068            let result = self.process_invalidation(
1069                invalidation,
1070                descendant_invalidations,
1071                sibling_invalidations,
1072                InvalidationKind::Descendant(descendant_invalidation_kind),
1073            );
1074
1075            invalidated |= result.invalidated_self;
1076            if invalidation.effective_for_next() {
1077                let mut invalidation = invalidation.clone();
1078                invalidation.matched_by_any_previous |= result.matched;
1079                debug_assert_eq!(
1080                    descendant_invalidation_kind,
1081                    DescendantInvalidationKind::Dom,
1082                    "Slotted or part invalidations don't propagate."
1083                );
1084                descendant_invalidations.dom_descendants.push(invalidation);
1085            }
1086        }
1087
1088        invalidated
1089    }
1090
1091    #[inline(always)]
1092    fn handle_fully_matched(
1093        &mut self,
1094        invalidation: &Invalidation<'b>,
1095    ) -> (ProcessInvalidationResult, SmallVec<[Invalidation<'b>; 1]>) {
1096        debug!(" > Invalidation matched completely");
1097        // We matched completely. If we're an inner selector now we need
1098        // to go outside our selector and carry on invalidating.
1099        let mut to_process: SmallVec<[&Dependency; 1]> = SmallVec::from([invalidation.dependency]);
1100        let mut next_invalidations: SmallVec<[Invalidation; 1]> = SmallVec::new();
1101        let mut result = ProcessInvalidationResult {
1102            invalidated_self: false,
1103            matched: false,
1104        };
1105
1106        while !to_process.is_empty() {
1107            let mut next_dependencies: SmallVec<[&Dependency; 1]> = SmallVec::new();
1108
1109            while let Some(dependency) = to_process.pop() {
1110                if let DependencyInvalidationKind::Scope(scope_kind) =
1111                    dependency.invalidation_kind()
1112                {
1113                    if scope_kind == ScopeDependencyInvalidationKind::ImplicitScope {
1114                        if let Some(ref deps) = dependency.next {
1115                            for dep in deps.as_ref().slice() {
1116                                let invalidation =
1117                                    Invalidation::new_always_effective_for_next_descendant(
1118                                        dep,
1119                                        invalidation.host,
1120                                        invalidation.scope,
1121                                    );
1122                                next_invalidations.push(invalidation);
1123                            }
1124                        }
1125                        continue;
1126                    }
1127
1128                    let force_add = any_next_has_scope_in_negation(dependency);
1129                    if scope_kind == ScopeDependencyInvalidationKind::ScopeEnd || force_add {
1130                        let invalidations = note_scope_dependency_force_at_subject(
1131                            dependency,
1132                            invalidation.host,
1133                            invalidation.scope,
1134                            force_add,
1135                        );
1136
1137                        next_invalidations.extend(invalidations);
1138
1139                        continue;
1140                    }
1141                }
1142
1143                match dependency.next {
1144                    None => {
1145                        result.invalidated_self = true;
1146                        result.matched = true;
1147                    },
1148                    Some(ref deps) => {
1149                        for n in deps.as_ref().slice() {
1150                            let invalidation_kind = n.invalidation_kind();
1151                            match invalidation_kind {
1152                                DependencyInvalidationKind::FullSelector => unreachable!(),
1153                                DependencyInvalidationKind::Normal(_) => next_dependencies.push(n),
1154                                //TODO(descalente, bug 1934061): Add specific handling for implicit scopes.
1155                                DependencyInvalidationKind::Scope(_) => {
1156                                    next_dependencies.push(n);
1157                                },
1158                                DependencyInvalidationKind::Relative(kind) => {
1159                                    self.processor.found_relative_selector_invalidation(
1160                                        self.element,
1161                                        kind,
1162                                        n,
1163                                    );
1164                                    result.matched = true;
1165                                },
1166                            }
1167                        }
1168                    },
1169                };
1170            }
1171
1172            for cur_dependency in next_dependencies.as_ref() {
1173                let scope = matches!(
1174                    invalidation.dependency.invalidation_kind(),
1175                    DependencyInvalidationKind::Scope(_)
1176                )
1177                .then(|| self.element.opaque());
1178                debug!(" > Checking outer dependency {:?}", cur_dependency);
1179
1180                // The inner selector changed, now check if the full
1181                // previous part of the selector did, before keeping
1182                // checking for descendants.
1183                if !self
1184                    .processor
1185                    .check_outer_dependency(cur_dependency, self.element, scope)
1186                {
1187                    // Dependency is not relevant, do not note it down
1188                    continue;
1189                }
1190
1191                let invalidation_kind = cur_dependency.invalidation_kind();
1192                if matches!(
1193                    invalidation_kind,
1194                    DependencyInvalidationKind::Normal(NormalDependencyInvalidationKind::Element)
1195                ) || (matches!(invalidation_kind, DependencyInvalidationKind::Scope(_))
1196                    && cur_dependency
1197                        .selector
1198                        .is_rightmost(cur_dependency.selector_offset))
1199                {
1200                    // Add to dependency stack to process its next dependencies.
1201                    to_process.push(cur_dependency);
1202                    continue;
1203                }
1204
1205                debug!(" > Generating invalidation");
1206                next_invalidations.push(Invalidation::new(
1207                    cur_dependency,
1208                    invalidation.host,
1209                    scope,
1210                ));
1211            }
1212        }
1213        (result, next_invalidations)
1214    }
1215
1216    /// Processes a given invalidation, potentially invalidating the style of
1217    /// the current element.
1218    ///
1219    /// Returns whether invalidated the style of the element, and whether the
1220    /// invalidation should be effective to subsequent siblings or descendants
1221    /// down in the tree.
1222    fn process_invalidation(
1223        &mut self,
1224        invalidation: &Invalidation<'b>,
1225        descendant_invalidations: &mut DescendantInvalidationLists<'b>,
1226        sibling_invalidations: &mut InvalidationVector<'b>,
1227        invalidation_kind: InvalidationKind,
1228    ) -> ProcessInvalidationResult {
1229        debug!(
1230            "TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})",
1231            self.element, invalidation, invalidation_kind
1232        );
1233
1234        let matching_result = {
1235            let context = self.processor.matching_context();
1236            context.current_host = invalidation.host;
1237
1238            context.nest_for_scope_condition(invalidation.scope, |ctx| {
1239                matches_compound_selector_from(
1240                    &invalidation.dependency.selector,
1241                    invalidation.offset,
1242                    ctx,
1243                    &self.element,
1244                )
1245            })
1246        };
1247
1248        let (mut result, next_invalidations) = match matching_result {
1249            CompoundSelectorMatchingResult::NotMatched => {
1250                return ProcessInvalidationResult {
1251                    invalidated_self: false,
1252                    matched: false,
1253                }
1254            },
1255            CompoundSelectorMatchingResult::FullyMatched => self.handle_fully_matched(invalidation),
1256            CompoundSelectorMatchingResult::Matched {
1257                next_combinator_offset,
1258            } => (
1259                ProcessInvalidationResult {
1260                    invalidated_self: false,
1261                    matched: true,
1262                },
1263                smallvec![Invalidation {
1264                    dependency: invalidation.dependency,
1265                    host: invalidation.host,
1266                    scope: invalidation.scope,
1267                    offset: next_combinator_offset + 1,
1268                    matched_by_any_previous: false,
1269                    always_effective_for_next_descendant: invalidation
1270                        .always_effective_for_next_descendant,
1271                }],
1272            ),
1273        };
1274
1275        for next_invalidation in next_invalidations {
1276            let next_invalidation_kind = if next_invalidation.always_effective_for_next_descendant {
1277                InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
1278            } else {
1279                debug_assert_ne!(
1280                    next_invalidation.offset, 0,
1281                    "Rightmost selectors shouldn't generate more invalidations",
1282                );
1283
1284                let next_combinator = next_invalidation
1285                    .dependency
1286                    .selector
1287                    .combinator_at_parse_order(next_invalidation.offset - 1);
1288
1289                if matches!(next_combinator, Combinator::PseudoElement)
1290                    && self.processor.invalidates_on_pseudo_element()
1291                {
1292                    // We need to invalidate the element whenever pseudos change, for
1293                    // two reasons:
1294                    //
1295                    //  * Eager pseudo styles are stored as part of the originating
1296                    //    element's computed style.
1297                    //
1298                    //  * Lazy pseudo-styles might be cached on the originating
1299                    //    element's pseudo-style cache.
1300                    //
1301                    // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS
1302                    // hint?).
1303                    //
1304                    // Note that we'll also restyle the pseudo-element because it would
1305                    // match this invalidation.
1306                    result.invalidated_self = true;
1307
1308                    // For highlight pseudos (::selection, ::highlight, ::target-text),
1309                    // we also need to trigger a repaint since their styles are resolved
1310                    // lazily during painting.
1311                    if next_invalidation
1312                        .dependency
1313                        .selector
1314                        .pseudo_element()
1315                        .is_some_and(|p| p.is_lazy_painted_highlight_pseudo())
1316                    {
1317                        self.processor.invalidated_highlight_pseudo(self.element);
1318                    }
1319                }
1320
1321                debug!(
1322                    " > Invalidation matched, next: {:?}, ({:?})",
1323                    next_invalidation, next_combinator
1324                );
1325
1326                next_invalidation.kind()
1327            };
1328
1329            // We can skip pushing under some circumstances, and we should
1330            // because otherwise the invalidation list could grow
1331            // exponentially.
1332            //
1333            //  * First of all, both invalidations need to be of the same
1334            //    kind. This is because of how we propagate them going to
1335            //    the right of the tree for sibling invalidations and going
1336            //    down the tree for children invalidations. A sibling
1337            //    invalidation that ends up generating a children
1338            //    invalidation ends up (correctly) in five different lists,
1339            //    not in the same list five different times.
1340            //
1341            //  * Then, the invalidation needs to be matched by a previous
1342            //    ancestor/sibling, in order to know that this invalidation
1343            //    has been generated already.
1344            //
1345            //  * Finally, the new invalidation needs to be
1346            //    `effective_for_next()`, in order for us to know that it is
1347            //    still in the list, since we remove the dependencies that
1348            //    aren't from the lists for our children / siblings.
1349            //
1350            // To go through an example, let's imagine we are processing a
1351            // dom subtree like:
1352            //
1353            //   <div><address><div><div/></div></address></div>
1354            //
1355            // And an invalidation list with a single invalidation like:
1356            //
1357            //   [div div div]
1358            //
1359            // When we process the invalidation list for the outer div, we
1360            // match it, and generate a `div div` invalidation, so for the
1361            // <address> child we have:
1362            //
1363            //   [div div div, div div]
1364            //
1365            // With the first of them marked as `matched`.
1366            //
1367            // When we process the <address> child, we don't match any of
1368            // them, so both invalidations go untouched to our children.
1369            //
1370            // When we process the second <div>, we match _both_
1371            // invalidations.
1372            //
1373            // However, when matching the first, we can tell it's been
1374            // matched, and not push the corresponding `div div`
1375            // invalidation, since we know it's necessarily already on the
1376            // list.
1377            //
1378            // Thus, without skipping the push, we'll arrive to the
1379            // innermost <div> with:
1380            //
1381            //   [div div div, div div, div div, div]
1382            //
1383            // While skipping it, we won't arrive here with duplicating
1384            // dependencies:
1385            //
1386            //   [div div div, div div, div]
1387            //
1388            let can_skip_pushing = next_invalidation_kind == invalidation_kind
1389                && invalidation.matched_by_any_previous
1390                && next_invalidation.effective_for_next();
1391
1392            if can_skip_pushing {
1393                debug!(
1394                    " > Can avoid push, since the invalidation had \
1395                    already been matched before"
1396                );
1397            } else {
1398                match next_invalidation_kind {
1399                    InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
1400                        descendant_invalidations
1401                            .dom_descendants
1402                            .push(next_invalidation);
1403                    },
1404                    InvalidationKind::Descendant(DescendantInvalidationKind::Part) => {
1405                        descendant_invalidations.parts.push(next_invalidation);
1406                    },
1407                    InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
1408                        descendant_invalidations
1409                            .slotted_descendants
1410                            .push(next_invalidation);
1411                    },
1412                    InvalidationKind::Sibling => {
1413                        sibling_invalidations.push(next_invalidation);
1414                    },
1415                }
1416            }
1417        }
1418
1419        result
1420    }
1421}
1422
1423/// Note the child dependencies of a scope end selector
1424/// This is necessary because the scope end selector is not bound to :scope
1425///
1426/// e.g @scope to (.b) {:scope .a .c {...}}
1427/// in the case of the following:
1428/// <div class=a><div id=x class=b><div class=c></div></div></div>
1429///
1430/// If we toggle class "b" in x, we would have to go up to find .a
1431/// if we wanted to invalidate correctly. However, this is costly.
1432/// Instead we just invalidate to the subject of the selector .c
1433pub fn note_scope_dependency_force_at_subject<'selectors>(
1434    dependency: &'selectors Dependency,
1435    current_host: Option<OpaqueElement>,
1436    scope: Option<OpaqueElement>,
1437    traversed_non_subject: bool,
1438) -> Vec<Invalidation<'selectors>> {
1439    let mut invalidations: Vec<Invalidation> = Vec::new();
1440    if let Some(next) = dependency.next.as_ref() {
1441        for dep in next.slice() {
1442            if dep.selector.is_rightmost(dep.selector_offset) && !traversed_non_subject {
1443                continue;
1444            }
1445
1446            // Follow the normal dependencies as far as we can, leaving
1447            // other kinds to their own invalidation mechanisms elsewhere
1448            if dep.next.is_some()
1449                && matches!(
1450                    dep.invalidation_kind(),
1451                    DependencyInvalidationKind::Normal(_)
1452                )
1453            {
1454                invalidations.extend(note_scope_dependency_force_at_subject(
1455                    dep,
1456                    current_host,
1457                    scope,
1458                    // Force add from now on because we
1459                    // passed through a non-subject compound
1460                    true,
1461                ));
1462            } else {
1463                let invalidation = Invalidation::new_subject_invalidation(dep, current_host, scope);
1464
1465                invalidations.push(invalidation);
1466            }
1467        }
1468    }
1469    invalidations
1470}