servo: Merge #17292 - style: Implement a more fine-grained invalidation method (from emilio:better-style-invalidation); r=heycam

Source-Repo: https://github.com/servo/servo
Source-Revision: 07f6e114850896eae2fd206351808fb31cceaaf0

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 404a65bc41b59d8019a0dbdc83f7d2df165594d2
This commit is contained in:
Emilio Cobos Álvarez 2017-06-13 04:56:09 -07:00
parent a7015fc300
commit 25b326679c
28 changed files with 2171 additions and 1537 deletions

View File

@ -131,7 +131,7 @@ use std::rc::Rc;
use std::time::{Duration, Instant};
use style::attr::AttrValue;
use style::context::{QuirksMode, ReflowGoal};
use style::restyle_hints::{RestyleHint, RESTYLE_STYLE_ATTRIBUTE};
use style::invalidation::element::restyle_hints::{RestyleHint, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE};
use style::selector_parser::{RestyleDamage, Snapshot};
use style::shared_lock::SharedRwLock as StyleSharedRwLock;
use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join};
@ -2376,17 +2376,24 @@ impl Document {
entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document()));
}
if attr.local_name() == &local_name!("style") {
entry.hint.insert(RestyleHint::for_replacements(RESTYLE_STYLE_ATTRIBUTE));
entry.hint.insert(RESTYLE_STYLE_ATTRIBUTE);
}
// FIXME(emilio): This should become something like
// element.is_attribute_mapped(attr.local_name()).
if attr.local_name() == &local_name!("width") ||
attr.local_name() == &local_name!("height") {
entry.hint.insert(RestyleHint::for_self());
entry.hint.insert(RESTYLE_SELF);
}
let mut snapshot = entry.snapshot.as_mut().unwrap();
if attr.local_name() == &local_name!("id") {
snapshot.id_changed = true;
} else if attr.local_name() == &local_name!("class") {
snapshot.class_changed = true;
} else {
snapshot.other_attributes_changed = true;
}
if snapshot.attrs.is_none() {
let attrs = el.attrs()
.iter()

View File

@ -102,9 +102,9 @@ use style::applicable_declarations::ApplicableDeclarationBlock;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::context::{QuirksMode, ReflowGoal};
use style::element_state::*;
use style::invalidation::element::restyle_hints::RESTYLE_SELF;
use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x};
use style::restyle_hints::RestyleHint;
use style::rule_tree::CascadeLevel;
use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser};
use style::selector_parser::extended_filtering;
@ -253,7 +253,7 @@ impl Element {
// FIXME(bholley): I think we should probably only do this for
// NodeStyleDamaged, but I'm preserving existing behavior.
restyle.hint.insert(RestyleHint::for_self());
restyle.hint.insert(RESTYLE_SELF);
if damage == NodeDamage::OtherNodeDamage {
restyle.damage = RestyleDamage::rebuild_and_reflow();

View File

@ -48,6 +48,12 @@ pub enum MatchingMode {
pub enum VisitedHandlingMode {
/// All links are matched as if they are unvisted.
AllLinksUnvisited,
/// All links are matched as if they are visited and unvisited (both :link
/// and :visited match).
///
/// This is intended to be used from invalidation code, to be conservative
/// about whether we need to restyle a link.
AllLinksVisitedAndUnvisited,
/// A element's "relevant link" is the element being matched if it is a link
/// or the nearest ancestor link. The relevant link is matched as though it
/// is visited, and all other links are matched as if they are unvisited.

View File

@ -253,6 +253,10 @@ impl RelevantLinkStatus {
return false
}
if context.visited_handling == VisitedHandlingMode::AllLinksVisitedAndUnvisited {
return true;
}
// Non-relevant links are always unvisited.
if *self != RelevantLinkStatus::Found {
return false
@ -274,6 +278,10 @@ impl RelevantLinkStatus {
return false
}
if context.visited_handling == VisitedHandlingMode::AllLinksVisitedAndUnvisited {
return true;
}
// Non-relevant links are always unvisited.
if *self != RelevantLinkStatus::Found {
return true
@ -335,9 +343,9 @@ enum SelectorMatchingResult {
/// Matches a selector, fast-rejecting against a bloom filter.
///
/// We accept an offset to allow consumers to represent and match against partial
/// selectors (indexed from the right). We use this API design, rather than
/// having the callers pass a SelectorIter, because creating a SelectorIter
/// We accept an offset to allow consumers to represent and match against
/// partial selectors (indexed from the right). We use this API design, rather
/// than having the callers pass a SelectorIter, because creating a SelectorIter
/// requires dereferencing the selector to get the length, which adds an
/// unncessary cache miss for cases when we can fast-reject with AncestorHashes
/// (which the caller can store inline with the selector pointer).
@ -360,12 +368,74 @@ pub fn matches_selector<E, F>(selector: &Selector<E::Impl>,
}
let mut local_context = LocalMatchingContext::new(context, selector);
matches_complex_selector(&selector, offset, element, &mut local_context, flags_setter)
let iter = if offset == 0 {
selector.iter()
} else {
selector.iter_from(offset)
};
matches_complex_selector(iter, element, &mut local_context, flags_setter)
}
/// Whether a compound selector matched, and whether it was the rightmost
/// selector inside the complex selector.
pub enum CompoundSelectorMatchingResult {
/// The compound selector matched, and the next combinator offset is
/// `next_combinator_offset`.
///
/// If the next combinator offset is zero, it means that it's the rightmost
/// selector.
Matched { next_combinator_offset: usize, },
/// The selector didn't match.
NotMatched,
}
/// Matches a compound selector belonging to `selector`, starting at offset
/// `from_offset`, matching left to right.
///
/// Requires that `from_offset` points to a `Combinator`.
///
/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the
/// complex selector, but it happens to be the case we don't need it.
pub fn matches_compound_selector<E>(
selector: &Selector<E::Impl>,
mut from_offset: usize,
context: &mut MatchingContext,
element: &E,
) -> CompoundSelectorMatchingResult
where
E: Element
{
if cfg!(debug_assertions) {
selector.combinator_at(from_offset); // This asserts.
}
let mut local_context = LocalMatchingContext::new(context, selector);
for component in selector.iter_raw_rev_from(from_offset - 1) {
if matches!(*component, Component::Combinator(..)) {
return CompoundSelectorMatchingResult::Matched {
next_combinator_offset: from_offset - 1,
}
}
if !matches_simple_selector(
component,
element,
&mut local_context,
&RelevantLinkStatus::NotLooking,
&mut |_, _| {}) {
return CompoundSelectorMatchingResult::NotMatched;
}
from_offset -= 1;
}
return CompoundSelectorMatchingResult::Matched {
next_combinator_offset: 0,
}
}
/// Matches a complex selector.
pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
offset: usize,
pub fn matches_complex_selector<E, F>(mut iter: SelectorIter<E::Impl>,
element: &E,
mut context: &mut LocalMatchingContext<E::Impl>,
flags_setter: &mut F)
@ -373,12 +443,6 @@ pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
let mut iter = if offset == 0 {
complex_selector.iter()
} else {
complex_selector.iter_from(offset)
};
if cfg!(debug_assertions) {
if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
assert!(iter.clone().any(|c| {

View File

@ -449,18 +449,36 @@ impl<Impl: SelectorImpl> Selector<Impl> {
}
}
/// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from right to left.
/// Returns the combinator at index `index`, or panics if the component is
/// not a combinator.
pub fn combinator_at(&self, index: usize) -> Combinator {
match self.0.slice[self.0.slice.len() - index] {
Component::Combinator(c) => c,
ref other => {
panic!("Not a combinator: {:?}, {:?}, index: {}",
other, self, index)
}
}
}
/// Returns an iterator over the entire sequence of simple selectors and
/// combinators, from right to left.
pub fn iter_raw(&self) -> Rev<slice::Iter<Component<Impl>>> {
self.iter_raw_rev().rev()
}
/// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from left to right.
/// Returns an iterator over the entire sequence of simple selectors and
/// combinators, from left to right.
pub fn iter_raw_rev(&self) -> slice::Iter<Component<Impl>> {
self.0.slice.iter()
}
/// Returns an iterator over the sequence of simple selectors and
/// combinators after `offset`, from left to right.
pub fn iter_raw_rev_from(&self, offset: usize) -> slice::Iter<Component<Impl>> {
self.0.slice[(self.0.slice.len() - offset)..].iter()
}
/// Creates a Selector from a vec of Components. Used in tests.
pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> Self {
let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len());

View File

@ -250,7 +250,8 @@ impl TraversalStatistics {
self.traversal_time_ms = (time::precise_time_s() - start) * 1000.0;
self.selectors = traversal.shared_context().stylist.num_selectors() as u32;
self.revalidation_selectors = traversal.shared_context().stylist.num_revalidation_selectors() as u32;
self.dependency_selectors = traversal.shared_context().stylist.num_dependencies() as u32;
self.dependency_selectors =
traversal.shared_context().stylist.invalidation_map().len() as u32;
self.declarations = traversal.shared_context().stylist.num_declarations() as u32;
self.stylist_rebuilds = traversal.shared_context().stylist.num_rebuilds() as u32;
}

View File

@ -7,9 +7,9 @@
use arrayvec::ArrayVec;
use context::SharedStyleContext;
use dom::TElement;
use invalidation::element::restyle_hints::RestyleHint;
use properties::{AnimationRules, ComputedValues, PropertyDeclarationBlock};
use properties::longhands::display::computed_value as display;
use restyle_hints::{CascadeHint, HintComputationContext, RestyleReplacements, RestyleHint};
use rule_tree::StrongRuleNode;
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
use selectors::matching::VisitedHandlingMode;
@ -268,6 +268,10 @@ impl EagerPseudoStyles {
rules: StrongRuleNode)
-> bool {
match visited_handling {
VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
unreachable!("We should never try to selector match with \
AllLinksVisitedAndUnvisited");
},
VisitedHandlingMode::AllLinksUnvisited => {
self.add_unvisited_rules(&pseudo, rules)
},
@ -286,6 +290,10 @@ impl EagerPseudoStyles {
visited_handling: VisitedHandlingMode)
-> bool {
match visited_handling {
VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
unreachable!("We should never try to selector match with \
AllLinksVisitedAndUnvisited");
},
VisitedHandlingMode::AllLinksUnvisited => {
self.remove_unvisited_rules(&pseudo)
},
@ -345,8 +353,10 @@ impl ElementStyles {
///
/// We wrap it in a newtype to force the encapsulation of the complexity of
/// handling the correct invalidations in this file.
#[derive(Clone, Debug)]
pub struct StoredRestyleHint(RestyleHint);
///
/// TODO(emilio): This will probably be a non-issue in a bit.
#[derive(Clone, Copy, Debug)]
pub struct StoredRestyleHint(pub RestyleHint);
impl StoredRestyleHint {
/// Propagates this restyle hint to a child element.
@ -378,14 +388,7 @@ impl StoredRestyleHint {
/// Creates a restyle hint that forces the whole subtree to be restyled,
/// including the element.
pub fn subtree() -> Self {
StoredRestyleHint(RestyleHint::subtree())
}
/// Creates a restyle hint that forces the element and all its later
/// siblings to have their whole subtrees restyled, including the elements
/// themselves.
pub fn subtree_and_later_siblings() -> Self {
StoredRestyleHint(RestyleHint::subtree_and_later_siblings())
StoredRestyleHint(RestyleHint::restyle_subtree())
}
/// Creates a restyle hint that indicates the element must be recascaded.
@ -398,12 +401,6 @@ impl StoredRestyleHint {
self.0.affects_self()
}
/// Returns true if the hint indicates that our sibling's style may be
/// invalidated.
pub fn has_sibling_invalidations(&self) -> bool {
self.0.affects_later_siblings()
}
/// Whether the restyle hint is empty (nothing requires to be restyled).
pub fn is_empty(&self) -> bool {
self.0.is_empty()
@ -416,12 +413,7 @@ impl StoredRestyleHint {
/// Contains whether the whole subtree is invalid.
pub fn contains_subtree(&self) -> bool {
self.0.contains(&RestyleHint::subtree())
}
/// Insert another restyle hint, effectively resulting in the union of both.
pub fn insert_from(&mut self, other: &Self) {
self.0.insert_from(&other.0)
self.0.contains(RestyleHint::restyle_subtree())
}
/// Returns true if the hint has animation-only restyle.
@ -434,16 +426,11 @@ impl StoredRestyleHint {
pub fn has_recascade_self(&self) -> bool {
self.0.has_recascade_self()
}
/// Insert the specified `CascadeHint`.
pub fn insert_cascade_hint(&mut self, cascade_hint: CascadeHint) {
self.0.insert_cascade_hint(cascade_hint);
}
}
impl Default for StoredRestyleHint {
fn default() -> Self {
StoredRestyleHint::empty()
Self::empty()
}
}
@ -483,11 +470,6 @@ impl RestyleData {
self.hint.has_self_invalidations()
}
/// Returns true if this RestyleData might invalidate sibling styles.
pub fn has_sibling_invalidations(&self) -> bool {
self.hint.has_sibling_invalidations()
}
/// Returns damage handled.
#[cfg(feature = "gecko")]
pub fn damage_handled(&self) -> RestyleDamage {
@ -533,66 +515,41 @@ pub enum RestyleKind {
MatchAndCascade,
/// We need to recascade with some replacement rule, such as the style
/// attribute, or animation rules.
CascadeWithReplacements(RestyleReplacements),
CascadeWithReplacements(RestyleHint),
/// We only need to recascade, for example, because only inherited
/// properties in the parent changed.
CascadeOnly,
}
impl ElementData {
/// Computes the final restyle hint for this element, potentially allocating
/// a `RestyleData` if we need to.
///
/// This expands the snapshot (if any) into a restyle hint, and handles
/// explicit sibling restyle hints from the stored restyle hint.
///
/// Returns true if later siblings must be restyled.
pub fn compute_final_hint<'a, E: TElement>(
/// Invalidates style for this element, its descendants, and later siblings,
/// based on the snapshot of the element that we took when attributes or
/// state changed.
pub fn invalidate_style_if_needed<'a, E: TElement>(
&mut self,
element: E,
shared_context: &SharedStyleContext,
hint_context: HintComputationContext<'a, E>)
-> bool
shared_context: &SharedStyleContext)
{
debug!("compute_final_hint: {:?}, {:?}",
element,
shared_context.traversal_flags);
use invalidation::element::invalidator::TreeStyleInvalidator;
let mut hint = match self.get_restyle() {
Some(r) => r.hint.0.clone(),
None => RestyleHint::empty(),
};
debug!("compute_final_hint: {:?}, has_snapshot: {}, handled_snapshot: {}, \
pseudo: {:?}",
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
handled_snapshot: {}, pseudo: {:?}",
element,
shared_context.traversal_flags,
element.has_snapshot(),
element.handled_snapshot(),
element.implemented_pseudo_element());
if element.has_snapshot() && !element.handled_snapshot() {
let snapshot_hint =
shared_context.stylist.compute_restyle_hint(&element,
shared_context,
hint_context);
hint.insert(snapshot_hint);
let invalidator = TreeStyleInvalidator::new(
element,
Some(self),
shared_context,
);
invalidator.invalidate();
unsafe { element.set_handled_snapshot() }
debug_assert!(element.handled_snapshot());
}
let empty_hint = hint.is_empty();
// If the hint includes a directive for later siblings, strip it out and
// notify the caller to modify the base hint for future siblings.
let later_siblings = hint.remove_later_siblings_hint();
// Insert the hint, overriding the previous hint. This effectively takes
// care of removing the later siblings restyle hint.
if !empty_hint {
self.ensure_restyle().hint = hint.into();
}
later_siblings
}
@ -626,13 +583,13 @@ impl ElementData {
debug_assert!(self.restyle.is_some());
let restyle_data = self.restyle.as_ref().unwrap();
let hint = &restyle_data.hint.0;
let hint = restyle_data.hint.0;
if hint.match_self() {
return RestyleKind::MatchAndCascade;
}
if hint.has_replacements() {
return RestyleKind::CascadeWithReplacements(hint.replacements);
return RestyleKind::CascadeWithReplacements(hint & RestyleHint::replacements());
}
debug_assert!(hint.has_recascade_self(), "We definitely need to do something!");

View File

@ -9235,31 +9235,106 @@ pub mod root {
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn mClassAttributeChanged(&self) -> bool {
let mask = 32usize as u8;
let unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
let val = (unit_field_val & mask) >> 5usize;
unsafe { ::std::mem::transmute(val as u8) }
}
#[inline]
pub fn set_mClassAttributeChanged(&mut self, val: bool) {
let mask = 32usize as u8;
let val = val as u8 as u8;
let mut unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
unit_field_val &= !mask;
unit_field_val |= (val << 5usize) & mask;
self._bitfield_1 =
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn mIdAttributeChanged(&self) -> bool {
let mask = 64usize as u8;
let unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
let val = (unit_field_val & mask) >> 6usize;
unsafe { ::std::mem::transmute(val as u8) }
}
#[inline]
pub fn set_mIdAttributeChanged(&mut self, val: bool) {
let mask = 64usize as u8;
let val = val as u8 as u8;
let mut unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
unit_field_val &= !mask;
unit_field_val |= (val << 6usize) & mask;
self._bitfield_1 =
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn mOtherAttributeChanged(&self) -> bool {
let mask = 128usize as u8;
let unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
let val = (unit_field_val & mask) >> 7usize;
unsafe { ::std::mem::transmute(val as u8) }
}
#[inline]
pub fn set_mOtherAttributeChanged(&mut self, val: bool) {
let mask = 128usize as u8;
let val = val as u8 as u8;
let mut unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
unit_field_val &= !mask;
unit_field_val |= (val << 7usize) & mask;
self._bitfield_1 =
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn new_bitfield_1(mIsHTMLElementInHTMLDocument: bool,
mIsInChromeDocument: bool,
mSupportsLangAttr: bool,
mIsTableBorderNonzero: bool,
mIsMozBrowserFrame: bool) -> u8 {
mIsMozBrowserFrame: bool,
mClassAttributeChanged: bool,
mIdAttributeChanged: bool,
mOtherAttributeChanged: bool) -> u8 {
({
({
({
({
({ 0 } |
((mIsHTMLElementInHTMLDocument as u8
as u8) << 0usize) &
(1usize as u8))
({
({
({
({ 0 } |
((mIsHTMLElementInHTMLDocument
as u8 as u8) <<
0usize) &
(1usize as u8))
} |
((mIsInChromeDocument as u8
as u8) << 1usize) &
(2usize as u8))
} |
((mSupportsLangAttr as u8 as u8)
<< 2usize) &
(4usize as u8))
} |
((mIsTableBorderNonzero as u8 as u8)
<< 3usize) & (8usize as u8))
} |
((mIsInChromeDocument as u8 as u8) <<
1usize) & (2usize as u8))
((mIsMozBrowserFrame as u8 as u8) <<
4usize) & (16usize as u8))
} |
((mSupportsLangAttr as u8 as u8) << 2usize) &
(4usize as u8))
((mClassAttributeChanged as u8 as u8) <<
5usize) & (32usize as u8))
} |
((mIsTableBorderNonzero as u8 as u8) << 3usize) &
(8usize as u8))
((mIdAttributeChanged as u8 as u8) << 6usize) &
(64usize as u8))
} |
((mIsMozBrowserFrame as u8 as u8) << 4usize) &
(16usize as u8))
((mOtherAttributeChanged as u8 as u8) << 7usize) &
(128usize as u8))
}
}
#[repr(C)]

View File

@ -8976,31 +8976,106 @@ pub mod root {
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn mClassAttributeChanged(&self) -> bool {
let mask = 32usize as u8;
let unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
let val = (unit_field_val & mask) >> 5usize;
unsafe { ::std::mem::transmute(val as u8) }
}
#[inline]
pub fn set_mClassAttributeChanged(&mut self, val: bool) {
let mask = 32usize as u8;
let val = val as u8 as u8;
let mut unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
unit_field_val &= !mask;
unit_field_val |= (val << 5usize) & mask;
self._bitfield_1 =
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn mIdAttributeChanged(&self) -> bool {
let mask = 64usize as u8;
let unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
let val = (unit_field_val & mask) >> 6usize;
unsafe { ::std::mem::transmute(val as u8) }
}
#[inline]
pub fn set_mIdAttributeChanged(&mut self, val: bool) {
let mask = 64usize as u8;
let val = val as u8 as u8;
let mut unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
unit_field_val &= !mask;
unit_field_val |= (val << 6usize) & mask;
self._bitfield_1 =
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn mOtherAttributeChanged(&self) -> bool {
let mask = 128usize as u8;
let unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
let val = (unit_field_val & mask) >> 7usize;
unsafe { ::std::mem::transmute(val as u8) }
}
#[inline]
pub fn set_mOtherAttributeChanged(&mut self, val: bool) {
let mask = 128usize as u8;
let val = val as u8 as u8;
let mut unit_field_val: u8 =
unsafe { ::std::mem::transmute(self._bitfield_1) };
unit_field_val &= !mask;
unit_field_val |= (val << 7usize) & mask;
self._bitfield_1 =
unsafe { ::std::mem::transmute(unit_field_val) };
}
#[inline]
pub fn new_bitfield_1(mIsHTMLElementInHTMLDocument: bool,
mIsInChromeDocument: bool,
mSupportsLangAttr: bool,
mIsTableBorderNonzero: bool,
mIsMozBrowserFrame: bool) -> u8 {
mIsMozBrowserFrame: bool,
mClassAttributeChanged: bool,
mIdAttributeChanged: bool,
mOtherAttributeChanged: bool) -> u8 {
({
({
({
({
({ 0 } |
((mIsHTMLElementInHTMLDocument as u8
as u8) << 0usize) &
(1usize as u8))
({
({
({
({ 0 } |
((mIsHTMLElementInHTMLDocument
as u8 as u8) <<
0usize) &
(1usize as u8))
} |
((mIsInChromeDocument as u8
as u8) << 1usize) &
(2usize as u8))
} |
((mSupportsLangAttr as u8 as u8)
<< 2usize) &
(4usize as u8))
} |
((mIsTableBorderNonzero as u8 as u8)
<< 3usize) & (8usize as u8))
} |
((mIsInChromeDocument as u8 as u8) <<
1usize) & (2usize as u8))
((mIsMozBrowserFrame as u8 as u8) <<
4usize) & (16usize as u8))
} |
((mSupportsLangAttr as u8 as u8) << 2usize) &
(4usize as u8))
((mClassAttributeChanged as u8 as u8) <<
5usize) & (32usize as u8))
} |
((mIsTableBorderNonzero as u8 as u8) << 3usize) &
(8usize as u8))
((mIdAttributeChanged as u8 as u8) << 6usize) &
(64usize as u8))
} |
((mIsMozBrowserFrame as u8 as u8) << 4usize) &
(16usize as u8))
((mOtherAttributeChanged as u8 as u8) << 7usize) &
(128usize as u8))
}
}
#[repr(C)]

View File

@ -13,7 +13,7 @@ use gecko_bindings::bindings;
use gecko_bindings::structs::ServoElementSnapshot;
use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
use gecko_bindings::structs::ServoElementSnapshotTable;
use restyle_hints::ElementSnapshot;
use invalidation::element::element_wrapper::ElementSnapshot;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
use string_cache::{Atom, Namespace};
@ -62,6 +62,25 @@ impl GeckoElementSnapshot {
self.has_any(Flags::OtherPseudoClassState)
}
/// Returns true if the snapshot recorded an id change.
#[inline]
pub fn id_changed(&self) -> bool {
self.mIdAttributeChanged()
}
/// Returns true if the snapshot recorded a class attribute change.
#[inline]
pub fn class_changed(&self) -> bool {
self.mClassAttributeChanged()
}
/// Returns true if the snapshot recorded an attribute change which isn't a
/// class or id change.
#[inline]
pub fn other_attr_changed(&self) -> bool {
self.mOtherAttributeChanged()
}
/// selectors::Element::attr_matches
pub fn attr_matches(&self,
ns: &NamespaceConstraint<&Namespace>,

View File

@ -408,6 +408,24 @@ impl<'le> fmt::Debug for GeckoElement<'le> {
if let Some(id) = self.get_id() {
try!(write!(f, " id={}", id));
}
let mut first = true;
let mut any = false;
self.each_class(|c| {
if first {
first = false;
any = true;
let _ = f.write_str(" class=\"");
} else {
let _ = f.write_str(" ");
}
let _ = write!(f, "{}", c);
});
if any {
f.write_str("\"")?;
}
write!(f, "> ({:#x})", self.as_node().opaque().0)
}
}
@ -1240,6 +1258,10 @@ impl<'le> PresentationalHintsSynthesizer for GeckoElement<'le> {
// Unvisited vs. visited styles are computed up-front based on the
// visited mode (not the element's actual state).
let declarations = match visited_handling {
VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
unreachable!("We should never try to selector match with \
AllLinksVisitedAndUnvisited");
},
VisitedHandlingMode::AllLinksUnvisited => unsafe {
Gecko_GetUnvisitedLinkAttrDeclarationBlock(self.0)
},
@ -1547,7 +1569,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
let old_value = context.within_functional_pseudo_class_argument;
context.within_functional_pseudo_class_argument = true;
let result = sels.iter().any(|s| {
matches_complex_selector(s, 0, self, context, flags_setter)
matches_complex_selector(s.iter(), self, context, flags_setter)
});
context.within_functional_pseudo_class_argument = old_value;
result

View File

@ -0,0 +1,341 @@
/* 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 wrapper over an element and a snapshot, that allows us to selector-match
//! against a past state of the element.
use {Atom, CaseSensitivityExt, LocalName, Namespace};
use dom::TElement;
use element_state::ElementState;
use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
use selectors::Element;
use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext};
use selectors::matching::RelevantLinkStatus;
use std::cell::Cell;
use std::fmt;
/// In order to compute restyle hints, we perform a selector match against a
/// list of partial selectors whose rightmost simple selector may be sensitive
/// to the thing being changed. We do this matching twice, once for the element
/// as it exists now and once for the element as it existed at the time of the
/// last restyle. If the results of the selector match differ, that means that
/// the given partial selector is sensitive to the change, and we compute a
/// restyle hint based on its combinator.
///
/// In order to run selector matching against the old element state, we generate
/// a wrapper for the element which claims to have the old state. This is the
/// ElementWrapper logic below.
///
/// Gecko does this differently for element states, and passes a mask called
/// mStateMask, which indicates the states that need to be ignored during
/// selector matching. This saves an ElementWrapper allocation and an additional
/// selector match call at the expense of additional complexity inside the
/// selector matching logic. This only works for boolean states though, so we
/// still need to take the ElementWrapper approach for attribute-dependent
/// style. So we do it the same both ways for now to reduce complexity, but it's
/// worth measuring the performance impact (if any) of the mStateMask approach.
pub trait ElementSnapshot : Sized {
/// The state of the snapshot, if any.
fn state(&self) -> Option<ElementState>;
/// If this snapshot contains attribute information.
fn has_attrs(&self) -> bool;
/// The ID attribute per this snapshot. Should only be called if
/// `has_attrs()` returns true.
fn id_attr(&self) -> Option<Atom>;
/// Whether this snapshot contains the class `name`. Should only be called
/// if `has_attrs()` returns true.
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool;
/// A callback that should be called for each class of the snapshot. Should
/// only be called if `has_attrs()` returns true.
fn each_class<F>(&self, F)
where F: FnMut(&Atom);
/// The `xml:lang=""` or `lang=""` attribute value per this snapshot.
fn lang_attr(&self) -> Option<AttrValue>;
}
/// A simple wrapper over an element and a snapshot, that allows us to
/// selector-match against a past state of the element.
#[derive(Clone)]
pub struct ElementWrapper<'a, E>
where E: TElement,
{
element: E,
cached_snapshot: Cell<Option<&'a Snapshot>>,
snapshot_map: &'a SnapshotMap,
}
impl<'a, E> ElementWrapper<'a, E>
where E: TElement,
{
/// Trivially constructs an `ElementWrapper`.
pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self {
ElementWrapper {
element: el,
cached_snapshot: Cell::new(None),
snapshot_map: snapshot_map,
}
}
/// Gets the snapshot associated with this element, if any.
pub fn snapshot(&self) -> Option<&'a Snapshot> {
if !self.element.has_snapshot() {
return None;
}
if let Some(s) = self.cached_snapshot.get() {
return Some(s);
}
let snapshot = self.snapshot_map.get(&self.element);
debug_assert!(snapshot.is_some(), "has_snapshot lied!");
self.cached_snapshot.set(snapshot);
snapshot
}
/// Returns the states that have changed since the element was snapshotted.
pub fn state_changes(&self) -> ElementState {
let snapshot = match self.snapshot() {
Some(s) => s,
None => return ElementState::empty(),
};
match snapshot.state() {
Some(state) => state ^ self.element.get_state(),
None => ElementState::empty(),
}
}
/// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`)
/// attribute from this element's snapshot or the closest ancestor
/// element snapshot with the attribute specified.
fn get_lang(&self) -> Option<AttrValue> {
let mut current = self.clone();
loop {
let lang = match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(),
_ => current.element.lang_attr(),
};
if lang.is_some() {
return lang;
}
match current.parent_element() {
Some(parent) => current = parent,
None => return None,
}
}
}
}
impl<'a, E> fmt::Debug for ElementWrapper<'a, E>
where E: TElement,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Ignore other fields for now, can change later if needed.
self.element.fmt(f)
}
}
impl<'a, E> Element for ElementWrapper<'a, E>
where E: TElement,
{
type Impl = SelectorImpl;
fn match_non_ts_pseudo_class<F>(&self,
pseudo_class: &NonTSPseudoClass,
context: &mut LocalMatchingContext<Self::Impl>,
relevant_link: &RelevantLinkStatus,
_setter: &mut F)
-> bool
where F: FnMut(&Self, ElementSelectorFlags),
{
// Some pseudo-classes need special handling to evaluate them against
// the snapshot.
match *pseudo_class {
#[cfg(feature = "gecko")]
NonTSPseudoClass::MozAny(ref selectors) => {
use selectors::matching::matches_complex_selector;
return selectors.iter().any(|s| {
matches_complex_selector(s.iter(), self, context, _setter)
})
}
// :dir is implemented in terms of state flags, but which state flag
// it maps to depends on the argument to :dir. That means we can't
// just add its state flags to the NonTSPseudoClass, because if we
// added all of them there, and tested via intersects() here, we'd
// get incorrect behavior for :not(:dir()) cases.
//
// FIXME(bz): How can I set this up so once Servo adds :dir()
// support we don't forget to update this code?
#[cfg(feature = "gecko")]
NonTSPseudoClass::Dir(ref s) => {
use invalidation::element::invalidation_map::dir_selector_to_state;
let selector_flag = dir_selector_to_state(s);
if selector_flag.is_empty() {
// :dir() with some random argument; does not match.
return false;
}
let state = match self.snapshot().and_then(|s| s.state()) {
Some(snapshot_state) => snapshot_state,
None => self.element.get_state(),
};
return state.contains(selector_flag);
}
// For :link and :visited, we don't actually want to test the element
// state directly. Instead, we use the `relevant_link` to determine if
// they match.
NonTSPseudoClass::Link => {
return relevant_link.is_unvisited(self, context.shared);
}
NonTSPseudoClass::Visited => {
return relevant_link.is_visited(self, context.shared);
}
#[cfg(feature = "gecko")]
NonTSPseudoClass::MozTableBorderNonzero => {
if let Some(snapshot) = self.snapshot() {
if snapshot.has_other_pseudo_class_state() {
return snapshot.mIsTableBorderNonzero();
}
}
}
#[cfg(feature = "gecko")]
NonTSPseudoClass::MozBrowserFrame => {
if let Some(snapshot) = self.snapshot() {
if snapshot.has_other_pseudo_class_state() {
return snapshot.mIsMozBrowserFrame();
}
}
}
// :lang() needs to match using the closest ancestor xml:lang="" or
// lang="" attribtue from snapshots.
NonTSPseudoClass::Lang(ref lang_arg) => {
return self.element.match_element_lang(Some(self.get_lang()), lang_arg);
}
_ => {}
}
let flag = pseudo_class.state_flag();
if flag.is_empty() {
return self.element.match_non_ts_pseudo_class(pseudo_class,
context,
relevant_link,
&mut |_, _| {})
}
match self.snapshot().and_then(|s| s.state()) {
Some(snapshot_state) => snapshot_state.intersects(flag),
None => {
self.element.match_non_ts_pseudo_class(pseudo_class,
context,
relevant_link,
&mut |_, _| {})
}
}
}
fn match_pseudo_element(&self,
pseudo_element: &PseudoElement,
context: &mut MatchingContext)
-> bool
{
self.element.match_pseudo_element(pseudo_element, context)
}
fn is_link(&self) -> bool {
self.element.is_link()
}
fn parent_element(&self) -> Option<Self> {
self.element.parent_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn first_child_element(&self) -> Option<Self> {
self.element.first_child_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn last_child_element(&self) -> Option<Self> {
self.element.last_child_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn prev_sibling_element(&self) -> Option<Self> {
self.element.prev_sibling_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn next_sibling_element(&self) -> Option<Self> {
self.element.next_sibling_element()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
fn is_html_element_in_html_document(&self) -> bool {
self.element.is_html_element_in_html_document()
}
fn get_local_name(&self) -> &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName {
self.element.get_local_name()
}
fn get_namespace(&self) -> &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl {
self.element.get_namespace()
}
fn attr_matches(&self,
ns: &NamespaceConstraint<&Namespace>,
local_name: &LocalName,
operation: &AttrSelectorOperation<&AttrValue>)
-> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => {
snapshot.attr_matches(ns, local_name, operation)
}
_ => self.element.attr_matches(ns, local_name, operation)
}
}
fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => {
snapshot.id_attr().map_or(false, |atom| case_sensitivity.eq_atom(&atom, id))
}
_ => self.element.has_id(id, case_sensitivity)
}
}
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => {
snapshot.has_class(name, case_sensitivity)
}
_ => self.element.has_class(name, case_sensitivity)
}
}
fn is_empty(&self) -> bool {
self.element.is_empty()
}
fn is_root(&self) -> bool {
self.element.is_root()
}
fn pseudo_element_originating_element(&self) -> Option<Self> {
self.element.closest_non_native_anonymous_ancestor()
.map(|e| ElementWrapper::new(e, self.snapshot_map))
}
}

View File

@ -0,0 +1,402 @@
/* 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/. */
//! Code for invalidations due to state or attribute changes.
use {Atom, LocalName, Namespace};
use context::QuirksMode;
use element_state::ElementState;
use selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry};
use selector_parser::SelectorImpl;
use selectors::attr::NamespaceConstraint;
use selectors::parser::{AncestorHashes, Combinator, Component};
use selectors::parser::{Selector, SelectorAndHashes, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;
#[cfg(feature = "gecko")]
/// Gets the element state relevant to the given `:dir` pseudo-class selector.
pub fn dir_selector_to_state(s: &[u16]) -> ElementState {
use element_state::{IN_LTR_STATE, IN_RTL_STATE};
// Jump through some hoops to deal with our Box<[u16]> thing.
const LTR: [u16; 4] = [b'l' as u16, b't' as u16, b'r' as u16, 0];
const RTL: [u16; 4] = [b'r' as u16, b't' as u16, b'l' as u16, 0];
if LTR == *s {
IN_LTR_STATE
} else if RTL == *s {
IN_RTL_STATE
} else {
// :dir(something-random) is a valid selector, but shouldn't
// match anything.
ElementState::empty()
}
}
/// Mapping between (partial) CompoundSelectors (and the combinator to their
/// right) and the states and attributes they depend on.
///
/// In general, for all selectors in all applicable stylesheets of the form:
///
/// |a _ b _ c _ d _ e|
///
/// Where:
/// * |b| and |d| are simple selectors that depend on state (like :hover) or
/// attributes (like [attr...], .foo, or #foo).
/// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on
/// state or attributes.
///
/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|,
/// even though those selectors may not appear on their own in any stylesheet.
/// This allows us to quickly scan through the dependency sites of all style
/// rules and determine the maximum effect that a given state or attribute
/// change may have on the style of elements in the document.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Dependency {
/// The dependency selector.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub selector: Selector<SelectorImpl>,
/// The ancestor hashes associated with the above selector at the given
/// offset.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "No heap data")]
pub hashes: AncestorHashes,
/// The offset into the selector that we should match on.
pub selector_offset: usize,
}
impl Dependency {
/// Returns the combinator to the right of the partial selector this
/// dependency represents.
///
/// TODO(emilio): Consider storing inline if it helps cache locality?
pub fn combinator(&self) -> Option<Combinator> {
if self.selector_offset == 0 {
return None;
}
Some(self.selector.combinator_at(self.selector_offset))
}
/// Whether this dependency affects the style of the element.
///
/// NOTE(emilio): pseudo-elements need to be here to account for eager
/// pseudos, since they just grab the style from the originating element.
///
/// TODO(emilio): We could look at the selector itself to see if it's an
/// eager pseudo, and return false here if not.
pub fn affects_self(&self) -> bool {
matches!(self.combinator(), None | Some(Combinator::PseudoElement))
}
/// Whether this dependency may affect style of any of our descendants.
pub fn affects_descendants(&self) -> bool {
matches!(self.combinator(), Some(Combinator::PseudoElement) |
Some(Combinator::Child) |
Some(Combinator::Descendant))
}
/// Whether this dependency may affect style of any of our later siblings.
pub fn affects_later_siblings(&self) -> bool {
matches!(self.combinator(), Some(Combinator::NextSibling) |
Some(Combinator::LaterSibling))
}
}
impl SelectorMapEntry for Dependency {
fn selector(&self) -> SelectorIter<SelectorImpl> {
self.selector.iter_from(self.selector_offset)
}
fn hashes(&self) -> &AncestorHashes {
&self.hashes
}
}
/// The same, but for state selectors, which can track more exactly what state
/// do they track.
#[derive(Clone)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct StateDependency {
/// The other dependency fields.
pub dep: Dependency,
/// The state this dependency is affected by.
pub state: ElementState,
}
impl SelectorMapEntry for StateDependency {
fn selector(&self) -> SelectorIter<SelectorImpl> {
self.dep.selector.iter_from(self.dep.selector_offset)
}
fn hashes(&self) -> &AncestorHashes {
&self.dep.hashes
}
}
/// A map where we store invalidations.
///
/// This is slightly different to a SelectorMap, in the sense of that the same
/// selector may appear multiple times.
///
/// In particular, we want to lookup as few things as possible to get the fewer
/// selectors the better, so this looks up by id, class, or looks at the list of
/// state/other attribute affecting selectors.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct InvalidationMap {
/// A map from a given class name to all the selectors with that class
/// selector.
pub class_to_selector: MaybeCaseInsensitiveHashMap<Atom, SelectorMap<Dependency>>,
/// A map from a given id to all the selectors with that ID in the
/// stylesheets currently applying to the document.
pub id_to_selector: MaybeCaseInsensitiveHashMap<Atom, SelectorMap<Dependency>>,
/// A map of all the state dependencies.
pub state_affecting_selectors: SelectorMap<StateDependency>,
/// A map of other attribute affecting selectors.
pub other_attribute_affecting_selectors: SelectorMap<Dependency>,
/// Whether there are attribute rules of the form `[class~="foo"]` that may
/// match. In that case, we need to look at
/// `other_attribute_affecting_selectors` too even if only the `class` has
/// changed.
pub has_class_attribute_selectors: bool,
/// Whether there are attribute rules of the form `[id|="foo"]` that may
/// match. In that case, we need to look at
/// `other_attribute_affecting_selectors` too even if only the `id` has
/// changed.
pub has_id_attribute_selectors: bool,
}
impl InvalidationMap {
/// Creates an empty `InvalidationMap`.
pub fn new() -> Self {
Self {
class_to_selector: MaybeCaseInsensitiveHashMap::new(),
id_to_selector: MaybeCaseInsensitiveHashMap::new(),
state_affecting_selectors: SelectorMap::new(),
other_attribute_affecting_selectors: SelectorMap::new(),
has_class_attribute_selectors: false,
has_id_attribute_selectors: false,
}
}
/// Returns the number of dependencies stored in the invalidation map.
pub fn len(&self) -> usize {
self.state_affecting_selectors.len() +
self.other_attribute_affecting_selectors.len() +
self.id_to_selector.iter().fold(0, |accum, (_, ref v)| {
accum + v.len()
}) +
self.class_to_selector.iter().fold(0, |accum, (_, ref v)| {
accum + v.len()
})
}
/// Adds a selector to this `InvalidationMap`.
pub fn note_selector(
&mut self,
selector_and_hashes: &SelectorAndHashes<SelectorImpl>,
quirks_mode: QuirksMode)
{
self.collect_invalidations_for(selector_and_hashes, quirks_mode)
}
/// Clears this map, leaving it empty.
pub fn clear(&mut self) {
self.class_to_selector.clear();
self.id_to_selector.clear();
self.state_affecting_selectors = SelectorMap::new();
self.other_attribute_affecting_selectors = SelectorMap::new();
self.has_id_attribute_selectors = false;
self.has_class_attribute_selectors = false;
}
fn collect_invalidations_for(
&mut self,
selector_and_hashes: &SelectorAndHashes<SelectorImpl>,
quirks_mode: QuirksMode)
{
debug!("InvalidationMap::collect_invalidations_for({:?})",
selector_and_hashes.selector);
let mut iter = selector_and_hashes.selector.iter();
let mut combinator;
let mut index = 0;
loop {
let sequence_start = index;
let mut compound_visitor = CompoundSelectorDependencyCollector {
classes: SmallVec::new(),
ids: SmallVec::new(),
state: ElementState::empty(),
other_attributes: false,
has_id_attribute_selectors: false,
has_class_attribute_selectors: false,
};
// Visit all the simple selectors in this sequence.
//
// Note that this works because we can't have combinators nested
// inside simple selectors (i.e. in :not() or :-moz-any()).
//
// If we ever support that we'll need to visit nested complex
// selectors as well, in order to mark them as affecting descendants
// at least.
for ss in &mut iter {
ss.visit(&mut compound_visitor);
index += 1; // Account for the simple selector.
}
// Reuse the bloom hashes if this is the base selector. Otherwise,
// rebuild them.
let mut hashes = None;
let mut get_hashes = || -> AncestorHashes {
if hashes.is_none() {
hashes = Some(if sequence_start == 0 {
selector_and_hashes.hashes.clone()
} else {
let seq_iter = selector_and_hashes.selector.iter_from(sequence_start);
AncestorHashes::from_iter(seq_iter)
});
}
hashes.clone().unwrap()
};
self.has_id_attribute_selectors |= compound_visitor.has_id_attribute_selectors;
self.has_class_attribute_selectors |= compound_visitor.has_class_attribute_selectors;
for class in compound_visitor.classes {
self.class_to_selector
.entry(class, quirks_mode)
.or_insert_with(SelectorMap::new)
.insert(Dependency {
selector: selector_and_hashes.selector.clone(),
selector_offset: sequence_start,
hashes: get_hashes(),
}, quirks_mode);
}
for id in compound_visitor.ids {
self.id_to_selector
.entry(id, quirks_mode)
.or_insert_with(SelectorMap::new)
.insert(Dependency {
selector: selector_and_hashes.selector.clone(),
selector_offset: sequence_start,
hashes: get_hashes(),
}, quirks_mode);
}
if !compound_visitor.state.is_empty() {
self.state_affecting_selectors
.insert(StateDependency {
dep: Dependency {
selector: selector_and_hashes.selector.clone(),
selector_offset: sequence_start,
hashes: get_hashes(),
},
state: compound_visitor.state,
}, quirks_mode);
}
if compound_visitor.other_attributes {
self.other_attribute_affecting_selectors
.insert(Dependency {
selector: selector_and_hashes.selector.clone(),
selector_offset: sequence_start,
hashes: get_hashes(),
}, quirks_mode);
}
combinator = iter.next_sequence();
if combinator.is_none() {
break;
}
index += 1; // Account for the combinator.
}
}
}
/// A struct that collects invalidations for a given compound selector.
struct CompoundSelectorDependencyCollector {
/// The state this compound selector is affected by.
state: ElementState,
/// The classes this compound selector is affected by.
///
/// NB: This will be often a single class, but could be multiple in
/// presence of :not, :-moz-any, .foo.bar.baz, etc.
classes: SmallVec<[Atom; 5]>,
/// The IDs this compound selector is affected by.
///
/// NB: This will be almost always a single id, but could be multiple in
/// presence of :not, :-moz-any, #foo#bar, etc.
ids: SmallVec<[Atom; 5]>,
/// Whether it affects other attribute-dependent selectors that aren't ID or
/// class selectors (NB: We still set this to true in presence of [class] or
/// [id] attribute selectors).
other_attributes: bool,
/// Whether there were attribute selectors with the id attribute.
has_id_attribute_selectors: bool,
/// Whether there were attribute selectors with the class attribute.
has_class_attribute_selectors: bool,
}
impl SelectorVisitor for CompoundSelectorDependencyCollector {
type Impl = SelectorImpl;
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
#[cfg(feature = "gecko")]
use selector_parser::NonTSPseudoClass;
match *s {
Component::ID(ref id) => {
self.ids.push(id.clone());
}
Component::Class(ref class) => {
self.classes.push(class.clone());
}
Component::NonTSPseudoClass(ref pc) => {
self.other_attributes |= pc.is_attr_based();
self.state |= match *pc {
#[cfg(feature = "gecko")]
NonTSPseudoClass::Dir(ref s) => {
dir_selector_to_state(s)
}
_ => pc.state_flag(),
};
}
_ => {}
}
true
}
fn visit_attribute_selector(
&mut self,
constraint: &NamespaceConstraint<&Namespace>,
_local_name: &LocalName,
local_name_lower: &LocalName,
) -> bool {
self.other_attributes = true;
let may_match_in_no_namespace = match *constraint {
NamespaceConstraint::Any => true,
NamespaceConstraint::Specific(ref ns) => ns.is_empty(),
};
if may_match_in_no_namespace {
self.has_id_attribute_selectors |= *local_name_lower == local_name!("id");
self.has_class_attribute_selectors |= *local_name_lower == local_name!("class");
}
true
}
}

View File

@ -0,0 +1,704 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The struct that takes care of encapsulating all the logic on where and how
//! element styles need to be invalidated.
use Atom;
use context::SharedStyleContext;
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;
use selectors::attr::CaseSensitivity;
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
use selectors::matching::{matches_selector, matches_compound_selector};
use selectors::matching::CompoundSelectorMatchingResult;
use selectors::parser::{Combinator, Component, Selector};
use smallvec::SmallVec;
use std::fmt;
/// The struct that takes care of encapsulating all the logic on where and how
/// element styles need to be invalidated.
pub struct TreeStyleInvalidator<'a, 'b: 'a, E>
where E: TElement,
{
element: E,
data: Option<&'a mut ElementData>,
shared_context: &'a SharedStyleContext<'b>,
}
type InvalidationVector = SmallVec<[Invalidation; 10]>;
/// An `Invalidation` is a complex selector that describes which elements,
/// relative to a current element we are processing, must be restyled.
///
/// When `offset` points to the right-most compound selector in `selector`,
/// then the Invalidation `represents` the fact that the current element
/// must be restyled if the compound selector matches. Otherwise, if
/// describes which descendants (or later siblings) must be restyled.
#[derive(Clone)]
struct Invalidation {
selector: Selector<SelectorImpl>,
offset: usize,
}
impl fmt::Debug for Invalidation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use cssparser::ToCss;
f.write_str("Invalidation(")?;
for component in self.selector.iter_raw_rev_from(self.offset - 1) {
if matches!(*component, Component::Combinator(..)) {
break;
}
component.to_css(f)?;
}
f.write_str(")")
}
}
/// The result of processing a single invalidation for a given element.
struct InvalidationResult {
/// Whether the element itself was invalidated.
invalidated_self: bool,
/// Whether the invalidation we've processed is effective for the next
/// sibling or descendant after us.
effective_for_next: bool,
}
impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
where E: TElement,
{
/// Trivially constructs a new `TreeStyleInvalidator`.
pub fn new(
element: E,
data: Option<&'a mut ElementData>,
shared_context: &'a SharedStyleContext<'b>,
) -> Self {
Self {
element: element,
data: data,
shared_context: shared_context,
}
}
/// Perform the invalidation pass.
pub fn invalidate(mut self) {
debug!("StyleTreeInvalidator::invalidate({:?})", self.element);
debug_assert!(self.element.has_snapshot(), "Why bothering?");
debug_assert!(self.data.is_some(), "How exactly?");
let shared_context = self.shared_context;
let wrapper =
ElementWrapper::new(self.element, shared_context.snapshot_map);
let state_changes = wrapper.state_changes();
let snapshot = wrapper.snapshot().expect("has_snapshot lied");
if !snapshot.has_attrs() && state_changes.is_empty() {
return;
}
// If we are sensitive to visitedness and the visited state changed, we
// force a restyle here. Matching doesn't depend on the actual visited
// state at all, so we can't look at matching results to decide what to
// do for this case.
if state_changes.intersects(IN_VISITED_OR_UNVISITED_STATE) {
trace!(" > visitedness change, force subtree restyle");
// We can't just return here because there may also be attribute
// changes as well that imply additional hints.
let mut data = self.data.as_mut().unwrap();
data.ensure_restyle().hint.insert(RestyleHint::restyle_subtree().into());
}
let mut classes_removed = SmallVec::<[Atom; 8]>::new();
let mut classes_added = SmallVec::<[Atom; 8]>::new();
if snapshot.class_changed() {
// TODO(emilio): Do this more efficiently!
snapshot.each_class(|c| {
if !self.element.has_class(c, CaseSensitivity::CaseSensitive) {
classes_removed.push(c.clone())
}
});
self.element.each_class(|c| {
if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
classes_added.push(c.clone())
}
})
}
let mut id_removed = None;
let mut id_added = None;
if snapshot.id_changed() {
let old_id = snapshot.id_attr();
let current_id = self.element.get_id();
if old_id != current_id {
id_removed = old_id;
id_added = current_id;
}
}
let lookup_element =
if self.element.implemented_pseudo_element().is_some() {
self.element.pseudo_element_originating_element().unwrap()
} else {
self.element
};
let mut descendant_invalidations = InvalidationVector::new();
let mut sibling_invalidations = InvalidationVector::new();
let invalidated_self = {
let mut collector = InvalidationCollector {
wrapper: wrapper,
element: self.element,
shared_context: self.shared_context,
lookup_element: lookup_element,
removed_id: id_removed.as_ref(),
classes_removed: &classes_removed,
descendant_invalidations: &mut descendant_invalidations,
sibling_invalidations: &mut sibling_invalidations,
invalidates_self: false,
};
let map = shared_context.stylist.invalidation_map();
if let Some(ref id) = id_removed {
if let Some(deps) = map.id_to_selector.get(id, shared_context.quirks_mode) {
collector.collect_dependencies_in_map(deps)
}
}
if let Some(ref id) = id_added {
if let Some(deps) = map.id_to_selector.get(id, shared_context.quirks_mode) {
collector.collect_dependencies_in_map(deps)
}
}
for class in classes_added.iter().chain(classes_removed.iter()) {
if let Some(deps) = map.class_to_selector.get(class, shared_context.quirks_mode) {
collector.collect_dependencies_in_map(deps)
}
}
let should_examine_attribute_selector_map =
snapshot.other_attr_changed() ||
(snapshot.class_changed() && map.has_class_attribute_selectors) ||
(snapshot.id_changed() && map.has_id_attribute_selectors);
if should_examine_attribute_selector_map {
collector.collect_dependencies_in_map(
&map.other_attribute_affecting_selectors
)
}
if !state_changes.is_empty() {
collector.collect_state_dependencies(
&map.state_affecting_selectors,
state_changes,
)
}
collector.invalidates_self
};
if invalidated_self {
if let Some(ref mut data) = self.data {
data.ensure_restyle().hint.insert(RESTYLE_SELF.into());
}
}
debug!("Collected invalidations (self: {}): ", invalidated_self);
debug!(" > descendants: {:?}", descendant_invalidations);
debug!(" > siblings: {:?}", sibling_invalidations);
self.invalidate_descendants(&descendant_invalidations);
self.invalidate_siblings(&mut sibling_invalidations);
}
/// Go through later DOM siblings, invalidating style as needed using the
/// `sibling_invalidations` list.
///
/// Returns whether any sibling's style or any sibling descendant's style
/// was invalidated.
fn invalidate_siblings(
&mut self,
sibling_invalidations: &mut InvalidationVector,
) -> bool {
if sibling_invalidations.is_empty() {
return false;
}
let mut current = self.element.next_sibling_element();
let mut any_invalidated = false;
while let Some(sibling) = current {
let mut sibling_data = sibling.get_data().map(|d| d.borrow_mut());
let sibling_data = sibling_data.as_mut().map(|d| &mut **d);
let mut sibling_invalidator = TreeStyleInvalidator::new(
sibling,
sibling_data,
self.shared_context
);
let mut invalidations_for_descendants = InvalidationVector::new();
any_invalidated |=
sibling_invalidator.process_sibling_invalidations(
&mut invalidations_for_descendants,
sibling_invalidations,
);
any_invalidated |=
sibling_invalidator.invalidate_descendants(
&invalidations_for_descendants
);
if sibling_invalidations.is_empty() {
break;
}
current = sibling.next_sibling_element();
}
any_invalidated
}
/// Given a descendant invalidation list, go through the current element's
/// descendants, and invalidate style on them.
fn invalidate_descendants(
&mut self,
invalidations: &InvalidationVector,
) -> bool {
if invalidations.is_empty() {
return false;
}
debug!("StyleTreeInvalidator::invalidate_descendants({:?})",
self.element);
debug!(" > {:?}", invalidations);
match self.data {
None => return false,
Some(ref data) => {
if let Some(restyle) = data.get_restyle() {
if restyle.hint.contains_subtree() {
return false;
}
}
}
}
let mut sibling_invalidations = InvalidationVector::new();
let mut any_children = false;
for child in self.element.as_node().traversal_children() {
let child = match child.as_element() {
Some(e) => e,
None => continue,
};
let mut child_data = child.get_data().map(|d| d.borrow_mut());
let child_data = child_data.as_mut().map(|d| &mut **d);
let mut child_invalidator = TreeStyleInvalidator::new(
child,
child_data,
self.shared_context
);
let mut invalidations_for_descendants = InvalidationVector::new();
any_children |= child_invalidator.process_sibling_invalidations(
&mut invalidations_for_descendants,
&mut sibling_invalidations,
);
any_children |= child_invalidator.process_descendant_invalidations(
invalidations,
&mut invalidations_for_descendants,
&mut sibling_invalidations,
);
any_children |= child_invalidator.invalidate_descendants(
&invalidations_for_descendants
);
}
if any_children {
unsafe { self.element.set_dirty_descendants() };
}
any_children
}
/// Process the given sibling invalidations coming from our previous
/// sibling.
///
/// The sibling invalidations are somewhat special because they can be
/// modified on the fly. New invalidations may be added and removed.
///
/// In particular, all descendants get the same set of invalidations from
/// the parent, but the invalidations from a given sibling depend on the
/// ones we got from the previous one.
///
/// Returns whether invalidated the current element's style.
fn process_sibling_invalidations(
&mut self,
descendant_invalidations: &mut InvalidationVector,
sibling_invalidations: &mut InvalidationVector,
) -> bool {
let mut i = 0;
let mut new_sibling_invalidations = InvalidationVector::new();
let mut invalidated_self = false;
while i < sibling_invalidations.len() {
let result = self.process_invalidation(
&sibling_invalidations[i],
descendant_invalidations,
&mut new_sibling_invalidations
);
invalidated_self |= result.invalidated_self;
if !result.effective_for_next {
sibling_invalidations.remove(i);
} else {
i += 1;
}
}
sibling_invalidations.extend(new_sibling_invalidations.into_iter());
invalidated_self
}
/// Process a given invalidation list coming from our parent,
/// adding to `descendant_invalidations` and `sibling_invalidations` as
/// needed.
///
/// Returns whether our style was invalidated as a result.
fn process_descendant_invalidations(
&mut self,
invalidations: &InvalidationVector,
descendant_invalidations: &mut InvalidationVector,
sibling_invalidations: &mut InvalidationVector,
) -> bool {
let mut invalidated = false;
for invalidation in invalidations {
let result = self.process_invalidation(
invalidation,
descendant_invalidations,
sibling_invalidations,
);
invalidated |= result.invalidated_self;
if result.effective_for_next {
descendant_invalidations.push(invalidation.clone());
}
}
invalidated
}
/// Processes a given invalidation, potentially invalidating the style of
/// the current element.
///
/// Returns whether invalidated the style of the element, and whether the
/// invalidation should be effective to subsequent siblings or descendants
/// down in the tree.
fn process_invalidation(
&mut self,
invalidation: &Invalidation,
descendant_invalidations: &mut InvalidationVector,
sibling_invalidations: &mut InvalidationVector
) -> InvalidationResult {
debug!("TreeStyleInvalidator::process_invalidation({:?}, {:?})",
self.element, invalidation);
let mut context =
MatchingContext::new_for_visited(
MatchingMode::Normal,
None,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
self.shared_context.quirks_mode,
);
let matching_result = matches_compound_selector(
&invalidation.selector,
invalidation.offset,
&mut context,
&self.element
);
let mut invalidated_self = false;
match matching_result {
CompoundSelectorMatchingResult::Matched { next_combinator_offset: 0 } => {
debug!(" > Invalidation matched completely");
invalidated_self = true;
}
CompoundSelectorMatchingResult::Matched { next_combinator_offset } => {
let next_combinator =
invalidation.selector.combinator_at(next_combinator_offset);
if matches!(next_combinator, Combinator::PseudoElement) {
let pseudo_selector =
invalidation.selector
.iter_raw_rev_from(next_combinator_offset - 1)
.next()
.unwrap();
let pseudo = match *pseudo_selector {
Component::PseudoElement(ref pseudo) => pseudo,
_ => unreachable!("Someone seriously messed up selector parsing"),
};
// FIXME(emilio): This is not ideal, and could not be
// accurate if we ever have stateful element-backed eager
// pseudos.
//
// Ideally, we'd just remove element-backed eager pseudos
// altogether, given they work fine without it. Only gotcha
// is that we wouldn't style them in parallel, which may or
// may not be an issue.
//
// Also, this could be more fine grained now (perhaps a
// RESTYLE_PSEUDOS hint?).
//
// Note that we'll also restyle the pseudo-element because
// it would match this invalidation.
if pseudo.is_eager() {
invalidated_self = true;
}
}
let next_invalidation = Invalidation {
selector: invalidation.selector.clone(),
offset: next_combinator_offset,
};
debug!(" > Invalidation matched, next: {:?}, ({:?})",
next_invalidation, next_combinator);
if next_combinator.is_ancestor() {
descendant_invalidations.push(next_invalidation);
} else {
sibling_invalidations.push(next_invalidation);
}
}
CompoundSelectorMatchingResult::NotMatched => {}
}
if invalidated_self {
if let Some(ref mut data) = self.data {
data.ensure_restyle().hint.insert(RESTYLE_SELF.into());
}
}
// TODO(emilio): For pseudo-elements this should be mostly false, except
// for the weird pseudos in <input type="number">.
//
// We should be able to do better here!
let effective_for_next =
match invalidation.selector.combinator_at(invalidation.offset) {
Combinator::NextSibling |
Combinator::Child => false,
_ => true,
};
InvalidationResult {
invalidated_self: invalidated_self,
effective_for_next: effective_for_next,
}
}
}
struct InvalidationCollector<'a, 'b: 'a, E>
where E: TElement,
{
element: E,
wrapper: ElementWrapper<'b, E>,
shared_context: &'a SharedStyleContext<'b>,
lookup_element: E,
removed_id: Option<&'a Atom>,
classes_removed: &'a SmallVec<[Atom; 8]>,
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_map(
&mut self,
map: &SelectorMap<Dependency>,
) {
map.lookup_with_additional(
self.lookup_element,
self.shared_context.quirks_mode,
self.removed_id,
self.classes_removed,
&mut |dependency| {
self.scan_dependency(
dependency,
/* is_visited_dependent = */ false
);
true
},
);
}
fn collect_state_dependencies(
&mut self,
map: &SelectorMap<StateDependency>,
state_changes: ElementState,
) {
map.lookup_with_additional(
self.lookup_element,
self.shared_context.quirks_mode,
self.removed_id,
self.classes_removed,
&mut |dependency| {
if !dependency.state.intersects(state_changes) {
return true;
}
self.scan_dependency(
&dependency.dep,
dependency.state.intersects(IN_VISITED_OR_UNVISITED_STATE)
);
true
},
);
}
fn scan_dependency(
&mut self,
dependency: &Dependency,
is_visited_dependent: bool
) {
debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {})",
self.element,
dependency,
is_visited_dependent);
if !self.dependency_may_be_relevant(dependency) {
return;
}
// TODO(emilio): Add a bloom filter here?
//
// If we decide to do so, we can't use the bloom filter for snapshots,
// given that arbitrary elements in the parent chain may have mutated
// their id's/classes, which means that they won't be in the filter, and
// as such we may fast-reject selectors incorrectly.
//
// We may be able to improve this if we record as we go down the tree
// whether any parent had a snapshot, and whether those snapshots were
// taken due to an element class/id change, but it's not clear it'd be
// worth it.
let mut now_context =
MatchingContext::new_for_visited(MatchingMode::Normal, None,
VisitedHandlingMode::AllLinksUnvisited,
self.shared_context.quirks_mode);
let mut then_context =
MatchingContext::new_for_visited(MatchingMode::Normal, None,
VisitedHandlingMode::AllLinksUnvisited,
self.shared_context.quirks_mode);
let matched_then =
matches_selector(&dependency.selector,
dependency.selector_offset,
&dependency.hashes,
&self.wrapper,
&mut then_context,
&mut |_, _| {});
let matches_now =
matches_selector(&dependency.selector,
dependency.selector_offset,
&dependency.hashes,
&self.element,
&mut now_context,
&mut |_, _| {});
// Check for mismatches in both the match result and also the status
// of whether a relevant link was found.
if matched_then != matches_now ||
then_context.relevant_link_found != now_context.relevant_link_found {
return self.note_dependency(dependency);
}
// If there is a relevant link, then we also matched in visited
// mode.
//
// Match again in this mode to ensure this also matches.
//
// Note that we never actually match directly against the element's true
// visited state at all, since that would expose us to timing attacks.
//
// The matching process only considers the relevant link state and
// visited handling mode when deciding if visited matches. Instead, we
// are rematching here in case there is some :visited selector whose
// matching result changed for some other state or attribute change of
// this element (for example, for things like [foo]:visited).
//
// NOTE: This thing is actually untested because testing it is flaky,
// see the tests that were added and then backed out in bug 1328509.
if is_visited_dependent && now_context.relevant_link_found {
then_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited;
let matched_then =
matches_selector(&dependency.selector,
dependency.selector_offset,
&dependency.hashes,
&self.wrapper,
&mut then_context,
&mut |_, _| {});
now_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited;
let matches_now =
matches_selector(&dependency.selector,
dependency.selector_offset,
&dependency.hashes,
&self.element,
&mut now_context,
&mut |_, _| {});
if matched_then != matches_now {
return self.note_dependency(dependency);
}
}
}
fn note_dependency(&mut self, dependency: &Dependency) {
if dependency.affects_self() {
self.invalidates_self = true;
}
if dependency.affects_descendants() {
debug_assert_ne!(dependency.selector_offset, 0);
debug_assert!(!dependency.affects_later_siblings());
self.descendant_invalidations.push(Invalidation {
selector: dependency.selector.clone(),
offset: dependency.selector_offset,
});
} 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,
});
}
}
/// 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
}
}

View File

@ -0,0 +1,10 @@
/* 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/. */
//! Invalidation of element styles due to attribute or style changes.
pub mod element_wrapper;
pub mod invalidation_map;
pub mod invalidator;
pub mod restyle_hints;

View File

@ -0,0 +1,212 @@
/* 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/. */
//! Restyle hints: an optimization to avoid unnecessarily matching selectors.
#[cfg(feature = "gecko")]
use gecko_bindings::structs::nsRestyleHint;
bitflags! {
/// The kind of restyle we need to do for a given element.
pub flags RestyleHint: u8 {
/// Do a selector match of the element.
const RESTYLE_SELF = 1 << 0,
/// Do a selector match of the element's descendants.
const RESTYLE_DESCENDANTS = 1 << 1,
/// Recascade the current element.
const RECASCADE_SELF = 1 << 2,
/// Recascade all descendant elements.
const RECASCADE_DESCENDANTS = 1 << 3,
/// Replace the style data coming from CSS transitions without updating
/// any other style data. This hint is only processed in animation-only
/// traversal which is prior to normal traversal.
const RESTYLE_CSS_TRANSITIONS = 1 << 4,
/// Replace the style data coming from CSS animations without updating
/// any other style data. This hint is only processed in animation-only
/// traversal which is prior to normal traversal.
const RESTYLE_CSS_ANIMATIONS = 1 << 5,
/// Don't re-run selector-matching on the element, only the style
/// attribute has changed, and this change didn't have any other
/// dependencies.
const RESTYLE_STYLE_ATTRIBUTE = 1 << 6,
/// Replace the style data coming from SMIL animations without updating
/// any other style data. This hint is only processed in animation-only
/// traversal which is prior to normal traversal.
const RESTYLE_SMIL = 1 << 7,
}
}
impl RestyleHint {
/// Creates a new `RestyleHint` indicating that the current element and all
/// its descendants must be fully restyled.
pub fn restyle_subtree() -> Self {
RESTYLE_SELF | RESTYLE_DESCENDANTS
}
/// Creates a new `RestyleHint` indicating that the current element and all
/// its descendants must be recascaded.
pub fn recascade_subtree() -> Self {
RECASCADE_SELF | RECASCADE_DESCENDANTS
}
/// Returns a new `CascadeHint` appropriate for children of the current
/// element.
pub fn propagate_for_non_animation_restyle(&self) -> Self {
if self.contains(RESTYLE_DESCENDANTS) {
return Self::restyle_subtree()
}
if self.contains(RECASCADE_DESCENDANTS) {
return Self::recascade_subtree()
}
Self::empty()
}
/// Creates a new `RestyleHint` that indicates the element must be
/// recascaded.
pub fn recascade_self() -> Self {
RECASCADE_SELF
}
/// Returns a hint that contains all the replacement hints.
pub fn replacements() -> Self {
RESTYLE_STYLE_ATTRIBUTE | Self::for_animations()
}
/// The replacements for the animation cascade levels.
#[inline]
pub fn for_animations() -> Self {
RESTYLE_SMIL | RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS
}
/// Returns whether the hint specifies that some work must be performed on
/// the current element.
#[inline]
pub fn affects_self(&self) -> bool {
self.intersects(RESTYLE_SELF | RECASCADE_SELF | Self::replacements())
}
/// Returns whether the hint specifies that the currently element must be
/// recascaded.
pub fn has_recascade_self(&self) -> bool {
self.contains(RECASCADE_SELF)
}
/// Returns whether the hint specifies that an animation cascade level must
/// be replaced.
#[inline]
pub fn has_animation_hint(&self) -> bool {
self.intersects(Self::for_animations())
}
/// Returns whether the hint specifies some restyle work other than an
/// animation cascade level replacement.
#[inline]
pub fn has_non_animation_hint(&self) -> bool {
!(*self & !Self::for_animations()).is_empty()
}
/// Returns whether the hint specifies that selector matching must be re-run
/// for the element.
#[inline]
pub fn match_self(&self) -> bool {
self.intersects(RESTYLE_SELF)
}
/// Returns whether the hint specifies that some cascade levels must be
/// replaced.
#[inline]
pub fn has_replacements(&self) -> bool {
self.intersects(Self::replacements())
}
/// Removes all of the animation-related hints.
#[inline]
pub fn remove_animation_hints(&mut self) {
self.remove(Self::for_animations());
// While RECASCADE_SELF is not animation-specific, we only ever add and
// process it during traversal. If we are here, removing animation
// hints, then we are in an animation-only traversal, and we know that
// any RECASCADE_SELF flag must have been set due to changes in
// inherited values after restyling for animations, and thus we want to
// remove it so that we don't later try to restyle the element during a
// normal restyle. (We could have separate RECASCADE_SELF_NORMAL and
// RECASCADE_SELF_ANIMATIONS flags to make it clear, but this isn't
// currently necessary.)
self.remove(RECASCADE_SELF);
}
}
#[cfg(feature = "gecko")]
impl From<nsRestyleHint> for RestyleHint {
fn from(raw: nsRestyleHint) -> Self {
use gecko_bindings::structs::nsRestyleHint_eRestyle_ForceDescendants as eRestyle_ForceDescendants;
use gecko_bindings::structs::nsRestyleHint_eRestyle_LaterSiblings as eRestyle_LaterSiblings;
use gecko_bindings::structs::nsRestyleHint_eRestyle_Self as eRestyle_Self;
use gecko_bindings::structs::nsRestyleHint_eRestyle_SomeDescendants as eRestyle_SomeDescendants;
use gecko_bindings::structs::nsRestyleHint_eRestyle_Subtree as eRestyle_Subtree;
let mut hint = RestyleHint::empty();
debug_assert!(raw.0 & eRestyle_LaterSiblings.0 == 0,
"Handle later siblings manually if necessary plz.");
if (raw.0 & (eRestyle_Self.0 | eRestyle_Subtree.0)) != 0 {
hint.insert(RESTYLE_SELF);
}
if (raw.0 & (eRestyle_Subtree.0 | eRestyle_SomeDescendants.0)) != 0 {
hint.insert(RESTYLE_DESCENDANTS);
}
if (raw.0 & eRestyle_ForceDescendants.0) != 0 {
hint.insert(RECASCADE_DESCENDANTS);
}
hint.insert(RestyleHint::from_bits_truncate(raw.0 as u8));
hint
}
}
#[cfg(feature = "servo")]
impl ::heapsize::HeapSizeOf for RestyleHint {
fn heap_size_of_children(&self) -> usize { 0 }
}
/// Asserts that all replacement hints have a matching nsRestyleHint value.
#[cfg(feature = "gecko")]
#[inline]
pub fn assert_restyle_hints_match() {
use gecko_bindings::structs;
macro_rules! check_restyle_hints {
( $( $a:ident => $b:ident ),*, ) => {
if cfg!(debug_assertions) {
let mut replacements = RestyleHint::replacements();
$(
assert_eq!(structs::$a.0 as usize, $b.bits() as usize, stringify!($b));
replacements.remove($b);
)*
assert_eq!(replacements, RestyleHint::empty(),
"all RestyleHint replacement bits should have an \
assertion");
}
}
}
check_restyle_hints! {
nsRestyleHint_eRestyle_CSSTransitions => RESTYLE_CSS_TRANSITIONS,
nsRestyleHint_eRestyle_CSSAnimations => RESTYLE_CSS_ANIMATIONS,
nsRestyleHint_eRestyle_StyleAttribute => RESTYLE_STYLE_ATTRIBUTE,
nsRestyleHint_eRestyle_StyleAttribute_Animations => RESTYLE_SMIL,
}
}

View File

@ -4,5 +4,6 @@
//! Different bits of code related to invalidating style.
pub mod element;
pub mod media_queries;
pub mod stylesheets;

View File

@ -116,12 +116,37 @@ impl StylesheetInvalidationSet {
where E: TElement,
{
if let Some(e) = document_element {
self.process_invalidations_in_subtree(e);
self.process_invalidations(e);
}
self.invalid_scopes.clear();
self.fully_invalid = false;
}
fn process_invalidations<E>(&self, element: E) -> bool
where E: TElement,
{
{
let mut data = match element.mutate_data() {
Some(data) => data,
None => return false,
};
if self.fully_invalid {
debug!("process_invalidations: fully_invalid({:?})",
element);
data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
return true;
}
}
if self.invalid_scopes.is_empty() {
debug!("process_invalidations: empty invalidation set");
return false;
}
self.process_invalidations_in_subtree(element)
}
/// Process style invalidations in a given subtree, that is, look for all
/// the relevant scopes in the subtree, and mark as dirty only the relevant
/// ones.
@ -148,13 +173,6 @@ impl StylesheetInvalidationSet {
}
}
if self.fully_invalid {
debug!("process_invalidations_in_subtree: fully_invalid({:?})",
element);
data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
return true;
}
for scope in &self.invalid_scopes {
if scope.matches(element) {
debug!("process_invalidations_in_subtree: {:?} matched {:?}",

View File

@ -117,7 +117,6 @@ pub mod matching;
pub mod media_queries;
pub mod parallel;
pub mod parser;
pub mod restyle_hints;
pub mod rule_tree;
pub mod scoped_tls;
pub mod selector_map;

View File

@ -13,13 +13,14 @@ use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, RestyleData};
use dom::{TElement, TNode};
use font_metrics::FontMetricsProvider;
use invalidation::element::restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS};
use invalidation::element::restyle_hints::{RESTYLE_SMIL, RESTYLE_STYLE_ATTRIBUTE};
use invalidation::element::restyle_hints::RestyleHint;
use log::LogLevel::Trace;
use properties::{ALLOW_SET_ROOT_FONT_SIZE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
use properties::{AnimationRules, CascadeFlags, ComputedValues};
use properties::{VISITED_DEPENDENT_ONLY, cascade};
use properties::longhands::display::computed_value as display;
use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleReplacements};
use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL};
use rule_tree::{CascadeLevel, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
@ -990,6 +991,10 @@ pub trait MatchMethods : TElement {
&context.shared.guards);
let rules_changed = match visited_handling {
VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
unreachable!("We should never try to selector match with \
AllLinksVisitedAndUnvisited");
},
VisitedHandlingMode::AllLinksUnvisited => {
data.set_primary_rules(rules)
},
@ -1069,6 +1074,10 @@ pub trait MatchMethods : TElement {
);
let rules_changed = match visited_handling {
VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
unreachable!("We should never try to selector match with \
AllLinksVisitedAndUnvisited");
},
VisitedHandlingMode::AllLinksUnvisited => {
data.set_primary_rules(primary_rule_node)
},
@ -1276,11 +1285,12 @@ pub trait MatchMethods : TElement {
/// the rule tree.
///
/// Returns true if an !important rule was replaced.
fn replace_rules(&self,
replacements: RestyleReplacements,
context: &StyleContext<Self>,
data: &mut ElementData)
-> bool {
fn replace_rules(
&self,
replacements: RestyleHint,
context: &StyleContext<Self>,
data: &mut ElementData
) -> bool {
let mut result = false;
result |= self.replace_rules_internal(replacements, context, data,
CascadeVisitedMode::Unvisited);
@ -1295,15 +1305,19 @@ pub trait MatchMethods : TElement {
/// the rule tree, for a specific visited mode.
///
/// Returns true if an !important rule was replaced.
fn replace_rules_internal(&self,
replacements: RestyleReplacements,
context: &StyleContext<Self>,
data: &mut ElementData,
cascade_visited: CascadeVisitedMode)
-> bool {
fn replace_rules_internal(
&self,
replacements: RestyleHint,
context: &StyleContext<Self>,
data: &mut ElementData,
cascade_visited: CascadeVisitedMode
) -> bool {
use properties::PropertyDeclarationBlock;
use shared_lock::Locked;
debug_assert!(replacements.intersects(RestyleHint::replacements()) &&
(replacements & !RestyleHint::replacements()).is_empty());
let element_styles = &mut data.styles_mut();
let primary_rules = match cascade_visited.get_rules_mut(&mut element_styles.primary) {
Some(r) => r,
@ -1344,7 +1358,7 @@ pub trait MatchMethods : TElement {
//
// Non-animation restyle hints will be processed in a subsequent
// normal traversal.
if replacements.intersects(RestyleReplacements::for_animations()) {
if replacements.intersects(RestyleHint::for_animations()) {
debug_assert!(context.shared.traversal_flags.for_animation_only());
if replacements.contains(RESTYLE_SMIL) {

File diff suppressed because it is too large Load Diff

View File

@ -331,7 +331,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
pub fn lookup_with_additional<E, F>(&self,
element: E,
quirks_mode: QuirksMode,
additional_id: Option<Atom>,
additional_id: Option<&Atom>,
additional_classes: &[Atom],
f: &mut F)
-> bool
@ -345,7 +345,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
// Check the additional id.
if let Some(id) = additional_id {
if let Some(v) = self.id_hash.get(&id, quirks_mode) {
if let Some(v) = self.id_hash.get(id, quirks_mode) {
for entry in v.iter() {
if !f(&entry) {
return false;
@ -468,6 +468,16 @@ impl<V> MaybeCaseInsensitiveHashMap<Atom, V> {
self.0.entry(key)
}
/// HashMap::iter
pub fn iter(&self) -> hash_map::Iter<Atom, V> {
self.0.iter()
}
/// HashMap::clear
pub fn clear(&mut self) {
self.0.clear()
}
/// HashMap::get
pub fn get(&self, key: &Atom, quirks_mode: QuirksMode) -> Option<&V> {
if quirks_mode == QuirksMode::Quirks {

View File

@ -12,7 +12,7 @@ use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
use dom::{OpaqueNode, TElement, TNode};
use element_state::ElementState;
use fnv::FnvHashMap;
use restyle_hints::ElementSnapshot;
use invalidation::element::element_wrapper::ElementSnapshot;
use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
use selectors::Element;
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
@ -545,6 +545,12 @@ pub struct ServoElementSnapshot {
pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
/// Whether this element is an HTML element in an HTML document.
pub is_html_element_in_html_document: bool,
/// Whether the class attribute changed or not.
pub class_changed: bool,
/// Whether the id attribute changed or not.
pub id_changed: bool,
/// Whether other attributes other than id or class changed or not.
pub other_attributes_changed: bool,
}
impl ServoElementSnapshot {
@ -554,9 +560,27 @@ impl ServoElementSnapshot {
state: None,
attrs: None,
is_html_element_in_html_document: is_html_element_in_html_document,
class_changed: false,
id_changed: false,
other_attributes_changed: false,
}
}
/// Returns whether the id attribute changed or not.
pub fn id_changed(&self) -> bool {
self.id_changed
}
/// Returns whether the class attribute changed or not.
pub fn class_changed(&self) -> bool {
self.class_changed
}
/// Returns whether other attributes other than id or class changed or not.
pub fn other_attr_changed(&self) -> bool {
self.other_attributes_changed
}
fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
self.attrs.as_ref().unwrap().iter()
.find(|&&(ref ident, _)| ident.local_name == *name &&

View File

@ -7,7 +7,7 @@
use {Atom, LocalName, Namespace};
use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
use bit_vec::BitVec;
use context::{QuirksMode, SharedStyleContext};
use context::QuirksMode;
use data::ComputedStyle;
use dom::TElement;
use element_state::ElementState;
@ -15,13 +15,13 @@ use error_reporting::create_error_reporter;
use font_metrics::FontMetricsProvider;
#[cfg(feature = "gecko")]
use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
use invalidation::element::invalidation_map::InvalidationMap;
use invalidation::media_queries::EffectiveMediaQueryResults;
use media_queries::Device;
use properties::{self, CascadeFlags, ComputedValues};
use properties::{AnimationRules, PropertyDeclarationBlock};
#[cfg(feature = "servo")]
use properties::INHERIT_ALL;
use restyle_hints::{HintComputationContext, DependencySet, RestyleHint};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_map::{SelectorMap, SelectorMapEntry};
use selector_parser::{SelectorImpl, PseudoElement};
@ -116,8 +116,8 @@ pub struct Stylist {
/// style rule appears in a stylesheet, needed to sort them by source order.
rules_source_order: u32,
/// Selector dependencies used to compute restyle hints.
dependencies: DependencySet,
/// The invalidation map for this document.
invalidation_map: InvalidationMap,
/// The attribute local names that appear in attribute selectors. Used
/// to avoid taking element snapshots when an irrelevant attribute changes.
@ -249,7 +249,7 @@ impl Stylist {
precomputed_pseudo_element_decls: Default::default(),
rules_source_order: 0,
rule_tree: RuleTree::new(),
dependencies: DependencySet::new(),
invalidation_map: InvalidationMap::new(),
attribute_dependencies: BloomFilter::new(),
style_attribute_dependency: false,
state_dependencies: ElementState::empty(),
@ -284,16 +284,16 @@ impl Stylist {
self.num_rebuilds
}
/// Returns the number of dependencies in the DependencySet.
pub fn num_dependencies(&self) -> usize {
self.dependencies.len()
}
/// Returns the number of revalidation_selectors.
pub fn num_revalidation_selectors(&self) -> usize {
self.selectors_for_cache_revalidation.len()
}
/// Gets a reference to the invalidation map.
pub fn invalidation_map(&self) -> &InvalidationMap {
&self.invalidation_map
}
/// Clear the stylist's state, effectively resetting it to more or less
/// the state Stylist::new creates.
///
@ -324,7 +324,7 @@ impl Stylist {
self.precomputed_pseudo_element_decls = Default::default();
self.rules_source_order = 0;
// We want to keep rule_tree around across stylist rebuilds.
self.dependencies.clear();
self.invalidation_map.clear();
self.attribute_dependencies.clear();
self.style_attribute_dependency = false;
self.state_dependencies = ElementState::empty();
@ -484,7 +484,7 @@ impl Stylist {
self.rules_source_order),
self.quirks_mode);
self.dependencies.note_selector(selector_and_hashes, self.quirks_mode);
self.invalidation_map.note_selector(selector_and_hashes, self.quirks_mode);
if needs_revalidation(&selector_and_hashes.selector) {
self.selectors_for_cache_revalidation.insert(
RevalidationSelectorAndHashes::new(&selector_and_hashes),
@ -1201,19 +1201,6 @@ impl Stylist {
results
}
/// Given an element, and a snapshot table that represents a previous state
/// of the tree, compute the appropriate restyle hint, that is, the kind of
/// restyle we need to do.
pub fn compute_restyle_hint<'a, E>(&self,
element: &E,
shared_context: &SharedStyleContext,
context: HintComputationContext<'a, E>)
-> RestyleHint
where E: TElement,
{
self.dependencies.compute_hint(element, shared_context, context)
}
/// Computes styles for a given declaration with parent_style.
pub fn compute_for_declarations(&self,
guards: &StylesheetGuards,

View File

@ -8,9 +8,8 @@ use atomic_refcell::AtomicRefCell;
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
use data::{ElementData, ElementStyles, StoredRestyleHint};
use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
use matching::{ChildCascadeRequirement, MatchMethods};
use restyle_hints::{CascadeHint, HintComputationContext, RECASCADE_SELF};
use restyle_hints::{RECASCADE_DESCENDANTS, RestyleHint};
use selector_parser::RestyleDamage;
use sharing::{StyleSharingBehavior, StyleSharingTarget};
#[cfg(feature = "servo")] use servo_config::opts;
@ -242,24 +241,11 @@ pub trait DomTraversal<E: TElement> : Sync {
};
}
// Expand the snapshot, if any. This is normally handled by the parent, so
// we need a special case for the root.
//
// Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which
// we propagate to the next sibling element.
// Look at whether there has been any attribute or state change, and
// invalidate our style, and the one of our siblings and descendants as
// needed.
if let Some(mut data) = root.mutate_data() {
let later_siblings =
data.compute_final_hint(root,
shared_context,
HintComputationContext::Root);
if later_siblings {
if let Some(next) = root.next_sibling_element() {
if let Some(mut next_data) = next.mutate_data() {
let hint = StoredRestyleHint::subtree_and_later_siblings();
next_data.ensure_restyle().hint.insert(hint);
}
}
}
data.invalidate_style_if_needed(root, shared_context);
}
PreTraverseToken {
@ -668,12 +654,9 @@ pub fn recalc_style_at<E, D>(traversal: &D,
context.thread_local.statistics.elements_traversed += 1;
debug_assert!(!element.has_snapshot() || element.handled_snapshot(),
"Should've handled snapshots here already");
debug_assert!(data.get_restyle().map_or(true, |r| {
!r.has_sibling_invalidations()
}), "Should've computed the final hint and handled later_siblings already");
let compute_self = !element.has_current_styles(data);
let mut cascade_hint = CascadeHint::empty();
let mut hint = RestyleHint::empty();
debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})",
element, compute_self, element.has_dirty_descendants(), data);
@ -682,10 +665,10 @@ pub fn recalc_style_at<E, D>(traversal: &D,
if compute_self {
match compute_style(traversal, traversal_data, context, element, data) {
ChildCascadeRequirement::MustCascadeChildren => {
cascade_hint |= RECASCADE_SELF;
hint |= RECASCADE_SELF;
}
ChildCascadeRequirement::MustCascadeDescendants => {
cascade_hint |= RECASCADE_SELF | RECASCADE_DESCENDANTS;
hint |= RECASCADE_SELF | RECASCADE_DESCENDANTS;
}
ChildCascadeRequirement::CanSkipCascade => {}
};
@ -693,7 +676,7 @@ pub fn recalc_style_at<E, D>(traversal: &D,
// We must always cascade native anonymous subtrees, since they inherit styles
// from their first non-NAC ancestor.
if element.is_native_anonymous() {
cascade_hint |= RECASCADE_SELF;
hint |= RECASCADE_SELF;
}
// If we're restyling this element to display:none, throw away all style
@ -720,11 +703,11 @@ pub fn recalc_style_at<E, D>(traversal: &D,
// FIXME(bholley): Need to handle explicitly-inherited reset properties
// somewhere.
propagated_hint.insert_cascade_hint(cascade_hint);
propagated_hint.insert(hint.into());
trace!("propagated_hint={:?}, cascade_hint={:?}, \
trace!("propagated_hint={:?} \
is_display_none={:?}, implementing_pseudo={:?}",
propagated_hint, cascade_hint,
propagated_hint,
data.styles().is_display_none(),
element.implemented_pseudo_element());
debug_assert!(element.has_current_styles(data) ||
@ -739,7 +722,8 @@ pub fn recalc_style_at<E, D>(traversal: &D,
element.has_dirty_descendants()
};
// Preprocess children, propagating restyle hints and handling sibling relationships.
// Preprocess children, propagating restyle hints and handling sibling
// relationships.
if traversal.should_traverse_children(&mut context.thread_local,
element,
&data,
@ -751,7 +735,6 @@ pub fn recalc_style_at<E, D>(traversal: &D,
});
preprocess_children::<E, D>(context,
traversal_data,
element,
propagated_hint,
damage_handled);
@ -853,9 +836,8 @@ fn compute_style<E, D>(_traversal: &D,
}
fn preprocess_children<E, D>(context: &mut StyleContext<E>,
parent_traversal_data: &PerLevelTraversalData,
element: E,
mut propagated_hint: StoredRestyleHint,
propagated_hint: StoredRestyleHint,
damage_handled: RestyleDamage)
where E: TElement,
D: DomTraversal<E>,
@ -878,39 +860,32 @@ fn preprocess_children<E, D>(context: &mut StyleContext<E>,
continue;
}
// Handle element snapshots and sibling restyle hints.
// Handle element snapshots and invalidation of descendants and siblings
// as needed.
//
// NB: This will be a no-op if there's no restyle data and no snapshot.
let later_siblings =
child_data.compute_final_hint(child,
&context.shared,
HintComputationContext::Child {
local_context: &mut context.thread_local,
dom_depth: parent_traversal_data.current_dom_depth + 1,
});
child_data.invalidate_style_if_needed(child, &context.shared);
trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}, later_siblings: {:?}",
trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}",
child,
child_data.get_restyle().map(|r| &r.hint),
propagated_hint,
child.implemented_pseudo_element(),
later_siblings);
child.implemented_pseudo_element());
// If the child doesn't have pre-existing RestyleData and we don't have
// any reason to create one, avoid the useless allocation and move on to
// the next child.
if propagated_hint.is_empty() && damage_handled.is_empty() && !child_data.has_restyle() {
if propagated_hint.is_empty() &&
damage_handled.is_empty() &&
!child_data.has_restyle() {
continue;
}
let mut restyle_data = child_data.ensure_restyle();
// Propagate the parent and sibling restyle hint.
restyle_data.hint.insert_from(&propagated_hint);
if later_siblings {
propagated_hint.insert(RestyleHint::subtree().into());
}
// Propagate the parent restyle hint, that may make us restyle the whole
// subtree.
restyle_data.hint.insert(propagated_hint);
// Store the damage already handled by ancestors.
restyle_data.set_damage_handled(damage_handled);

View File

@ -88,6 +88,7 @@ use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI,
use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
use style::gecko_bindings::sugar::refptr::RefPtr;
use style::gecko_properties::{self, style_structs};
use style::invalidation::element::restyle_hints::{self, RestyleHint};
use style::media_queries::{MediaList, parse_media_query_list};
use style::parallel;
use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
@ -96,7 +97,6 @@ use style::properties::{LonghandIdSet, PropertyDeclaration, PropertyDeclarationB
use style::properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP;
use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty};
use style::properties::parse_one_declaration_into;
use style::restyle_hints::{self, RestyleHint};
use style::rule_tree::StyleSource;
use style::selector_parser::PseudoElementCascadeType;
use style::sequential;

View File

@ -29,7 +29,6 @@ mod logical_geometry;
mod media_queries;
mod parsing;
mod properties;
mod restyle_hints;
mod rule_tree;
mod size_of;
mod str;

View File

@ -1,30 +0,0 @@
/* 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/. */
use style::context::QuirksMode;
#[test]
fn smoke_restyle_hints() {
use cssparser::{Parser, ParserInput};
use selectors::parser::SelectorList;
use style::restyle_hints::DependencySet;
use style::selector_parser::SelectorParser;
use style::stylesheets::{Origin, Namespaces};
let namespaces = Namespaces::default();
let parser = SelectorParser {
stylesheet_origin: Origin::Author,
namespaces: &namespaces,
};
let mut dependencies = DependencySet::new();
let mut input = ParserInput::new(":not(:active) ~ label");
let mut p = Parser::new(&mut input);
let selectors = SelectorList::parse(&parser, &mut p).unwrap();
assert_eq!((selectors.0).len(), 1);
let selector = (selectors.0).first().unwrap();
dependencies.note_selector(selector, QuirksMode::NoQuirks);
assert_eq!(dependencies.len(), 1);
}