mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Auto merge of #15317 - emilio:style-attr-restyle, r=bholley
style: Avoid selector-matching when only the style attribute is changed. r? @bholley <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15317) <!-- Reviewable:end -->
This commit is contained in:
commit
fb7f65fc57
5 changed files with 336 additions and 93 deletions
|
@ -9,7 +9,7 @@
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
use properties::ComputedValues;
|
use properties::ComputedValues;
|
||||||
use properties::longhands::display::computed_value as display;
|
use properties::longhands::display::computed_value as display;
|
||||||
use restyle_hints::{RESTYLE_LATER_SIBLINGS, RestyleHint};
|
use restyle_hints::{RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
|
||||||
use rule_tree::StrongRuleNode;
|
use rule_tree::StrongRuleNode;
|
||||||
use selector_parser::{PseudoElement, RestyleDamage, Snapshot};
|
use selector_parser::{PseudoElement, RestyleDamage, Snapshot};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -44,8 +44,8 @@ impl ComputedStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We manually implement Debug for ComputedStyle so tht we can avoid the verbose
|
// We manually implement Debug for ComputedStyle so that we can avoid the
|
||||||
// stringification of ComputedValues for normal logging.
|
// verbose stringification of ComputedValues for normal logging.
|
||||||
impl fmt::Debug for ComputedStyle {
|
impl fmt::Debug for ComputedStyle {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "ComputedStyle {{ rules: {:?}, values: {{..}} }}", self.rules)
|
write!(f, "ComputedStyle {{ rules: {:?}, values: {{..}} }}", self.rules)
|
||||||
|
@ -55,6 +55,13 @@ impl fmt::Debug for ComputedStyle {
|
||||||
type PseudoStylesInner = HashMap<PseudoElement, ComputedStyle,
|
type PseudoStylesInner = HashMap<PseudoElement, ComputedStyle,
|
||||||
BuildHasherDefault<::fnv::FnvHasher>>;
|
BuildHasherDefault<::fnv::FnvHasher>>;
|
||||||
|
|
||||||
|
/// The rule nodes for each of the pseudo-elements of an element.
|
||||||
|
///
|
||||||
|
/// TODO(emilio): Probably shouldn't be a `HashMap` by default, but a smaller
|
||||||
|
/// array.
|
||||||
|
pub type PseudoRuleNodes = HashMap<PseudoElement, StrongRuleNode,
|
||||||
|
BuildHasherDefault<::fnv::FnvHasher>>;
|
||||||
|
|
||||||
/// A set of styles for a given element's pseudo-elements.
|
/// A set of styles for a given element's pseudo-elements.
|
||||||
///
|
///
|
||||||
/// This is a map from pseudo-element to `ComputedStyle`.
|
/// This is a map from pseudo-element to `ComputedStyle`.
|
||||||
|
@ -69,6 +76,19 @@ impl PseudoStyles {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
PseudoStyles(HashMap::with_hasher(Default::default()))
|
PseudoStyles(HashMap::with_hasher(Default::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the rules that the different pseudo-elements matched.
|
||||||
|
///
|
||||||
|
/// FIXME(emilio): We could in theory avoid creating these when we have
|
||||||
|
/// support for just re-cascading an element. Then the input to
|
||||||
|
/// `cascade_node` could be `MatchResults` or just `UseExistingStyle`.
|
||||||
|
pub fn get_rules(&self) -> PseudoRuleNodes {
|
||||||
|
let mut rules = HashMap::with_hasher(Default::default());
|
||||||
|
for (pseudo, style) in &self.0 {
|
||||||
|
rules.insert(pseudo.clone(), style.rules.clone());
|
||||||
|
}
|
||||||
|
rules
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for PseudoStyles {
|
impl Deref for PseudoStyles {
|
||||||
|
@ -144,8 +164,11 @@ impl DescendantRestyleHint {
|
||||||
/// to provide more type safety while propagating restyle hints down the tree.
|
/// to provide more type safety while propagating restyle hints down the tree.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StoredRestyleHint {
|
pub struct StoredRestyleHint {
|
||||||
/// Whether this element should be restyled during the traversal.
|
/// Whether this element should be restyled during the traversal, and how.
|
||||||
pub restyle_self: bool,
|
///
|
||||||
|
/// This hint is stripped down, and only contains hints that are a subset of
|
||||||
|
/// RestyleHint::for_single_element().
|
||||||
|
pub self_: RestyleHint,
|
||||||
/// Whether the descendants of this element need to be restyled.
|
/// Whether the descendants of this element need to be restyled.
|
||||||
pub descendants: DescendantRestyleHint,
|
pub descendants: DescendantRestyleHint,
|
||||||
}
|
}
|
||||||
|
@ -154,7 +177,11 @@ impl StoredRestyleHint {
|
||||||
/// Propagates this restyle hint to a child element.
|
/// Propagates this restyle hint to a child element.
|
||||||
pub fn propagate(&self) -> Self {
|
pub fn propagate(&self) -> Self {
|
||||||
StoredRestyleHint {
|
StoredRestyleHint {
|
||||||
restyle_self: self.descendants != DescendantRestyleHint::Empty,
|
self_: if self.descendants == DescendantRestyleHint::Empty {
|
||||||
|
RestyleHint::empty()
|
||||||
|
} else {
|
||||||
|
RESTYLE_SELF
|
||||||
|
},
|
||||||
descendants: self.descendants.propagate(),
|
descendants: self.descendants.propagate(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +189,7 @@ impl StoredRestyleHint {
|
||||||
/// Creates an empty `StoredRestyleHint`.
|
/// Creates an empty `StoredRestyleHint`.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
StoredRestyleHint {
|
StoredRestyleHint {
|
||||||
restyle_self: false,
|
self_: RestyleHint::empty(),
|
||||||
descendants: DescendantRestyleHint::Empty,
|
descendants: DescendantRestyleHint::Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,29 +198,32 @@ impl StoredRestyleHint {
|
||||||
/// including the element.
|
/// including the element.
|
||||||
pub fn subtree() -> Self {
|
pub fn subtree() -> Self {
|
||||||
StoredRestyleHint {
|
StoredRestyleHint {
|
||||||
restyle_self: true,
|
self_: RESTYLE_SELF,
|
||||||
descendants: DescendantRestyleHint::Descendants,
|
descendants: DescendantRestyleHint::Descendants,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the hint indicates that our style may be invalidated.
|
||||||
|
pub fn has_self_invalidations(&self) -> bool {
|
||||||
|
!self.self_.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// 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.restyle_self && self.descendants == DescendantRestyleHint::Empty
|
!self.has_self_invalidations() &&
|
||||||
|
self.descendants == DescendantRestyleHint::Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert another restyle hint, effectively resulting in the union of both.
|
/// Insert another restyle hint, effectively resulting in the union of both.
|
||||||
pub fn insert(&mut self, other: &Self) {
|
pub fn insert(&mut self, other: &Self) {
|
||||||
self.restyle_self = self.restyle_self || other.restyle_self;
|
self.self_ |= other.self_;
|
||||||
self.descendants = self.descendants.union(other.descendants);
|
self.descendants = self.descendants.union(other.descendants);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StoredRestyleHint {
|
impl Default for StoredRestyleHint {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
StoredRestyleHint {
|
StoredRestyleHint::empty()
|
||||||
restyle_self: false,
|
|
||||||
descendants: DescendantRestyleHint::Empty,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +233,7 @@ impl From<RestyleHint> for StoredRestyleHint {
|
||||||
use self::DescendantRestyleHint::*;
|
use self::DescendantRestyleHint::*;
|
||||||
debug_assert!(!hint.contains(RESTYLE_LATER_SIBLINGS), "Caller should apply sibling hints");
|
debug_assert!(!hint.contains(RESTYLE_LATER_SIBLINGS), "Caller should apply sibling hints");
|
||||||
StoredRestyleHint {
|
StoredRestyleHint {
|
||||||
restyle_self: hint.contains(RESTYLE_SELF) || hint.contains(RESTYLE_STYLE_ATTRIBUTE),
|
self_: hint & RestyleHint::for_self(),
|
||||||
descendants: if hint.contains(RESTYLE_DESCENDANTS) { Descendants } else { Empty },
|
descendants: if hint.contains(RESTYLE_DESCENDANTS) { Descendants } else { Empty },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,7 +349,9 @@ impl RestyleData {
|
||||||
|
|
||||||
/// Returns true if this RestyleData might invalidate the current style.
|
/// Returns true if this RestyleData might invalidate the current style.
|
||||||
pub fn has_invalidations(&self) -> bool {
|
pub fn has_invalidations(&self) -> bool {
|
||||||
self.hint.restyle_self || self.recascade || self.snapshot.is_some()
|
self.hint.has_self_invalidations() ||
|
||||||
|
self.recascade ||
|
||||||
|
self.snapshot.is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +370,19 @@ pub struct ElementData {
|
||||||
restyle: Option<Box<RestyleData>>,
|
restyle: Option<Box<RestyleData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The kind of restyle that a single element should do.
|
||||||
|
pub enum RestyleKind {
|
||||||
|
/// We need to run selector matching plus re-cascade, that is, a full
|
||||||
|
/// restyle.
|
||||||
|
MatchAndCascade,
|
||||||
|
/// We need to recascade with some replacement rule, such as the style
|
||||||
|
/// attribute, or animation rules.
|
||||||
|
CascadeWithReplacements(RestyleHint),
|
||||||
|
/// We only need to recascade, for example, because only inherited
|
||||||
|
/// properties in the parent changed.
|
||||||
|
CascadeOnly,
|
||||||
|
}
|
||||||
|
|
||||||
impl ElementData {
|
impl ElementData {
|
||||||
/// Trivially construct an ElementData.
|
/// Trivially construct an ElementData.
|
||||||
pub fn new(existing: Option<ElementStyles>) -> Self {
|
pub fn new(existing: Option<ElementStyles>) -> Self {
|
||||||
|
@ -356,7 +401,31 @@ impl ElementData {
|
||||||
/// invalidation.
|
/// invalidation.
|
||||||
pub fn has_current_styles(&self) -> bool {
|
pub fn has_current_styles(&self) -> bool {
|
||||||
self.has_styles() &&
|
self.has_styles() &&
|
||||||
self.restyle.as_ref().map_or(true, |r| !r.has_invalidations())
|
self.restyle.as_ref().map_or(true, |r| !r.has_invalidations())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the kind of restyling that we're going to need to do on this
|
||||||
|
/// element, based of the stored restyle hint.
|
||||||
|
pub fn restyle_kind(&self) -> RestyleKind {
|
||||||
|
debug_assert!(!self.has_current_styles(), "Should've stopped earlier");
|
||||||
|
if !self.has_styles() {
|
||||||
|
return RestyleKind::MatchAndCascade;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(self.restyle.is_some());
|
||||||
|
let restyle_data = self.restyle.as_ref().unwrap();
|
||||||
|
let hint = restyle_data.hint.self_;
|
||||||
|
if hint.contains(RESTYLE_SELF) {
|
||||||
|
return RestyleKind::MatchAndCascade;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hint.is_empty() {
|
||||||
|
return RestyleKind::CascadeWithReplacements(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(restyle_data.recascade,
|
||||||
|
"We definitely need to do something!");
|
||||||
|
return RestyleKind::CascadeOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the element styles, if any.
|
/// Gets the element styles, if any.
|
||||||
|
|
|
@ -13,19 +13,19 @@ use atomic_refcell::AtomicRefMut;
|
||||||
use cache::LRUCache;
|
use cache::LRUCache;
|
||||||
use cascade_info::CascadeInfo;
|
use cascade_info::CascadeInfo;
|
||||||
use context::{SharedStyleContext, StyleContext};
|
use context::{SharedStyleContext, StyleContext};
|
||||||
use data::{ComputedStyle, ElementData, ElementStyles, PseudoStyles};
|
use data::{ComputedStyle, ElementData, ElementStyles, PseudoRuleNodes, PseudoStyles};
|
||||||
use dom::{SendElement, TElement, TNode};
|
use dom::{SendElement, TElement, TNode};
|
||||||
use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
|
use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
|
||||||
use properties::longhands::display::computed_value as display;
|
use properties::longhands::display::computed_value as display;
|
||||||
use rule_tree::StrongRuleNode;
|
use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RestyleHint};
|
||||||
|
use rule_tree::{CascadeLevel, StrongRuleNode};
|
||||||
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
|
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
|
||||||
use selectors::MatchAttr;
|
use selectors::MatchAttr;
|
||||||
use selectors::bloom::BloomFilter;
|
use selectors::bloom::BloomFilter;
|
||||||
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, MatchingReason, StyleRelations};
|
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, AFFECTED_BY_STYLE_ATTRIBUTE, MatchingReason, StyleRelations};
|
||||||
use servo_config::opts;
|
use servo_config::opts;
|
||||||
use sink::ForgetfulSink;
|
use sink::ForgetfulSink;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::BuildHasherDefault;
|
|
||||||
use std::slice::IterMut;
|
use std::slice::IterMut;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stylist::ApplicableDeclarationBlock;
|
use stylist::ApplicableDeclarationBlock;
|
||||||
|
@ -60,13 +60,6 @@ fn create_common_style_affecting_attributes_from_element<E: TElement>(element: &
|
||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The rule nodes for each of the pseudo-elements of an element.
|
|
||||||
///
|
|
||||||
/// TODO(emilio): Probably shouldn't be a `HashMap` by default, but a smaller
|
|
||||||
/// array.
|
|
||||||
type PseudoRuleNodes = HashMap<PseudoElement, StrongRuleNode,
|
|
||||||
BuildHasherDefault<::fnv::FnvHasher>>;
|
|
||||||
|
|
||||||
/// The results of selector matching on an element.
|
/// The results of selector matching on an element.
|
||||||
pub struct MatchResults {
|
pub struct MatchResults {
|
||||||
/// The rule node reference that represents the rules matched by the
|
/// The rule node reference that represents the rules matched by the
|
||||||
|
@ -635,6 +628,59 @@ pub trait MatchMethods : TElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the appropriate MatchResults from the current styles, to perform a
|
||||||
|
/// recascade.
|
||||||
|
///
|
||||||
|
/// TODO(emilio): Stop using `MachResults`, use an enum, or something.
|
||||||
|
fn match_results_from_current_style(&self, data: &ElementData) -> MatchResults {
|
||||||
|
let rule_node = data.styles().primary.rules.clone();
|
||||||
|
MatchResults {
|
||||||
|
primary: rule_node,
|
||||||
|
// FIXME(emilio): Same concern as below.
|
||||||
|
relations: StyleRelations::empty(),
|
||||||
|
// The per-pseudo rule-nodes haven't changed, but still need to be
|
||||||
|
// recascaded.
|
||||||
|
per_pseudo: data.styles().pseudos.get_rules(),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the rule nodes without re-running selector matching, using just
|
||||||
|
/// the rule tree.
|
||||||
|
fn cascade_with_replacements(&self,
|
||||||
|
hint: RestyleHint,
|
||||||
|
context: &StyleContext<Self>,
|
||||||
|
data: &mut AtomicRefMut<ElementData>)
|
||||||
|
-> MatchResults {
|
||||||
|
let mut rule_node = data.styles().primary.rules.clone();
|
||||||
|
|
||||||
|
if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
|
||||||
|
let style_attribute = self.style_attribute();
|
||||||
|
|
||||||
|
rule_node = context.shared.stylist.rule_tree
|
||||||
|
.update_rule_at_level(CascadeLevel::StyleAttributeNormal,
|
||||||
|
style_attribute,
|
||||||
|
rule_node);
|
||||||
|
|
||||||
|
rule_node = context.shared.stylist.rule_tree
|
||||||
|
.update_rule_at_level(CascadeLevel::StyleAttributeImportant,
|
||||||
|
style_attribute,
|
||||||
|
rule_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchResults {
|
||||||
|
primary: rule_node,
|
||||||
|
// FIXME(emilio): This is ok, for now, we shouldn't need to fake
|
||||||
|
// this.
|
||||||
|
relations: AFFECTED_BY_STYLE_ATTRIBUTE,
|
||||||
|
// The per-pseudo rule-nodes haven't changed, but still need to be
|
||||||
|
// recomputed.
|
||||||
|
//
|
||||||
|
// TODO(emilio): We could probably optimize this quite a bit.
|
||||||
|
per_pseudo: data.styles().pseudos.get_rules(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to share a style with another node. This method is unsafe
|
/// Attempts to share a style with another node. This method is unsafe
|
||||||
/// because it depends on the `style_sharing_candidate_cache` having only
|
/// 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
|
/// live nodes in it, and we have no way to guarantee that at the type
|
||||||
|
@ -649,6 +695,10 @@ pub trait MatchMethods : TElement {
|
||||||
return StyleSharingResult::CannotShare
|
return StyleSharingResult::CannotShare
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.parent_element().is_none() {
|
||||||
|
return StyleSharingResult::CannotShare
|
||||||
|
}
|
||||||
|
|
||||||
if self.style_attribute().is_some() {
|
if self.style_attribute().is_some() {
|
||||||
return StyleSharingResult::CannotShare
|
return StyleSharingResult::CannotShare
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,14 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RestyleHint {
|
||||||
|
/// The subset hints that affect the styling of a single element during the
|
||||||
|
/// traversal.
|
||||||
|
pub fn for_self() -> Self {
|
||||||
|
RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
impl From<nsRestyleHint> for RestyleHint {
|
impl From<nsRestyleHint> for RestyleHint {
|
||||||
fn from(raw: nsRestyleHint) -> Self {
|
fn from(raw: nsRestyleHint) -> Self {
|
||||||
|
|
|
@ -153,9 +153,20 @@ impl RuleTree {
|
||||||
pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode
|
pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode
|
||||||
where I: Iterator<Item=(StyleSource, CascadeLevel)>,
|
where I: Iterator<Item=(StyleSource, CascadeLevel)>,
|
||||||
{
|
{
|
||||||
let mut current = self.root.clone();
|
self.insert_ordered_rules_from(self.root.clone(), iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_ordered_rules_from<'a, I>(&self,
|
||||||
|
from: StrongRuleNode,
|
||||||
|
iter: I) -> StrongRuleNode
|
||||||
|
where I: Iterator<Item=(StyleSource, CascadeLevel)>,
|
||||||
|
{
|
||||||
|
let mut current = from;
|
||||||
|
let mut last_level = current.get().level;
|
||||||
for (source, level) in iter {
|
for (source, level) in iter {
|
||||||
|
debug_assert!(last_level <= level, "Not really ordered");
|
||||||
current = current.ensure_child(self.root.downgrade(), source, level);
|
current = current.ensure_child(self.root.downgrade(), source, level);
|
||||||
|
last_level = level;
|
||||||
}
|
}
|
||||||
current
|
current
|
||||||
}
|
}
|
||||||
|
@ -169,6 +180,90 @@ impl RuleTree {
|
||||||
pub unsafe fn maybe_gc(&self) {
|
pub unsafe fn maybe_gc(&self) {
|
||||||
self.root.maybe_gc();
|
self.root.maybe_gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces a rule in a given level (if present) for another rule.
|
||||||
|
///
|
||||||
|
/// Returns the resulting node that represents the new path.
|
||||||
|
pub fn update_rule_at_level(&self,
|
||||||
|
level: CascadeLevel,
|
||||||
|
pdb: Option<&Arc<RwLock<PropertyDeclarationBlock>>>,
|
||||||
|
path: StrongRuleNode)
|
||||||
|
-> StrongRuleNode {
|
||||||
|
debug_assert!(level.is_unique_per_element());
|
||||||
|
// TODO(emilio): Being smarter with lifetimes we could avoid a bit of
|
||||||
|
// the refcount churn.
|
||||||
|
let mut current = path.clone();
|
||||||
|
|
||||||
|
// First walk up until the first less-or-equally specific rule.
|
||||||
|
let mut children = vec![];
|
||||||
|
while current.get().level > level {
|
||||||
|
children.push((current.get().source.clone().unwrap(), current.get().level));
|
||||||
|
current = current.parent().unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then remove the one at the level we want to replace, if any.
|
||||||
|
//
|
||||||
|
// NOTE: Here we assume that only one rule can be at the level we're
|
||||||
|
// replacing.
|
||||||
|
//
|
||||||
|
// This is certainly true for HTML style attribute rules, animations and
|
||||||
|
// transitions, but could not be so for SMIL animations, which we'd need
|
||||||
|
// to special-case (isn't hard, it's just about removing the `if` and
|
||||||
|
// special cases, and replacing them for a `while` loop, avoiding the
|
||||||
|
// optimizations).
|
||||||
|
if current.get().level == level {
|
||||||
|
if let Some(pdb) = pdb {
|
||||||
|
// If the only rule at the level we're replacing is exactly the
|
||||||
|
// same as `pdb`, we're done, and `path` is still valid.
|
||||||
|
//
|
||||||
|
// TODO(emilio): Another potential optimization is the one where
|
||||||
|
// we can just replace the rule at that level for `pdb`, and
|
||||||
|
// then we don't need to re-create the children, and `path` is
|
||||||
|
// also equally valid. This is less likely, and would require an
|
||||||
|
// in-place mutation of the source, which is, at best, fiddly,
|
||||||
|
// so let's skip it for now.
|
||||||
|
let is_here_already = match current.get().source.as_ref() {
|
||||||
|
Some(&StyleSource::Declarations(ref already_here)) => {
|
||||||
|
arc_ptr_eq(pdb, already_here)
|
||||||
|
},
|
||||||
|
_ => unreachable!("Replacing non-declarations style?"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_here_already {
|
||||||
|
debug!("Picking the fast path in rule replacement");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = current.parent().unwrap().clone();
|
||||||
|
}
|
||||||
|
debug_assert!(current.get().level != level,
|
||||||
|
"Multiple rules should've been replaced?");
|
||||||
|
|
||||||
|
// Insert the rule if it's relevant at this level in the cascade.
|
||||||
|
//
|
||||||
|
// These optimizations are likely to be important, because the levels
|
||||||
|
// where replacements apply (style and animations) tend to trigger
|
||||||
|
// pretty bad styling cases already.
|
||||||
|
if let Some(pdb) = pdb {
|
||||||
|
if level.is_important() {
|
||||||
|
if pdb.read().any_important() {
|
||||||
|
current = current.ensure_child(self.root.downgrade(),
|
||||||
|
StyleSource::Declarations(pdb.clone()),
|
||||||
|
level);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if pdb.read().any_normal() {
|
||||||
|
current = current.ensure_child(self.root.downgrade(),
|
||||||
|
StyleSource::Declarations(pdb.clone()),
|
||||||
|
level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the rule is in the relevant place, push the children as
|
||||||
|
// necessary.
|
||||||
|
self.insert_ordered_rules_from(current, children.into_iter().rev())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of RuleNodes added to the free list before we will consider
|
/// The number of RuleNodes added to the free list before we will consider
|
||||||
|
@ -183,7 +278,7 @@ const RULE_TREE_GC_INTERVAL: usize = 300;
|
||||||
///
|
///
|
||||||
/// [1]: https://drafts.csswg.org/css-cascade/#cascade-origin
|
/// [1]: https://drafts.csswg.org/css-cascade/#cascade-origin
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
#[derive(Eq, PartialEq, Copy, Clone, Debug, PartialOrd)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
pub enum CascadeLevel {
|
pub enum CascadeLevel {
|
||||||
/// Normal User-Agent rules.
|
/// Normal User-Agent rules.
|
||||||
|
@ -211,6 +306,18 @@ pub enum CascadeLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CascadeLevel {
|
impl CascadeLevel {
|
||||||
|
/// Returns whether this cascade level is unique per element, in which case
|
||||||
|
/// we can replace the path in the cascade without fear.
|
||||||
|
pub fn is_unique_per_element(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
CascadeLevel::Transitions |
|
||||||
|
CascadeLevel::Animations |
|
||||||
|
CascadeLevel::StyleAttributeNormal |
|
||||||
|
CascadeLevel::StyleAttributeImportant => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether this cascade level represents important rules of some
|
/// Returns whether this cascade level represents important rules of some
|
||||||
/// sort.
|
/// sort.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -248,7 +355,7 @@ struct RuleNode {
|
||||||
source: Option<StyleSource>,
|
source: Option<StyleSource>,
|
||||||
|
|
||||||
/// The cascade level this rule is positioned at.
|
/// The cascade level this rule is positioned at.
|
||||||
cascade_level: CascadeLevel,
|
level: CascadeLevel,
|
||||||
|
|
||||||
refcount: AtomicUsize,
|
refcount: AtomicUsize,
|
||||||
first_child: AtomicPtr<RuleNode>,
|
first_child: AtomicPtr<RuleNode>,
|
||||||
|
@ -274,13 +381,13 @@ impl RuleNode {
|
||||||
fn new(root: WeakRuleNode,
|
fn new(root: WeakRuleNode,
|
||||||
parent: StrongRuleNode,
|
parent: StrongRuleNode,
|
||||||
source: StyleSource,
|
source: StyleSource,
|
||||||
cascade_level: CascadeLevel) -> Self {
|
level: CascadeLevel) -> Self {
|
||||||
debug_assert!(root.upgrade().parent().is_none());
|
debug_assert!(root.upgrade().parent().is_none());
|
||||||
RuleNode {
|
RuleNode {
|
||||||
root: Some(root),
|
root: Some(root),
|
||||||
parent: Some(parent),
|
parent: Some(parent),
|
||||||
source: Some(source),
|
source: Some(source),
|
||||||
cascade_level: cascade_level,
|
level: level,
|
||||||
refcount: AtomicUsize::new(1),
|
refcount: AtomicUsize::new(1),
|
||||||
first_child: AtomicPtr::new(ptr::null_mut()),
|
first_child: AtomicPtr::new(ptr::null_mut()),
|
||||||
next_sibling: AtomicPtr::new(ptr::null_mut()),
|
next_sibling: AtomicPtr::new(ptr::null_mut()),
|
||||||
|
@ -295,7 +402,7 @@ impl RuleNode {
|
||||||
root: None,
|
root: None,
|
||||||
parent: None,
|
parent: None,
|
||||||
source: None,
|
source: None,
|
||||||
cascade_level: CascadeLevel::UANormal,
|
level: CascadeLevel::UANormal,
|
||||||
refcount: AtomicUsize::new(1),
|
refcount: AtomicUsize::new(1),
|
||||||
first_child: AtomicPtr::new(ptr::null_mut()),
|
first_child: AtomicPtr::new(ptr::null_mut()),
|
||||||
next_sibling: AtomicPtr::new(ptr::null_mut()),
|
next_sibling: AtomicPtr::new(ptr::null_mut()),
|
||||||
|
@ -444,10 +551,10 @@ impl StrongRuleNode {
|
||||||
fn ensure_child(&self,
|
fn ensure_child(&self,
|
||||||
root: WeakRuleNode,
|
root: WeakRuleNode,
|
||||||
source: StyleSource,
|
source: StyleSource,
|
||||||
cascade_level: CascadeLevel) -> StrongRuleNode {
|
level: CascadeLevel) -> StrongRuleNode {
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
for child in self.get().iter_children() {
|
for child in self.get().iter_children() {
|
||||||
if child .get().cascade_level == cascade_level &&
|
if child .get().level == level &&
|
||||||
child.get().source.as_ref().unwrap().ptr_equals(&source) {
|
child.get().source.as_ref().unwrap().ptr_equals(&source) {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
@ -455,9 +562,9 @@ impl StrongRuleNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut node = Box::new(RuleNode::new(root,
|
let mut node = Box::new(RuleNode::new(root,
|
||||||
self.clone(),
|
self.clone(),
|
||||||
source.clone(),
|
source.clone(),
|
||||||
cascade_level));
|
level));
|
||||||
let new_ptr: *mut RuleNode = &mut *node;
|
let new_ptr: *mut RuleNode = &mut *node;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -521,7 +628,7 @@ impl StrongRuleNode {
|
||||||
|
|
||||||
/// Get the importance that this rule node represents.
|
/// Get the importance that this rule node represents.
|
||||||
pub fn importance(&self) -> Importance {
|
pub fn importance(&self) -> Importance {
|
||||||
self.get().cascade_level.importance()
|
self.get().level.importance()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator for this rule node and its ancestors.
|
/// Get an iterator for this rule node and its ancestors.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
||||||
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
|
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
|
||||||
use data::{ElementData, ElementStyles, StoredRestyleHint};
|
use data::{ElementData, ElementStyles, RestyleKind, StoredRestyleHint};
|
||||||
use dom::{NodeInfo, TElement, TNode};
|
use dom::{NodeInfo, TElement, TNode};
|
||||||
use matching::{MatchMethods, StyleSharingResult};
|
use matching::{MatchMethods, StyleSharingResult};
|
||||||
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
|
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
|
||||||
|
@ -453,9 +453,6 @@ pub fn recalc_style_at<E, D>(traversal: &D,
|
||||||
|
|
||||||
// Computes style, returning true if the inherited styles changed for this
|
// Computes style, returning true if the inherited styles changed for this
|
||||||
// element.
|
// element.
|
||||||
//
|
|
||||||
// FIXME(bholley): This should differentiate between matching and cascading,
|
|
||||||
// since we have separate bits for each now.
|
|
||||||
fn compute_style<E, D>(_traversal: &D,
|
fn compute_style<E, D>(_traversal: &D,
|
||||||
traversal_data: &mut PerLevelTraversalData,
|
traversal_data: &mut PerLevelTraversalData,
|
||||||
context: &mut StyleContext<E>,
|
context: &mut StyleContext<E>,
|
||||||
|
@ -466,63 +463,75 @@ fn compute_style<E, D>(_traversal: &D,
|
||||||
{
|
{
|
||||||
context.thread_local.statistics.elements_styled += 1;
|
context.thread_local.statistics.elements_styled += 1;
|
||||||
let shared_context = context.shared;
|
let shared_context = context.shared;
|
||||||
// Ensure the bloom filter is up to date.
|
|
||||||
let dom_depth = context.thread_local.bloom_filter
|
|
||||||
.insert_parents_recovering(element, traversal_data.current_dom_depth);
|
|
||||||
|
|
||||||
// Update the dom depth with the up-to-date dom depth.
|
// TODO(emilio): Make cascade_input less expensive to compute in the cases
|
||||||
//
|
// we don't need to run selector matching.
|
||||||
// Note that this is always the same than the pre-existing depth, but it can
|
let cascade_input = match data.restyle_kind() {
|
||||||
// change from unknown to known at this step.
|
RestyleKind::MatchAndCascade => {
|
||||||
traversal_data.current_dom_depth = Some(dom_depth);
|
// Check to see whether we can share a style with someone.
|
||||||
|
let sharing_result = unsafe {
|
||||||
|
element.share_style_if_possible(&mut context.thread_local.style_sharing_candidate_cache,
|
||||||
|
shared_context,
|
||||||
|
&mut data)
|
||||||
|
};
|
||||||
|
|
||||||
context.thread_local.bloom_filter.assert_complete(element);
|
match sharing_result {
|
||||||
|
StyleSharingResult::StyleWasShared(index) => {
|
||||||
// Check to see whether we can share a style with someone.
|
context.thread_local.statistics.styles_shared += 1;
|
||||||
let sharing_result = if element.parent_element().is_none() {
|
context.thread_local.style_sharing_candidate_cache.touch(index);
|
||||||
StyleSharingResult::CannotShare
|
|
||||||
} else {
|
|
||||||
unsafe { element.share_style_if_possible(&mut context.thread_local.style_sharing_candidate_cache,
|
|
||||||
shared_context, &mut data) }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Otherwise, match and cascade selectors.
|
|
||||||
match sharing_result {
|
|
||||||
StyleSharingResult::CannotShare => {
|
|
||||||
let match_results;
|
|
||||||
let shareable_element = {
|
|
||||||
// Perform the CSS selector matching.
|
|
||||||
context.thread_local.statistics.elements_matched += 1;
|
|
||||||
let filter = context.thread_local.bloom_filter.filter();
|
|
||||||
match_results = element.match_element(context, Some(filter));
|
|
||||||
if match_results.primary_is_shareable() {
|
|
||||||
Some(element)
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
StyleSharingResult::CannotShare => {
|
||||||
let relations = match_results.relations;
|
// Ensure the bloom filter is up to date.
|
||||||
|
let dom_depth =
|
||||||
|
context.thread_local.bloom_filter
|
||||||
|
.insert_parents_recovering(element,
|
||||||
|
traversal_data.current_dom_depth);
|
||||||
|
|
||||||
// Perform the CSS cascade.
|
// Update the dom depth with the up-to-date dom depth.
|
||||||
unsafe {
|
//
|
||||||
let shareable = match_results.primary_is_shareable();
|
// Note that this is always the same than the pre-existing depth,
|
||||||
element.cascade_node(context, &mut data,
|
// but it can change from unknown to known at this step.
|
||||||
element.parent_element(),
|
traversal_data.current_dom_depth = Some(dom_depth);
|
||||||
match_results.primary,
|
|
||||||
match_results.per_pseudo,
|
|
||||||
shareable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add ourselves to the LRU cache.
|
context.thread_local.bloom_filter.assert_complete(element);
|
||||||
if let Some(element) = shareable_element {
|
|
||||||
context.thread_local
|
// Perform the CSS selector matching.
|
||||||
.style_sharing_candidate_cache
|
context.thread_local.statistics.elements_matched += 1;
|
||||||
.insert_if_possible(&element, &data.styles().primary.values, relations);
|
|
||||||
|
let filter = context.thread_local.bloom_filter.filter();
|
||||||
|
Some(element.match_element(context, Some(filter)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StyleSharingResult::StyleWasShared(index) => {
|
RestyleKind::CascadeWithReplacements(hint) => {
|
||||||
context.thread_local.statistics.styles_shared += 1;
|
Some(element.cascade_with_replacements(hint, context, &mut data))
|
||||||
context.thread_local.style_sharing_candidate_cache.touch(index);
|
}
|
||||||
|
RestyleKind::CascadeOnly => {
|
||||||
|
// TODO(emilio): Stop doing this work, and teach cascade_node about
|
||||||
|
// the current style instead.
|
||||||
|
Some(element.match_results_from_current_style(&*data))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(match_results) = cascade_input {
|
||||||
|
// Perform the CSS cascade.
|
||||||
|
let shareable = match_results.primary_is_shareable();
|
||||||
|
unsafe {
|
||||||
|
element.cascade_node(context, &mut data,
|
||||||
|
element.parent_element(),
|
||||||
|
match_results.primary,
|
||||||
|
match_results.per_pseudo,
|
||||||
|
shareable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if shareable {
|
||||||
|
// Add ourselves to the LRU cache.
|
||||||
|
context.thread_local
|
||||||
|
.style_sharing_candidate_cache
|
||||||
|
.insert_if_possible(&element,
|
||||||
|
&data.styles().primary.values,
|
||||||
|
match_results.relations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue