mirror of
https://github.com/servo/servo.git
synced 2025-07-29 10:10:34 +01:00
This patch introduces infrastructure for the rule tree, and constructs it. We don't use it yet, nor have good heuristics for GC'ing it, but this should not happen anymore once we store the rule node reference in the node. I haven't messed up with memory orders because I want to do a try run with it, then mess with them. Take down the ApplicableDeclarationsCache, use the rule tree for doing the cascade.
882 lines
36 KiB
Rust
882 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_impl::{TheSelectorImpl, PseudoElement};
|
|
use selector_matching::{ApplicableDeclarationBlock, Stylist};
|
|
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 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
|
|
}
|
|
|
|
pub struct ApplicableDeclarations {
|
|
pub normal: Vec<ApplicableDeclarationBlock>,
|
|
pub per_pseudo: HashMap<PseudoElement,
|
|
Vec<ApplicableDeclarationBlock>,
|
|
BuildHasherDefault<::fnv::FnvHasher>>,
|
|
|
|
/// Whether the `normal` declarations are shareable with other nodes.
|
|
pub normal_shareable: bool,
|
|
}
|
|
|
|
impl ApplicableDeclarations {
|
|
pub fn new() -> Self {
|
|
let mut applicable_declarations = ApplicableDeclarations {
|
|
normal: Vec::with_capacity(16),
|
|
per_pseudo: HashMap::with_hasher(Default::default()),
|
|
normal_shareable: false,
|
|
};
|
|
|
|
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
|
|
applicable_declarations.per_pseudo.insert(pseudo, vec![]);
|
|
});
|
|
|
|
applicable_declarations
|
|
}
|
|
}
|
|
|
|
/// 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<ConcreteRestyleDamage: TRestyleDamage> {
|
|
/// 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, ConcreteRestyleDamage),
|
|
}
|
|
|
|
// 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>>,
|
|
applicable_declarations: &mut Vec<ApplicableDeclarationBlock>,
|
|
booleans: CascadeBooleans)
|
|
-> (Arc<ComputedValues>, StrongRuleNode)
|
|
where Ctx: StyleContext<'a>
|
|
{
|
|
let shared_context = context.shared_context();
|
|
let rule_node =
|
|
shared_context.stylist.rule_tree
|
|
.insert_ordered_rules(
|
|
applicable_declarations.drain(..).map(|d| (d.source, d.importance)));
|
|
|
|
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, rule_node)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
impl<E: TElement> PrivateMatchMethods for E {}
|
|
|
|
pub trait MatchMethods : TElement {
|
|
fn match_element(&self,
|
|
stylist: &Stylist,
|
|
parent_bf: Option<&BloomFilter>,
|
|
mut applicable_declarations: &mut ApplicableDeclarations)
|
|
-> StyleRelations {
|
|
use traversal::relations_are_shareable;
|
|
|
|
let style_attribute = self.style_attribute();
|
|
|
|
let mut relations =
|
|
stylist.push_applicable_declarations(self,
|
|
parent_bf,
|
|
style_attribute,
|
|
None,
|
|
&mut applicable_declarations.normal,
|
|
MatchingReason::ForStyling);
|
|
|
|
applicable_declarations.normal_shareable = relations_are_shareable(&relations);
|
|
|
|
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
|
|
stylist.push_applicable_declarations(self,
|
|
parent_bf,
|
|
None,
|
|
Some(&pseudo.clone()),
|
|
applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![]),
|
|
MatchingReason::ForStyling);
|
|
});
|
|
|
|
let has_pseudos =
|
|
applicable_declarations.per_pseudo.values().any(|v| !v.is_empty());
|
|
|
|
if has_pseudos {
|
|
relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
|
|
}
|
|
|
|
relations
|
|
}
|
|
|
|
/// 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<Self::ConcreteRestyleDamage> {
|
|
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) => {
|
|
Self::ConcreteRestyleDamage::compute(source, &shared_style)
|
|
}
|
|
None => {
|
|
Self::ConcreteRestyleDamage::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>)
|
|
-> Self::ConcreteRestyleDamage
|
|
{
|
|
match self.existing_style_for_restyle_damage(old_style, pseudo) {
|
|
Some(ref source) => {
|
|
Self::ConcreteRestyleDamage::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);
|
|
Self::ConcreteRestyleDamage::rebuild_and_reflow()
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn cascade_node<'a, Ctx>(&self,
|
|
context: &Ctx,
|
|
mut data: AtomicRefMut<ElementData>,
|
|
parent: Option<Self>,
|
|
mut applicable_declarations: ApplicableDeclarations)
|
|
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 shareable = applicable_declarations.normal_shareable;
|
|
|
|
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, rule_node) =
|
|
self.cascade_node_pseudo_element(context,
|
|
parent_style,
|
|
old_primary,
|
|
&mut applicable_declarations.normal,
|
|
CascadeBooleans {
|
|
shareable: shareable,
|
|
animate: true,
|
|
});
|
|
|
|
new_styles = ElementStyles::new(new_style, rule_node);
|
|
|
|
let damage =
|
|
self.compute_damage_and_cascade_pseudos(old_primary,
|
|
old_pseudos,
|
|
&new_styles.primary,
|
|
&mut new_styles.pseudos,
|
|
context,
|
|
&mut applicable_declarations);
|
|
|
|
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,
|
|
applicable_declarations: &mut ApplicableDeclarations)
|
|
-> Self::ConcreteRestyleDamage
|
|
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 => {
|
|
Self::ConcreteRestyleDamage::empty()
|
|
}
|
|
_ => Self::ConcreteRestyleDamage::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 =
|
|
Self::ConcreteRestyleDamage::rebuild_and_reflow();
|
|
|
|
debug_assert!(new_pseudos.is_empty());
|
|
<Self as MatchAttr>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
|
|
let mut applicable_declarations_for_this_pseudo =
|
|
applicable_declarations.per_pseudo.get_mut(&pseudo).unwrap();
|
|
|
|
let has_declarations =
|
|
!applicable_declarations_for_this_pseudo.is_empty();
|
|
|
|
// 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 has_declarations {
|
|
// 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, new_rule_node) =
|
|
self.cascade_node_pseudo_element(context, Some(new_primary),
|
|
maybe_old_pseudo_style_and_rule_node.as_ref().map(|s| &s.0),
|
|
&mut applicable_declarations_for_this_pseudo,
|
|
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 {}
|