/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Invalidation of element styles relative selectors. use crate::data::ElementData; use crate::dom::{TElement, TNode}; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::ServoElementSnapshotTable; use crate::invalidation::element::element_wrapper::ElementWrapper; use crate::invalidation::element::invalidation_map::{ Dependency, DependencyInvalidationKind, NormalDependencyInvalidationKind, RelativeDependencyInvalidationKind, RelativeSelectorInvalidationMap, }; use crate::invalidation::element::invalidator::{ DescendantInvalidationLists, Invalidation, InvalidationProcessor, InvalidationResult, InvalidationVector, SiblingTraversalMap, TreeStyleInvalidator, }; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::invalidation::element::state_and_attributes::{ check_dependency, dependency_may_be_relevant, invalidated_descendants, invalidated_self, invalidated_sibling, push_invalidation, should_process_descendants, }; #[cfg(feature = "servo")] use crate::selector_parser::SnapshotMap as ServoElementSnapshotTable; use crate::stylist::{CascadeData, Stylist}; use dom::ElementState; use fxhash::FxHashMap; use selectors::matching::{ matches_compound_selector_from, matches_selector, CompoundSelectorMatchingResult, ElementSelectorFlags, IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode, }; use selectors::parser::{Combinator, SelectorKey}; use selectors::OpaqueElement; use smallvec::SmallVec; use std::ops::DerefMut; /// Kind of DOM mutation this relative selector invalidation is being carried out in. #[derive(Clone, Copy)] pub enum DomMutationOperation { /// Insertion operation, can cause side effect, but presumed already happened. Insert, /// Append operation, cannot cause side effect. Append, /// Removal operation, can cause side effect, but presumed already happened. Sibling relationships are destroyed. Remove, /// Invalidating for side effect of a DOM operation, for the previous sibling. SideEffectPrevSibling, /// Invalidating for side effect of a DOM operation, for the next sibling. SideEffectNextSibling, } impl DomMutationOperation { fn accept(&self, d: &Dependency, e: E) -> bool { match self { Self::Insert | Self::Append | Self::Remove => { !e.relative_selector_search_direction().is_empty() }, // `:has(+ .a + .b)` with `.anchor + .a + .remove + .b` - `.a` would be present // in the search path. Self::SideEffectPrevSibling => { !e.relative_selector_search_direction().is_empty() && d.right_combinator_is_next_sibling() }, // If an element is being removed and would cause next-sibling match to happen, // e.g. `:has(+ .a)` with `.anchor + .remove + .a`, `.a` isn't yet searched // for relative selector matching. Self::SideEffectNextSibling => d.dependency_is_relative_with_single_next_sibling(), } } fn is_side_effect(&self) -> bool { match self { Self::Insert | Self::Append | Self::Remove => false, Self::SideEffectPrevSibling | Self::SideEffectNextSibling => true, } } } /// Context required to try and optimize away relative dependencies. struct OptimizationContext<'a, E: TElement> { sibling_traversal_map: &'a SiblingTraversalMap, quirks_mode: QuirksMode, operation: DomMutationOperation, } impl<'a, E: TElement> OptimizationContext<'a, E> { fn can_be_ignored( &self, is_subtree: bool, element: E, host: Option, dependency: &Dependency, ) -> bool { if is_subtree { // Subtree elements don't have unaffected sibling to look at. return false; } debug_assert!( matches!( dependency.invalidation_kind(), DependencyInvalidationKind::Relative(..) ), "Non-relative selector being evaluated for optimization" ); // This optimization predecates on the fact that there may be a sibling that can readily // "take over" this element. let sibling = match self.sibling_traversal_map.prev_sibling_for(&element) { None => { if matches!(self.operation, DomMutationOperation::Append) { return false; } match self.sibling_traversal_map.next_sibling_for(&element) { Some(s) => s, None => return false, } }, Some(s) => s, }; { // Run through the affected compund. let mut iter = dependency.selector.iter_from(dependency.selector_offset); while let Some(c) = iter.next() { if c.has_indexed_selector_in_subject() { // We do not calculate indices during invalidation as they're wasteful - as a side effect, // such selectors always return true, breaking this optimization. Note that we only check // this compound only because the check to skip compares against this element's sibling. // i.e. Given `:has(:nth-child(2) .foo)`, we'd try to find `.foo`'s sibling, which // shares `:nth-child` up the selector. return false; } } } let is_rightmost = dependency.selector_offset == 0; if !is_rightmost { let combinator = dependency .selector .combinator_at_match_order(dependency.selector_offset - 1); if combinator.is_ancestor() { // We can safely ignore these, since we're about to traverse the // rest of the affected tree anyway to find the rightmost invalidated element. return true; } if combinator.is_sibling() && matches!(self.operation, DomMutationOperation::Append) { // If we're in the subtree, same argument applies as ancestor combinator case. // If we're at the top of the DOM tree being mutated, we can ignore it if the // operation is append - we know we'll cover all the later siblings and their descendants. return true; } } let mut caches = SelectorCaches::default(); let mut matching_context = MatchingContext::new( MatchingMode::Normal, None, &mut caches, self.quirks_mode, NeedsSelectorFlags::No, MatchingForInvalidation::Yes, ); matching_context.current_host = host; let sibling_matches = matches_selector( &dependency.selector, dependency.selector_offset, None, &sibling, &mut matching_context, ); if sibling_matches { // Remember that at this point, we know that the combinator to the right of this // compound is a sibling combinator. Effectively, we've found a standin for the // element we're mutating. // e.g. Given `:has(... .a ~ .b ...)`, we're the mutating element matching `... .a`, // if we find a sibling that matches the `... .a`, it can stand in for us. debug_assert!( dependency.parent.is_some(), "No relative selector outer dependency?" ); return dependency.parent.as_ref().map_or(false, |par| { // ... However, if the standin sibling can be the anchor, we can't skip it, since // that sibling should be invlidated to become the anchor. !matches_selector( &par.selector, par.selector_offset, None, &sibling, &mut matching_context, ) }); } // Ok, there's no standin element - but would this element have matched the upstream // selector anyway? If we don't, either the match exists somewhere far from us // (In which case our mutation doesn't really matter), or it doesn't exist at all, // so we can just skip the invalidation. let (combinator, prev_offset) = { let mut iter = dependency.selector.iter_from(dependency.selector_offset); let mut o = dependency.selector_offset; while iter.next().is_some() { o += 1; } let combinator = iter.next_sequence(); o += 1; debug_assert!( combinator.is_some(), "Should at least see a relative combinator" ); (combinator.unwrap(), o) }; if combinator.is_sibling() { if prev_offset >= dependency.selector.len() - 1 { // Hit the relative combinator - we don't have enough information to // see if there's going to be a downstream match. return false; } if matches!(self.operation, DomMutationOperation::Remove) { // This is sad :( The sibling relation of a removed element is lost, and we don't // propagate sibling traversal map to selector matching context, so we need to do // manual matching here. TODO(dshin): Worth changing selector matching for this? // Try matching this compound, then... // Note: We'll not hit the leftmost sequence (Since we would have returned early // if we'd hit the relative selector anchor). if matches!( matches_compound_selector_from( &dependency.selector, dependency.selector.len() - prev_offset + 1, &mut matching_context, &element ), CompoundSelectorMatchingResult::NotMatched ) { return true; } // ... Match the rest of the selector, manually traversing. let mut prev_sibling = self.sibling_traversal_map.prev_sibling_for(&element); while let Some(sib) = prev_sibling { if matches_selector( &dependency.selector, prev_offset, None, &sib, &mut matching_context, ) { return false; } if matches!(combinator, Combinator::NextSibling) { break; } prev_sibling = self.sibling_traversal_map.prev_sibling_for(&sib); } return true; } } !matches_selector( &dependency.selector, dependency.selector_offset, None, &element, &mut matching_context, ) } } /// Overall invalidator for handling relative selector invalidations. pub struct RelativeSelectorInvalidator<'a, 'b, E> where E: TElement + 'a, { /// Element triggering the invalidation. pub element: E, /// Quirks mode of the current invalidation. pub quirks_mode: QuirksMode, /// Snapshot containing changes to invalidate against. /// Can be None if it's a DOM mutation. pub snapshot_table: Option<&'b ServoElementSnapshotTable>, /// Callback to trigger when the subject element is invalidated. pub invalidated: fn(E, &InvalidationResult), /// The traversal map that should be used to process invalidations. pub sibling_traversal_map: SiblingTraversalMap, /// Marker for 'a lifetime. pub _marker: ::std::marker::PhantomData<&'a ()>, } struct RelativeSelectorInvalidation<'a> { host: Option, kind: RelativeDependencyInvalidationKind, dependency: &'a Dependency, } type ElementDependencies<'a> = SmallVec<[(Option, &'a Dependency); 1]>; type Dependencies<'a, E> = SmallVec<[(E, ElementDependencies<'a>); 1]>; type AlreadyInvalidated<'a, E> = SmallVec<[(E, Option, &'a Dependency); 2]>; /// Interface for collecting relative selector dependencies. pub struct RelativeSelectorDependencyCollector<'a, E> where E: TElement, { /// Dependencies that need to run through the normal invalidation that may generate /// a relative selector invalidation. dependencies: FxHashMap>, /// Dependencies that created an invalidation right away. invalidations: AlreadyInvalidated<'a, E>, /// The top element in the subtree being invalidated. top: E, /// Optional context that will be used to try and skip invalidations /// by running selector matches. optimization_context: Option>, } type Invalidations<'a> = SmallVec<[RelativeSelectorInvalidation<'a>; 1]>; type InnerInvalidations<'a, E> = SmallVec<[(E, RelativeSelectorInvalidation<'a>); 1]>; struct ToInvalidate<'a, E: TElement + 'a> { /// Dependencies to run through normal invalidator. dependencies: Dependencies<'a, E>, /// Dependencies already invalidated. invalidations: Invalidations<'a>, } impl<'a, E: TElement + 'a> Default for ToInvalidate<'a, E> { fn default() -> Self { Self { dependencies: Dependencies::default(), invalidations: Invalidations::default(), } } } fn dependency_selectors_match(a: &Dependency, b: &Dependency) -> bool { if a.invalidation_kind() != b.invalidation_kind() { return false; } if SelectorKey::new(&a.selector) != SelectorKey::new(&b.selector) { return false; } let mut a_parent = a.parent.as_ref(); let mut b_parent = b.parent.as_ref(); while let (Some(a_p), Some(b_p)) = (a_parent, b_parent) { if SelectorKey::new(&a_p.selector) != SelectorKey::new(&b_p.selector) { return false; } a_parent = a_p.parent.as_ref(); b_parent = b_p.parent.as_ref(); } a_parent.is_none() && b_parent.is_none() } impl<'a, E> RelativeSelectorDependencyCollector<'a, E> where E: TElement, { fn new(top: E, optimization_context: Option>) -> Self { Self { dependencies: FxHashMap::default(), invalidations: AlreadyInvalidated::default(), top, optimization_context, } } fn insert_invalidation( &mut self, element: E, dependency: &'a Dependency, host: Option, ) { match self .invalidations .iter_mut() .find(|(_, _, d)| dependency_selectors_match(dependency, d)) { Some((e, h, d)) => { // Just keep one. if d.selector_offset > dependency.selector_offset { (*e, *h, *d) = (element, host, dependency); } }, None => { self.invalidations.push((element, host, dependency)); }, } } /// Add this dependency, if it is unique (i.e. Different outer dependency or same outer dependency /// but requires a different invalidation traversal). pub fn add_dependency( &mut self, dependency: &'a Dependency, element: E, host: Option, ) { match dependency.invalidation_kind() { DependencyInvalidationKind::Normal(..) => { self.dependencies .entry(element) .and_modify(|v| v.push((host, dependency))) .or_default() .push((host, dependency)); }, DependencyInvalidationKind::Relative(kind) => { debug_assert!( dependency.parent.is_some(), "Orphaned inner relative selector?" ); if element != self.top && matches!( kind, RelativeDependencyInvalidationKind::Parent | RelativeDependencyInvalidationKind::PrevSibling | RelativeDependencyInvalidationKind::EarlierSibling ) { return; } self.insert_invalidation(element, dependency, host); }, }; } /// Get the dependencies in a list format. fn get(self) -> ToInvalidate<'a, E> { let mut result = ToInvalidate::default(); for (element, host, dependency) in self.invalidations { match dependency.invalidation_kind() { DependencyInvalidationKind::Normal(_) => { unreachable!("Inner selector in invalidation?") }, DependencyInvalidationKind::Relative(kind) => { if let Some(context) = self.optimization_context.as_ref() { if context.can_be_ignored(element != self.top, element, host, dependency) { continue; } } let dependency = dependency.parent.as_ref().unwrap(); result.invalidations.push(RelativeSelectorInvalidation { kind, host, dependency, }); // We move the invalidation up to the top of the subtree to avoid unnecessary traveral, but // this means that we need to take ancestor-earlier sibling invalidations into account, as // they'd look into earlier siblings of the top of the subtree as well. if element != self.top && matches!( kind, RelativeDependencyInvalidationKind::AncestorEarlierSibling | RelativeDependencyInvalidationKind::AncestorPrevSibling ) { result.invalidations.push(RelativeSelectorInvalidation { kind: if matches!( kind, RelativeDependencyInvalidationKind::AncestorPrevSibling ) { RelativeDependencyInvalidationKind::PrevSibling } else { RelativeDependencyInvalidationKind::EarlierSibling }, host, dependency, }); } }, }; } for (key, element_dependencies) in self.dependencies { // At least for now, we don't try to optimize away dependencies emitted from nested selectors. result.dependencies.push((key, element_dependencies)); } result } fn collect_all_dependencies_for_element( &mut self, element: E, scope: Option, quirks_mode: QuirksMode, map: &'a RelativeSelectorInvalidationMap, operation: DomMutationOperation, ) { element .id() .map(|v| match map.map.id_to_selector.get(v, quirks_mode) { Some(v) => { for dependency in v { if !operation.accept(dependency, element) { continue; } self.add_dependency(dependency, element, scope); } }, None => (), }); element.each_class(|v| match map.map.class_to_selector.get(v, quirks_mode) { Some(v) => { for dependency in v { if !operation.accept(dependency, element) { continue; } self.add_dependency(dependency, element, scope); } }, None => (), }); element.each_custom_state(|v| match map.map.custom_state_affecting_selectors.get(v) { Some(v) => { for dependency in v { if !operation.accept(dependency, element) { continue; } self.add_dependency(dependency, element, scope); } }, None => (), }); element.each_attr_name( |v| match map.map.other_attribute_affecting_selectors.get(v) { Some(v) => { for dependency in v { if !operation.accept(dependency, element) { continue; } self.add_dependency(dependency, element, scope); } }, None => (), }, ); let state = element.state(); map.map.state_affecting_selectors.lookup_with_additional( element, quirks_mode, None, &[], ElementState::empty(), |dependency| { if !dependency.state.intersects(state) { return true; } if !operation.accept(&dependency.dep, element) { return true; } self.add_dependency(&dependency.dep, element, scope); true }, ); map.ts_state_to_selector.lookup_with_additional( element, quirks_mode, None, &[], ElementState::empty(), |dependency| { if !operation.accept(&dependency.dep, element) { return true; } if dependency .state .avoid_blanket_invalidation_on_dom_mutation() { // We assume here that these dependencies are handled elsewhere, // in a more constrained manner. return true; } self.add_dependency(&dependency.dep, element, scope); true }, ); if let Some(v) = map.type_to_selector.get(element.local_name()) { for dependency in v { if !operation.accept(dependency, element) { continue; } self.add_dependency(dependency, element, scope); } } for dependency in &map.any_to_selector { if !operation.accept(dependency, element) { continue; } self.add_dependency(dependency, element, scope); } } fn is_empty(&self) -> bool { self.invalidations.is_empty() && self.dependencies.is_empty() } } impl<'a, 'b, E> RelativeSelectorInvalidator<'a, 'b, E> where E: TElement + 'a, { /// Gather relative selector dependencies for the given element, and invalidate as necessary. #[inline(never)] pub fn invalidate_relative_selectors_for_this( self, stylist: &'a Stylist, mut gather_dependencies: F, ) where F: FnMut( &E, Option, &'a CascadeData, QuirksMode, &mut RelativeSelectorDependencyCollector<'a, E>, ), { let mut collector = RelativeSelectorDependencyCollector::new(self.element, None); stylist.for_each_cascade_data_with_scope(self.element, |data, scope| { let map = data.relative_selector_invalidation_map(); if !map.used { return; } gather_dependencies( &self.element, scope.map(|e| e.opaque()), data, self.quirks_mode, &mut collector, ); }); if collector.is_empty() { return; } self.invalidate_from_dependencies(collector.get()); } /// Gather relative selector dependencies for the given element (And its subtree) that mutated, and invalidate as necessary. #[inline(never)] pub fn invalidate_relative_selectors_for_dom_mutation( self, subtree: bool, stylist: &'a Stylist, inherited_search_path: ElementSelectorFlags, operation: DomMutationOperation, ) { let mut collector = RelativeSelectorDependencyCollector::new( self.element, if operation.is_side_effect() { None } else { Some(OptimizationContext { sibling_traversal_map: &self.sibling_traversal_map, quirks_mode: self.quirks_mode, operation, }) }, ); let mut traverse_subtree = false; self.element.apply_selector_flags(inherited_search_path); stylist.for_each_cascade_data_with_scope(self.element, |data, scope| { let map = data.relative_selector_invalidation_map(); if !map.used { return; } traverse_subtree |= map.needs_ancestors_traversal; collector.collect_all_dependencies_for_element( self.element, scope.map(|e| e.opaque()), self.quirks_mode, map, operation, ); }); if subtree && traverse_subtree { for node in self.element.as_node().dom_descendants() { let descendant = match node.as_element() { Some(e) => e, None => continue, }; descendant.apply_selector_flags(inherited_search_path); stylist.for_each_cascade_data_with_scope(descendant, |data, scope| { let map = data.relative_selector_invalidation_map(); if !map.used { return; } collector.collect_all_dependencies_for_element( descendant, scope.map(|e| e.opaque()), self.quirks_mode, map, operation, ); }); } } if collector.is_empty() { return; } self.invalidate_from_dependencies(collector.get()); } /// Carry out complete invalidation triggered by a relative selector invalidation. fn invalidate_from_dependencies(&self, to_invalidate: ToInvalidate<'a, E>) { for (element, dependencies) in to_invalidate.dependencies { let mut selector_caches = SelectorCaches::default(); let mut processor = RelativeSelectorInnerInvalidationProcessor::new( self.quirks_mode, self.snapshot_table, &dependencies, &mut selector_caches, &self.sibling_traversal_map, ); TreeStyleInvalidator::new(element, None, &mut processor).invalidate(); for (element, invalidation) in processor.take_invalidations() { self.invalidate_upwards(element, &invalidation); } } for invalidation in to_invalidate.invalidations { self.invalidate_upwards(self.element, &invalidation); } } fn invalidate_upwards(&self, element: E, invalidation: &RelativeSelectorInvalidation<'a>) { // This contains the main reason for why relative selector invalidation is handled // separately - It travels ancestor and/or earlier sibling direction. match invalidation.kind { RelativeDependencyInvalidationKind::Parent => { element.parent_element().map(|e| { if !Self::in_search_direction( &e, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR, ) { return; } self.handle_anchor(e, invalidation.dependency, invalidation.host); }); }, RelativeDependencyInvalidationKind::Ancestors => { let mut parent = element.parent_element(); while let Some(par) = parent { if !Self::in_search_direction( &par, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR, ) { return; } self.handle_anchor(par, invalidation.dependency, invalidation.host); parent = par.parent_element(); } }, RelativeDependencyInvalidationKind::PrevSibling => { self.sibling_traversal_map .prev_sibling_for(&element) .map(|e| { if !Self::in_search_direction( &e, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING, ) { return; } self.handle_anchor(e, invalidation.dependency, invalidation.host); }); }, RelativeDependencyInvalidationKind::AncestorPrevSibling => { let mut parent = element.parent_element(); while let Some(par) = parent { if !Self::in_search_direction( &par, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR, ) { return; } par.prev_sibling_element().map(|e| { if !Self::in_search_direction( &e, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING, ) { return; } self.handle_anchor(e, invalidation.dependency, invalidation.host); }); parent = par.parent_element(); } }, RelativeDependencyInvalidationKind::EarlierSibling => { let mut sibling = self.sibling_traversal_map.prev_sibling_for(&element); while let Some(sib) = sibling { if !Self::in_search_direction( &sib, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING, ) { return; } self.handle_anchor(sib, invalidation.dependency, invalidation.host); sibling = sib.prev_sibling_element(); } }, RelativeDependencyInvalidationKind::AncestorEarlierSibling => { let mut parent = element.parent_element(); while let Some(par) = parent { if !Self::in_search_direction( &par, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR, ) { return; } let mut sibling = par.prev_sibling_element(); while let Some(sib) = sibling { if !Self::in_search_direction( &sib, ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING, ) { return; } self.handle_anchor(sib, invalidation.dependency, invalidation.host); sibling = sib.prev_sibling_element(); } parent = par.parent_element(); } }, } } /// Is this element in the direction of the given relative selector search path? fn in_search_direction(element: &E, desired: ElementSelectorFlags) -> bool { element .relative_selector_search_direction() .intersects(desired) } /// Handle a potential relative selector anchor. fn handle_anchor( &self, element: E, outer_dependency: &Dependency, host: Option, ) { let is_rightmost = Self::is_subject(outer_dependency); if (is_rightmost && !element.has_selector_flags(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR)) || (!is_rightmost && !element.has_selector_flags( ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR_NON_SUBJECT, )) { // If it was never a relative selector anchor, don't bother. return; } let mut selector_caches = SelectorCaches::default(); let matching_context = MatchingContext::<'_, E::Impl>::new_for_visited( MatchingMode::Normal, None, &mut selector_caches, VisitedHandlingMode::AllLinksVisitedAndUnvisited, IncludeStartingStyle::No, self.quirks_mode, NeedsSelectorFlags::No, MatchingForInvalidation::Yes, ); let mut data = match element.mutate_data() { Some(data) => data, None => return, }; let mut processor = RelativeSelectorOuterInvalidationProcessor { element, host, data: data.deref_mut(), dependency: &*outer_dependency, matching_context, traversal_map: &self.sibling_traversal_map, }; let result = TreeStyleInvalidator::new(element, None, &mut processor).invalidate(); (self.invalidated)(element, &result); } /// Does this relative selector dependency have its relative selector in the subject position? fn is_subject(outer_dependency: &Dependency) -> bool { debug_assert!( matches!( outer_dependency.invalidation_kind(), DependencyInvalidationKind::Normal(_) ), "Outer selector of relative selector is relative?" ); if let Some(p) = outer_dependency.parent.as_ref() { if !Self::is_subject(p.as_ref()) { // Not subject in outer selector. return false; } } outer_dependency .selector .is_rightmost(outer_dependency.selector_offset) } } /// Blindly invalidate everything outside of a relative selector. /// Consider `:is(.a :has(.b) .c ~ .d) ~ .e .f`, where .b gets deleted. /// Since the tree mutated, we cannot rely on snapshots. pub struct RelativeSelectorOuterInvalidationProcessor<'a, 'b, E: TElement> { /// Element being invalidated. pub element: E, /// The current shadow host, if any. pub host: Option, /// Data for the element being invalidated. pub data: &'a mut ElementData, /// Dependency to be processed. pub dependency: &'b Dependency, /// Matching context to use for invalidation. pub matching_context: MatchingContext<'a, E::Impl>, /// Traversal map for this invalidation. pub traversal_map: &'a SiblingTraversalMap, } impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'b, 'a, E> for RelativeSelectorOuterInvalidationProcessor<'a, 'b, E> where E: TElement, { fn invalidates_on_pseudo_element(&self) -> bool { true } fn check_outer_dependency(&mut self, _dependency: &Dependency, _element: E) -> bool { // At this point, we know a relative selector invalidated, and are ignoring them. true } fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { &mut self.matching_context } fn sibling_traversal_map(&self) -> &SiblingTraversalMap { self.traversal_map } fn collect_invalidations( &mut self, element: E, _self_invalidations: &mut InvalidationVector<'b>, descendant_invalidations: &mut DescendantInvalidationLists<'b>, sibling_invalidations: &mut InvalidationVector<'b>, ) -> bool { debug_assert_eq!(element, self.element); debug_assert!( self.matching_context.matching_for_invalidation(), "Not matching for invalidation?" ); // Ok, this element can potentially an anchor to the given dependency. // Before we do the potentially-costly ancestor/earlier sibling traversal, // See if it can actuall be an anchor by trying to match the "rest" of the selector // outside and to the left of `:has` in question. // e.g. Element under consideration can only be the anchor to `:has` in // `.foo .bar ~ .baz:has()`, iff it matches `.foo .bar ~ .baz`. let invalidated_self = { let mut d = self.dependency; loop { debug_assert!( matches!(d.invalidation_kind(), DependencyInvalidationKind::Normal(_)), "Unexpected outer relative dependency" ); if !dependency_may_be_relevant(d, &element, false) { break false; } if !matches_selector( &d.selector, d.selector_offset, None, &element, self.matching_context(), ) { break false; } let invalidation_kind = d.normal_invalidation_kind(); if matches!(invalidation_kind, NormalDependencyInvalidationKind::Element) { if let Some(ref parent) = d.parent { d = parent; continue; } break true; } debug_assert_ne!(d.selector_offset, 0); debug_assert_ne!(d.selector_offset, d.selector.len()); let invalidation = Invalidation::new(&d, self.host); break push_invalidation( invalidation, invalidation_kind, descendant_invalidations, sibling_invalidations, ); } }; if invalidated_self { self.data.hint.insert(RestyleHint::RESTYLE_SELF); } invalidated_self } fn should_process_descendants(&mut self, element: E) -> bool { if element == self.element { return should_process_descendants(&self.data); } match element.borrow_data() { Some(d) => should_process_descendants(&d), None => return false, } } fn recursion_limit_exceeded(&mut self, _element: E) { unreachable!("Unexpected recursion limit"); } fn invalidated_descendants(&mut self, element: E, child: E) { invalidated_descendants(element, child) } fn invalidated_self(&mut self, element: E) { debug_assert_ne!(element, self.element); invalidated_self(element); } fn invalidated_sibling(&mut self, element: E, of: E) { debug_assert_ne!(element, self.element); invalidated_sibling(element, of); } } /// Invalidation for the selector(s) inside a relative selector. pub struct RelativeSelectorInnerInvalidationProcessor<'a, 'b, 'c, E> where E: TElement + 'a, { /// Matching context to be used. matching_context: MatchingContext<'b, E::Impl>, /// Table of snapshots. snapshot_table: Option<&'c ServoElementSnapshotTable>, /// Incoming dependencies to be processed. dependencies: &'c ElementDependencies<'a>, /// Generated invalidations. invalidations: InnerInvalidations<'a, E>, /// Traversal map for this invalidation. traversal_map: &'b SiblingTraversalMap, } impl<'a, 'b, 'c, E> RelativeSelectorInnerInvalidationProcessor<'a, 'b, 'c, E> where E: TElement + 'a, { fn new( quirks_mode: QuirksMode, snapshot_table: Option<&'c ServoElementSnapshotTable>, dependencies: &'c ElementDependencies<'a>, selector_caches: &'b mut SelectorCaches, traversal_map: &'b SiblingTraversalMap, ) -> Self { let matching_context = MatchingContext::new_for_visited( MatchingMode::Normal, None, selector_caches, VisitedHandlingMode::AllLinksVisitedAndUnvisited, IncludeStartingStyle::No, quirks_mode, NeedsSelectorFlags::No, MatchingForInvalidation::Yes, ); Self { matching_context, snapshot_table, dependencies, invalidations: InnerInvalidations::default(), traversal_map, } } fn note_dependency( &mut self, element: E, scope: Option, dependency: &'a Dependency, descendant_invalidations: &mut DescendantInvalidationLists<'a>, sibling_invalidations: &mut InvalidationVector<'a>, ) { match dependency.invalidation_kind() { DependencyInvalidationKind::Normal(_) => (), DependencyInvalidationKind::Relative(kind) => { self.found_relative_selector_invalidation(element, kind, dependency); return; }, } if matches!( dependency.normal_invalidation_kind(), NormalDependencyInvalidationKind::Element ) { // Ok, keep heading outwards. debug_assert!( dependency.parent.is_some(), "Orphaned inner selector dependency?" ); if let Some(parent) = dependency.parent.as_ref() { self.note_dependency( element, scope, parent, descendant_invalidations, sibling_invalidations, ); } return; } let invalidation = Invalidation::new(&dependency, scope); match dependency.normal_invalidation_kind() { NormalDependencyInvalidationKind::Descendants => { // Descendant invalidations are simplified due to pseudo-elements not being available within the relative selector. descendant_invalidations.dom_descendants.push(invalidation) }, NormalDependencyInvalidationKind::Siblings => sibling_invalidations.push(invalidation), _ => unreachable!(), } } /// Take the generated invalidations. fn take_invalidations(self) -> InnerInvalidations<'a, E> { self.invalidations } } impl<'a, 'b, 'c, E> InvalidationProcessor<'a, 'b, E> for RelativeSelectorInnerInvalidationProcessor<'a, 'b, 'c, E> where E: TElement + 'a, { fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool { if let Some(snapshot_table) = self.snapshot_table { let wrapper = ElementWrapper::new(element, snapshot_table); return check_dependency(dependency, &element, &wrapper, &mut self.matching_context); } // Just invalidate if we don't have a snapshot. true } fn matching_context(&mut self) -> &mut MatchingContext<'b, E::Impl> { return &mut self.matching_context; } fn collect_invalidations( &mut self, element: E, _self_invalidations: &mut InvalidationVector<'a>, descendant_invalidations: &mut DescendantInvalidationLists<'a>, sibling_invalidations: &mut InvalidationVector<'a>, ) -> bool { for (scope, dependency) in self.dependencies { self.note_dependency( element, *scope, dependency, descendant_invalidations, sibling_invalidations, ) } false } fn should_process_descendants(&mut self, _element: E) -> bool { true } fn recursion_limit_exceeded(&mut self, _element: E) { unreachable!("Unexpected recursion limit"); } // Don't do anything for normal invalidations. fn invalidated_self(&mut self, _element: E) {} fn invalidated_sibling(&mut self, _sibling: E, _of: E) {} fn invalidated_descendants(&mut self, _element: E, _child: E) {} fn found_relative_selector_invalidation( &mut self, element: E, kind: RelativeDependencyInvalidationKind, dep: &'a Dependency, ) { debug_assert!(dep.parent.is_some(), "Orphaned inners selector?"); if element.relative_selector_search_direction().is_empty() { return; } self.invalidations.push(( element, RelativeSelectorInvalidation { host: self.matching_context.current_host, kind, dependency: dep.parent.as_ref().unwrap(), }, )); } fn sibling_traversal_map(&self) -> &SiblingTraversalMap { &self.traversal_map } }