servo/components/style/matching.rs
2016-11-22 14:50:22 +01:00

875 lines
36 KiB
Rust

/* 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/. */
//! High-level interface to CSS selector matching.
#![allow(unsafe_code)]
use {Atom, LocalName};
use animation;
use atomic_refcell::AtomicRefMut;
use cache::LRUCache;
use cascade_info::CascadeInfo;
use context::{SharedStyleContext, StyleContext};
use data::{ElementData, ElementStyles, PseudoStyles};
use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
use properties::{CascadeFlags, ComputedValues, SHAREABLE, cascade};
use properties::longhands::display::computed_value as display;
use rule_tree::StrongRuleNode;
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::MatchAttr;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, MatchingReason, StyleRelations};
use sink::ForgetfulSink;
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::mem;
use std::slice::IterMut;
use std::sync::Arc;
use stylist::ApplicableDeclarationBlock;
use util::opts;
fn create_common_style_affecting_attributes_from_element<E: TElement>(element: &E)
-> CommonStyleAffectingAttributes {
let mut flags = CommonStyleAffectingAttributes::empty();
for attribute_info in &common_style_affecting_attributes() {
match attribute_info.mode {
CommonStyleAffectingAttributeMode::IsPresent(flag) => {
if element.has_attr(&ns!(), &attribute_info.attr_name) {
flags.insert(flag)
}
}
CommonStyleAffectingAttributeMode::IsEqual(ref target_value, flag) => {
if element.attr_equals(&ns!(), &attribute_info.attr_name, target_value) {
flags.insert(flag)
}
}
}
}
flags
}
type PseudoRuleNodes = HashMap<PseudoElement, StrongRuleNode,
BuildHasherDefault<::fnv::FnvHasher>>;
pub struct MatchResults {
pub primary: StrongRuleNode,
pub relations: StyleRelations,
pub per_pseudo: PseudoRuleNodes,
}
impl MatchResults {
/// Returns true if the primary rule node is shareable with other nodes.
pub fn primary_is_shareable(&self) -> bool {
use traversal::relations_are_shareable;
relations_are_shareable(&self.relations)
}
}
/// Information regarding a candidate.
///
/// TODO: We can stick a lot more info here.
#[derive(Debug)]
struct StyleSharingCandidate {
/// The node, guaranteed to be an element.
node: UnsafeNode,
/// The cached common style affecting attribute info.
common_style_affecting_attributes: Option<CommonStyleAffectingAttributes>,
/// the cached class names.
class_attributes: Option<Vec<Atom>>,
}
impl PartialEq<StyleSharingCandidate> for StyleSharingCandidate {
fn eq(&self, other: &Self) -> bool {
self.node == other.node &&
self.common_style_affecting_attributes == other.common_style_affecting_attributes
}
}
/// An LRU cache of the last few nodes seen, so that we can aggressively try to
/// reuse their styles.
///
/// Note that this cache is flushed every time we steal work from the queue, so
/// storing nodes here temporarily is safe.
///
/// NB: We store UnsafeNode's, but this is not unsafe. It's a shame being
/// generic over elements is unfeasible (you can make compile style without much
/// difficulty, but good luck with layout and all the types with assoc.
/// lifetimes).
pub struct StyleSharingCandidateCache {
cache: LRUCache<StyleSharingCandidate, ()>,
}
#[derive(Clone, Debug)]
pub enum CacheMiss {
Parent,
LocalName,
Namespace,
Link,
State,
IdAttr,
StyleAttr,
Class,
CommonStyleAffectingAttributes,
PresHints,
SiblingRules,
NonCommonAttrRules,
}
fn element_matches_candidate<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate,
candidate_element: &E,
shared_context: &SharedStyleContext)
-> Result<(Arc<ComputedValues>, StrongRuleNode), CacheMiss> {
macro_rules! miss {
($miss: ident) => {
return Err(CacheMiss::$miss);
}
}
if element.parent_element() != candidate_element.parent_element() {
miss!(Parent)
}
if *element.get_local_name() != *candidate_element.get_local_name() {
miss!(LocalName)
}
if *element.get_namespace() != *candidate_element.get_namespace() {
miss!(Namespace)
}
if element.is_link() != candidate_element.is_link() {
miss!(Link)
}
if element.get_state() != candidate_element.get_state() {
miss!(State)
}
if element.get_id().is_some() {
miss!(IdAttr)
}
if element.style_attribute().is_some() {
miss!(StyleAttr)
}
if !have_same_class(element, candidate, candidate_element) {
miss!(Class)
}
if !have_same_common_style_affecting_attributes(element,
candidate,
candidate_element) {
miss!(CommonStyleAffectingAttributes)
}
if !have_same_presentational_hints(element, candidate_element) {
miss!(PresHints)
}
if !match_same_sibling_affecting_rules(element,
candidate_element,
shared_context) {
miss!(SiblingRules)
}
if !match_same_not_common_style_affecting_attributes_rules(element,
candidate_element,
shared_context) {
miss!(NonCommonAttrRules)
}
let data = candidate_element.borrow_data().unwrap();
let current_styles = data.get_current_styles().unwrap();
Ok((current_styles.primary.clone(),
current_styles.rule_node.clone()))
}
fn have_same_common_style_affecting_attributes<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate,
candidate_element: &E) -> bool {
if candidate.common_style_affecting_attributes.is_none() {
candidate.common_style_affecting_attributes =
Some(create_common_style_affecting_attributes_from_element(candidate_element))
}
create_common_style_affecting_attributes_from_element(element) ==
candidate.common_style_affecting_attributes.unwrap()
}
fn have_same_presentational_hints<E: TElement>(element: &E, candidate: &E) -> bool {
let mut first = ForgetfulSink::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut first);
if cfg!(debug_assertions) {
let mut second = vec![];
candidate.synthesize_presentational_hints_for_legacy_attributes(&mut second);
debug_assert!(second.is_empty(),
"Should never have inserted an element with preshints in the cache!");
}
first.is_empty()
}
bitflags! {
pub flags CommonStyleAffectingAttributes: u8 {
const HIDDEN_ATTRIBUTE = 0x01,
const NO_WRAP_ATTRIBUTE = 0x02,
const ALIGN_LEFT_ATTRIBUTE = 0x04,
const ALIGN_CENTER_ATTRIBUTE = 0x08,
const ALIGN_RIGHT_ATTRIBUTE = 0x10,
}
}
pub struct CommonStyleAffectingAttributeInfo {
pub attr_name: LocalName,
pub mode: CommonStyleAffectingAttributeMode,
}
#[derive(Clone)]
pub enum CommonStyleAffectingAttributeMode {
IsPresent(CommonStyleAffectingAttributes),
IsEqual(Atom, CommonStyleAffectingAttributes),
}
// NB: This must match the order in `selectors::matching::CommonStyleAffectingAttributes`.
#[inline]
pub fn common_style_affecting_attributes() -> [CommonStyleAffectingAttributeInfo; 5] {
[
CommonStyleAffectingAttributeInfo {
attr_name: local_name!("hidden"),
mode: CommonStyleAffectingAttributeMode::IsPresent(HIDDEN_ATTRIBUTE),
},
CommonStyleAffectingAttributeInfo {
attr_name: local_name!("nowrap"),
mode: CommonStyleAffectingAttributeMode::IsPresent(NO_WRAP_ATTRIBUTE),
},
CommonStyleAffectingAttributeInfo {
attr_name: local_name!("align"),
mode: CommonStyleAffectingAttributeMode::IsEqual(atom!("left"), ALIGN_LEFT_ATTRIBUTE),
},
CommonStyleAffectingAttributeInfo {
attr_name: local_name!("align"),
mode: CommonStyleAffectingAttributeMode::IsEqual(atom!("center"), ALIGN_CENTER_ATTRIBUTE),
},
CommonStyleAffectingAttributeInfo {
attr_name: local_name!("align"),
mode: CommonStyleAffectingAttributeMode::IsEqual(atom!("right"), ALIGN_RIGHT_ATTRIBUTE),
}
]
}
/// Attributes that, if present, disable style sharing. All legacy HTML attributes must be in
/// either this list or `common_style_affecting_attributes`. See the comment in
/// `synthesize_presentational_hints_for_legacy_attributes`.
pub fn rare_style_affecting_attributes() -> [LocalName; 3] {
[ local_name!("bgcolor"), local_name!("border"), local_name!("colspan") ]
}
fn have_same_class<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate,
candidate_element: &E) -> bool {
// XXX Efficiency here, I'm only validating ideas.
let mut element_class_attributes = vec![];
element.each_class(|c| element_class_attributes.push(c.clone()));
if candidate.class_attributes.is_none() {
let mut attrs = vec![];
candidate_element.each_class(|c| attrs.push(c.clone()));
candidate.class_attributes = Some(attrs)
}
element_class_attributes == *candidate.class_attributes.as_ref().unwrap()
}
// TODO: These re-match the candidate every time, which is suboptimal.
#[inline]
fn match_same_not_common_style_affecting_attributes_rules<E: TElement>(element: &E,
candidate: &E,
ctx: &SharedStyleContext) -> bool {
ctx.stylist.match_same_not_common_style_affecting_attributes_rules(element, candidate)
}
#[inline]
fn match_same_sibling_affecting_rules<E: TElement>(element: &E,
candidate: &E,
ctx: &SharedStyleContext) -> bool {
ctx.stylist.match_same_sibling_affecting_rules(element, candidate)
}
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 8;
impl StyleSharingCandidateCache {
pub fn new() -> Self {
StyleSharingCandidateCache {
cache: LRUCache::new(STYLE_SHARING_CANDIDATE_CACHE_SIZE),
}
}
fn iter_mut(&mut self) -> IterMut<(StyleSharingCandidate, ())> {
self.cache.iter_mut()
}
pub fn insert_if_possible<E: TElement>(&mut self,
element: &E,
style: &Arc<ComputedValues>,
relations: StyleRelations) {
use traversal::relations_are_shareable;
let parent = match element.parent_element() {
Some(element) => element,
None => {
debug!("Failing to insert to the cache: no parent element");
return;
}
};
// These are things we don't check in the candidate match because they
// are either uncommon or expensive.
if !relations_are_shareable(&relations) {
debug!("Failing to insert to the cache: {:?}", relations);
return;
}
let box_style = style.get_box();
if box_style.transition_property_count() > 0 {
debug!("Failing to insert to the cache: transitions");
return;
}
if box_style.animation_name_count() > 0 {
debug!("Failing to insert to the cache: animations");
return;
}
debug!("Inserting into cache: {:?} with parent {:?}",
element.as_node().to_unsafe(), parent.as_node().to_unsafe());
self.cache.insert(StyleSharingCandidate {
node: element.as_node().to_unsafe(),
common_style_affecting_attributes: None,
class_attributes: None,
}, ());
}
pub fn touch(&mut self, index: usize) {
self.cache.touch(index);
}
pub fn clear(&mut self) {
self.cache.evict_all()
}
}
/// The results of attempting to share a style.
pub enum StyleSharingResult {
/// We didn't find anybody to share the style with.
CannotShare,
/// The node's style can be shared. The integer specifies the index in the
/// LRU cache that was hit and the damage that was done, and the restyle
/// result the original result of the candidate's styling, that is, whether
/// it should stop the traversal or not.
StyleWasShared(usize, RestyleDamage),
}
// Callers need to pass several boolean flags to cascade_node_pseudo_element.
// We encapsulate them in this struct to avoid mixing them up.
//
// FIXME(pcwalton): Unify with `CascadeFlags`, perhaps?
struct CascadeBooleans {
shareable: bool,
animate: bool,
}
trait PrivateMatchMethods: TElement {
/// Actually cascades style for a node or a pseudo-element of a node.
///
/// Note that animations only apply to nodes or ::before or ::after
/// pseudo-elements.
fn cascade_node_pseudo_element<'a, Ctx>(&self,
context: &Ctx,
parent_style: Option<&Arc<ComputedValues>>,
old_style: Option<&Arc<ComputedValues>>,
rule_node: &StrongRuleNode,
booleans: CascadeBooleans)
-> Arc<ComputedValues>
where Ctx: StyleContext<'a>
{
let shared_context = context.shared_context();
let mut cascade_info = CascadeInfo::new();
let mut cascade_flags = CascadeFlags::empty();
if booleans.shareable {
cascade_flags.insert(SHAREABLE)
}
let this_style = match parent_style {
Some(ref parent_style) => {
cascade(shared_context.viewport_size,
rule_node,
Some(&***parent_style),
Some(&mut cascade_info),
shared_context.error_reporter.clone(),
cascade_flags)
}
None => {
cascade(shared_context.viewport_size,
rule_node,
None,
Some(&mut cascade_info),
shared_context.error_reporter.clone(),
cascade_flags)
}
};
cascade_info.finish(&self.as_node());
let mut this_style = Arc::new(this_style);
if booleans.animate {
let new_animations_sender = &context.local_context().new_animations_sender;
let this_opaque = self.as_node().opaque();
// Trigger any present animations if necessary.
animation::maybe_start_animations(&shared_context,
new_animations_sender,
this_opaque, &this_style);
// Trigger transitions if necessary. This will reset `this_style` back
// to its old value if it did trigger a transition.
if let Some(ref style) = old_style {
animation::start_transitions_if_applicable(
new_animations_sender,
this_opaque,
self.as_node().to_unsafe(),
&**style,
&mut this_style,
&shared_context.timer);
}
}
this_style
}
fn update_animations_for_cascade(&self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>) -> bool {
// Finish any expired transitions.
let this_opaque = self.as_node().opaque();
let had_animations_to_expire =
animation::complete_expired_transitions(this_opaque, style, context);
// Merge any running transitions into the current style, and cancel them.
let had_running_animations = context.running_animations
.read()
.get(&this_opaque)
.is_some();
if had_running_animations {
let mut all_running_animations = context.running_animations.write();
for mut running_animation in all_running_animations.get_mut(&this_opaque).unwrap() {
// This shouldn't happen frequently, but under some
// circumstances mainly huge load or debug builds, the
// constellation might be delayed in sending the
// `TickAllAnimations` message to layout.
//
// Thus, we can't assume all the animations have been already
// updated by layout, because other restyle due to script might
// be triggered by layout before the animation tick.
//
// See #12171 and the associated PR for an example where this
// happened while debugging other release panic.
if !running_animation.is_expired() {
animation::update_style_for_animation(context,
running_animation,
style);
running_animation.mark_as_expired();
}
}
}
had_animations_to_expire || had_running_animations
}
fn share_style_with_candidate_if_possible(&self,
shared_context: &SharedStyleContext,
candidate: &mut StyleSharingCandidate)
-> Result<(Arc<ComputedValues>, StrongRuleNode), CacheMiss> {
let candidate_element = unsafe {
Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
};
element_matches_candidate(self, candidate, &candidate_element,
shared_context)
}
}
fn compute_rule_node<'a, Ctx>(context: &Ctx,
applicable_declarations: &mut Vec<ApplicableDeclarationBlock>)
-> StrongRuleNode
where Ctx: StyleContext<'a>
{
let shared_context = context.shared_context();
let rules = applicable_declarations.drain(..).map(|d| (d.source, d.importance));
let rule_node = shared_context.stylist.rule_tree.insert_ordered_rules(rules);
rule_node
}
impl<E: TElement> PrivateMatchMethods for E {}
pub trait MatchMethods : TElement {
fn match_element<'a, Ctx>(&self, context: &Ctx, parent_bf: Option<&BloomFilter>)
-> MatchResults
where Ctx: StyleContext<'a>
{
let mut applicable_declarations: Vec<ApplicableDeclarationBlock> = Vec::with_capacity(16);
let stylist = &context.shared_context().stylist;
let style_attribute = self.style_attribute();
// Compute the primary rule node.
let mut primary_relations =
stylist.push_applicable_declarations(self,
parent_bf,
style_attribute,
None,
&mut applicable_declarations,
MatchingReason::ForStyling);
let primary_rule_node = compute_rule_node(context, &mut applicable_declarations);
// Compute the pseudo rule nodes.
let mut per_pseudo: PseudoRuleNodes = HashMap::with_hasher(Default::default());
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
debug_assert!(applicable_declarations.is_empty());
stylist.push_applicable_declarations(self, parent_bf, None,
Some(&pseudo.clone()),
&mut applicable_declarations,
MatchingReason::ForStyling);
if !applicable_declarations.is_empty() {
let rule_node = compute_rule_node(context, &mut applicable_declarations);
per_pseudo.insert(pseudo, rule_node);
}
});
// If we have any pseudo elements, indicate so in the primary StyleRelations.
if !per_pseudo.is_empty() {
primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
}
MatchResults {
primary: primary_rule_node,
relations: primary_relations,
per_pseudo: per_pseudo,
}
}
/// Attempts to share a style with another node. This method is unsafe because it depends on
/// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
/// guarantee that at the type system level yet.
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
shared_context: &SharedStyleContext,
data: &mut AtomicRefMut<ElementData>)
-> StyleSharingResult {
if opts::get().disable_share_style_cache {
return StyleSharingResult::CannotShare
}
if self.style_attribute().is_some() {
return StyleSharingResult::CannotShare
}
if self.has_attr(&ns!(), &local_name!("id")) {
return StyleSharingResult::CannotShare
}
let mut should_clear_cache = false;
for (i, &mut (ref mut candidate, ())) in style_sharing_candidate_cache.iter_mut().enumerate() {
let sharing_result = self.share_style_with_candidate_if_possible(shared_context, candidate);
match sharing_result {
Ok((shared_style, rule_node)) => {
// Yay, cache hit. Share the style.
// TODO: add the display: none optimisation here too! Even
// better, factor it out/make it a bit more generic so Gecko
// can decide more easily if it knows that it's a child of
// replaced content, or similar stuff!
let damage =
match self.existing_style_for_restyle_damage(data.previous_styles().map(|x| &x.primary), None) {
Some(ref source) => RestyleDamage::compute(source, &shared_style),
None => RestyleDamage::rebuild_and_reflow(),
};
data.finish_styling(ElementStyles::new(shared_style, rule_node));
return StyleSharingResult::StyleWasShared(i, damage)
}
Err(miss) => {
debug!("Cache miss: {:?}", miss);
// Cache miss, let's see what kind of failure to decide
// whether we keep trying or not.
match miss {
// Cache miss because of parent, clear the candidate cache.
CacheMiss::Parent => {
should_clear_cache = true;
break;
},
// Too expensive failure, give up, we don't want another
// one of these.
CacheMiss::CommonStyleAffectingAttributes |
CacheMiss::PresHints |
CacheMiss::SiblingRules |
CacheMiss::NonCommonAttrRules => break,
_ => {}
}
}
}
}
if should_clear_cache {
style_sharing_candidate_cache.clear();
}
StyleSharingResult::CannotShare
}
// The below two functions are copy+paste because I can't figure out how to
// write a function which takes a generic function. I don't think it can
// be done.
//
// Ideally, I'd want something like:
//
// > fn with_really_simple_selectors(&self, f: <H: Hash>|&H|);
// In terms of `SimpleSelector`s, these two functions will insert and remove:
// - `SimpleSelector::LocalName`
// - `SimpleSelector::Namepace`
// - `SimpleSelector::ID`
// - `SimpleSelector::Class`
/// Inserts and removes the matching `Descendant` selectors from a bloom
/// filter. This is used to speed up CSS selector matching to remove
/// unnecessary tree climbs for `Descendant` queries.
///
/// A bloom filter of the local names, namespaces, IDs, and classes is kept.
/// Therefore, each node must have its matching selectors inserted _after_
/// its own selector matching and _before_ its children start.
fn insert_into_bloom_filter(&self, bf: &mut BloomFilter) {
bf.insert(&*self.get_local_name());
bf.insert(&*self.get_namespace());
self.get_id().map(|id| bf.insert(&id));
// TODO: case-sensitivity depends on the document type and quirks mode
self.each_class(|class| bf.insert(class));
}
/// After all the children are done css selector matching, this must be
/// called to reset the bloom filter after an `insert`.
fn remove_from_bloom_filter(&self, bf: &mut BloomFilter) {
bf.remove(&*self.get_local_name());
bf.remove(&*self.get_namespace());
self.get_id().map(|id| bf.remove(&id));
// TODO: case-sensitivity depends on the document type and quirks mode
self.each_class(|class| bf.remove(class));
}
fn compute_restyle_damage(&self,
old_style: Option<&Arc<ComputedValues>>,
new_style: &Arc<ComputedValues>,
pseudo: Option<&PseudoElement>)
-> RestyleDamage
{
match self.existing_style_for_restyle_damage(old_style, pseudo) {
Some(ref source) => RestyleDamage::compute(source, new_style),
None => {
// If there's no style source, two things can happen:
//
// 1. This is not an incremental restyle (old_style is none).
// In this case we can't do too much than sending
// rebuild_and_reflow.
//
// 2. This is an incremental restyle, but the old display value
// is none, so there's no effective way for Gecko to get the
// style source. In this case, we could return either
// RestyleDamage::empty(), in the case both displays are
// none, or rebuild_and_reflow, otherwise. The first case
// should be already handled when calling this function, so
// we can assert that the new display value is not none.
//
// Also, this can be a text node (in which case we don't
// care of watching the new display value).
//
// Unfortunately we can't strongly assert part of this, since
// we style some nodes that in Gecko never generate a frame,
// like children of replaced content. Arguably, we shouldn't be
// styling those here, but until we implement that we'll have to
// stick without the assertions.
debug_assert!(pseudo.is_none() ||
new_style.get_box().clone_display() != display::T::none);
RestyleDamage::rebuild_and_reflow()
}
}
}
unsafe fn cascade_node<'a, Ctx>(&self,
context: &Ctx,
mut data: AtomicRefMut<ElementData>,
parent: Option<Self>,
primary_rule_node: StrongRuleNode,
pseudo_rule_nodes: PseudoRuleNodes,
primary_is_shareable: bool)
where Ctx: StyleContext<'a>
{
// Get our parent's style.
let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap());
let parent_style = parent_data.as_ref().map(|x| &x.current_styles().primary);
let mut new_styles;
let damage = {
let (old_primary, old_pseudos) = match data.previous_styles_mut() {
None => (None, None),
Some(previous) => {
// Update animations before the cascade. This may modify the
// value of the old primary style.
self.update_animations_for_cascade(context.shared_context(),
&mut previous.primary);
(Some(&previous.primary), Some(&mut previous.pseudos))
}
};
let new_style =
self.cascade_node_pseudo_element(context,
parent_style,
old_primary,
&primary_rule_node,
CascadeBooleans {
shareable: primary_is_shareable,
animate: true,
});
new_styles = ElementStyles::new(new_style, primary_rule_node);
let damage =
self.compute_damage_and_cascade_pseudos(old_primary,
old_pseudos,
&new_styles.primary,
&mut new_styles.pseudos,
context,
pseudo_rule_nodes);
self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
p.as_node().can_be_fragmented() ||
parent_style.unwrap().is_multicol()
}));
damage
};
data.finish_styling(new_styles);
// Drop the mutable borrow early, since Servo's set_restyle_damage also borrows.
mem::drop(data);
self.set_restyle_damage(damage);
}
fn compute_damage_and_cascade_pseudos<'a, Ctx>(&self,
old_primary: Option<&Arc<ComputedValues>>,
mut old_pseudos: Option<&mut PseudoStyles>,
new_primary: &Arc<ComputedValues>,
new_pseudos: &mut PseudoStyles,
context: &Ctx,
mut pseudo_rule_nodes: PseudoRuleNodes)
-> RestyleDamage
where Ctx: StyleContext<'a>
{
// Here we optimise the case of the style changing but both the
// previous and the new styles having display: none. In this
// case, we can always optimize the traversal, regardless of the
// restyle hint.
let this_display = new_primary.get_box().clone_display();
if this_display == display::T::none {
let old_display = old_primary.map(|old| {
old.get_box().clone_display()
});
// If display passed from none to something, then we need to reflow,
// otherwise, we don't do anything.
let damage = match old_display {
Some(display) if display == this_display => {
RestyleDamage::empty()
}
_ => RestyleDamage::rebuild_and_reflow()
};
debug!("Short-circuiting traversal: {:?} {:?} {:?}",
this_display, old_display, damage);
return damage
}
// Compute the damage and sum up the damage related to pseudo-elements.
let mut damage =
self.compute_restyle_damage(old_primary, new_primary, None);
// If the new style is display:none, we don't need pseudo-elements styles.
if new_primary.get_box().clone_display() == display::T::none {
return damage;
}
let rebuild_and_reflow = RestyleDamage::rebuild_and_reflow();
debug_assert!(new_pseudos.is_empty());
<Self as MatchAttr>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let maybe_rule_node = pseudo_rule_nodes.remove(&pseudo);
// Grab the old pseudo style for analysis.
let mut maybe_old_pseudo_style_and_rule_node =
old_pseudos.as_mut().and_then(|x| x.remove(&pseudo));
if maybe_rule_node.is_some() {
let new_rule_node = maybe_rule_node.unwrap();
// We have declarations, so we need to cascade. Compute parameters.
let animate = <Self as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo);
if animate {
if let Some((ref mut old_pseudo_style, _)) = maybe_old_pseudo_style_and_rule_node {
// Update animations before the cascade. This may modify
// the value of old_pseudo_style.
self.update_animations_for_cascade(context.shared_context(),
old_pseudo_style);
}
}
let new_pseudo_style =
self.cascade_node_pseudo_element(context, Some(new_primary),
maybe_old_pseudo_style_and_rule_node.as_ref().map(|s| &s.0),
&new_rule_node,
CascadeBooleans {
shareable: false,
animate: animate,
});
// Compute restyle damage unless we've already maxed it out.
if damage != rebuild_and_reflow {
damage = damage | match maybe_old_pseudo_style_and_rule_node {
None => rebuild_and_reflow,
Some((ref old, _)) => self.compute_restyle_damage(Some(old), &new_pseudo_style,
Some(&pseudo)),
};
}
// Insert the new entry into the map.
let existing = new_pseudos.insert(pseudo, (new_pseudo_style, new_rule_node));
debug_assert!(existing.is_none());
} else {
if maybe_old_pseudo_style_and_rule_node.is_some() {
damage = rebuild_and_reflow;
}
}
});
damage
}
}
impl<E: TElement> MatchMethods for E {}