/* 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 http://mozilla.org/MPL/2.0/. */ //! The struct that takes care of encapsulating all the logic on where and how //! element styles need to be invalidated. use Atom; use context::{SharedStyleContext, StackLimitChecker}; use data::ElementData; use dom::{TElement, TNode}; use element_state::{ElementState, IN_VISITED_OR_UNVISITED_STATE}; use invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; use invalidation::element::invalidation_map::*; use invalidation::element::restyle_hints::*; use selector_map::SelectorMap; use selector_parser::{SelectorImpl, Snapshot}; use selectors::attr::CaseSensitivity; use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode}; use selectors::matching::{matches_selector, matches_compound_selector}; use selectors::matching::CompoundSelectorMatchingResult; use selectors::parser::{Combinator, Component, Selector}; use smallvec::SmallVec; use std::fmt; /// The struct that takes care of encapsulating all the logic on where and how /// element styles need to be invalidated. pub struct TreeStyleInvalidator<'a, 'b: 'a, E> where E: TElement, { element: E, // TODO(emilio): It's tempting enough to just avoid running invalidation for // elements without data. // // But that's be wrong for sibling invalidations when a new element has been // inserted in the tree and still has no data (though I _think_ the slow // selector bits save us, it'd be nice not to depend on them). // // Seems like we could at least avoid running invalidation for the // descendants if an element has no data, though. data: Option<&'a mut ElementData>, shared_context: &'a SharedStyleContext<'b>, stack_limit_checker: Option<&'a StackLimitChecker>, } type InvalidationVector = SmallVec<[Invalidation; 10]>; /// The kind of invalidation we're processing. /// /// We can use this to avoid pushing invalidations of the same kind to our /// descendants or siblings. #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum InvalidationKind { Descendant, Sibling, } /// An `Invalidation` is a complex selector that describes which elements, /// relative to a current element we are processing, must be restyled. /// /// When `offset` points to the right-most compound selector in `selector`, /// then the Invalidation `represents` the fact that the current element /// must be restyled if the compound selector matches. Otherwise, if /// describes which descendants (or later siblings) must be restyled. #[derive(Clone)] struct Invalidation { selector: Selector, offset: usize, /// Whether the invalidation was already matched by any previous sibling or /// ancestor. /// /// If this is the case, we can avoid pushing invalidations generated by /// this one if the generated invalidation is effective for all the siblings /// or descendants after us. matched_by_any_previous: bool, } impl Invalidation { /// Whether this invalidation is effective for the next sibling or /// descendant after us. fn effective_for_next(&self) -> bool { // TODO(emilio): For pseudo-elements this should be mostly false, except // for the weird pseudos in . // // We should be able to do better here! match self.selector.combinator_at(self.offset) { Combinator::NextSibling | Combinator::Child => false, _ => true, } } fn kind(&self) -> InvalidationKind { if self.selector.combinator_at(self.offset).is_ancestor() { InvalidationKind::Descendant } else { InvalidationKind::Sibling } } } impl fmt::Debug for Invalidation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use cssparser::ToCss; f.write_str("Invalidation(")?; for component in self.selector.iter_raw_parse_order_from(self.offset - 1) { if matches!(*component, Component::Combinator(..)) { break; } component.to_css(f)?; } f.write_str(")") } } /// The result of processing a single invalidation for a given element. struct InvalidationResult { /// Whether the element itself was invalidated. invalidated_self: bool, /// Whether the invalidation matched, either invalidating the element or /// generating another invalidation. matched: bool, } impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E> where E: TElement, { /// Trivially constructs a new `TreeStyleInvalidator`. pub fn new( element: E, data: Option<&'a mut ElementData>, shared_context: &'a SharedStyleContext<'b>, stack_limit_checker: Option<&'a StackLimitChecker>, ) -> Self { Self { element, data, shared_context, stack_limit_checker, } } /// Perform the invalidation pass. pub fn invalidate(mut self) { debug!("StyleTreeInvalidator::invalidate({:?})", self.element); debug_assert!(self.element.has_snapshot(), "Why bothering?"); debug_assert!(self.data.is_some(), "How exactly?"); let shared_context = self.shared_context; let wrapper = ElementWrapper::new(self.element, shared_context.snapshot_map); let state_changes = wrapper.state_changes(); let snapshot = wrapper.snapshot().expect("has_snapshot lied"); if !snapshot.has_attrs() && state_changes.is_empty() { return; } // If we are sensitive to visitedness and the visited state changed, we // force a restyle here. Matching doesn't depend on the actual visited // state at all, so we can't look at matching results to decide what to // do for this case. if state_changes.intersects(IN_VISITED_OR_UNVISITED_STATE) { trace!(" > visitedness change, force subtree restyle"); // We can't just return here because there may also be attribute // changes as well that imply additional hints. let data = self.data.as_mut().unwrap(); data.restyle.hint.insert(RestyleHint::restyle_subtree()); } let mut classes_removed = SmallVec::<[Atom; 8]>::new(); let mut classes_added = SmallVec::<[Atom; 8]>::new(); if snapshot.class_changed() { // TODO(emilio): Do this more efficiently! snapshot.each_class(|c| { if !self.element.has_class(c, CaseSensitivity::CaseSensitive) { classes_removed.push(c.clone()) } }); self.element.each_class(|c| { if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) { classes_added.push(c.clone()) } }) } let mut id_removed = None; let mut id_added = None; if snapshot.id_changed() { let old_id = snapshot.id_attr(); let current_id = self.element.get_id(); if old_id != current_id { id_removed = old_id; id_added = current_id; } } let lookup_element = if self.element.implemented_pseudo_element().is_some() { self.element.pseudo_element_originating_element().unwrap() } else { self.element }; let mut descendant_invalidations = InvalidationVector::new(); let mut sibling_invalidations = InvalidationVector::new(); let invalidated_self = { let mut collector = InvalidationCollector { wrapper: wrapper, element: self.element, snapshot: &snapshot, shared_context: self.shared_context, lookup_element: lookup_element, removed_id: id_removed.as_ref(), added_id: id_added.as_ref(), classes_removed: &classes_removed, classes_added: &classes_added, state_changes: state_changes, descendant_invalidations: &mut descendant_invalidations, sibling_invalidations: &mut sibling_invalidations, invalidates_self: false, }; shared_context.stylist.each_invalidation_map(|invalidation_map| { collector.collect_dependencies_in_invalidation_map(invalidation_map); }); // TODO(emilio): Consider storing dependencies from the UA sheet in // a different map. If we do that, we can skip the stuff on the // shared stylist iff cut_off_inheritance is true, and we can look // just at that map. let _cut_off_inheritance = self.element.each_xbl_stylist(|stylist| { stylist.each_invalidation_map(|invalidation_map| { collector.collect_dependencies_in_invalidation_map(invalidation_map); }); }); collector.invalidates_self }; if invalidated_self { if let Some(ref mut data) = self.data { data.restyle.hint.insert(RESTYLE_SELF); } } debug!("Collected invalidations (self: {}): ", invalidated_self); debug!(" > descendants: {:?}", descendant_invalidations); debug!(" > siblings: {:?}", sibling_invalidations); self.invalidate_descendants(&descendant_invalidations); self.invalidate_siblings(&mut sibling_invalidations); } /// Go through later DOM siblings, invalidating style as needed using the /// `sibling_invalidations` list. /// /// Returns whether any sibling's style or any sibling descendant's style /// was invalidated. fn invalidate_siblings( &mut self, sibling_invalidations: &mut InvalidationVector, ) -> bool { if sibling_invalidations.is_empty() { return false; } let mut current = self.element.next_sibling_element(); let mut any_invalidated = false; while let Some(sibling) = current { let mut sibling_data = sibling.mutate_data(); let sibling_data = sibling_data.as_mut().map(|d| &mut **d); let mut sibling_invalidator = TreeStyleInvalidator::new( sibling, sibling_data, self.shared_context, self.stack_limit_checker, ); let mut invalidations_for_descendants = InvalidationVector::new(); any_invalidated |= sibling_invalidator.process_sibling_invalidations( &mut invalidations_for_descendants, sibling_invalidations, ); any_invalidated |= sibling_invalidator.invalidate_descendants( &invalidations_for_descendants ); if sibling_invalidations.is_empty() { break; } current = sibling.next_sibling_element(); } any_invalidated } fn invalidate_pseudo_element_or_nac( &mut self, child: E, invalidations: &InvalidationVector ) -> bool { let mut sibling_invalidations = InvalidationVector::new(); let result = self.invalidate_child( child, invalidations, &mut sibling_invalidations ); // Roots of NAC subtrees can indeed generate sibling invalidations, but // they can be just ignored, since they have no siblings. // // Note that we can end up testing selectors that wouldn't end up // matching due to this being NAC, like those coming from document // rules, but we overinvalidate instead of checking this. result } /// Invalidate a child and recurse down invalidating its descendants if /// needed. fn invalidate_child( &mut self, child: E, invalidations: &InvalidationVector, sibling_invalidations: &mut InvalidationVector, ) -> bool { let mut child_data = child.mutate_data(); let child_data = child_data.as_mut().map(|d| &mut **d); let mut child_invalidator = TreeStyleInvalidator::new( child, child_data, self.shared_context, self.stack_limit_checker, ); let mut invalidations_for_descendants = InvalidationVector::new(); let mut invalidated_child = false; invalidated_child |= child_invalidator.process_sibling_invalidations( &mut invalidations_for_descendants, sibling_invalidations, ); invalidated_child |= child_invalidator.process_descendant_invalidations( invalidations, &mut invalidations_for_descendants, sibling_invalidations, ); // The child may not be a flattened tree child of the current element, // but may be arbitrarily deep. // // Since we keep the traversal flags in terms of the flattened tree, // we need to propagate it as appropriate. if invalidated_child && child.get_data().is_some() { let mut current = child.traversal_parent(); while let Some(parent) = current.take() { if parent == self.element { break; } unsafe { parent.set_dirty_descendants() }; current = parent.traversal_parent(); } } let invalidated_descendants = child_invalidator.invalidate_descendants( &invalidations_for_descendants ); invalidated_child || invalidated_descendants } fn invalidate_nac( &mut self, invalidations: &InvalidationVector, ) -> bool { let mut any_nac_root = false; let element = self.element; element.each_anonymous_content_child(|nac| { any_nac_root |= self.invalidate_pseudo_element_or_nac(nac, invalidations); }); any_nac_root } // NB: It's important that this operates on DOM children, which is what // selector-matching operates on. fn invalidate_dom_descendants_of( &mut self, parent: E::ConcreteNode, invalidations: &InvalidationVector, ) -> bool { let mut any_descendant = false; let mut sibling_invalidations = InvalidationVector::new(); for child in parent.children() { // TODO(emilio): We handle fine, because they appear // in selector-matching (note bug 1374247, though). // // This probably needs a shadow root check on `child` here, and // recursing if that's the case. // // Also, what's the deal with HTML ? We don't need to // support that for now, though we probably need to recurse into the // distributed children too. let child = match child.as_element() { Some(e) => e, None => continue, }; any_descendant |= self.invalidate_child( child, invalidations, &mut sibling_invalidations, ); } any_descendant } /// Given a descendant invalidation list, go through the current element's /// descendants, and invalidate style on them. fn invalidate_descendants( &mut self, invalidations: &InvalidationVector, ) -> bool { if invalidations.is_empty() { return false; } debug!("StyleTreeInvalidator::invalidate_descendants({:?})", self.element); debug!(" > {:?}", invalidations); match self.data { None => return false, Some(ref data) => { // FIXME(emilio): Only needs to check RESTYLE_DESCENDANTS, // really. if data.restyle.hint.contains_subtree() { return false; } } } if let Some(checker) = self.stack_limit_checker { if checker.limit_exceeded() { self.data.as_mut().unwrap().restyle.hint.insert(RESTYLE_DESCENDANTS); return true; } } let mut any_descendant = false; if let Some(anon_content) = self.element.xbl_binding_anonymous_content() { any_descendant |= self.invalidate_dom_descendants_of(anon_content, invalidations); } // TODO(emilio): Having a list of invalidations just for pseudo-elements // may save some work here and there. if let Some(before) = self.element.before_pseudo_element() { any_descendant |= self.invalidate_pseudo_element_or_nac(before, invalidations); } let node = self.element.as_node(); any_descendant |= self.invalidate_dom_descendants_of(node, invalidations); if let Some(after) = self.element.after_pseudo_element() { any_descendant |= self.invalidate_pseudo_element_or_nac(after, invalidations); } any_descendant |= self.invalidate_nac(invalidations); if any_descendant && self.data.as_ref().map_or(false, |d| !d.styles.is_display_none()) { unsafe { self.element.set_dirty_descendants() }; } any_descendant } /// Process the given sibling invalidations coming from our previous /// sibling. /// /// The sibling invalidations are somewhat special because they can be /// modified on the fly. New invalidations may be added and removed. /// /// In particular, all descendants get the same set of invalidations from /// the parent, but the invalidations from a given sibling depend on the /// ones we got from the previous one. /// /// Returns whether invalidated the current element's style. fn process_sibling_invalidations( &mut self, descendant_invalidations: &mut InvalidationVector, sibling_invalidations: &mut InvalidationVector, ) -> bool { let mut i = 0; let mut new_sibling_invalidations = InvalidationVector::new(); let mut invalidated_self = false; while i < sibling_invalidations.len() { let result = self.process_invalidation( &sibling_invalidations[i], descendant_invalidations, &mut new_sibling_invalidations, InvalidationKind::Sibling, ); invalidated_self |= result.invalidated_self; sibling_invalidations[i].matched_by_any_previous |= result.matched; if sibling_invalidations[i].effective_for_next() { i += 1; } else { sibling_invalidations.remove(i); } } sibling_invalidations.extend(new_sibling_invalidations.drain()); invalidated_self } /// Process a given invalidation list coming from our parent, /// adding to `descendant_invalidations` and `sibling_invalidations` as /// needed. /// /// Returns whether our style was invalidated as a result. fn process_descendant_invalidations( &mut self, invalidations: &InvalidationVector, descendant_invalidations: &mut InvalidationVector, sibling_invalidations: &mut InvalidationVector, ) -> bool { let mut invalidated = false; for invalidation in invalidations { let result = self.process_invalidation( invalidation, descendant_invalidations, sibling_invalidations, InvalidationKind::Descendant, ); invalidated |= result.invalidated_self; if invalidation.effective_for_next() { let mut invalidation = invalidation.clone(); invalidation.matched_by_any_previous |= result.matched; descendant_invalidations.push(invalidation.clone()); } } invalidated } /// Processes a given invalidation, potentially invalidating the style of /// the current element. /// /// Returns whether invalidated the style of the element, and whether the /// invalidation should be effective to subsequent siblings or descendants /// down in the tree. fn process_invalidation( &mut self, invalidation: &Invalidation, descendant_invalidations: &mut InvalidationVector, sibling_invalidations: &mut InvalidationVector, invalidation_kind: InvalidationKind, ) -> InvalidationResult { debug!("TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})", self.element, invalidation, invalidation_kind); let mut context = MatchingContext::new_for_visited( MatchingMode::Normal, None, VisitedHandlingMode::AllLinksVisitedAndUnvisited, self.shared_context.quirks_mode(), ); let matching_result = matches_compound_selector( &invalidation.selector, invalidation.offset, &mut context, &self.element ); let mut invalidated_self = false; let mut matched = false; match matching_result { CompoundSelectorMatchingResult::Matched { next_combinator_offset: 0 } => { debug!(" > Invalidation matched completely"); matched = true; invalidated_self = true; } CompoundSelectorMatchingResult::Matched { next_combinator_offset } => { let next_combinator = invalidation.selector.combinator_at(next_combinator_offset); matched = true; if matches!(next_combinator, Combinator::PseudoElement) { // This will usually be the very next component, except for // the fact that we store compound selectors the other way // around, so there could also be state pseudo-classes. let pseudo_selector = invalidation.selector .iter_raw_parse_order_from(next_combinator_offset - 1) .skip_while(|c| matches!(**c, Component::NonTSPseudoClass(..))) .next() .unwrap(); let pseudo = match *pseudo_selector { Component::PseudoElement(ref pseudo) => pseudo, _ => { unreachable!( "Someone seriously messed up selector parsing: \ {:?} at offset {:?}: {:?}", invalidation.selector, next_combinator_offset, pseudo_selector, ) } }; // FIXME(emilio): This is not ideal, and could not be // accurate if we ever have stateful element-backed eager // pseudos. // // Ideally, we'd just remove element-backed eager pseudos // altogether, given they work fine without it. Only gotcha // is that we wouldn't style them in parallel, which may or // may not be an issue. // // Also, this could be more fine grained now (perhaps a // RESTYLE_PSEUDOS hint?). // // Note that we'll also restyle the pseudo-element because // it would match this invalidation. if pseudo.is_eager() { invalidated_self = true; } } let next_invalidation = Invalidation { selector: invalidation.selector.clone(), offset: next_combinator_offset, matched_by_any_previous: false, }; debug!(" > Invalidation matched, next: {:?}, ({:?})", next_invalidation, next_combinator); let next_invalidation_kind = next_invalidation.kind(); // We can skip pushing under some circumstances, and we should // because otherwise the invalidation list could grow // exponentially. // // * First of all, both invalidations need to be of the same // kind. This is because of how we propagate them going to // the right of the tree for sibling invalidations and going // down the tree for children invalidations. A sibling // invalidation that ends up generating a children // invalidation ends up (correctly) in five different lists, // not in the same list five different times. // // * Then, the invalidation needs to be matched by a previous // ancestor/sibling, in order to know that this invalidation // has been generated already. // // * Finally, the new invalidation needs to be // `effective_for_next()`, in order for us to know that it is // still in the list, since we remove the dependencies that // aren't from the lists for our children / siblings. // // To go through an example, let's imagine we are processing a // dom subtree like: // //
// // And an invalidation list with a single invalidation like: // // [div div div] // // When we process the invalidation list for the outer div, we // match it, and generate a `div div` invalidation, so for the //
child we have: // // [div div div, div div] // // With the first of them marked as `matched`. // // When we process the
child, we don't match any of // them, so both invalidations go untouched to our children. // // When we process the second
, we match _both_ // invalidations. // // However, when matching the first, we can tell it's been // matched, and not push the corresponding `div div` // invalidation, since we know it's necessarily already on the // list. // // Thus, without skipping the push, we'll arrive to the // innermost
with: // // [div div div, div div, div div, div] // // While skipping it, we won't arrive here with duplicating // dependencies: // // [div div div, div div, div] // let can_skip_pushing = next_invalidation_kind == invalidation_kind && invalidation.matched_by_any_previous && next_invalidation.effective_for_next(); if can_skip_pushing { debug!(" > Can avoid push, since the invalidation had \ already been matched before"); } else { match next_invalidation_kind { InvalidationKind::Descendant => { descendant_invalidations.push(next_invalidation); } InvalidationKind::Sibling => { sibling_invalidations.push(next_invalidation); } } } } CompoundSelectorMatchingResult::NotMatched => {} } if invalidated_self { if let Some(ref mut data) = self.data { data.restyle.hint.insert(RESTYLE_SELF); } } InvalidationResult { invalidated_self, matched, } } } struct InvalidationCollector<'a, 'b: 'a, E> where E: TElement, { element: E, wrapper: ElementWrapper<'b, E>, snapshot: &'a Snapshot, shared_context: &'a SharedStyleContext<'b>, lookup_element: E, removed_id: Option<&'a Atom>, added_id: Option<&'a Atom>, classes_removed: &'a SmallVec<[Atom; 8]>, classes_added: &'a SmallVec<[Atom; 8]>, state_changes: ElementState, descendant_invalidations: &'a mut InvalidationVector, sibling_invalidations: &'a mut InvalidationVector, invalidates_self: bool, } impl<'a, 'b: 'a, E> InvalidationCollector<'a, 'b, E> where E: TElement, { fn collect_dependencies_in_invalidation_map( &mut self, map: &InvalidationMap, ) { let quirks_mode = self.shared_context.quirks_mode(); let removed_id = self.removed_id; if let Some(ref id) = removed_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { self.collect_dependencies_in_map(deps) } } let added_id = self.added_id; if let Some(ref id) = added_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { self.collect_dependencies_in_map(deps) } } for class in self.classes_added.iter().chain(self.classes_removed.iter()) { if let Some(deps) = map.class_to_selector.get(class, quirks_mode) { self.collect_dependencies_in_map(deps) } } let should_examine_attribute_selector_map = self.snapshot.other_attr_changed() || (self.snapshot.class_changed() && map.has_class_attribute_selectors) || (self.snapshot.id_changed() && map.has_id_attribute_selectors); if should_examine_attribute_selector_map { self.collect_dependencies_in_map( &map.other_attribute_affecting_selectors ) } let state_changes = self.state_changes; if !state_changes.is_empty() { self.collect_state_dependencies( &map.state_affecting_selectors, state_changes, ) } } fn collect_dependencies_in_map( &mut self, map: &SelectorMap, ) { map.lookup_with_additional( self.lookup_element, self.shared_context.quirks_mode(), self.removed_id, self.classes_removed, &mut |dependency| { self.scan_dependency( dependency, /* is_visited_dependent = */ false ); true }, ); } fn collect_state_dependencies( &mut self, map: &SelectorMap, state_changes: ElementState, ) { map.lookup_with_additional( self.lookup_element, self.shared_context.quirks_mode(), self.removed_id, self.classes_removed, &mut |dependency| { if !dependency.state.intersects(state_changes) { return true; } self.scan_dependency( &dependency.dep, dependency.state.intersects(IN_VISITED_OR_UNVISITED_STATE) ); true }, ); } fn scan_dependency( &mut self, dependency: &Dependency, is_visited_dependent: bool ) { debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {})", self.element, dependency, is_visited_dependent); if !self.dependency_may_be_relevant(dependency) { return; } // TODO(emilio): Add a bloom filter here? // // If we decide to do so, we can't use the bloom filter for snapshots, // given that arbitrary elements in the parent chain may have mutated // their id's/classes, which means that they won't be in the filter, and // as such we may fast-reject selectors incorrectly. // // We may be able to improve this if we record as we go down the tree // whether any parent had a snapshot, and whether those snapshots were // taken due to an element class/id change, but it's not clear it'd be // worth it. let mut now_context = MatchingContext::new_for_visited(MatchingMode::Normal, None, VisitedHandlingMode::AllLinksUnvisited, self.shared_context.quirks_mode()); let mut then_context = MatchingContext::new_for_visited(MatchingMode::Normal, None, VisitedHandlingMode::AllLinksUnvisited, self.shared_context.quirks_mode()); let matched_then = matches_selector(&dependency.selector, dependency.selector_offset, None, &self.wrapper, &mut then_context, &mut |_, _| {}); let matches_now = matches_selector(&dependency.selector, dependency.selector_offset, None, &self.element, &mut now_context, &mut |_, _| {}); // Check for mismatches in both the match result and also the status // of whether a relevant link was found. if matched_then != matches_now || then_context.relevant_link_found != now_context.relevant_link_found { return self.note_dependency(dependency); } // If there is a relevant link, then we also matched in visited // mode. // // Match again in this mode to ensure this also matches. // // Note that we never actually match directly against the element's true // visited state at all, since that would expose us to timing attacks. // // The matching process only considers the relevant link state and // visited handling mode when deciding if visited matches. Instead, we // are rematching here in case there is some :visited selector whose // matching result changed for some other state or attribute change of // this element (for example, for things like [foo]:visited). // // NOTE: This thing is actually untested because testing it is flaky, // see the tests that were added and then backed out in bug 1328509. if is_visited_dependent && now_context.relevant_link_found { then_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited; let matched_then = matches_selector(&dependency.selector, dependency.selector_offset, None, &self.wrapper, &mut then_context, &mut |_, _| {}); now_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited; let matches_now = matches_selector(&dependency.selector, dependency.selector_offset, None, &self.element, &mut now_context, &mut |_, _| {}); if matched_then != matches_now { return self.note_dependency(dependency); } } } fn note_dependency(&mut self, dependency: &Dependency) { if dependency.affects_self() { self.invalidates_self = true; } if dependency.affects_descendants() { debug_assert_ne!(dependency.selector_offset, 0); debug_assert!(!dependency.affects_later_siblings()); self.descendant_invalidations.push(Invalidation { selector: dependency.selector.clone(), offset: dependency.selector_offset, matched_by_any_previous: false, }); } else if dependency.affects_later_siblings() { debug_assert_ne!(dependency.selector_offset, 0); self.sibling_invalidations.push(Invalidation { selector: dependency.selector.clone(), offset: dependency.selector_offset, matched_by_any_previous: false, }); } } /// Returns whether `dependency` may cause us to invalidate the style of /// more elements than what we've already invalidated. fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool { if dependency.affects_descendants() || dependency.affects_later_siblings() { return true; } debug_assert!(dependency.affects_self()); !self.invalidates_self } }