mirror of
https://github.com/servo/servo.git
synced 2025-07-18 21:03:45 +01:00
Auto merge of #18847 - emilio:invalidation-generic, r=heycam
style: Split the invalidation processing from the invalidator step. This is the first step in reusing the invalidation machinery for other stuff, potentially including QuerySelector / QuerySelectorAll. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/18847) <!-- Reviewable:end -->
This commit is contained in:
commit
a89a76e1fc
4 changed files with 638 additions and 442 deletions
|
@ -244,6 +244,7 @@ impl ElementData {
|
|||
return InvalidationResult::empty();
|
||||
}
|
||||
|
||||
use invalidation::element::collector::StateAndAttrInvalidationProcessor;
|
||||
use invalidation::element::invalidator::TreeStyleInvalidator;
|
||||
|
||||
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
|
||||
|
@ -258,17 +259,21 @@ impl ElementData {
|
|||
return InvalidationResult::empty();
|
||||
}
|
||||
|
||||
let mut processor = StateAndAttrInvalidationProcessor;
|
||||
let invalidator = TreeStyleInvalidator::new(
|
||||
element,
|
||||
Some(self),
|
||||
shared_context,
|
||||
stack_limit_checker,
|
||||
nth_index_cache,
|
||||
&mut processor,
|
||||
);
|
||||
|
||||
let result = invalidator.invalidate();
|
||||
|
||||
unsafe { element.set_handled_snapshot() }
|
||||
debug_assert!(element.handled_snapshot());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
|
490
components/style/invalidation/element/collector.rs
Normal file
490
components/style/invalidation/element/collector.rs
Normal file
|
@ -0,0 +1,490 @@
|
|||
/* 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/. */
|
||||
|
||||
//! An invalidation processor for style changes due to state and attribute
|
||||
//! changes.
|
||||
|
||||
use Atom;
|
||||
use context::{QuirksMode, SharedStyleContext};
|
||||
use data::ElementData;
|
||||
use dom::TElement;
|
||||
use element_state::{ElementState, IN_VISITED_OR_UNVISITED_STATE};
|
||||
use invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
|
||||
use invalidation::element::invalidation_map::*;
|
||||
use invalidation::element::invalidator::{InvalidationVector, Invalidation, InvalidationProcessor};
|
||||
use invalidation::element::restyle_hints::*;
|
||||
use selector_map::SelectorMap;
|
||||
use selector_parser::Snapshot;
|
||||
use selectors::NthIndexCache;
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
|
||||
use selectors::matching::matches_selector;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum VisitedDependent {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// The collector implementation.
|
||||
struct Collector<'a, 'b: 'a, E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
element: E,
|
||||
wrapper: ElementWrapper<'b, E>,
|
||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||
snapshot: &'a Snapshot,
|
||||
quirks_mode: QuirksMode,
|
||||
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,
|
||||
}
|
||||
|
||||
/// An invalidation processor for style changes due to state and attribute
|
||||
/// changes.
|
||||
pub struct StateAndAttrInvalidationProcessor;
|
||||
|
||||
impl<E> InvalidationProcessor<E> for StateAndAttrInvalidationProcessor
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
/// We need to invalidate style on an eager pseudo-element, in order to
|
||||
/// process changes that could otherwise end up in ::before or ::after
|
||||
/// content being generated.
|
||||
fn invalidates_on_eager_pseudo_element(&self) -> bool { true }
|
||||
|
||||
fn collect_invalidations(
|
||||
&mut self,
|
||||
element: E,
|
||||
mut data: Option<&mut ElementData>,
|
||||
nth_index_cache: Option<&mut NthIndexCache>,
|
||||
shared_context: &SharedStyleContext,
|
||||
descendant_invalidations: &mut InvalidationVector,
|
||||
sibling_invalidations: &mut InvalidationVector,
|
||||
) -> bool {
|
||||
debug_assert!(element.has_snapshot(), "Why bothering?");
|
||||
debug_assert!(data.is_some(), "How exactly?");
|
||||
|
||||
let wrapper =
|
||||
ElementWrapper::new(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 false;
|
||||
}
|
||||
|
||||
// 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 = data.as_mut().unwrap();
|
||||
data.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 !element.has_class(c, CaseSensitivity::CaseSensitive) {
|
||||
classes_removed.push(c.clone())
|
||||
}
|
||||
});
|
||||
|
||||
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 = element.get_id();
|
||||
|
||||
if old_id != current_id {
|
||||
id_removed = old_id;
|
||||
id_added = current_id;
|
||||
}
|
||||
}
|
||||
|
||||
let lookup_element =
|
||||
if element.implemented_pseudo_element().is_some() {
|
||||
element.pseudo_element_originating_element().unwrap()
|
||||
} else {
|
||||
element
|
||||
};
|
||||
|
||||
let invalidated_self = {
|
||||
let mut collector = Collector {
|
||||
wrapper,
|
||||
lookup_element,
|
||||
nth_index_cache,
|
||||
state_changes,
|
||||
element,
|
||||
snapshot: &snapshot,
|
||||
quirks_mode: shared_context.quirks_mode(),
|
||||
removed_id: id_removed.as_ref(),
|
||||
added_id: id_added.as_ref(),
|
||||
classes_removed: &classes_removed,
|
||||
classes_added: &classes_added,
|
||||
descendant_invalidations,
|
||||
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 =
|
||||
element.each_xbl_stylist(|stylist| {
|
||||
// FIXME(emilio): Replace with assert / remove when we
|
||||
// figure out what to do with the quirks mode mismatches
|
||||
// (that is, when bug 1406875 is properly fixed).
|
||||
collector.quirks_mode = stylist.quirks_mode();
|
||||
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) = data {
|
||||
data.hint.insert(RESTYLE_SELF);
|
||||
}
|
||||
}
|
||||
|
||||
invalidated_self
|
||||
}
|
||||
|
||||
fn should_process_descendants(
|
||||
&mut self,
|
||||
_element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
) -> bool {
|
||||
let data = match data {
|
||||
None => return false,
|
||||
Some(ref data) => data,
|
||||
};
|
||||
|
||||
!data.styles.is_display_none() &&
|
||||
!data.hint.contains(RESTYLE_DESCENDANTS)
|
||||
}
|
||||
|
||||
fn recursion_limit_exceeded(
|
||||
&mut self,
|
||||
_element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
) {
|
||||
if let Some(data) = data {
|
||||
data.hint.insert(RESTYLE_DESCENDANTS);
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidated_descendants(
|
||||
&mut self,
|
||||
element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
child: E,
|
||||
) {
|
||||
if child.get_data().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if data.as_ref().map_or(true, |d| d.styles.is_display_none()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
let mut current = child.traversal_parent();
|
||||
while let Some(parent) = current.take() {
|
||||
unsafe { parent.set_dirty_descendants() };
|
||||
current = parent.traversal_parent();
|
||||
|
||||
if parent == element {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidated_self(
|
||||
&mut self,
|
||||
_element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
) {
|
||||
if let Some(data) = data {
|
||||
data.hint.insert(RESTYLE_SELF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, E> Collector<'a, 'b, E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
fn collect_dependencies_in_invalidation_map(
|
||||
&mut self,
|
||||
map: &InvalidationMap,
|
||||
) {
|
||||
let quirks_mode = self.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) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
|
||||
if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<Dependency>,
|
||||
) {
|
||||
map.lookup_with_additional(
|
||||
self.lookup_element,
|
||||
self.quirks_mode,
|
||||
self.removed_id,
|
||||
self.classes_removed,
|
||||
&mut |dependency| {
|
||||
self.scan_dependency(dependency, VisitedDependent::No);
|
||||
true
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn collect_state_dependencies(
|
||||
&mut self,
|
||||
map: &SelectorMap<StateDependency>,
|
||||
state_changes: ElementState,
|
||||
) {
|
||||
map.lookup_with_additional(
|
||||
self.lookup_element,
|
||||
self.quirks_mode,
|
||||
self.removed_id,
|
||||
self.classes_removed,
|
||||
&mut |dependency| {
|
||||
if !dependency.state.intersects(state_changes) {
|
||||
return true;
|
||||
}
|
||||
let visited_dependent =
|
||||
if dependency.state.intersects(IN_VISITED_OR_UNVISITED_STATE) {
|
||||
VisitedDependent::Yes
|
||||
} else {
|
||||
VisitedDependent::No
|
||||
};
|
||||
self.scan_dependency(&dependency.dep, visited_dependent);
|
||||
true
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Check whether a dependency should be taken into account, using a given
|
||||
/// visited handling mode.
|
||||
fn check_dependency(
|
||||
&mut self,
|
||||
visited_handling_mode: VisitedHandlingMode,
|
||||
dependency: &Dependency,
|
||||
relevant_link_found: &mut bool,
|
||||
) -> bool {
|
||||
let (matches_now, relevant_link_found_now) = {
|
||||
let mut context = MatchingContext::new_for_visited(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
visited_handling_mode,
|
||||
self.quirks_mode,
|
||||
);
|
||||
|
||||
let matches_now = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
&self.element,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
(matches_now, context.relevant_link_found)
|
||||
};
|
||||
|
||||
let (matched_then, relevant_link_found_then) = {
|
||||
let mut context = MatchingContext::new_for_visited(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
visited_handling_mode,
|
||||
self.quirks_mode,
|
||||
);
|
||||
|
||||
let matched_then = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
&self.wrapper,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
(matched_then, context.relevant_link_found)
|
||||
};
|
||||
|
||||
*relevant_link_found = relevant_link_found_now;
|
||||
|
||||
// Check for mismatches in both the match result and also the status
|
||||
// of whether a relevant link was found.
|
||||
matched_then != matches_now ||
|
||||
relevant_link_found_now != relevant_link_found_then
|
||||
}
|
||||
|
||||
fn scan_dependency(
|
||||
&mut self,
|
||||
dependency: &Dependency,
|
||||
is_visited_dependent: VisitedDependent,
|
||||
) {
|
||||
debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})",
|
||||
self.element,
|
||||
dependency,
|
||||
is_visited_dependent);
|
||||
|
||||
if !self.dependency_may_be_relevant(dependency) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut relevant_link_found = false;
|
||||
|
||||
let should_account_for_dependency = self.check_dependency(
|
||||
VisitedHandlingMode::AllLinksUnvisited,
|
||||
dependency,
|
||||
&mut relevant_link_found,
|
||||
);
|
||||
|
||||
if should_account_for_dependency {
|
||||
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 == VisitedDependent::Yes && relevant_link_found {
|
||||
let should_account_for_dependency = self.check_dependency(
|
||||
VisitedHandlingMode::RelevantLinkVisited,
|
||||
dependency,
|
||||
&mut false,
|
||||
);
|
||||
|
||||
if should_account_for_dependency {
|
||||
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::new(
|
||||
dependency.selector.clone(),
|
||||
dependency.selector_offset,
|
||||
));
|
||||
} else if dependency.affects_later_siblings() {
|
||||
debug_assert_ne!(dependency.selector_offset, 0);
|
||||
self.sibling_invalidations.push(Invalidation::new(
|
||||
dependency.selector.clone(),
|
||||
dependency.selector_offset,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
|
@ -5,35 +5,82 @@
|
|||
//! The struct that takes care of encapsulating all the logic on where and how
|
||||
//! element styles need to be invalidated.
|
||||
|
||||
use Atom;
|
||||
use context::{QuirksMode, SharedStyleContext, StackLimitChecker};
|
||||
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 selector_parser::SelectorImpl;
|
||||
use selectors::NthIndexCache;
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
|
||||
use selectors::matching::{matches_selector, matches_compound_selector};
|
||||
use selectors::matching::CompoundSelectorMatchingResult;
|
||||
use selectors::matching::matches_compound_selector;
|
||||
use selectors::parser::{Combinator, Component, Selector};
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum VisitedDependent {
|
||||
Yes,
|
||||
No,
|
||||
/// A trait to abstract the collection of invalidations for a given pass.
|
||||
///
|
||||
/// The `data` argument is a mutable reference to the element's style data, if
|
||||
/// any.
|
||||
pub trait InvalidationProcessor<E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
/// Whether an invalidation that contains only an eager pseudo-element
|
||||
/// selector like ::before or ::after triggers invalidation of the element
|
||||
/// that would originate it.
|
||||
fn invalidates_on_eager_pseudo_element(&self) -> bool { false }
|
||||
|
||||
/// Collect invalidations for a given element's descendants and siblings.
|
||||
///
|
||||
/// Returns whether the element itself was invalidated.
|
||||
fn collect_invalidations(
|
||||
&mut self,
|
||||
element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
nth_index_cache: Option<&mut NthIndexCache>,
|
||||
shared_context: &SharedStyleContext,
|
||||
descendant_invalidations: &mut InvalidationVector,
|
||||
sibling_invalidations: &mut InvalidationVector,
|
||||
) -> bool;
|
||||
|
||||
/// Returns whether the invalidation process should process the descendants
|
||||
/// of the given element.
|
||||
fn should_process_descendants(
|
||||
&mut self,
|
||||
element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
) -> bool;
|
||||
|
||||
/// Executes an arbitrary action when the recursion limit is exceded (if
|
||||
/// any).
|
||||
fn recursion_limit_exceeded(
|
||||
&mut self,
|
||||
element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
);
|
||||
|
||||
/// Executes an action when `Self` is invalidated.
|
||||
fn invalidated_self(
|
||||
&mut self,
|
||||
element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
);
|
||||
|
||||
/// Executes an action when any descendant of `Self` is invalidated.
|
||||
fn invalidated_descendants(
|
||||
&mut self,
|
||||
element: E,
|
||||
data: Option<&mut ElementData>,
|
||||
child: E,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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,
|
||||
pub struct TreeStyleInvalidator<'a, 'b: 'a, E, P: 'a>
|
||||
where
|
||||
E: TElement,
|
||||
P: InvalidationProcessor<E>
|
||||
{
|
||||
element: E,
|
||||
// TODO(emilio): It's tempting enough to just avoid running invalidation for
|
||||
|
@ -49,9 +96,11 @@ pub struct TreeStyleInvalidator<'a, 'b: 'a, E>
|
|||
shared_context: &'a SharedStyleContext<'b>,
|
||||
stack_limit_checker: Option<&'a StackLimitChecker>,
|
||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||
processor: &'a mut P,
|
||||
}
|
||||
|
||||
type InvalidationVector = SmallVec<[Invalidation; 10]>;
|
||||
/// A vector of invalidations, optimized for small invalidation sets.
|
||||
pub type InvalidationVector = SmallVec<[Invalidation; 10]>;
|
||||
|
||||
/// The kind of invalidation we're processing.
|
||||
///
|
||||
|
@ -71,7 +120,7 @@ enum InvalidationKind {
|
|||
/// must be restyled if the compound selector matches. Otherwise, if
|
||||
/// describes which descendants (or later siblings) must be restyled.
|
||||
#[derive(Clone)]
|
||||
struct Invalidation {
|
||||
pub struct Invalidation {
|
||||
selector: Selector<SelectorImpl>,
|
||||
offset: usize,
|
||||
/// Whether the invalidation was already matched by any previous sibling or
|
||||
|
@ -84,6 +133,15 @@ struct Invalidation {
|
|||
}
|
||||
|
||||
impl Invalidation {
|
||||
/// Create a new invalidation for a given selector and offset.
|
||||
pub fn new(selector: Selector<SelectorImpl>, offset: usize) -> Self {
|
||||
Self {
|
||||
selector,
|
||||
offset,
|
||||
matched_by_any_previous: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this invalidation is effective for the next sibling or
|
||||
/// descendant after us.
|
||||
fn effective_for_next(&self) -> bool {
|
||||
|
@ -167,8 +225,10 @@ impl InvalidationResult {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
||||
where E: TElement,
|
||||
impl<'a, 'b: 'a, E, P: 'a> TreeStyleInvalidator<'a, 'b, E, P>
|
||||
where
|
||||
E: TElement,
|
||||
P: InvalidationProcessor<E>,
|
||||
{
|
||||
/// Trivially constructs a new `TreeStyleInvalidator`.
|
||||
pub fn new(
|
||||
|
@ -177,6 +237,7 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
shared_context: &'a SharedStyleContext<'b>,
|
||||
stack_limit_checker: Option<&'a StackLimitChecker>,
|
||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||
processor: &'a mut P,
|
||||
) -> Self {
|
||||
Self {
|
||||
element,
|
||||
|
@ -184,125 +245,30 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
shared_context,
|
||||
stack_limit_checker,
|
||||
nth_index_cache,
|
||||
processor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the invalidation pass.
|
||||
pub fn invalidate(mut self) -> InvalidationResult {
|
||||
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 InvalidationResult::empty();
|
||||
}
|
||||
|
||||
// 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.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,
|
||||
lookup_element,
|
||||
nth_index_cache: self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
state_changes,
|
||||
element: self.element,
|
||||
snapshot: &snapshot,
|
||||
quirks_mode: self.shared_context.quirks_mode(),
|
||||
removed_id: id_removed.as_ref(),
|
||||
added_id: id_added.as_ref(),
|
||||
classes_removed: &classes_removed,
|
||||
classes_added: &classes_added,
|
||||
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| {
|
||||
// FIXME(emilio): Replace with assert / remove when we
|
||||
// figure out what to do with the quirks mode mismatches
|
||||
// (that is, when bug 1406875 is properly fixed).
|
||||
collector.quirks_mode = stylist.quirks_mode();
|
||||
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.hint.insert(RESTYLE_SELF);
|
||||
}
|
||||
}
|
||||
let invalidated_self = self.processor.collect_invalidations(
|
||||
self.element,
|
||||
self.data.as_mut().map(|d| &mut **d),
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
self.shared_context,
|
||||
&mut descendant_invalidations,
|
||||
&mut sibling_invalidations,
|
||||
);
|
||||
|
||||
debug!("Collected invalidations (self: {}): ", invalidated_self);
|
||||
debug!(" > descendants: {:?}", descendant_invalidations);
|
||||
debug!(" > siblings: {:?}", sibling_invalidations);
|
||||
|
||||
let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations);
|
||||
let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations);
|
||||
|
||||
|
@ -327,14 +293,14 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
|
||||
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,
|
||||
sibling_data.as_mut().map(|d| &mut **d),
|
||||
self.shared_context,
|
||||
self.stack_limit_checker,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
self.processor,
|
||||
);
|
||||
|
||||
let mut invalidations_for_descendants = InvalidationVector::new();
|
||||
|
@ -390,54 +356,50 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, 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,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
);
|
||||
|
||||
let mut invalidations_for_descendants = InvalidationVector::new();
|
||||
|
||||
let mut invalidated_child = false;
|
||||
let invalidated_descendants = {
|
||||
let mut child_data = child.mutate_data();
|
||||
|
||||
invalidated_child |=
|
||||
child_invalidator.process_sibling_invalidations(
|
||||
&mut invalidations_for_descendants,
|
||||
sibling_invalidations,
|
||||
let mut child_invalidator = TreeStyleInvalidator::new(
|
||||
child,
|
||||
child_data.as_mut().map(|d| &mut **d),
|
||||
self.shared_context,
|
||||
self.stack_limit_checker,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
self.processor,
|
||||
);
|
||||
|
||||
invalidated_child |=
|
||||
child_invalidator.process_descendant_invalidations(
|
||||
invalidations,
|
||||
&mut invalidations_for_descendants,
|
||||
sibling_invalidations,
|
||||
);
|
||||
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,
|
||||
);
|
||||
|
||||
child_invalidator.invalidate_descendants(&invalidations_for_descendants)
|
||||
};
|
||||
|
||||
// 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();
|
||||
}
|
||||
if invalidated_child || invalidated_descendants {
|
||||
self.processor.invalidated_descendants(
|
||||
self.element,
|
||||
self.data.as_mut().map(|d| &mut **d),
|
||||
child,
|
||||
);
|
||||
}
|
||||
|
||||
let invalidated_descendants = child_invalidator.invalidate_descendants(
|
||||
&invalidations_for_descendants
|
||||
);
|
||||
|
||||
invalidated_child || invalidated_descendants
|
||||
}
|
||||
|
||||
|
@ -505,20 +467,22 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
self.element);
|
||||
debug!(" > {:?}", invalidations);
|
||||
|
||||
match self.data {
|
||||
None => return false,
|
||||
Some(ref data) => {
|
||||
// FIXME(emilio): Only needs to check RESTYLE_DESCENDANTS,
|
||||
// really.
|
||||
if data.hint.contains_subtree() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let should_process =
|
||||
self.processor.should_process_descendants(
|
||||
self.element,
|
||||
self.data.as_mut().map(|d| &mut **d),
|
||||
);
|
||||
|
||||
if !should_process {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(checker) = self.stack_limit_checker {
|
||||
if checker.limit_exceeded() {
|
||||
self.data.as_mut().unwrap().hint.insert(RESTYLE_DESCENDANTS);
|
||||
self.processor.recursion_limit_exceeded(
|
||||
self.element,
|
||||
self.data.as_mut().map(|d| &mut **d)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -548,10 +512,6 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -712,7 +672,8 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
//
|
||||
// Note that we'll also restyle the pseudo-element because
|
||||
// it would match this invalidation.
|
||||
if pseudo.is_eager() {
|
||||
if self.processor.invalidates_on_eager_pseudo_element() &&
|
||||
pseudo.is_eager() {
|
||||
invalidated_self = true;
|
||||
}
|
||||
}
|
||||
|
@ -811,274 +772,13 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
}
|
||||
|
||||
if invalidated_self {
|
||||
if let Some(ref mut data) = self.data {
|
||||
data.hint.insert(RESTYLE_SELF);
|
||||
}
|
||||
self.processor.invalidated_self(
|
||||
self.element,
|
||||
self.data.as_mut().map(|d| &mut **d),
|
||||
);
|
||||
}
|
||||
|
||||
SingleInvalidationResult { invalidated_self, matched, }
|
||||
}
|
||||
}
|
||||
|
||||
struct InvalidationCollector<'a, 'b: 'a, E>
|
||||
where E: TElement,
|
||||
{
|
||||
element: E,
|
||||
wrapper: ElementWrapper<'b, E>,
|
||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||
snapshot: &'a Snapshot,
|
||||
quirks_mode: QuirksMode,
|
||||
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.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) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
|
||||
if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<Dependency>,
|
||||
) {
|
||||
map.lookup_with_additional(
|
||||
self.lookup_element,
|
||||
self.quirks_mode,
|
||||
self.removed_id,
|
||||
self.classes_removed,
|
||||
&mut |dependency| {
|
||||
self.scan_dependency(dependency, VisitedDependent::No);
|
||||
true
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn collect_state_dependencies(
|
||||
&mut self,
|
||||
map: &SelectorMap<StateDependency>,
|
||||
state_changes: ElementState,
|
||||
) {
|
||||
map.lookup_with_additional(
|
||||
self.lookup_element,
|
||||
self.quirks_mode,
|
||||
self.removed_id,
|
||||
self.classes_removed,
|
||||
&mut |dependency| {
|
||||
if !dependency.state.intersects(state_changes) {
|
||||
return true;
|
||||
}
|
||||
let visited_dependent =
|
||||
if dependency.state.intersects(IN_VISITED_OR_UNVISITED_STATE) {
|
||||
VisitedDependent::Yes
|
||||
} else {
|
||||
VisitedDependent::No
|
||||
};
|
||||
self.scan_dependency(&dependency.dep, visited_dependent);
|
||||
true
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Check whether a dependency should be taken into account, using a given
|
||||
/// visited handling mode.
|
||||
fn check_dependency(
|
||||
&mut self,
|
||||
visited_handling_mode: VisitedHandlingMode,
|
||||
dependency: &Dependency,
|
||||
relevant_link_found: &mut bool,
|
||||
) -> bool {
|
||||
let (matches_now, relevant_link_found_now) = {
|
||||
let mut context = MatchingContext::new_for_visited(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
visited_handling_mode,
|
||||
self.quirks_mode,
|
||||
);
|
||||
|
||||
let matches_now = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
&self.element,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
(matches_now, context.relevant_link_found)
|
||||
};
|
||||
|
||||
let (matched_then, relevant_link_found_then) = {
|
||||
let mut context = MatchingContext::new_for_visited(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||
visited_handling_mode,
|
||||
self.quirks_mode,
|
||||
);
|
||||
|
||||
let matched_then = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
&self.wrapper,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
(matched_then, context.relevant_link_found)
|
||||
};
|
||||
|
||||
*relevant_link_found = relevant_link_found_now;
|
||||
|
||||
// Check for mismatches in both the match result and also the status
|
||||
// of whether a relevant link was found.
|
||||
matched_then != matches_now ||
|
||||
relevant_link_found_now != relevant_link_found_then
|
||||
}
|
||||
|
||||
fn scan_dependency(
|
||||
&mut self,
|
||||
dependency: &Dependency,
|
||||
is_visited_dependent: VisitedDependent,
|
||||
) {
|
||||
debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})",
|
||||
self.element,
|
||||
dependency,
|
||||
is_visited_dependent);
|
||||
|
||||
if !self.dependency_may_be_relevant(dependency) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut relevant_link_found = false;
|
||||
|
||||
let should_account_for_dependency = self.check_dependency(
|
||||
VisitedHandlingMode::AllLinksUnvisited,
|
||||
dependency,
|
||||
&mut relevant_link_found,
|
||||
);
|
||||
|
||||
if should_account_for_dependency {
|
||||
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 == VisitedDependent::Yes && relevant_link_found {
|
||||
let should_account_for_dependency = self.check_dependency(
|
||||
VisitedHandlingMode::RelevantLinkVisited,
|
||||
dependency,
|
||||
&mut false,
|
||||
);
|
||||
|
||||
if should_account_for_dependency {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
//! Invalidation of element styles due to attribute or style changes.
|
||||
|
||||
pub mod collector;
|
||||
pub mod element_wrapper;
|
||||
pub mod invalidation_map;
|
||||
pub mod invalidator;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue