mirror of
https://github.com/servo/servo.git
synced 2025-08-01 03:30:33 +01:00
style: Implement a more fine-grained invalidation method.
This commit also removes the old restyle_hints module and splits it into multiple modules under components/style/invalidation/element/. The basic approach is to walk down the tree using compound selectors as needed, in order to do as little selector-matching as possible. Bug: 1368240 MozReview-Commit-ID: 2YO8fKFygZI
This commit is contained in:
parent
fd10729941
commit
cb06375fe2
21 changed files with 1673 additions and 1486 deletions
|
@ -131,7 +131,7 @@ use std::rc::Rc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use style::attr::AttrValue;
|
use style::attr::AttrValue;
|
||||||
use style::context::{QuirksMode, ReflowGoal};
|
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::selector_parser::{RestyleDamage, Snapshot};
|
||||||
use style::shared_lock::SharedRwLock as StyleSharedRwLock;
|
use style::shared_lock::SharedRwLock as StyleSharedRwLock;
|
||||||
use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join};
|
use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join};
|
||||||
|
@ -2376,14 +2376,14 @@ impl Document {
|
||||||
entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document()));
|
entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document()));
|
||||||
}
|
}
|
||||||
if attr.local_name() == &local_name!("style") {
|
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
|
// FIXME(emilio): This should become something like
|
||||||
// element.is_attribute_mapped(attr.local_name()).
|
// element.is_attribute_mapped(attr.local_name()).
|
||||||
if attr.local_name() == &local_name!("width") ||
|
if attr.local_name() == &local_name!("width") ||
|
||||||
attr.local_name() == &local_name!("height") {
|
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();
|
let mut snapshot = entry.snapshot.as_mut().unwrap();
|
||||||
|
|
|
@ -102,9 +102,9 @@ use style::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
||||||
use style::context::{QuirksMode, ReflowGoal};
|
use style::context::{QuirksMode, ReflowGoal};
|
||||||
use style::element_state::*;
|
use style::element_state::*;
|
||||||
|
use style::invalidation::element::restyle_hints::RESTYLE_SELF;
|
||||||
use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
|
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::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::rule_tree::CascadeLevel;
|
||||||
use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser};
|
use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser};
|
||||||
use style::selector_parser::extended_filtering;
|
use style::selector_parser::extended_filtering;
|
||||||
|
@ -253,7 +253,7 @@ impl Element {
|
||||||
|
|
||||||
// FIXME(bholley): I think we should probably only do this for
|
// FIXME(bholley): I think we should probably only do this for
|
||||||
// NodeStyleDamaged, but I'm preserving existing behavior.
|
// NodeStyleDamaged, but I'm preserving existing behavior.
|
||||||
restyle.hint.insert(RestyleHint::for_self());
|
restyle.hint.insert(RESTYLE_SELF);
|
||||||
|
|
||||||
if damage == NodeDamage::OtherNodeDamage {
|
if damage == NodeDamage::OtherNodeDamage {
|
||||||
restyle.damage = RestyleDamage::rebuild_and_reflow();
|
restyle.damage = RestyleDamage::rebuild_and_reflow();
|
||||||
|
|
|
@ -250,7 +250,8 @@ impl TraversalStatistics {
|
||||||
self.traversal_time_ms = (time::precise_time_s() - start) * 1000.0;
|
self.traversal_time_ms = (time::precise_time_s() - start) * 1000.0;
|
||||||
self.selectors = traversal.shared_context().stylist.num_selectors() as u32;
|
self.selectors = traversal.shared_context().stylist.num_selectors() as u32;
|
||||||
self.revalidation_selectors = traversal.shared_context().stylist.num_revalidation_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.declarations = traversal.shared_context().stylist.num_declarations() as u32;
|
||||||
self.stylist_rebuilds = traversal.shared_context().stylist.num_rebuilds() as u32;
|
self.stylist_rebuilds = traversal.shared_context().stylist.num_rebuilds() as u32;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use context::SharedStyleContext;
|
use context::SharedStyleContext;
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
|
use invalidation::element::restyle_hints::RestyleHint;
|
||||||
use properties::{AnimationRules, ComputedValues, PropertyDeclarationBlock};
|
use properties::{AnimationRules, ComputedValues, PropertyDeclarationBlock};
|
||||||
use properties::longhands::display::computed_value as display;
|
use properties::longhands::display::computed_value as display;
|
||||||
use restyle_hints::{CascadeHint, HintComputationContext, RestyleReplacements, RestyleHint};
|
|
||||||
use rule_tree::StrongRuleNode;
|
use rule_tree::StrongRuleNode;
|
||||||
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
|
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
|
||||||
use selectors::matching::VisitedHandlingMode;
|
use selectors::matching::VisitedHandlingMode;
|
||||||
|
@ -345,8 +345,10 @@ impl ElementStyles {
|
||||||
///
|
///
|
||||||
/// We wrap it in a newtype to force the encapsulation of the complexity of
|
/// We wrap it in a newtype to force the encapsulation of the complexity of
|
||||||
/// handling the correct invalidations in this file.
|
/// 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 {
|
impl StoredRestyleHint {
|
||||||
/// Propagates this restyle hint to a child element.
|
/// Propagates this restyle hint to a child element.
|
||||||
|
@ -378,14 +380,7 @@ impl StoredRestyleHint {
|
||||||
/// Creates a restyle hint that forces the whole subtree to be restyled,
|
/// Creates a restyle hint that forces the whole subtree to be restyled,
|
||||||
/// including the element.
|
/// including the element.
|
||||||
pub fn subtree() -> Self {
|
pub fn subtree() -> Self {
|
||||||
StoredRestyleHint(RestyleHint::subtree())
|
StoredRestyleHint(RestyleHint::restyle_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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a restyle hint that indicates the element must be recascaded.
|
/// Creates a restyle hint that indicates the element must be recascaded.
|
||||||
|
@ -398,12 +393,6 @@ impl StoredRestyleHint {
|
||||||
self.0.affects_self()
|
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).
|
/// Whether the restyle hint is empty (nothing requires to be restyled).
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
|
@ -416,12 +405,7 @@ impl StoredRestyleHint {
|
||||||
|
|
||||||
/// Contains whether the whole subtree is invalid.
|
/// Contains whether the whole subtree is invalid.
|
||||||
pub fn contains_subtree(&self) -> bool {
|
pub fn contains_subtree(&self) -> bool {
|
||||||
self.0.contains(&RestyleHint::subtree())
|
self.0.contains(RestyleHint::restyle_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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the hint has animation-only restyle.
|
/// Returns true if the hint has animation-only restyle.
|
||||||
|
@ -434,16 +418,11 @@ impl StoredRestyleHint {
|
||||||
pub fn has_recascade_self(&self) -> bool {
|
pub fn has_recascade_self(&self) -> bool {
|
||||||
self.0.has_recascade_self()
|
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 {
|
impl Default for StoredRestyleHint {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
StoredRestyleHint::empty()
|
Self::empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,11 +462,6 @@ impl RestyleData {
|
||||||
self.hint.has_self_invalidations()
|
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.
|
/// Returns damage handled.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub fn damage_handled(&self) -> RestyleDamage {
|
pub fn damage_handled(&self) -> RestyleDamage {
|
||||||
|
@ -533,66 +507,41 @@ pub enum RestyleKind {
|
||||||
MatchAndCascade,
|
MatchAndCascade,
|
||||||
/// We need to recascade with some replacement rule, such as the style
|
/// We need to recascade with some replacement rule, such as the style
|
||||||
/// attribute, or animation rules.
|
/// attribute, or animation rules.
|
||||||
CascadeWithReplacements(RestyleReplacements),
|
CascadeWithReplacements(RestyleHint),
|
||||||
/// We only need to recascade, for example, because only inherited
|
/// We only need to recascade, for example, because only inherited
|
||||||
/// properties in the parent changed.
|
/// properties in the parent changed.
|
||||||
CascadeOnly,
|
CascadeOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementData {
|
impl ElementData {
|
||||||
/// Computes the final restyle hint for this element, potentially allocating
|
/// Invalidates style for this element, its descendants, and later siblings,
|
||||||
/// a `RestyleData` if we need to.
|
/// based on the snapshot of the element that we took when attributes or
|
||||||
///
|
/// state changed.
|
||||||
/// This expands the snapshot (if any) into a restyle hint, and handles
|
pub fn invalidate_style_if_needed<'a, E: TElement>(
|
||||||
/// 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>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
element: E,
|
element: E,
|
||||||
shared_context: &SharedStyleContext,
|
shared_context: &SharedStyleContext)
|
||||||
hint_context: HintComputationContext<'a, E>)
|
|
||||||
-> bool
|
|
||||||
{
|
{
|
||||||
debug!("compute_final_hint: {:?}, {:?}",
|
use invalidation::element::invalidator::TreeStyleInvalidator;
|
||||||
element,
|
|
||||||
shared_context.traversal_flags);
|
|
||||||
|
|
||||||
let mut hint = match self.get_restyle() {
|
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
|
||||||
Some(r) => r.hint.0.clone(),
|
handled_snapshot: {}, pseudo: {:?}",
|
||||||
None => RestyleHint::empty(),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("compute_final_hint: {:?}, has_snapshot: {}, handled_snapshot: {}, \
|
|
||||||
pseudo: {:?}",
|
|
||||||
element,
|
element,
|
||||||
|
shared_context.traversal_flags,
|
||||||
element.has_snapshot(),
|
element.has_snapshot(),
|
||||||
element.handled_snapshot(),
|
element.handled_snapshot(),
|
||||||
element.implemented_pseudo_element());
|
element.implemented_pseudo_element());
|
||||||
|
|
||||||
if element.has_snapshot() && !element.handled_snapshot() {
|
if element.has_snapshot() && !element.handled_snapshot() {
|
||||||
let snapshot_hint =
|
let invalidator = TreeStyleInvalidator::new(
|
||||||
shared_context.stylist.compute_restyle_hint(&element,
|
element,
|
||||||
shared_context,
|
Some(self),
|
||||||
hint_context);
|
shared_context,
|
||||||
hint.insert(snapshot_hint);
|
);
|
||||||
|
invalidator.invalidate();
|
||||||
unsafe { element.set_handled_snapshot() }
|
unsafe { element.set_handled_snapshot() }
|
||||||
debug_assert!(element.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 +575,13 @@ impl ElementData {
|
||||||
debug_assert!(self.restyle.is_some());
|
debug_assert!(self.restyle.is_some());
|
||||||
let restyle_data = self.restyle.as_ref().unwrap();
|
let restyle_data = self.restyle.as_ref().unwrap();
|
||||||
|
|
||||||
let hint = &restyle_data.hint.0;
|
let hint = restyle_data.hint.0;
|
||||||
if hint.match_self() {
|
if hint.match_self() {
|
||||||
return RestyleKind::MatchAndCascade;
|
return RestyleKind::MatchAndCascade;
|
||||||
}
|
}
|
||||||
|
|
||||||
if hint.has_replacements() {
|
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!");
|
debug_assert!(hint.has_recascade_self(), "We definitely need to do something!");
|
||||||
|
|
|
@ -13,7 +13,7 @@ use gecko_bindings::bindings;
|
||||||
use gecko_bindings::structs::ServoElementSnapshot;
|
use gecko_bindings::structs::ServoElementSnapshot;
|
||||||
use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
|
use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
|
||||||
use gecko_bindings::structs::ServoElementSnapshotTable;
|
use gecko_bindings::structs::ServoElementSnapshotTable;
|
||||||
use restyle_hints::ElementSnapshot;
|
use invalidation::element::element_wrapper::ElementSnapshot;
|
||||||
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
|
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
|
||||||
use string_cache::{Atom, Namespace};
|
use string_cache::{Atom, Namespace};
|
||||||
|
|
||||||
|
|
341
components/style/invalidation/element/element_wrapper.rs
Normal file
341
components/style/invalidation/element/element_wrapper.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
402
components/style/invalidation/element/invalidation_map.rs
Normal file
402
components/style/invalidation/element/invalidation_map.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
601
components/style/invalidation/element/invalidator.rs
Normal file
601
components/style/invalidation/element/invalidator.rs
Normal file
|
@ -0,0 +1,601 @@
|
||||||
|
/* 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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(MatchingMode::Normal, None,
|
||||||
|
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 } => {
|
||||||
|
if let Some(ref mut data) = self.data {
|
||||||
|
data.ensure_restyle().hint.insert(RESTYLE_SELF.into());
|
||||||
|
invalidated_self = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompoundSelectorMatchingResult::Matched { next_combinator_offset } => {
|
||||||
|
let next_combinator =
|
||||||
|
invalidation.selector.combinator_at(next_combinator_offset);
|
||||||
|
|
||||||
|
if next_combinator.is_ancestor() {
|
||||||
|
descendant_invalidations.push(Invalidation {
|
||||||
|
selector: invalidation.selector.clone(),
|
||||||
|
offset: next_combinator_offset,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
sibling_invalidations.push(Invalidation {
|
||||||
|
selector: invalidation.selector.clone(),
|
||||||
|
offset: next_combinator_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompoundSelectorMatchingResult::NotMatched => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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);
|
||||||
|
true
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_dependency(&mut self, dependency: &Dependency) {
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
10
components/style/invalidation/element/mod.rs
Normal file
10
components/style/invalidation/element/mod.rs
Normal 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;
|
212
components/style/invalidation/element/restyle_hints.rs
Normal file
212
components/style/invalidation/element/restyle_hints.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,5 +4,6 @@
|
||||||
|
|
||||||
//! Different bits of code related to invalidating style.
|
//! Different bits of code related to invalidating style.
|
||||||
|
|
||||||
|
pub mod element;
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
pub mod stylesheets;
|
pub mod stylesheets;
|
||||||
|
|
|
@ -117,7 +117,6 @@ pub mod matching;
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
pub mod parallel;
|
pub mod parallel;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod restyle_hints;
|
|
||||||
pub mod rule_tree;
|
pub mod rule_tree;
|
||||||
pub mod scoped_tls;
|
pub mod scoped_tls;
|
||||||
pub mod selector_map;
|
pub mod selector_map;
|
||||||
|
|
|
@ -13,13 +13,14 @@ use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
|
||||||
use data::{ComputedStyle, ElementData, RestyleData};
|
use data::{ComputedStyle, ElementData, RestyleData};
|
||||||
use dom::{TElement, TNode};
|
use dom::{TElement, TNode};
|
||||||
use font_metrics::FontMetricsProvider;
|
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 log::LogLevel::Trace;
|
||||||
use properties::{ALLOW_SET_ROOT_FONT_SIZE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
|
use properties::{ALLOW_SET_ROOT_FONT_SIZE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
|
||||||
use properties::{AnimationRules, CascadeFlags, ComputedValues};
|
use properties::{AnimationRules, CascadeFlags, ComputedValues};
|
||||||
use properties::{VISITED_DEPENDENT_ONLY, cascade};
|
use properties::{VISITED_DEPENDENT_ONLY, cascade};
|
||||||
use properties::longhands::display::computed_value as display;
|
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 rule_tree::{CascadeLevel, StrongRuleNode};
|
||||||
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
|
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
|
||||||
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
|
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
|
||||||
|
@ -1276,11 +1277,12 @@ pub trait MatchMethods : TElement {
|
||||||
/// the rule tree.
|
/// the rule tree.
|
||||||
///
|
///
|
||||||
/// Returns true if an !important rule was replaced.
|
/// Returns true if an !important rule was replaced.
|
||||||
fn replace_rules(&self,
|
fn replace_rules(
|
||||||
replacements: RestyleReplacements,
|
&self,
|
||||||
context: &StyleContext<Self>,
|
replacements: RestyleHint,
|
||||||
data: &mut ElementData)
|
context: &StyleContext<Self>,
|
||||||
-> bool {
|
data: &mut ElementData
|
||||||
|
) -> bool {
|
||||||
let mut result = false;
|
let mut result = false;
|
||||||
result |= self.replace_rules_internal(replacements, context, data,
|
result |= self.replace_rules_internal(replacements, context, data,
|
||||||
CascadeVisitedMode::Unvisited);
|
CascadeVisitedMode::Unvisited);
|
||||||
|
@ -1295,15 +1297,19 @@ pub trait MatchMethods : TElement {
|
||||||
/// the rule tree, for a specific visited mode.
|
/// the rule tree, for a specific visited mode.
|
||||||
///
|
///
|
||||||
/// Returns true if an !important rule was replaced.
|
/// Returns true if an !important rule was replaced.
|
||||||
fn replace_rules_internal(&self,
|
fn replace_rules_internal(
|
||||||
replacements: RestyleReplacements,
|
&self,
|
||||||
context: &StyleContext<Self>,
|
replacements: RestyleHint,
|
||||||
data: &mut ElementData,
|
context: &StyleContext<Self>,
|
||||||
cascade_visited: CascadeVisitedMode)
|
data: &mut ElementData,
|
||||||
-> bool {
|
cascade_visited: CascadeVisitedMode
|
||||||
|
) -> bool {
|
||||||
use properties::PropertyDeclarationBlock;
|
use properties::PropertyDeclarationBlock;
|
||||||
use shared_lock::Locked;
|
use shared_lock::Locked;
|
||||||
|
|
||||||
|
debug_assert!(replacements.intersects(RestyleHint::replacements()) &&
|
||||||
|
(replacements & !RestyleHint::replacements()).is_empty());
|
||||||
|
|
||||||
let element_styles = &mut data.styles_mut();
|
let element_styles = &mut data.styles_mut();
|
||||||
let primary_rules = match cascade_visited.get_rules_mut(&mut element_styles.primary) {
|
let primary_rules = match cascade_visited.get_rules_mut(&mut element_styles.primary) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
|
@ -1344,7 +1350,7 @@ pub trait MatchMethods : TElement {
|
||||||
//
|
//
|
||||||
// Non-animation restyle hints will be processed in a subsequent
|
// Non-animation restyle hints will be processed in a subsequent
|
||||||
// normal traversal.
|
// normal traversal.
|
||||||
if replacements.intersects(RestyleReplacements::for_animations()) {
|
if replacements.intersects(RestyleHint::for_animations()) {
|
||||||
debug_assert!(context.shared.traversal_flags.for_animation_only());
|
debug_assert!(context.shared.traversal_flags.for_animation_only());
|
||||||
|
|
||||||
if replacements.contains(RESTYLE_SMIL) {
|
if replacements.contains(RESTYLE_SMIL) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -331,7 +331,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
|
||||||
pub fn lookup_with_additional<E, F>(&self,
|
pub fn lookup_with_additional<E, F>(&self,
|
||||||
element: E,
|
element: E,
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
additional_id: Option<Atom>,
|
additional_id: Option<&Atom>,
|
||||||
additional_classes: &[Atom],
|
additional_classes: &[Atom],
|
||||||
f: &mut F)
|
f: &mut F)
|
||||||
-> bool
|
-> bool
|
||||||
|
@ -345,7 +345,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
|
||||||
|
|
||||||
// Check the additional id.
|
// Check the additional id.
|
||||||
if let Some(id) = 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() {
|
for entry in v.iter() {
|
||||||
if !f(&entry) {
|
if !f(&entry) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -468,6 +468,16 @@ impl<V> MaybeCaseInsensitiveHashMap<Atom, V> {
|
||||||
self.0.entry(key)
|
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
|
/// HashMap::get
|
||||||
pub fn get(&self, key: &Atom, quirks_mode: QuirksMode) -> Option<&V> {
|
pub fn get(&self, key: &Atom, quirks_mode: QuirksMode) -> Option<&V> {
|
||||||
if quirks_mode == QuirksMode::Quirks {
|
if quirks_mode == QuirksMode::Quirks {
|
||||||
|
|
|
@ -12,7 +12,7 @@ use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
|
||||||
use dom::{OpaqueNode, TElement, TNode};
|
use dom::{OpaqueNode, TElement, TNode};
|
||||||
use element_state::ElementState;
|
use element_state::ElementState;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use restyle_hints::ElementSnapshot;
|
use invalidation::element::element_wrapper::ElementSnapshot;
|
||||||
use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
|
use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
|
||||||
use selectors::Element;
|
use selectors::Element;
|
||||||
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
|
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
use {Atom, LocalName, Namespace};
|
use {Atom, LocalName, Namespace};
|
||||||
use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
|
use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
|
||||||
use bit_vec::BitVec;
|
use bit_vec::BitVec;
|
||||||
use context::{QuirksMode, SharedStyleContext};
|
use context::QuirksMode;
|
||||||
use data::ComputedStyle;
|
use data::ComputedStyle;
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
use element_state::ElementState;
|
use element_state::ElementState;
|
||||||
|
@ -15,13 +15,13 @@ use error_reporting::create_error_reporter;
|
||||||
use font_metrics::FontMetricsProvider;
|
use font_metrics::FontMetricsProvider;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
|
use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
|
||||||
|
use invalidation::element::invalidation_map::InvalidationMap;
|
||||||
use invalidation::media_queries::EffectiveMediaQueryResults;
|
use invalidation::media_queries::EffectiveMediaQueryResults;
|
||||||
use media_queries::Device;
|
use media_queries::Device;
|
||||||
use properties::{self, CascadeFlags, ComputedValues};
|
use properties::{self, CascadeFlags, ComputedValues};
|
||||||
use properties::{AnimationRules, PropertyDeclarationBlock};
|
use properties::{AnimationRules, PropertyDeclarationBlock};
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
use properties::INHERIT_ALL;
|
use properties::INHERIT_ALL;
|
||||||
use restyle_hints::{HintComputationContext, DependencySet, RestyleHint};
|
|
||||||
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
|
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
|
||||||
use selector_map::{SelectorMap, SelectorMapEntry};
|
use selector_map::{SelectorMap, SelectorMapEntry};
|
||||||
use selector_parser::{SelectorImpl, PseudoElement};
|
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.
|
/// style rule appears in a stylesheet, needed to sort them by source order.
|
||||||
rules_source_order: u32,
|
rules_source_order: u32,
|
||||||
|
|
||||||
/// Selector dependencies used to compute restyle hints.
|
/// The invalidation map for this document.
|
||||||
dependencies: DependencySet,
|
invalidation_map: InvalidationMap,
|
||||||
|
|
||||||
/// The attribute local names that appear in attribute selectors. Used
|
/// The attribute local names that appear in attribute selectors. Used
|
||||||
/// to avoid taking element snapshots when an irrelevant attribute changes.
|
/// to avoid taking element snapshots when an irrelevant attribute changes.
|
||||||
|
@ -249,7 +249,7 @@ impl Stylist {
|
||||||
precomputed_pseudo_element_decls: Default::default(),
|
precomputed_pseudo_element_decls: Default::default(),
|
||||||
rules_source_order: 0,
|
rules_source_order: 0,
|
||||||
rule_tree: RuleTree::new(),
|
rule_tree: RuleTree::new(),
|
||||||
dependencies: DependencySet::new(),
|
invalidation_map: InvalidationMap::new(),
|
||||||
attribute_dependencies: BloomFilter::new(),
|
attribute_dependencies: BloomFilter::new(),
|
||||||
style_attribute_dependency: false,
|
style_attribute_dependency: false,
|
||||||
state_dependencies: ElementState::empty(),
|
state_dependencies: ElementState::empty(),
|
||||||
|
@ -284,16 +284,16 @@ impl Stylist {
|
||||||
self.num_rebuilds
|
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.
|
/// Returns the number of revalidation_selectors.
|
||||||
pub fn num_revalidation_selectors(&self) -> usize {
|
pub fn num_revalidation_selectors(&self) -> usize {
|
||||||
self.selectors_for_cache_revalidation.len()
|
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
|
/// Clear the stylist's state, effectively resetting it to more or less
|
||||||
/// the state Stylist::new creates.
|
/// the state Stylist::new creates.
|
||||||
///
|
///
|
||||||
|
@ -324,7 +324,7 @@ impl Stylist {
|
||||||
self.precomputed_pseudo_element_decls = Default::default();
|
self.precomputed_pseudo_element_decls = Default::default();
|
||||||
self.rules_source_order = 0;
|
self.rules_source_order = 0;
|
||||||
// We want to keep rule_tree around across stylist rebuilds.
|
// We want to keep rule_tree around across stylist rebuilds.
|
||||||
self.dependencies.clear();
|
self.invalidation_map.clear();
|
||||||
self.attribute_dependencies.clear();
|
self.attribute_dependencies.clear();
|
||||||
self.style_attribute_dependency = false;
|
self.style_attribute_dependency = false;
|
||||||
self.state_dependencies = ElementState::empty();
|
self.state_dependencies = ElementState::empty();
|
||||||
|
@ -484,7 +484,7 @@ impl Stylist {
|
||||||
self.rules_source_order),
|
self.rules_source_order),
|
||||||
self.quirks_mode);
|
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) {
|
if needs_revalidation(&selector_and_hashes.selector) {
|
||||||
self.selectors_for_cache_revalidation.insert(
|
self.selectors_for_cache_revalidation.insert(
|
||||||
RevalidationSelectorAndHashes::new(&selector_and_hashes),
|
RevalidationSelectorAndHashes::new(&selector_and_hashes),
|
||||||
|
@ -1198,19 +1198,6 @@ impl Stylist {
|
||||||
results
|
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.
|
/// Computes styles for a given declaration with parent_style.
|
||||||
pub fn compute_for_declarations(&self,
|
pub fn compute_for_declarations(&self,
|
||||||
guards: &StylesheetGuards,
|
guards: &StylesheetGuards,
|
||||||
|
|
|
@ -8,9 +8,8 @@ use atomic_refcell::AtomicRefCell;
|
||||||
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
|
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
|
||||||
use data::{ElementData, ElementStyles, StoredRestyleHint};
|
use data::{ElementData, ElementStyles, StoredRestyleHint};
|
||||||
use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
|
use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
|
||||||
|
use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
|
||||||
use matching::{ChildCascadeRequirement, MatchMethods};
|
use matching::{ChildCascadeRequirement, MatchMethods};
|
||||||
use restyle_hints::{CascadeHint, HintComputationContext, RECASCADE_SELF};
|
|
||||||
use restyle_hints::{RECASCADE_DESCENDANTS, RestyleHint};
|
|
||||||
use selector_parser::RestyleDamage;
|
use selector_parser::RestyleDamage;
|
||||||
use sharing::{StyleSharingBehavior, StyleSharingTarget};
|
use sharing::{StyleSharingBehavior, StyleSharingTarget};
|
||||||
#[cfg(feature = "servo")] use servo_config::opts;
|
#[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
|
// Look at whether there has been any attribute or state change, and
|
||||||
// we need a special case for the root.
|
// invalidate our style, and the one of our siblings and descendants as
|
||||||
//
|
// needed.
|
||||||
// Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which
|
|
||||||
// we propagate to the next sibling element.
|
|
||||||
if let Some(mut data) = root.mutate_data() {
|
if let Some(mut data) = root.mutate_data() {
|
||||||
let later_siblings =
|
data.invalidate_style_if_needed(root, shared_context);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PreTraverseToken {
|
PreTraverseToken {
|
||||||
|
@ -668,12 +654,9 @@ pub fn recalc_style_at<E, D>(traversal: &D,
|
||||||
context.thread_local.statistics.elements_traversed += 1;
|
context.thread_local.statistics.elements_traversed += 1;
|
||||||
debug_assert!(!element.has_snapshot() || element.handled_snapshot(),
|
debug_assert!(!element.has_snapshot() || element.handled_snapshot(),
|
||||||
"Should've handled snapshots here already");
|
"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 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={:?})",
|
debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})",
|
||||||
element, compute_self, element.has_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 {
|
if compute_self {
|
||||||
match compute_style(traversal, traversal_data, context, element, data) {
|
match compute_style(traversal, traversal_data, context, element, data) {
|
||||||
ChildCascadeRequirement::MustCascadeChildren => {
|
ChildCascadeRequirement::MustCascadeChildren => {
|
||||||
cascade_hint |= RECASCADE_SELF;
|
hint |= RECASCADE_SELF;
|
||||||
}
|
}
|
||||||
ChildCascadeRequirement::MustCascadeDescendants => {
|
ChildCascadeRequirement::MustCascadeDescendants => {
|
||||||
cascade_hint |= RECASCADE_SELF | RECASCADE_DESCENDANTS;
|
hint |= RECASCADE_SELF | RECASCADE_DESCENDANTS;
|
||||||
}
|
}
|
||||||
ChildCascadeRequirement::CanSkipCascade => {}
|
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
|
// We must always cascade native anonymous subtrees, since they inherit styles
|
||||||
// from their first non-NAC ancestor.
|
// from their first non-NAC ancestor.
|
||||||
if element.is_native_anonymous() {
|
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
|
// 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
|
// FIXME(bholley): Need to handle explicitly-inherited reset properties
|
||||||
// somewhere.
|
// 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={:?}",
|
is_display_none={:?}, implementing_pseudo={:?}",
|
||||||
propagated_hint, cascade_hint,
|
propagated_hint,
|
||||||
data.styles().is_display_none(),
|
data.styles().is_display_none(),
|
||||||
element.implemented_pseudo_element());
|
element.implemented_pseudo_element());
|
||||||
debug_assert!(element.has_current_styles(data) ||
|
debug_assert!(element.has_current_styles(data) ||
|
||||||
|
@ -739,7 +722,8 @@ pub fn recalc_style_at<E, D>(traversal: &D,
|
||||||
element.has_dirty_descendants()
|
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,
|
if traversal.should_traverse_children(&mut context.thread_local,
|
||||||
element,
|
element,
|
||||||
&data,
|
&data,
|
||||||
|
@ -751,7 +735,6 @@ pub fn recalc_style_at<E, D>(traversal: &D,
|
||||||
});
|
});
|
||||||
|
|
||||||
preprocess_children::<E, D>(context,
|
preprocess_children::<E, D>(context,
|
||||||
traversal_data,
|
|
||||||
element,
|
element,
|
||||||
propagated_hint,
|
propagated_hint,
|
||||||
damage_handled);
|
damage_handled);
|
||||||
|
@ -853,9 +836,8 @@ fn compute_style<E, D>(_traversal: &D,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preprocess_children<E, D>(context: &mut StyleContext<E>,
|
fn preprocess_children<E, D>(context: &mut StyleContext<E>,
|
||||||
parent_traversal_data: &PerLevelTraversalData,
|
|
||||||
element: E,
|
element: E,
|
||||||
mut propagated_hint: StoredRestyleHint,
|
propagated_hint: StoredRestyleHint,
|
||||||
damage_handled: RestyleDamage)
|
damage_handled: RestyleDamage)
|
||||||
where E: TElement,
|
where E: TElement,
|
||||||
D: DomTraversal<E>,
|
D: DomTraversal<E>,
|
||||||
|
@ -878,39 +860,32 @@ fn preprocess_children<E, D>(context: &mut StyleContext<E>,
|
||||||
continue;
|
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.
|
// NB: This will be a no-op if there's no restyle data and no snapshot.
|
||||||
let later_siblings =
|
child_data.invalidate_style_if_needed(child, &context.shared);
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}, later_siblings: {:?}",
|
trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}",
|
||||||
child,
|
child,
|
||||||
child_data.get_restyle().map(|r| &r.hint),
|
child_data.get_restyle().map(|r| &r.hint),
|
||||||
propagated_hint,
|
propagated_hint,
|
||||||
child.implemented_pseudo_element(),
|
child.implemented_pseudo_element());
|
||||||
later_siblings);
|
|
||||||
|
|
||||||
// If the child doesn't have pre-existing RestyleData and we don't have
|
// 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
|
// any reason to create one, avoid the useless allocation and move on to
|
||||||
// the next child.
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut restyle_data = child_data.ensure_restyle();
|
let mut restyle_data = child_data.ensure_restyle();
|
||||||
|
|
||||||
// Propagate the parent and sibling restyle hint.
|
// Propagate the parent restyle hint, that may make us restyle the whole
|
||||||
restyle_data.hint.insert_from(&propagated_hint);
|
// subtree.
|
||||||
|
restyle_data.hint.insert(propagated_hint);
|
||||||
if later_siblings {
|
|
||||||
propagated_hint.insert(RestyleHint::subtree().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the damage already handled by ancestors.
|
// Store the damage already handled by ancestors.
|
||||||
restyle_data.set_damage_handled(damage_handled);
|
restyle_data.set_damage_handled(damage_handled);
|
||||||
|
|
|
@ -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::ownership::{HasSimpleFFI, Strong};
|
||||||
use style::gecko_bindings::sugar::refptr::RefPtr;
|
use style::gecko_bindings::sugar::refptr::RefPtr;
|
||||||
use style::gecko_properties::{self, style_structs};
|
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::media_queries::{MediaList, parse_media_query_list};
|
||||||
use style::parallel;
|
use style::parallel;
|
||||||
use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
|
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::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP;
|
||||||
use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty};
|
use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty};
|
||||||
use style::properties::parse_one_declaration_into;
|
use style::properties::parse_one_declaration_into;
|
||||||
use style::restyle_hints::{self, RestyleHint};
|
|
||||||
use style::rule_tree::StyleSource;
|
use style::rule_tree::StyleSource;
|
||||||
use style::selector_parser::PseudoElementCascadeType;
|
use style::selector_parser::PseudoElementCascadeType;
|
||||||
use style::sequential;
|
use style::sequential;
|
||||||
|
|
|
@ -29,7 +29,6 @@ mod logical_geometry;
|
||||||
mod media_queries;
|
mod media_queries;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
mod properties;
|
mod properties;
|
||||||
mod restyle_hints;
|
|
||||||
mod rule_tree;
|
mod rule_tree;
|
||||||
mod size_of;
|
mod size_of;
|
||||||
mod str;
|
mod str;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue