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 {
503            match component {
504                Component::Scope => {
505                    self.found_scope_in_negation = true;
506                },
507                _ => {},
508            }
509        }
510        true
511    }
512
513    fn visit_relative_selector_list(
514        &mut self,
515        _list: &[selectors::parser::RelativeSelector<Self::Impl>],
516    ) -> bool {
517        true
518    }
519
520    fn visit_selector_list(
521        &mut self,
522        list_kind: selectors::visitor::SelectorListKind,
523        list: &[selectors::parser::Selector<Self::Impl>],
524    ) -> bool {
525        for nested in list {
526            let nested_visitor = Self {
527                in_negation: list_kind.in_negation(),
528                found_scope_in_negation: false,
529            };
530
531            self.found_scope_in_negation |= nested_visitor.traverse_selector(nested);
532        }
533        true
534    }
535
536    fn visit_complex_selector(&mut self, _combinator_to_right: Option<Combinator>) -> bool {
537        true
538    }
539}
540
541/// Determines if we can find a selector in the form of :not(:scope)
542/// anywhere down the chain of dependencies.
543pub fn any_next_has_scope_in_negation(dependency: &Dependency) -> bool {
544    let next = match dependency.next.as_ref() {
545        None => return false,
546        Some(l) => l,
547    };
548
549    next.slice().iter().any(|dep| {
550        let visitor = NegationScopeVisitor::new();
551        visitor.traverse_dependency(dep)
552    })
553}
554
555impl<'a> fmt::Debug for Invalidation<'a> {
556    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
557        use cssparser::ToCss;
558
559        f.write_str("Invalidation(")?;
560        for component in self
561            .dependency
562            .selector
563            .iter_raw_parse_order_from(self.offset)
564        {
565            if matches!(*component, Component::Combinator(..)) {
566                break;
567            }
568            component.to_css(f)?;
569        }
570        f.write_char(')')
571    }
572}
573
574/// The result of processing a single invalidation for a given element.
575struct ProcessInvalidationResult {
576    /// Whether the element itself was invalidated.
577    invalidated_self: bool,
578    /// Whether the invalidation matched, either invalidating the element or
579    /// generating another invalidation.
580    matched: bool,
581}
582
583/// The result of a whole invalidation process for a given element.
584pub struct InvalidationResult {
585    /// Whether the element itself was invalidated.
586    invalidated_self: bool,
587    /// Whether the element's descendants were invalidated.
588    invalidated_descendants: bool,
589    /// Whether the element's siblings were invalidated.
590    invalidated_siblings: bool,
591}
592
593impl InvalidationResult {
594    /// Create an emtpy result.
595    pub fn empty() -> Self {
596        Self {
597            invalidated_self: false,
598            invalidated_descendants: false,
599            invalidated_siblings: false,
600        }
601    }
602
603    /// Whether the invalidation has invalidate the element itself.
604    pub fn has_invalidated_self(&self) -> bool {
605        self.invalidated_self
606    }
607
608    /// Whether the invalidation has invalidate desendants.
609    pub fn has_invalidated_descendants(&self) -> bool {
610        self.invalidated_descendants
611    }
612
613    /// Whether the invalidation has invalidate siblings.
614    pub fn has_invalidated_siblings(&self) -> bool {
615        self.invalidated_siblings
616    }
617}
618
619impl<'a, 'b, 'c, E, P: 'a> TreeStyleInvalidator<'a, 'b, 'c, E, P>
620where
621    'b: 'a,
622    E: TElement,
623    P: InvalidationProcessor<'b, 'c, E>,
624{
625    /// Trivially constructs a new `TreeStyleInvalidator`.
626    pub fn new(
627        element: E,
628        stack_limit_checker: Option<&'a StackLimitChecker>,
629        processor: &'a mut P,
630    ) -> Self {
631        Self {
632            element,
633            stack_limit_checker,
634            processor,
635            _marker: std::marker::PhantomData,
636        }
637    }
638
639    /// Perform the invalidation pass.
640    pub fn invalidate(mut self) -> InvalidationResult {
641        debug!("StyleTreeInvalidator::invalidate({:?})", self.element);
642
643        let mut self_invalidations = InvalidationVector::new();
644        let mut descendant_invalidations = DescendantInvalidationLists::default();
645        let mut sibling_invalidations = InvalidationVector::new();
646
647        let mut invalidated_self = self.processor.collect_invalidations(
648            self.element,
649            &mut self_invalidations,
650            &mut descendant_invalidations,
651            &mut sibling_invalidations,
652        );
653
654        debug!("Collected invalidations (self: {}): ", invalidated_self);
655        debug!(
656            " > self: {}, {:?}",
657            self_invalidations.len(),
658            self_invalidations
659        );
660        debug!(" > descendants: {:?}", descendant_invalidations);
661        debug!(
662            " > siblings: {}, {:?}",
663            sibling_invalidations.len(),
664            sibling_invalidations
665        );
666
667        let invalidated_self_from_collection = invalidated_self;
668
669        invalidated_self |= self.process_descendant_invalidations(
670            &self_invalidations,
671            &mut descendant_invalidations,
672            &mut sibling_invalidations,
673            DescendantInvalidationKind::Dom,
674        );
675
676        if invalidated_self && !invalidated_self_from_collection {
677            self.processor.invalidated_self(self.element);
678        }
679
680        let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations);
681        let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations);
682
683        InvalidationResult {
684            invalidated_self,
685            invalidated_descendants,
686            invalidated_siblings,
687        }
688    }
689
690    /// Go through later DOM siblings, invalidating style as needed using the
691    /// `sibling_invalidations` list.
692    ///
693    /// Returns whether any sibling's style or any sibling descendant's style
694    /// was invalidated.
695    fn invalidate_siblings(&mut self, sibling_invalidations: &mut InvalidationVector<'b>) -> bool {
696        if sibling_invalidations.is_empty() {
697            return false;
698        }
699
700        let mut current = self
701            .processor
702            .sibling_traversal_map()
703            .next_sibling_for(&self.element);
704        let mut any_invalidated = false;
705
706        while let Some(sibling) = current {
707            let mut sibling_invalidator =
708                TreeStyleInvalidator::new(sibling, self.stack_limit_checker, self.processor);
709
710            let mut invalidations_for_descendants = DescendantInvalidationLists::default();
711            let invalidated_sibling = sibling_invalidator.process_sibling_invalidations(
712                &mut invalidations_for_descendants,
713                sibling_invalidations,
714            );
715
716            if invalidated_sibling {
717                sibling_invalidator
718                    .processor
719                    .invalidated_sibling(sibling, self.element);
720            }
721
722            any_invalidated |= invalidated_sibling;
723
724            any_invalidated |=
725                sibling_invalidator.invalidate_descendants(&invalidations_for_descendants);
726
727            if sibling_invalidations.is_empty() {
728                break;
729            }
730
731            current = self
732                .processor
733                .sibling_traversal_map()
734                .next_sibling_for(&sibling);
735        }
736
737        any_invalidated
738    }
739
740    fn invalidate_pseudo_element_or_nac(
741        &mut self,
742        child: E,
743        invalidations: &[Invalidation<'b>],
744    ) -> bool {
745        let mut sibling_invalidations = InvalidationVector::new();
746
747        let result = self.invalidate_child(
748            child,
749            invalidations,
750            &mut sibling_invalidations,
751            DescendantInvalidationKind::Dom,
752        );
753
754        // Roots of NAC subtrees can indeed generate sibling invalidations, but
755        // they can be just ignored, since they have no siblings.
756        //
757        // Note that we can end up testing selectors that wouldn't end up
758        // matching due to this being NAC, like those coming from document
759        // rules, but we overinvalidate instead of checking this.
760
761        result
762    }
763
764    /// Invalidate a child and recurse down invalidating its descendants if
765    /// needed.
766    fn invalidate_child(
767        &mut self,
768        child: E,
769        invalidations: &[Invalidation<'b>],
770        sibling_invalidations: &mut InvalidationVector<'b>,
771        descendant_invalidation_kind: DescendantInvalidationKind,
772    ) -> bool {
773        let mut invalidations_for_descendants = DescendantInvalidationLists::default();
774
775        let mut invalidated_child = false;
776        let invalidated_descendants = {
777            let mut child_invalidator =
778                TreeStyleInvalidator::new(child, self.stack_limit_checker, self.processor);
779
780            invalidated_child |= child_invalidator.process_sibling_invalidations(
781                &mut invalidations_for_descendants,
782                sibling_invalidations,
783            );
784
785            invalidated_child |= child_invalidator.process_descendant_invalidations(
786                invalidations,
787                &mut invalidations_for_descendants,
788                sibling_invalidations,
789                descendant_invalidation_kind,
790            );
791
792            if invalidated_child {
793                child_invalidator.processor.invalidated_self(child);
794            }
795
796            child_invalidator.invalidate_descendants(&invalidations_for_descendants)
797        };
798
799        // The child may not be a flattened tree child of the current element,
800        // but may be arbitrarily deep.
801        //
802        // Since we keep the traversal flags in terms of the flattened tree,
803        // we need to propagate it as appropriate.
804        if invalidated_child || invalidated_descendants {
805            self.processor.invalidated_descendants(self.element, child);
806        }
807
808        invalidated_child || invalidated_descendants
809    }
810
811    fn invalidate_nac(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
812        let mut any_nac_root = false;
813
814        let element = self.element;
815        element.each_anonymous_content_child(|nac| {
816            any_nac_root |= self.invalidate_pseudo_element_or_nac(nac, invalidations);
817        });
818
819        any_nac_root
820    }
821
822    // NB: It's important that this operates on DOM children, which is what
823    // selector-matching operates on.
824    fn invalidate_dom_descendants_of(
825        &mut self,
826        parent: E::ConcreteNode,
827        invalidations: &[Invalidation<'b>],
828    ) -> bool {
829        let mut any_descendant = false;
830
831        let mut sibling_invalidations = InvalidationVector::new();
832        for child in parent.dom_children() {
833            let child = match child.as_element() {
834                Some(e) => e,
835                None => continue,
836            };
837
838            any_descendant |= self.invalidate_child(
839                child,
840                invalidations,
841                &mut sibling_invalidations,
842                DescendantInvalidationKind::Dom,
843            );
844        }
845
846        any_descendant
847    }
848
849    fn invalidate_parts_in_shadow_tree(
850        &mut self,
851        shadow: <E::ConcreteNode as TNode>::ConcreteShadowRoot,
852        invalidations: &[Invalidation<'b>],
853    ) -> bool {
854        debug_assert!(!invalidations.is_empty());
855
856        let mut any = false;
857        let mut sibling_invalidations = InvalidationVector::new();
858
859        for node in shadow.as_node().dom_descendants() {
860            let element = match node.as_element() {
861                Some(e) => e,
862                None => continue,
863            };
864
865            if element.has_part_attr() {
866                any |= self.invalidate_child(
867                    element,
868                    invalidations,
869                    &mut sibling_invalidations,
870                    DescendantInvalidationKind::Part,
871                );
872                debug_assert!(
873                    sibling_invalidations.is_empty(),
874                    "::part() shouldn't have sibling combinators to the right, \
875                     this makes no sense! {:?}",
876                    sibling_invalidations
877                );
878            }
879
880            if let Some(shadow) = element.shadow_root() {
881                if element.exports_any_part() {
882                    any |= self.invalidate_parts_in_shadow_tree(shadow, invalidations)
883                }
884            }
885        }
886
887        any
888    }
889
890    fn invalidate_parts(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
891        if invalidations.is_empty() {
892            return false;
893        }
894
895        let shadow = match self.element.shadow_root() {
896            Some(s) => s,
897            None => return false,
898        };
899
900        self.invalidate_parts_in_shadow_tree(shadow, invalidations)
901    }
902
903    fn invalidate_slotted_elements(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
904        if invalidations.is_empty() {
905            return false;
906        }
907
908        let slot = self.element;
909        self.invalidate_slotted_elements_in_slot(slot, invalidations)
910    }
911
912    fn invalidate_slotted_elements_in_slot(
913        &mut self,
914        slot: E,
915        invalidations: &[Invalidation<'b>],
916    ) -> bool {
917        let mut any = false;
918
919        let mut sibling_invalidations = InvalidationVector::new();
920        for node in slot.slotted_nodes() {
921            let element = match node.as_element() {
922                Some(e) => e,
923                None => continue,
924            };
925
926            if element.is_html_slot_element() {
927                any |= self.invalidate_slotted_elements_in_slot(element, invalidations);
928            } else {
929                any |= self.invalidate_child(
930                    element,
931                    invalidations,
932                    &mut sibling_invalidations,
933                    DescendantInvalidationKind::Slotted,
934                );
935            }
936
937            debug_assert!(
938                sibling_invalidations.is_empty(),
939                "::slotted() shouldn't have sibling combinators to the right, \
940                 this makes no sense! {:?}",
941                sibling_invalidations
942            );
943        }
944
945        any
946    }
947
948    fn invalidate_non_slotted_descendants(&mut self, invalidations: &[Invalidation<'b>]) -> bool {
949        if invalidations.is_empty() {
950            return false;
951        }
952
953        if self.processor.light_tree_only() {
954            let node = self.element.as_node();
955            return self.invalidate_dom_descendants_of(node, invalidations);
956        }
957
958        let mut any_descendant = false;
959
960        // NOTE(emilio): This is only needed for Shadow DOM to invalidate
961        // correctly on :host(..) changes. Instead of doing this, we could add
962        // a third kind of invalidation list that walks shadow root children,
963        // but it's not clear it's worth it.
964        //
965        // Also, it's needed as of right now for document state invalidation,
966        // where we rely on iterating every element that ends up in the composed
967        // doc, but we could fix that invalidating per subtree.
968        if let Some(root) = self.element.shadow_root() {
969            any_descendant |= self.invalidate_dom_descendants_of(root.as_node(), invalidations);
970        }
971
972        any_descendant |= self.invalidate_dom_descendants_of(self.element.as_node(), invalidations);
973
974        any_descendant |= self.invalidate_nac(invalidations);
975
976        any_descendant
977    }
978
979    /// Given the descendant invalidation lists, go through the current
980    /// element's descendants, and invalidate style on them.
981    fn invalidate_descendants(&mut self, invalidations: &DescendantInvalidationLists<'b>) -> bool {
982        if invalidations.is_empty() {
983            return false;
984        }
985
986        debug!(
987            "StyleTreeInvalidator::invalidate_descendants({:?})",
988            self.element
989        );
990        debug!(" > {:?}", invalidations);
991
992        let should_process = self.processor.should_process_descendants(self.element);
993
994        if !should_process {
995            return false;
996        }
997
998        if let Some(checker) = self.stack_limit_checker {
999            if checker.limit_exceeded() {
1000                self.processor.recursion_limit_exceeded(self.element);
1001                return true;
1002            }
1003        }
1004
1005        let mut any_descendant = false;
1006
1007        any_descendant |= self.invalidate_non_slotted_descendants(&invalidations.dom_descendants);
1008        any_descendant |= self.invalidate_slotted_elements(&invalidations.slotted_descendants);
1009        any_descendant |= self.invalidate_parts(&invalidations.parts);
1010
1011        any_descendant
1012    }
1013
1014    /// Process the given sibling invalidations coming from our previous
1015    /// sibling.
1016    ///
1017    /// The sibling invalidations are somewhat special because they can be
1018    /// modified on the fly. New invalidations may be added and removed.
1019    ///
1020    /// In particular, all descendants get the same set of invalidations from
1021    /// the parent, but the invalidations from a given sibling depend on the
1022    /// ones we got from the previous one.
1023    ///
1024    /// Returns whether invalidated the current element's style.
1025    fn process_sibling_invalidations(
1026        &mut self,
1027        descendant_invalidations: &mut DescendantInvalidationLists<'b>,
1028        sibling_invalidations: &mut InvalidationVector<'b>,
1029    ) -> bool {
1030        let mut i = 0;
1031        let mut new_sibling_invalidations = InvalidationVector::new();
1032        let mut invalidated_self = false;
1033
1034        while i < sibling_invalidations.len() {
1035            let result = self.process_invalidation(
1036                &sibling_invalidations[i],
1037                descendant_invalidations,
1038                &mut new_sibling_invalidations,
1039                InvalidationKind::Sibling,
1040            );
1041
1042            invalidated_self |= result.invalidated_self;
1043            sibling_invalidations[i].matched_by_any_previous |= result.matched;
1044            if sibling_invalidations[i].effective_for_next() {
1045                i += 1;
1046            } else {
1047                sibling_invalidations.remove(i);
1048            }
1049        }
1050
1051        sibling_invalidations.extend(new_sibling_invalidations.drain(..));
1052        invalidated_self
1053    }
1054
1055    /// Process a given invalidation list coming from our parent,
1056    /// adding to `descendant_invalidations` and `sibling_invalidations` as
1057    /// needed.
1058    ///
1059    /// Returns whether our style was invalidated as a result.
1060    fn process_descendant_invalidations(
1061        &mut self,
1062        invalidations: &[Invalidation<'b>],
1063        descendant_invalidations: &mut DescendantInvalidationLists<'b>,
1064        sibling_invalidations: &mut InvalidationVector<'b>,
1065        descendant_invalidation_kind: DescendantInvalidationKind,
1066    ) -> bool {
1067        let mut invalidated = false;
1068
1069        for invalidation in invalidations {
1070            let result = self.process_invalidation(
1071                invalidation,
1072                descendant_invalidations,
1073                sibling_invalidations,
1074                InvalidationKind::Descendant(descendant_invalidation_kind),
1075            );
1076
1077            invalidated |= result.invalidated_self;
1078            if invalidation.effective_for_next() {
1079                let mut invalidation = invalidation.clone();
1080                invalidation.matched_by_any_previous |= result.matched;
1081                debug_assert_eq!(
1082                    descendant_invalidation_kind,
1083                    DescendantInvalidationKind::Dom,
1084                    "Slotted or part invalidations don't propagate."
1085                );
1086                descendant_invalidations.dom_descendants.push(invalidation);
1087            }
1088        }
1089
1090        invalidated
1091    }
1092
1093    #[inline(always)]
1094    fn handle_fully_matched(
1095        &mut self,
1096        invalidation: &Invalidation<'b>,
1097    ) -> (ProcessInvalidationResult, SmallVec<[Invalidation<'b>; 1]>) {
1098        debug!(" > Invalidation matched completely");
1099        // We matched completely. If we're an inner selector now we need
1100        // to go outside our selector and carry on invalidating.
1101        let mut to_process: SmallVec<[&Dependency; 1]> = SmallVec::from([invalidation.dependency]);
1102        let mut next_invalidations: SmallVec<[Invalidation; 1]> = SmallVec::new();
1103        let mut result = ProcessInvalidationResult {
1104            invalidated_self: false,
1105            matched: false,
1106        };
1107
1108        while !to_process.is_empty() {
1109            let mut next_dependencies: SmallVec<[&Dependency; 1]> = SmallVec::new();
1110
1111            while let Some(dependency) = to_process.pop() {
1112                if let DependencyInvalidationKind::Scope(scope_kind) =
1113                    dependency.invalidation_kind()
1114                {
1115                    if scope_kind == ScopeDependencyInvalidationKind::ImplicitScope {
1116                        if let Some(ref deps) = dependency.next {
1117                            for dep in deps.as_ref().slice() {
1118                                let invalidation =
1119                                    Invalidation::new_always_effective_for_next_descendant(
1120                                        dep,
1121                                        invalidation.host,
1122                                        invalidation.scope,
1123                                    );
1124                                next_invalidations.push(invalidation);
1125                            }
1126                        }
1127                        continue;
1128                    }
1129
1130                    let force_add = any_next_has_scope_in_negation(dependency);
1131                    if scope_kind == ScopeDependencyInvalidationKind::ScopeEnd || force_add {
1132                        let invalidations = note_scope_dependency_force_at_subject(
1133                            dependency,
1134                            invalidation.host,
1135                            invalidation.scope,
1136                            force_add,
1137                        );
1138
1139                        next_invalidations.extend(invalidations);
1140
1141                        continue;
1142                    }
1143                }
1144
1145                match dependency.next {
1146                    None => {
1147                        result.invalidated_self = true;
1148                        result.matched = true;
1149                    },
1150                    Some(ref deps) => {
1151                        for n in deps.as_ref().slice() {
1152                            let invalidation_kind = n.invalidation_kind();
1153                            match invalidation_kind {
1154                                DependencyInvalidationKind::FullSelector => unreachable!(),
1155                                DependencyInvalidationKind::Normal(_) => next_dependencies.push(n),
1156                                //TODO(descalente, bug 1934061): Add specific handling for implicit scopes.
1157                                DependencyInvalidationKind::Scope(_) => {
1158                                    next_dependencies.push(n);
1159                                },
1160                                DependencyInvalidationKind::Relative(kind) => {
1161                                    self.processor.found_relative_selector_invalidation(
1162                                        self.element,
1163                                        kind,
1164                                        n,
1165                                    );
1166                                    result.matched = true;
1167                                },
1168                            }
1169                        }
1170                    },
1171                };
1172            }
1173
1174            for cur_dependency in next_dependencies.as_ref() {
1175                let scope = matches!(
1176                    invalidation.dependency.invalidation_kind(),
1177                    DependencyInvalidationKind::Scope(_)
1178                )
1179                .then(|| self.element.opaque());
1180                debug!(" > Checking outer dependency {:?}", cur_dependency);
1181
1182                // The inner selector changed, now check if the full
1183                // previous part of the selector did, before keeping
1184                // checking for descendants.
1185                if !self
1186                    .processor
1187                    .check_outer_dependency(cur_dependency, self.element, scope)
1188                {
1189                    // Dependency is not relevant, do not note it down
1190                    continue;
1191                }
1192
1193                let invalidation_kind = cur_dependency.invalidation_kind();
1194                if matches!(
1195                    invalidation_kind,
1196                    DependencyInvalidationKind::Normal(NormalDependencyInvalidationKind::Element)
1197                ) || (matches!(invalidation_kind, DependencyInvalidationKind::Scope(_))
1198                    && cur_dependency
1199                        .selector
1200                        .is_rightmost(cur_dependency.selector_offset))
1201                {
1202                    // Add to dependency stack to process its next dependencies.
1203                    to_process.push(cur_dependency);
1204                    continue;
1205                }
1206
1207                debug!(" > Generating invalidation");
1208                next_invalidations.push(Invalidation::new(
1209                    cur_dependency,
1210                    invalidation.host,
1211                    scope,
1212                ));
1213            }
1214        }
1215        return (result, next_invalidations);
1216    }
1217
1218    /// Processes a given invalidation, potentially invalidating the style of
1219    /// the current element.
1220    ///
1221    /// Returns whether invalidated the style of the element, and whether the
1222    /// invalidation should be effective to subsequent siblings or descendants
1223    /// down in the tree.
1224    fn process_invalidation(
1225        &mut self,
1226        invalidation: &Invalidation<'b>,
1227        descendant_invalidations: &mut DescendantInvalidationLists<'b>,
1228        sibling_invalidations: &mut InvalidationVector<'b>,
1229        invalidation_kind: InvalidationKind,
1230    ) -> ProcessInvalidationResult {
1231        debug!(
1232            "TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})",
1233            self.element, invalidation, invalidation_kind
1234        );
1235
1236        let matching_result = {
1237            let context = self.processor.matching_context();
1238            context.current_host = invalidation.host;
1239
1240            context.nest_for_scope_condition(invalidation.scope, |ctx| {
1241                matches_compound_selector_from(
1242                    &invalidation.dependency.selector,
1243                    invalidation.offset,
1244                    ctx,
1245                    &self.element,
1246                )
1247            })
1248        };
1249
1250        let (mut result, next_invalidations) = match matching_result {
1251            CompoundSelectorMatchingResult::NotMatched => {
1252                return ProcessInvalidationResult {
1253                    invalidated_self: false,
1254                    matched: false,
1255                }
1256            },
1257            CompoundSelectorMatchingResult::FullyMatched => self.handle_fully_matched(invalidation),
1258            CompoundSelectorMatchingResult::Matched {
1259                next_combinator_offset,
1260            } => (
1261                ProcessInvalidationResult {
1262                    invalidated_self: false,
1263                    matched: true,
1264                },
1265                smallvec![Invalidation {
1266                    dependency: invalidation.dependency,
1267                    host: invalidation.host,
1268                    scope: invalidation.scope,
1269                    offset: next_combinator_offset + 1,
1270                    matched_by_any_previous: false,
1271                    always_effective_for_next_descendant: invalidation
1272                        .always_effective_for_next_descendant,
1273                }],
1274            ),
1275        };
1276
1277        for next_invalidation in next_invalidations {
1278            let next_invalidation_kind = if next_invalidation.always_effective_for_next_descendant {
1279                InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
1280            } else {
1281                debug_assert_ne!(
1282                    next_invalidation.offset, 0,
1283                    "Rightmost selectors shouldn't generate more invalidations",
1284                );
1285
1286                let next_combinator = next_invalidation
1287                    .dependency
1288                    .selector
1289                    .combinator_at_parse_order(next_invalidation.offset - 1);
1290
1291                if matches!(next_combinator, Combinator::PseudoElement)
1292                    && self.processor.invalidates_on_pseudo_element()
1293                {
1294                    // We need to invalidate the element whenever pseudos change, for
1295                    // two reasons:
1296                    //
1297                    //  * Eager pseudo styles are stored as part of the originating
1298                    //    element's computed style.
1299                    //
1300                    //  * Lazy pseudo-styles might be cached on the originating
1301                    //    element's pseudo-style cache.
1302                    //
1303                    // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS
1304                    // hint?).
1305                    //
1306                    // Note that we'll also restyle the pseudo-element because it would
1307                    // match this invalidation.
1308                    result.invalidated_self = true;
1309
1310                    // For highlight pseudos (::selection, ::highlight, ::target-text),
1311                    // we also need to trigger a repaint since their styles are resolved
1312                    // lazily during painting.
1313                    if next_invalidation
1314                        .dependency
1315                        .selector
1316                        .pseudo_element()
1317                        .is_some_and(|p| p.is_lazy_painted_highlight_pseudo())
1318                    {
1319                        self.processor.invalidated_highlight_pseudo(self.element);
1320                    }
1321                }
1322
1323                debug!(
1324                    " > Invalidation matched, next: {:?}, ({:?})",
1325                    next_invalidation, next_combinator
1326                );
1327
1328                next_invalidation.kind()
1329            };
1330
1331            // We can skip pushing under some circumstances, and we should
1332            // because otherwise the invalidation list could grow
1333            // exponentially.
1334            //
1335            //  * First of all, both invalidations need to be of the same
1336            //    kind. This is because of how we propagate them going to
1337            //    the right of the tree for sibling invalidations and going
1338            //    down the tree for children invalidations. A sibling
1339            //    invalidation that ends up generating a children
1340            //    invalidation ends up (correctly) in five different lists,
1341            //    not in the same list five different times.
1342            //
1343            //  * Then, the invalidation needs to be matched by a previous
1344            //    ancestor/sibling, in order to know that this invalidation
1345            //    has been generated already.
1346            //
1347            //  * Finally, the new invalidation needs to be
1348            //    `effective_for_next()`, in order for us to know that it is
1349            //    still in the list, since we remove the dependencies that
1350            //    aren't from the lists for our children / siblings.
1351            //
1352            // To go through an example, let's imagine we are processing a
1353            // dom subtree like:
1354            //
1355            //   <div><address><div><div/></div></address></div>
1356            //
1357            // And an invalidation list with a single invalidation like:
1358            //
1359            //   [div div div]
1360            //
1361            // When we process the invalidation list for the outer div, we
1362            // match it, and generate a `div div` invalidation, so for the
1363            // <address> child we have:
1364            //
1365            //   [div div div, div div]
1366            //
1367            // With the first of them marked as `matched`.
1368            //
1369            // When we process the <address> child, we don't match any of
1370            // them, so both invalidations go untouched to our children.
1371            //
1372            // When we process the second <div>, we match _both_
1373            // invalidations.
1374            //
1375            // However, when matching the first, we can tell it's been
1376            // matched, and not push the corresponding `div div`
1377            // invalidation, since we know it's necessarily already on the
1378            // list.
1379            //
1380            // Thus, without skipping the push, we'll arrive to the
1381            // innermost <div> with:
1382            //
1383            //   [div div div, div div, div div, div]
1384            //
1385            // While skipping it, we won't arrive here with duplicating
1386            // dependencies:
1387            //
1388            //   [div div div, div div, div]
1389            //
1390            let can_skip_pushing = next_invalidation_kind == invalidation_kind
1391                && invalidation.matched_by_any_previous
1392                && next_invalidation.effective_for_next();
1393
1394            if can_skip_pushing {
1395                debug!(
1396                    " > Can avoid push, since the invalidation had \
1397                    already been matched before"
1398                );
1399            } else {
1400                match next_invalidation_kind {
1401                    InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
1402                        descendant_invalidations
1403                            .dom_descendants
1404                            .push(next_invalidation);
1405                    },
1406                    InvalidationKind::Descendant(DescendantInvalidationKind::Part) => {
1407                        descendant_invalidations.parts.push(next_invalidation);
1408                    },
1409                    InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
1410                        descendant_invalidations
1411                            .slotted_descendants
1412                            .push(next_invalidation);
1413                    },
1414                    InvalidationKind::Sibling => {
1415                        sibling_invalidations.push(next_invalidation);
1416                    },
1417                }
1418            }
1419        }
1420
1421        result
1422    }
1423}
1424
1425/// Note the child dependencies of a scope end selector
1426/// This is necessary because the scope end selector is not bound to :scope
1427///
1428/// e.g @scope to (.b) {:scope .a .c {...}}
1429/// in the case of the following:
1430/// <div class=a><div id=x class=b><div class=c></div></div></div>
1431///
1432/// If we toggle class "b" in x, we would have to go up to find .a
1433/// if we wanted to invalidate correctly. However, this is costly.
1434/// Instead we just invalidate to the subject of the selector .c
1435pub fn note_scope_dependency_force_at_subject<'selectors>(
1436    dependency: &'selectors Dependency,
1437    current_host: Option<OpaqueElement>,
1438    scope: Option<OpaqueElement>,
1439    traversed_non_subject: bool,
1440) -> Vec<Invalidation<'selectors>> {
1441    let mut invalidations: Vec<Invalidation> = Vec::new();
1442    if let Some(next) = dependency.next.as_ref() {
1443        for dep in next.slice() {
1444            if dep.selector.is_rightmost(dep.selector_offset) && !traversed_non_subject {
1445                continue;
1446            }
1447
1448            // Follow the normal dependencies as far as we can, leaving
1449            // other kinds to their own invalidation mechanisms elsewhere
1450            if dep.next.is_some()
1451                && matches!(
1452                    dep.invalidation_kind(),
1453                    DependencyInvalidationKind::Normal(_)
1454                )
1455            {
1456                invalidations.extend(note_scope_dependency_force_at_subject(
1457                    dep,
1458                    current_host,
1459                    scope,
1460                    // Force add from now on because we
1461                    // passed through a non-subject compound
1462                    true,
1463                ));
1464            } else {
1465                let invalidation = Invalidation::new_subject_invalidation(dep, current_host, scope);
1466
1467                invalidations.push(invalidation);
1468            }
1469        }
1470    }
1471    invalidations
1472}