mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +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();
|
||||
}
|
||||
|
||||
use invalidation::element::collector::StateAndAttrInvalidationCollector;
|
||||
use invalidation::element::invalidator::TreeStyleInvalidator;
|
||||
|
||||
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
|
||||
|
@ -266,7 +267,8 @@ impl ElementData {
|
|||
nth_index_cache,
|
||||
);
|
||||
|
||||
let result = invalidator.invalidate();
|
||||
let result =
|
||||
invalidator.invalidate::<StateAndAttrInvalidationCollector>();
|
||||
unsafe { element.set_handled_snapshot() }
|
||||
debug_assert!(element.handled_snapshot());
|
||||
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
|
||||
//! 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.
|
||||
pub trait InvalidationCollector {
|
||||
/// Collect invalidations for a given element's descendants and siblings.
|
||||
///
|
||||
/// 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
|
||||
|
@ -51,7 +56,8 @@ pub struct TreeStyleInvalidator<'a, 'b: 'a, E>
|
|||
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.
|
||||
///
|
||||
|
@ -71,7 +77,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 +90,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 {
|
||||
|
@ -188,121 +203,25 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
|
|||
}
|
||||
|
||||
/// Perform the invalidation pass.
|
||||
pub fn invalidate(mut self) -> InvalidationResult {
|
||||
pub fn invalidate<C: InvalidationCollector>(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 = C::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);
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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