mirror of
https://github.com/servo/servo.git
synced 2025-08-04 05:00:08 +01:00
style: Split the invalidation collection from the invalidator step.
This is the first step in reusing the invalidation machinery for other stuff, potentially including QuerySelector / QuerySelectorAll.
This commit is contained in:
parent
ec00c660f0
commit
b9b3e592dd
4 changed files with 462 additions and 384 deletions
|
@ -244,6 +244,7 @@ impl ElementData {
|
||||||
return InvalidationResult::empty();
|
return InvalidationResult::empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use invalidation::element::collector::StateAndAttrInvalidationCollector;
|
||||||
use invalidation::element::invalidator::TreeStyleInvalidator;
|
use invalidation::element::invalidator::TreeStyleInvalidator;
|
||||||
|
|
||||||
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
|
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
|
||||||
|
@ -266,7 +267,8 @@ impl ElementData {
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = invalidator.invalidate();
|
let result =
|
||||||
|
invalidator.invalidate::<StateAndAttrInvalidationCollector>();
|
||||||
unsafe { element.set_handled_snapshot() }
|
unsafe { element.set_handled_snapshot() }
|
||||||
debug_assert!(element.handled_snapshot());
|
debug_assert!(element.handled_snapshot());
|
||||||
result
|
result
|
||||||
|
|
418
components/style/invalidation/element/collector.rs
Normal file
418
components/style/invalidation/element/collector.rs
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! A collector for invalidations 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, InvalidationCollector};
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collector for state and attribute invalidations.
|
||||||
|
pub struct StateAndAttrInvalidationCollector;
|
||||||
|
|
||||||
|
impl InvalidationCollector for StateAndAttrInvalidationCollector {
|
||||||
|
fn collect_invalidations<E>(
|
||||||
|
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
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,29 +5,34 @@
|
||||||
//! The struct that takes care of encapsulating all the logic on where and how
|
//! The struct that takes care of encapsulating all the logic on where and how
|
||||||
//! element styles need to be invalidated.
|
//! element styles need to be invalidated.
|
||||||
|
|
||||||
use Atom;
|
use context::{SharedStyleContext, StackLimitChecker};
|
||||||
use context::{QuirksMode, SharedStyleContext, StackLimitChecker};
|
|
||||||
use data::ElementData;
|
use data::ElementData;
|
||||||
use dom::{TElement, TNode};
|
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 invalidation::element::restyle_hints::*;
|
||||||
use selector_map::SelectorMap;
|
use selector_parser::SelectorImpl;
|
||||||
use selector_parser::{SelectorImpl, Snapshot};
|
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
use selectors::attr::CaseSensitivity;
|
|
||||||
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
|
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
|
||||||
use selectors::matching::{matches_selector, matches_compound_selector};
|
|
||||||
use selectors::matching::CompoundSelectorMatchingResult;
|
use selectors::matching::CompoundSelectorMatchingResult;
|
||||||
|
use selectors::matching::matches_compound_selector;
|
||||||
use selectors::parser::{Combinator, Component, Selector};
|
use selectors::parser::{Combinator, Component, Selector};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
/// A trait to abstract the collection of invalidations for a given pass.
|
||||||
enum VisitedDependent {
|
pub trait InvalidationCollector {
|
||||||
Yes,
|
/// Collect invalidations for a given element's descendants and siblings.
|
||||||
No,
|
///
|
||||||
|
/// Returns whether the element itself was invalidated.
|
||||||
|
fn collect_invalidations<E>(
|
||||||
|
element: E,
|
||||||
|
data: Option<&mut ElementData>,
|
||||||
|
nth_index_cache: Option<&mut NthIndexCache>,
|
||||||
|
shared_context: &SharedStyleContext,
|
||||||
|
descendant_invalidations: &mut InvalidationVector,
|
||||||
|
sibling_invalidations: &mut InvalidationVector,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
E: TElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The struct that takes care of encapsulating all the logic on where and how
|
/// The struct that takes care of encapsulating all the logic on where and how
|
||||||
|
@ -51,7 +56,8 @@ pub struct TreeStyleInvalidator<'a, 'b: 'a, E>
|
||||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
/// The kind of invalidation we're processing.
|
||||||
///
|
///
|
||||||
|
@ -71,7 +77,7 @@ enum InvalidationKind {
|
||||||
/// must be restyled if the compound selector matches. Otherwise, if
|
/// must be restyled if the compound selector matches. Otherwise, if
|
||||||
/// describes which descendants (or later siblings) must be restyled.
|
/// describes which descendants (or later siblings) must be restyled.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Invalidation {
|
pub struct Invalidation {
|
||||||
selector: Selector<SelectorImpl>,
|
selector: Selector<SelectorImpl>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
/// Whether the invalidation was already matched by any previous sibling or
|
/// Whether the invalidation was already matched by any previous sibling or
|
||||||
|
@ -84,6 +90,15 @@ struct Invalidation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// Whether this invalidation is effective for the next sibling or
|
||||||
/// descendant after us.
|
/// descendant after us.
|
||||||
fn effective_for_next(&self) -> bool {
|
fn effective_for_next(&self) -> bool {
|
||||||
|
@ -188,121 +203,25 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the invalidation pass.
|
/// Perform the invalidation pass.
|
||||||
pub fn invalidate(mut self) -> InvalidationResult {
|
pub fn invalidate<C: InvalidationCollector>(mut self) -> InvalidationResult {
|
||||||
debug!("StyleTreeInvalidator::invalidate({:?})", self.element);
|
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 descendant_invalidations = InvalidationVector::new();
|
||||||
let mut sibling_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| {
|
let invalidated_self = C::collect_invalidations(
|
||||||
collector.collect_dependencies_in_invalidation_map(invalidation_map);
|
self.element,
|
||||||
});
|
self.data.as_mut().map(|d| &mut **d),
|
||||||
|
self.nth_index_cache.as_mut().map(|c| &mut **c),
|
||||||
// TODO(emilio): Consider storing dependencies from the UA sheet in
|
self.shared_context,
|
||||||
// a different map. If we do that, we can skip the stuff on the
|
&mut descendant_invalidations,
|
||||||
// shared stylist iff cut_off_inheritance is true, and we can look
|
&mut sibling_invalidations,
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Collected invalidations (self: {}): ", invalidated_self);
|
debug!("Collected invalidations (self: {}): ", invalidated_self);
|
||||||
debug!(" > descendants: {:?}", descendant_invalidations);
|
debug!(" > descendants: {:?}", descendant_invalidations);
|
||||||
debug!(" > siblings: {:?}", sibling_invalidations);
|
debug!(" > siblings: {:?}", sibling_invalidations);
|
||||||
|
|
||||||
let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations);
|
let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations);
|
||||||
let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations);
|
let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations);
|
||||||
|
|
||||||
|
@ -820,265 +739,3 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
//! Invalidation of element styles due to attribute or style changes.
|
||||||
|
|
||||||
|
pub mod collector;
|
||||||
pub mod element_wrapper;
|
pub mod element_wrapper;
|
||||||
pub mod invalidation_map;
|
pub mod invalidation_map;
|
||||||
pub mod invalidator;
|
pub mod invalidator;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue