Auto merge of #12668 - emilio:style-cache, r=SimonSapin,pcwalton

Rewrite the style sharing candidate cache

<!-- Please describe your changes on the following line: -->

See the first commit's description for a bit of background. I'm making the PR in this state just to verify against try I've handled all cases of possibly incorrect sharing and, if not, fix it.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #12534

<!-- Either: -->
- [x] There are tests for these changes OR

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- 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/12668)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-08-17 16:34:30 -05:00 committed by GitHub
commit ec7efff14b
25 changed files with 575 additions and 279 deletions

View file

@ -33,7 +33,7 @@ range = {path = "../range"}
rustc-serialize = "0.3" rustc-serialize = "0.3"
script_layout_interface = {path = "../script_layout_interface"} script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"} script_traits = {path = "../script_traits"}
selectors = {version = "0.8", features = ["heap_size"]} selectors = {version = "0.9", features = ["heap_size"]}
serde_macros = "0.8" serde_macros = "0.8"
smallvec = "0.1" smallvec = "0.1"
string_cache = {version = "0.2.23", features = ["heap_size"]} string_cache = {version = "0.2.23", features = ["heap_size"]}

View file

@ -13,7 +13,7 @@ use gfx::display_list::OpaqueNode;
use script_layout_interface::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage}; use script_layout_interface::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use std::mem; use std::mem;
use style::context::SharedStyleContext; use style::context::{LocalStyleContext, SharedStyleContext, StyleContext};
use style::dom::TNode; use style::dom::TNode;
use style::selector_impl::ServoSelectorImpl; use style::selector_impl::ServoSelectorImpl;
use style::traversal::RestyleResult; use style::traversal::RestyleResult;
@ -81,6 +81,10 @@ impl<'lc, N> DomTraversalContext<N> for RecalcStyleAndConstructFlows<'lc>
fn process_postorder(&self, node: N) { fn process_postorder(&self, node: N) {
construct_flows_at(&self.context, self.root, node); construct_flows_at(&self.context, self.root, node);
} }
fn local_context(&self) -> &LocalStyleContext {
self.context.local_context()
}
} }
/// A bottom-up, parallelizable traversal. /// A bottom-up, parallelizable traversal.

View file

@ -65,7 +65,7 @@ regex = "0.1.43"
rustc-serialize = "0.3" rustc-serialize = "0.3"
script_layout_interface = {path = "../script_layout_interface"} script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"} script_traits = {path = "../script_traits"}
selectors = {version = "0.8", features = ["heap_size"]} selectors = {version = "0.9", features = ["heap_size"]}
serde = "0.8" serde = "0.8"
smallvec = "0.1" smallvec = "0.1"
string_cache = {version = "0.2.23", features = ["heap_size", "unstable"]} string_cache = {version = "0.2.23", features = ["heap_size", "unstable"]}

View file

@ -444,6 +444,11 @@ impl<'le> TElement for ServoLayoutElement<'le> {
} }
} }
impl<'le> PartialEq for ServoLayoutElement<'le> {
fn eq(&self, other: &Self) -> bool {
self.as_node() == other.as_node()
}
}
impl<'le> ServoLayoutElement<'le> { impl<'le> ServoLayoutElement<'le> {
fn from_layout_js(el: LayoutJS<Element>) -> ServoLayoutElement<'le> { fn from_layout_js(el: LayoutJS<Element>) -> ServoLayoutElement<'le> {

View file

@ -27,7 +27,7 @@ plugins = {path = "../plugins"}
profile_traits = {path = "../profile_traits"} profile_traits = {path = "../profile_traits"}
range = {path = "../range"} range = {path = "../range"}
script_traits = {path = "../script_traits"} script_traits = {path = "../script_traits"}
selectors = {version = "0.8", features = ["heap_size"]} selectors = {version = "0.9", features = ["heap_size"]}
string_cache = {version = "0.2.23", features = ["heap_size"]} string_cache = {version = "0.2.23", features = ["heap_size"]}
style = {path = "../style"} style = {path = "../style"}
url = {version = "1.2", features = ["heap_size"]} url = {version = "1.2", features = ["heap_size"]}

View file

@ -1162,7 +1162,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1", "script_layout_interface 0.0.1",
"script_traits 0.0.1", "script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1931,7 +1931,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1", "script_layout_interface 0.0.1",
"script_traits 0.0.1", "script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1968,7 +1968,7 @@ dependencies = [
"profile_traits 0.0.1", "profile_traits 0.0.1",
"range 0.0.1", "range 0.0.1",
"script_traits 0.0.1", "script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1", "style 0.0.1",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2017,7 +2017,7 @@ dependencies = [
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.8.2" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2239,7 +2239,7 @@ dependencies = [
"plugins 0.0.1", "plugins 0.0.1",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2259,7 +2259,7 @@ dependencies = [
"cssparser 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "cssparser 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1", "style 0.0.1",
"style_traits 0.0.1", "style_traits 0.0.1",

View file

@ -38,7 +38,7 @@ num-traits = "0.1.32"
ordered-float = "0.2.2" ordered-float = "0.2.2"
rand = "0.3" rand = "0.3"
rustc-serialize = "0.3" rustc-serialize = "0.3"
selectors = "0.8.2" selectors = "0.9"
serde = {version = "0.8", optional = true} serde = {version = "0.8", optional = true}
serde_macros = {version = "0.8", optional = true} serde_macros = {version = "0.8", optional = true}
smallvec = "0.1" smallvec = "0.1"

View file

@ -7,7 +7,7 @@
use rand; use rand;
use rand::Rng; use rand::Rng;
use std::hash::{Hash, Hasher, SipHasher}; use std::hash::{Hash, Hasher, SipHasher};
use std::slice::Iter; use std::slice::{Iter, IterMut};
pub struct LRUCache<K, V> { pub struct LRUCache<K, V> {
entries: Vec<(K, V)>, entries: Vec<(K, V)>,
@ -17,7 +17,7 @@ pub struct LRUCache<K, V> {
impl<K: PartialEq, V: Clone> LRUCache<K, V> { impl<K: PartialEq, V: Clone> LRUCache<K, V> {
pub fn new(size: usize) -> LRUCache<K, V> { pub fn new(size: usize) -> LRUCache<K, V> {
LRUCache { LRUCache {
entries: vec!(), entries: vec![],
cache_size: size, cache_size: size,
} }
} }
@ -36,6 +36,10 @@ impl<K: PartialEq, V: Clone> LRUCache<K, V> {
self.entries.iter() self.entries.iter()
} }
pub fn iter_mut(&mut self) -> IterMut<(K, V)> {
self.entries.iter_mut()
}
pub fn insert(&mut self, key: K, val: V) { pub fn insert(&mut self, key: K, val: V) {
if self.entries.len() == self.cache_size { if self.entries.len() == self.cache_size {
self.entries.remove(0); self.entries.remove(0);

View file

@ -201,7 +201,7 @@ pub trait PresentationalHintsSynthetizer {
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>; where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>;
} }
pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer { pub trait TElement : PartialEq + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
type ConcreteNode: TNode<ConcreteElement = Self, ConcreteDocument = Self::ConcreteDocument>; type ConcreteNode: TNode<ConcreteElement = Self, ConcreteDocument = Self::ConcreteDocument>;
type ConcreteDocument: TDocument<ConcreteNode = Self::ConcreteNode, ConcreteElement = Self>; type ConcreteDocument: TDocument<ConcreteNode = Self::ConcreteNode, ConcreteElement = Self>;
@ -236,6 +236,7 @@ pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynt
unsafe { node.set_dirty(true); } unsafe { node.set_dirty(true); }
// XXX(emilio): For now, dirty implies dirty descendants if found. // XXX(emilio): For now, dirty implies dirty descendants if found.
} else if hint.contains(RESTYLE_DESCENDANTS) { } else if hint.contains(RESTYLE_DESCENDANTS) {
unsafe { node.set_dirty_descendants(true); }
let mut current = node.first_child(); let mut current = node.first_child();
while let Some(node) = current { while let Some(node) = current {
unsafe { node.set_dirty(true); } unsafe { node.set_dirty(true); }

View file

@ -12,21 +12,21 @@ use cache::{LRUCache, SimpleHashCache};
use cascade_info::CascadeInfo; use cascade_info::CascadeInfo;
use context::{StyleContext, SharedStyleContext}; use context::{StyleContext, SharedStyleContext};
use data::PrivateStyleData; use data::PrivateStyleData;
use dom::{TElement, TNode, TRestyleDamage}; use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
use properties::longhands::display::computed_value as display; use properties::longhands::display::computed_value as display;
use properties::{ComputedValues, PropertyDeclaration, cascade}; use properties::{ComputedValues, PropertyDeclaration, cascade};
use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement}; use selector_impl::{TheSelectorImpl, PseudoElement};
use selector_matching::{DeclarationBlock, Stylist}; use selector_matching::{DeclarationBlock, Stylist};
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::{StyleRelations, AFFECTED_BY_PSEUDO_ELEMENTS};
use selectors::{Element, MatchAttr}; use selectors::{Element, MatchAttr};
use sink::ForgetfulSink; use sink::ForgetfulSink;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::slice::Iter; use std::slice::IterMut;
use std::sync::Arc; use std::sync::Arc;
use string_cache::{Atom, Namespace}; use string_cache::Atom;
use traversal::RestyleResult; use traversal::RestyleResult;
use util::opts; use util::opts;
@ -178,155 +178,147 @@ impl ApplicableDeclarationsCache {
} }
} }
/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles. /// Information regarding a candidate.
pub struct StyleSharingCandidateCache { ///
cache: LRUCache<StyleSharingCandidate, ()>, /// TODO: We can stick a lot more info here.
#[derive(Debug)]
struct StyleSharingCandidate {
/// The node, guaranteed to be an element.
node: UnsafeNode,
/// The cached computed style, here for convenience.
style: Arc<ComputedValues>,
/// The cached common style affecting attribute info.
common_style_affecting_attributes: Option<CommonStyleAffectingAttributes>,
} }
#[derive(Clone)] impl PartialEq<StyleSharingCandidate> for StyleSharingCandidate {
pub struct StyleSharingCandidate {
pub style: Arc<ComputedValues>,
pub parent_style: Arc<ComputedValues>,
pub local_name: Atom,
pub classes: Vec<Atom>,
pub namespace: Namespace,
pub common_style_affecting_attributes: CommonStyleAffectingAttributes,
pub link: bool,
}
impl PartialEq for StyleSharingCandidate {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.node == other.node &&
arc_ptr_eq(&self.style, &other.style) && arc_ptr_eq(&self.style, &other.style) &&
arc_ptr_eq(&self.parent_style, &other.parent_style) &&
self.local_name == other.local_name &&
self.classes == other.classes &&
self.link == other.link &&
self.namespace == other.namespace &&
self.common_style_affecting_attributes == other.common_style_affecting_attributes self.common_style_affecting_attributes == other.common_style_affecting_attributes
} }
} }
impl StyleSharingCandidate { /// An LRU cache of the last few nodes seen, so that we can aggressively try to
/// Attempts to create a style sharing candidate from this node. Returns /// reuse their styles.
/// the style sharing candidate or `None` if this node is ineligible for ///
/// style sharing. /// Note that this cache is flushed every time we steal work from the queue, so
#[allow(unsafe_code)] /// storing nodes here temporarily is safe.
fn new<N: TNode>(element: &N::ConcreteElement) -> Option<Self> { ///
let parent_element = match element.parent_element() { /// NB: We store UnsafeNode's, but this is not unsafe. It's a shame being
None => return None, /// generic over elements is unfeasible (you can make compile style without much
Some(parent_element) => parent_element, /// difficulty, but good luck with layout and all the types with assoc.
}; /// lifetimes).
pub struct StyleSharingCandidateCache {
cache: LRUCache<StyleSharingCandidate, ()>,
}
let style = unsafe { #[derive(Clone, Debug)]
match element.as_node().borrow_data_unchecked() { pub enum CacheMiss {
None => return None, Parent,
Some(data_ref) => { LocalName,
match (*data_ref).style { Namespace,
None => return None, Link,
Some(ref data) => (*data).clone(), 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>, CacheMiss> {
macro_rules! miss {
($miss: ident) => {
return Err(CacheMiss::$miss);
} }
} }
if element.parent_element() != candidate_element.parent_element() {
miss!(Parent)
} }
};
let parent_style = unsafe { if *element.get_local_name() != *candidate_element.get_local_name() {
match parent_element.as_node().borrow_data_unchecked() { miss!(LocalName)
None => return None,
Some(parent_data_ref) => {
match (*parent_data_ref).style {
None => return None,
Some(ref data) => (*data).clone(),
} }
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() { if element.style_attribute().is_some() {
return None miss!(StyleAttr)
} }
let mut classes = Vec::new(); if !have_same_class(element, candidate_element) {
element.each_class(|c| classes.push(c.clone())); miss!(Class)
Some(StyleSharingCandidate {
style: style,
parent_style: parent_style,
local_name: element.get_local_name().clone(),
classes: classes,
link: element.is_link(),
namespace: (*element.get_namespace()).clone(),
common_style_affecting_attributes:
create_common_style_affecting_attributes_from_element::<N::ConcreteElement>(&element)
})
} }
pub fn can_share_style_with<E: TElement>(&self, element: &E) -> bool { if !have_same_common_style_affecting_attributes(element,
if element.get_local_name() != self.local_name.borrow() { candidate,
return false candidate_element) {
miss!(CommonStyleAffectingAttributes)
} }
let mut num_classes = 0; if !have_same_presentational_hints(element, candidate_element) {
let mut classes_match = true; miss!(PresHints)
element.each_class(|c| {
num_classes += 1;
// Note that we could do this check more cheaply if we decided to
// only consider class lists as equal if the orders match, since
// we could then index by num_classes instead of using .contains().
if classes_match && !self.classes.contains(c) {
classes_match = false;
}
});
if !classes_match || num_classes != self.classes.len() {
return false;
} }
if element.get_namespace() != self.namespace.borrow() { if !match_same_sibling_affecting_rules(element,
return false candidate_element,
shared_context) {
miss!(SiblingRules)
} }
let mut matching_rules = ForgetfulSink::new(); if !match_same_not_common_style_affecting_attributes_rules(element,
element.synthesize_presentational_hints_for_legacy_attributes(&mut matching_rules); candidate_element,
if !matching_rules.is_empty() { shared_context) {
return false; miss!(NonCommonAttrRules)
} }
// FIXME(pcwalton): It's probably faster to iterate over all the element's attributes and Ok(candidate.style.clone())
// use the {common, rare}-style-affecting-attributes tables as lookup tables.
for attribute_info in &common_style_affecting_attributes() {
match attribute_info.mode {
CommonStyleAffectingAttributeMode::IsPresent(flag) => {
if self.common_style_affecting_attributes.contains(flag) !=
element.has_attr(&ns!(), &attribute_info.atom) {
return false
}
}
CommonStyleAffectingAttributeMode::IsEqual(ref target_value, flag) => {
let contains = self.common_style_affecting_attributes.contains(flag);
if element.has_attr(&ns!(), &attribute_info.atom) {
if !contains || !element.attr_equals(&ns!(), &attribute_info.atom, target_value) {
return false
}
} else if contains {
return false
}
}
}
} }
for attribute_name in &rare_style_affecting_attributes() { fn have_same_common_style_affecting_attributes<E: TElement>(element: &E,
if element.has_attr(&ns!(), attribute_name) { candidate: &mut StyleSharingCandidate,
return false 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()
} }
if element.is_link() != self.link { fn have_same_presentational_hints<E: TElement>(element: &E, candidate: &E) -> bool {
return false 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!");
} }
// TODO(pcwalton): We don't support visited links yet, but when we do there will need to first.is_empty()
// be some logic here.
true
}
} }
bitflags! { bitflags! {
@ -384,7 +376,33 @@ pub fn rare_style_affecting_attributes() -> [Atom; 3] {
[ atom!("bgcolor"), atom!("border"), atom!("colspan") ] [ atom!("bgcolor"), atom!("border"), atom!("colspan") ]
} }
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 40; fn have_same_class<E: TElement>(element: &E, candidate: &E) -> bool {
// XXX Efficiency here, I'm only validating ideas.
let mut first = vec![];
let mut second = vec![];
element.each_class(|c| first.push(c.clone()));
candidate.each_class(|c| second.push(c.clone()));
first == second
}
// 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 { impl StyleSharingCandidateCache {
pub fn new() -> Self { pub fn new() -> Self {
@ -393,20 +411,62 @@ impl StyleSharingCandidateCache {
} }
} }
pub fn iter(&self) -> Iter<(StyleSharingCandidate, ())> { fn iter_mut(&mut self) -> IterMut<(StyleSharingCandidate, ())> {
self.cache.iter() self.cache.iter_mut()
} }
pub fn insert_if_possible<N: TNode>(&mut self, element: &N::ConcreteElement) { pub fn insert_if_possible<E: TElement>(&mut self,
match StyleSharingCandidate::new::<N>(element) { element: &E,
None => {} relations: StyleRelations) {
Some(candidate) => self.cache.insert(candidate, ()) 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 node = element.as_node();
let data = node.borrow_data().unwrap();
let style = data.style.as_ref().unwrap();
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: node.to_unsafe(),
style: style.clone(),
common_style_affecting_attributes: None,
}, ());
} }
pub fn touch(&mut self, index: usize) { pub fn touch(&mut self, index: usize) {
self.cache.touch(index); self.cache.touch(index);
} }
pub fn clear(&mut self) {
self.cache.evict_all()
}
} }
/// The results of attempting to share a style. /// The results of attempting to share a style.
@ -562,31 +622,18 @@ impl<N: TNode> PrivateMatchMethods for N {}
trait PrivateElementMatchMethods: TElement { trait PrivateElementMatchMethods: TElement {
fn share_style_with_candidate_if_possible(&self, fn share_style_with_candidate_if_possible(&self,
parent_node: Option<Self::ConcreteNode>, parent_node: Self::ConcreteNode,
candidate: &StyleSharingCandidate) shared_context: &SharedStyleContext,
-> Option<Arc<ComputedValues>> { candidate: &mut StyleSharingCandidate)
let parent_node = match parent_node { -> Result<Arc<ComputedValues>, CacheMiss> {
Some(ref parent_node) if parent_node.as_element().is_some() => parent_node, debug_assert!(parent_node.is_element());
Some(_) | None => return None,
let candidate_element = unsafe {
Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
}; };
let parent_data: Option<&PrivateStyleData> = unsafe { element_matches_candidate(self, candidate, &candidate_element,
parent_node.borrow_data_unchecked().map(|d| &*d) shared_context)
};
if let Some(parent_data_ref) = parent_data {
// Check parent style.
let parent_style = (*parent_data_ref).style.as_ref().unwrap();
if !arc_ptr_eq(parent_style, &candidate.parent_style) {
return None
}
// Check tag names, classes, etc.
if !candidate.can_share_style_with(self) {
return None
}
return Some(candidate.style.clone())
}
None
} }
} }
@ -597,15 +644,19 @@ pub trait ElementMatchMethods : TElement {
stylist: &Stylist, stylist: &Stylist,
parent_bf: Option<&BloomFilter>, parent_bf: Option<&BloomFilter>,
applicable_declarations: &mut ApplicableDeclarations) applicable_declarations: &mut ApplicableDeclarations)
-> bool { -> StyleRelations {
use traversal::relations_are_shareable;
let style_attribute = self.style_attribute().as_ref(); let style_attribute = self.style_attribute().as_ref();
applicable_declarations.normal_shareable = let mut relations =
stylist.push_applicable_declarations(self, stylist.push_applicable_declarations(self,
parent_bf, parent_bf,
style_attribute, style_attribute,
None, None,
&mut applicable_declarations.normal); &mut applicable_declarations.normal);
applicable_declarations.normal_shareable = relations_are_shareable(&relations);
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
stylist.push_applicable_declarations(self, stylist.push_applicable_declarations(self,
parent_bf, parent_bf,
@ -614,8 +665,14 @@ pub trait ElementMatchMethods : TElement {
applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![])); applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![]));
}); });
applicable_declarations.normal_shareable && let has_pseudos =
applicable_declarations.per_pseudo.values().all(|v| v.is_empty()) 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 /// Attempts to share a style with another node. This method is unsafe because it depends on
@ -624,6 +681,7 @@ pub trait ElementMatchMethods : TElement {
unsafe fn share_style_if_possible(&self, unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache: style_sharing_candidate_cache:
&mut StyleSharingCandidateCache, &mut StyleSharingCandidateCache,
shared_context: &SharedStyleContext,
parent: Option<Self::ConcreteNode>) parent: Option<Self::ConcreteNode>)
-> StyleSharingResult<<Self::ConcreteNode as TNode>::ConcreteRestyleDamage> { -> StyleSharingResult<<Self::ConcreteNode as TNode>::ConcreteRestyleDamage> {
if opts::get().disable_share_style_cache { if opts::get().disable_share_style_cache {
@ -633,17 +691,30 @@ pub trait ElementMatchMethods : TElement {
if self.style_attribute().is_some() { if self.style_attribute().is_some() {
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
if self.has_attr(&ns!(), &atom!("id")) { if self.has_attr(&ns!(), &atom!("id")) {
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() { let parent = match parent {
if let Some(shared_style) = self.share_style_with_candidate_if_possible(parent.clone(), candidate) { Some(parent) if parent.is_element() => parent,
_ => return StyleSharingResult::CannotShare,
};
for (i, &mut (ref mut candidate, ())) in style_sharing_candidate_cache.iter_mut().enumerate() {
let sharing_result = self.share_style_with_candidate_if_possible(parent,
shared_context,
candidate);
match sharing_result {
Ok(shared_style) => {
// Yay, cache hit. Share the style. // Yay, cache hit. Share the style.
let node = self.as_node(); let node = self.as_node();
let style = &mut node.mutate_data().unwrap().style; let style = &mut node.mutate_data().unwrap().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 = let damage =
match node.existing_style_for_restyle_damage((*style).as_ref(), None) { match node.existing_style_for_restyle_damage((*style).as_ref(), None) {
Some(ref source) => { Some(ref source) => {
@ -666,6 +737,22 @@ pub trait ElementMatchMethods : TElement {
return StyleSharingResult::StyleWasShared(i, damage, restyle_result) return StyleSharingResult::StyleWasShared(i, damage, restyle_result)
} }
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 {
// Too expensive failure, give up, we don't want another
// one of these.
CacheMiss::CommonStyleAffectingAttributes |
CacheMiss::PresHints |
CacheMiss::SiblingRules |
CacheMiss::NonCommonAttrRules => break,
_ => {}
}
}
}
} }
StyleSharingResult::CannotShare StyleSharingResult::CannotShare

View file

@ -12,6 +12,8 @@ use dom::{OpaqueNode, TNode, UnsafeNode};
use std::mem; use std::mem;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use traversal::{RestyleResult, DomTraversalContext}; use traversal::{RestyleResult, DomTraversalContext};
use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES};
use util::opts;
use workqueue::{WorkQueue, WorkUnit, WorkerProxy}; use workqueue::{WorkQueue, WorkUnit, WorkerProxy};
#[allow(dead_code)] #[allow(dead_code)]
@ -42,13 +44,28 @@ pub fn run_queue_with_custom_work_data_type<To, F, SharedContext: Sync>(
pub fn traverse_dom<N, C>(root: N, pub fn traverse_dom<N, C>(root: N,
queue_data: &C::SharedContext, queue_data: &C::SharedContext,
queue: &mut WorkQueue<C::SharedContext, WorkQueueData>) queue: &mut WorkQueue<C::SharedContext, WorkQueueData>)
where N: TNode, C: DomTraversalContext<N> { where N: TNode,
C: DomTraversalContext<N>
{
if opts::get().style_sharing_stats {
STYLE_SHARING_CACHE_HITS.store(0, Ordering::SeqCst);
STYLE_SHARING_CACHE_MISSES.store(0, Ordering::SeqCst);
}
run_queue_with_custom_work_data_type(queue, |queue| { run_queue_with_custom_work_data_type(queue, |queue| {
queue.push(WorkUnit { queue.push(WorkUnit {
fun: top_down_dom::<N, C>, fun: top_down_dom::<N, C>,
data: (Box::new(vec![root.to_unsafe()]), root.opaque()), data: (Box::new(vec![root.to_unsafe()]), root.opaque()),
}); });
}, queue_data); }, queue_data);
if opts::get().style_sharing_stats {
let hits = STYLE_SHARING_CACHE_HITS.load(Ordering::SeqCst);
let misses = STYLE_SHARING_CACHE_MISSES.load(Ordering::SeqCst);
println!("Style sharing stats:");
println!(" * Hits: {}", hits);
println!(" * Misses: {}", misses);
}
} }
/// A parallel top-down DOM traversal. /// A parallel top-down DOM traversal.
@ -102,6 +119,10 @@ fn top_down_dom<N, C>(unsafe_nodes: UnsafeNodeList,
} }
} }
// NB: In parallel traversal mode we have to purge the LRU cache in order to
// be able to access it without races.
context.local_context().style_sharing_candidate_cache.borrow_mut().clear();
for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) { for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) {
proxy.push(WorkUnit { proxy.push(WorkUnit {
fun: top_down_dom::<N, C>, fun: top_down_dom::<N, C>,

View file

@ -6,6 +6,7 @@
use element_state::*; use element_state::*;
use selector_impl::{ElementExt, TheSelectorImpl, NonTSPseudoClass, AttrValue}; use selector_impl::{ElementExt, TheSelectorImpl, NonTSPseudoClass, AttrValue};
use selectors::matching::StyleRelations;
use selectors::matching::matches_compound_selector; use selectors::matching::matches_compound_selector;
use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SimpleSelector, SelectorImpl}; use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SimpleSelector, SelectorImpl};
use selectors::{Element, MatchAttr}; use selectors::{Element, MatchAttr};
@ -348,7 +349,11 @@ impl DependencySet {
DependencySet { deps: Vec::new() } DependencySet { deps: Vec::new() }
} }
pub fn note_selector(&mut self, selector: Arc<CompoundSelector<TheSelectorImpl>>) { pub fn len(&self) -> usize {
self.deps.len()
}
pub fn note_selector(&mut self, selector: &Arc<CompoundSelector<TheSelectorImpl>>) {
let mut cur = selector; let mut cur = selector;
let mut combinator: Option<Combinator> = None; let mut combinator: Option<Combinator> = None;
loop { loop {
@ -370,7 +375,7 @@ impl DependencySet {
cur = match cur.next { cur = match cur.next {
Some((ref sel, comb)) => { Some((ref sel, comb)) => {
combinator = Some(comb); combinator = Some(comb);
sel.clone() sel
} }
None => break, None => break,
} }
@ -389,14 +394,19 @@ impl DependencySet {
-> RestyleHint -> RestyleHint
where E: ElementExt + Clone where E: ElementExt + Clone
{ {
debug!("About to calculate restyle hint for element. Deps: {}",
self.deps.len());
let state_changes = snapshot.state().map_or_else(ElementState::empty, |old_state| current_state ^ old_state); let state_changes = snapshot.state().map_or_else(ElementState::empty, |old_state| current_state ^ old_state);
let attrs_changed = snapshot.has_attrs(); let attrs_changed = snapshot.has_attrs();
let mut hint = RestyleHint::empty(); let mut hint = RestyleHint::empty();
for dep in &self.deps { for dep in &self.deps {
if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) { if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) {
let old_el: ElementWrapper<E> = ElementWrapper::new_with_snapshot(el.clone(), snapshot); let old_el: ElementWrapper<E> = ElementWrapper::new_with_snapshot(el.clone(), snapshot);
let matched_then = matches_compound_selector(&*dep.selector, &old_el, None, &mut false); let matched_then =
let matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false); matches_compound_selector(&*dep.selector, &old_el, None, &mut StyleRelations::empty());
let matches_now =
matches_compound_selector(&*dep.selector, el, None, &mut StyleRelations::empty());
if matched_then != matches_now { if matched_then != matches_now {
hint.insert(combinator_to_restyle_hint(dep.combinator)); hint.insert(combinator_to_restyle_hint(dep.combinator));
if hint.is_all() { if hint.is_all() {

View file

@ -15,7 +15,9 @@ use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement};
use selectors::Element; use selectors::Element;
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::DeclarationBlock as GenericDeclarationBlock; use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
use selectors::matching::{Rule, SelectorMap}; use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{Rule, SelectorMap, StyleRelations};
use selectors::parser::Selector;
use sink::Push; use sink::Push;
use smallvec::VecLike; use smallvec::VecLike;
use std::collections::HashMap; use std::collections::HashMap;
@ -26,7 +28,6 @@ use style_traits::viewport::ViewportConstraints;
use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet}; use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
use viewport::{MaybeNew, ViewportRuleCascade}; use viewport::{MaybeNew, ViewportRuleCascade};
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>; pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
/// This structure holds all the selectors and device characteristics /// This structure holds all the selectors and device characteristics
@ -76,6 +77,13 @@ pub struct Stylist {
/// Selector dependencies used to compute restyle hints. /// Selector dependencies used to compute restyle hints.
state_deps: DependencySet, state_deps: DependencySet,
/// Selectors in the page affecting siblings
sibling_affecting_selectors: Vec<Selector<TheSelectorImpl>>,
/// Selectors in the page matching elements with non-common style-affecting
/// attributes.
non_common_style_affecting_attributes_selectors: Vec<Selector<TheSelectorImpl>>,
} }
impl Stylist { impl Stylist {
@ -93,6 +101,10 @@ impl Stylist {
precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()), precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()),
rules_source_order: 0, rules_source_order: 0,
state_deps: DependencySet::new(), state_deps: DependencySet::new(),
// XXX remember resetting them!
sibling_affecting_selectors: vec![],
non_common_style_affecting_attributes_selectors: vec![]
}; };
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
@ -120,6 +132,9 @@ impl Stylist {
self.rules_source_order = 0; self.rules_source_order = 0;
self.state_deps.clear(); self.state_deps.clear();
self.sibling_affecting_selectors.clear();
self.non_common_style_affecting_attributes_selectors.clear();
for ref stylesheet in TheSelectorImpl::get_user_or_user_agent_stylesheets().iter() { for ref stylesheet in TheSelectorImpl::get_user_or_user_agent_stylesheets().iter() {
self.add_stylesheet(&stylesheet); self.add_stylesheet(&stylesheet);
} }
@ -178,8 +193,16 @@ impl Stylist {
append!(style_rule, normal); append!(style_rule, normal);
append!(style_rule, important); append!(style_rule, important);
rules_source_order += 1; rules_source_order += 1;
for selector in &style_rule.selectors { for selector in &style_rule.selectors {
self.state_deps.note_selector(selector.compound_selectors.clone()); self.state_deps.note_selector(&selector.compound_selectors);
if selector.affects_siblings() {
self.sibling_affecting_selectors.push(selector.clone());
}
if selector.matches_non_common_style_affecting_attribute() {
self.non_common_style_affecting_attributes_selectors.push(selector.clone());
}
} }
self.rules_source_order = rules_source_order; self.rules_source_order = rules_source_order;
@ -202,6 +225,14 @@ impl Stylist {
} }
} }
debug!("Stylist stats:");
debug!(" - Got {} sibling-affecting selectors",
self.sibling_affecting_selectors.len());
debug!(" - Got {} non-common-style-attribute-affecting selectors",
self.non_common_style_affecting_attributes_selectors.len());
debug!(" - Got {} deps for style-hint calculation",
self.state_deps.len());
TheSelectorImpl::each_precomputed_pseudo_element(|pseudo| { TheSelectorImpl::each_precomputed_pseudo_element(|pseudo| {
// TODO: Consider not doing this and just getting the rules on the // TODO: Consider not doing this and just getting the rules on the
// fly. It should be a bit slower, but we'd take rid of the // fly. It should be a bit slower, but we'd take rid of the
@ -310,11 +341,11 @@ impl Stylist {
parent_bf: Option<&BloomFilter>, parent_bf: Option<&BloomFilter>,
style_attribute: Option<&PropertyDeclarationBlock>, style_attribute: Option<&PropertyDeclarationBlock>,
pseudo_element: Option<&PseudoElement>, pseudo_element: Option<&PseudoElement>,
applicable_declarations: &mut V) applicable_declarations: &mut V) -> StyleRelations
-> bool
where E: Element<Impl=TheSelectorImpl> + where E: Element<Impl=TheSelectorImpl> +
PresentationalHintsSynthetizer, PresentationalHintsSynthetizer,
V: Push<DeclarationBlock> + VecLike<DeclarationBlock> { V: Push<DeclarationBlock> + VecLike<DeclarationBlock>
{
assert!(!self.is_device_dirty); assert!(!self.is_device_dirty);
assert!(style_attribute.is_none() || pseudo_element.is_none(), assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements"); "Style attributes do not apply to pseudo-elements");
@ -327,65 +358,82 @@ impl Stylist {
None => &self.element_map, None => &self.element_map,
}; };
let mut shareable = true; let mut relations = StyleRelations::empty();
debug!("Determining if style is shareable: pseudo: {}", pseudo_element.is_some());
// Step 1: Normal user-agent rules. // Step 1: Normal user-agent rules.
map.user_agent.normal.get_all_matching_rules(element, map.user_agent.normal.get_all_matching_rules(element,
parent_bf, parent_bf,
applicable_declarations, applicable_declarations,
&mut shareable); &mut relations);
debug!("UA normal: {:?}", relations);
// Step 2: Presentational hints. // Step 2: Presentational hints.
let length = applicable_declarations.len(); let length = applicable_declarations.len();
element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations); element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
if applicable_declarations.len() != length { if applicable_declarations.len() != length {
// Never share style for elements with preshints // Never share style for elements with preshints
shareable = false; relations |= AFFECTED_BY_PRESENTATIONAL_HINTS;
} }
debug!("preshints: {:?}", relations);
// Step 3: User and author normal rules. // Step 3: User and author normal rules.
map.user.normal.get_all_matching_rules(element, map.user.normal.get_all_matching_rules(element,
parent_bf, parent_bf,
applicable_declarations, applicable_declarations,
&mut shareable); &mut relations);
debug!("user normal: {:?}", relations);
map.author.normal.get_all_matching_rules(element, map.author.normal.get_all_matching_rules(element,
parent_bf, parent_bf,
applicable_declarations, applicable_declarations,
&mut shareable); &mut relations);
debug!("author normal: {:?}", relations);
// Step 4: Normal style attributes. // Step 4: Normal style attributes.
style_attribute.map(|sa| { if let Some(ref sa) = style_attribute {
shareable = false; relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
Push::push( Push::push(
applicable_declarations, applicable_declarations,
GenericDeclarationBlock::from_declarations(sa.normal.clone())) GenericDeclarationBlock::from_declarations(sa.normal.clone()));
}); }
debug!("style attr: {:?}", relations);
// Step 5: Author-supplied `!important` rules. // Step 5: Author-supplied `!important` rules.
map.author.important.get_all_matching_rules(element, map.author.important.get_all_matching_rules(element,
parent_bf, parent_bf,
applicable_declarations, applicable_declarations,
&mut shareable); &mut relations);
debug!("author important: {:?}", relations);
// Step 6: `!important` style attributes. // Step 6: `!important` style attributes.
style_attribute.map(|sa| { if let Some(ref sa) = style_attribute {
shareable = false;
Push::push( Push::push(
applicable_declarations, applicable_declarations,
GenericDeclarationBlock::from_declarations(sa.important.clone())) GenericDeclarationBlock::from_declarations(sa.important.clone()));
}); }
debug!("style attr important: {:?}", relations);
// Step 7: User and UA `!important` rules. // Step 7: User and UA `!important` rules.
map.user.important.get_all_matching_rules(element, map.user.important.get_all_matching_rules(element,
parent_bf, parent_bf,
applicable_declarations, applicable_declarations,
&mut shareable); &mut relations);
debug!("user important: {:?}", relations);
map.user_agent.important.get_all_matching_rules(element, map.user_agent.important.get_all_matching_rules(element,
parent_bf, parent_bf,
applicable_declarations, applicable_declarations,
&mut shareable); &mut relations);
shareable debug!("UA important: {:?}", relations);
debug!("push_applicable_declarations: shareable: {:?}", relations);
relations
} }
#[inline] #[inline]
@ -398,6 +446,67 @@ impl Stylist {
&self.animations &self.animations
} }
pub fn match_same_not_common_style_affecting_attributes_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: ElementExt
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_compound_selector;
// XXX we can probably do better, the candidate should already know what
// rules it matches.
//
// XXX Could the bloom filter help here? Should be available.
for ref selector in self.non_common_style_affecting_attributes_selectors.iter() {
let element_matches = matches_compound_selector(&selector.compound_selectors,
element,
None,
&mut StyleRelations::empty());
let candidate_matches = matches_compound_selector(&selector.compound_selectors,
candidate,
None,
&mut StyleRelations::empty());
if element_matches != candidate_matches {
return false;
}
}
true
}
pub fn match_same_sibling_affecting_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: ElementExt
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_compound_selector;
// XXX we can probably do better, the candidate should already know what
// rules it matches.
//
// XXX The bloom filter would help here, and should be available.
for ref selector in self.sibling_affecting_selectors.iter() {
let element_matches = matches_compound_selector(&selector.compound_selectors,
element,
None,
&mut StyleRelations::empty());
let candidate_matches = matches_compound_selector(&selector.compound_selectors,
candidate,
None,
&mut StyleRelations::empty());
if element_matches != candidate_matches {
debug!("match_same_sibling_affecting_rules: Failure due to {:?}",
selector.compound_selectors);
return false;
}
}
true
}
pub fn compute_restyle_hint<E>(&self, element: &E, pub fn compute_restyle_hint<E>(&self, element: &E,
snapshot: &E::Snapshot, snapshot: &E::Snapshot,
// NB: We need to pass current_state as an argument because // NB: We need to pass current_state as an argument because

View file

@ -35,5 +35,6 @@ pub fn traverse_dom<N, C>(root: N,
if context.should_process(root) { if context.should_process(root) {
doit::<N, C>(&context, root); doit::<N, C>(&context, root);
} }
// Clear the local LRU cache since we store stateful elements inside.
context.local_context().style_sharing_candidate_cache.borrow_mut().clear();
} }

View file

@ -5,11 +5,13 @@
//! Traversing the DOM tree; the bloom filter. //! Traversing the DOM tree; the bloom filter.
use animation; use animation;
use context::{SharedStyleContext, StyleContext}; use context::{LocalStyleContext, SharedStyleContext, StyleContext};
use dom::{OpaqueNode, TElement, TNode, TRestyleDamage, UnsafeNode}; use dom::{OpaqueNode, TElement, TNode, TRestyleDamage, UnsafeNode};
use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult}; use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult};
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::StyleRelations;
use std::cell::RefCell; use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use tid::tid; use tid::tid;
use util::opts; use util::opts;
use values::HasViewportPercentage; use values::HasViewportPercentage;
@ -27,6 +29,10 @@ pub enum RestyleResult {
Stop, Stop,
} }
/// Style sharing candidate cache stats. These are only used when
/// `-Z style-sharing-stats` is given.
pub static STYLE_SHARING_CACHE_HITS: AtomicUsize = ATOMIC_USIZE_INIT;
pub static STYLE_SHARING_CACHE_MISSES: AtomicUsize = ATOMIC_USIZE_INIT;
/// A pair of the bloom filter used for css selector matching, and the node to /// A pair of the bloom filter used for css selector matching, and the node to
/// which it applies. This is used to efficiently do `Descendant` selector /// which it applies. This is used to efficiently do `Descendant` selector
@ -191,6 +197,19 @@ pub trait DomTraversalContext<N: TNode> {
} }
} }
} }
fn local_context(&self) -> &LocalStyleContext;
}
/// Determines the amount of relations where we're going to share style.
#[inline]
pub fn relations_are_shareable(relations: &StyleRelations) -> bool {
use selectors::matching::*;
!relations.intersects(AFFECTED_BY_ID_SELECTOR |
AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_STATE |
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR |
AFFECTED_BY_STYLE_ATTRIBUTE |
AFFECTED_BY_PRESENTATIONAL_HINTS)
} }
pub fn ensure_node_styled<'a, N, C>(node: N, pub fn ensure_node_styled<'a, N, C>(node: N,
@ -301,6 +320,7 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
Some(element) => { Some(element) => {
unsafe { unsafe {
element.share_style_if_possible(style_sharing_candidate_cache, element.share_style_if_possible(style_sharing_candidate_cache,
context.shared_context(),
parent_opt.clone()) parent_opt.clone())
} }
}, },
@ -312,20 +332,30 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
StyleSharingResult::CannotShare => { StyleSharingResult::CannotShare => {
let mut applicable_declarations = ApplicableDeclarations::new(); let mut applicable_declarations = ApplicableDeclarations::new();
let relations;
let shareable_element = match node.as_element() { let shareable_element = match node.as_element() {
Some(element) => { Some(element) => {
if opts::get().style_sharing_stats {
STYLE_SHARING_CACHE_MISSES.fetch_add(1, Ordering::Relaxed);
}
// Perform the CSS selector matching. // Perform the CSS selector matching.
let stylist = &context.shared_context().stylist; let stylist = &context.shared_context().stylist;
if element.match_element(&**stylist, relations = element.match_element(&**stylist,
Some(&*bf), Some(&*bf),
&mut applicable_declarations) { &mut applicable_declarations);
debug!("Result of selector matching: {:?}", relations);
if relations_are_shareable(&relations) {
Some(element) Some(element)
} else { } else {
None None
} }
}, },
None => { None => {
relations = StyleRelations::empty();
if node.has_changed() { if node.has_changed() {
node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow()) node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow())
} }
@ -342,11 +372,14 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
// Add ourselves to the LRU cache. // Add ourselves to the LRU cache.
if let Some(element) = shareable_element { if let Some(element) = shareable_element {
style_sharing_candidate_cache.insert_if_possible::<'ln, N>(&element); style_sharing_candidate_cache.insert_if_possible(&element, relations);
} }
} }
StyleSharingResult::StyleWasShared(index, damage, restyle_result_cascade) => { StyleSharingResult::StyleWasShared(index, damage, cached_restyle_result) => {
restyle_result = restyle_result_cascade; restyle_result = cached_restyle_result;
if opts::get().style_sharing_stats {
STYLE_SHARING_CACHE_HITS.fetch_add(1, Ordering::Relaxed);
}
style_sharing_candidate_cache.touch(index); style_sharing_candidate_cache.touch(index);
node.set_restyle_damage(damage); node.set_restyle_damage(damage);
} }

View file

@ -172,6 +172,9 @@ pub struct Opts {
/// Whether Style Sharing Cache is used /// Whether Style Sharing Cache is used
pub disable_share_style_cache: bool, pub disable_share_style_cache: bool,
/// Whether to show in stdout style sharing cache stats after a restyle.
pub style_sharing_stats: bool,
/// Translate mouse input into touch events. /// Translate mouse input into touch events.
pub convert_mouse_to_touch: bool, pub convert_mouse_to_touch: bool,
@ -275,6 +278,9 @@ pub struct DebugOptions {
/// Disable the style sharing cache. /// Disable the style sharing cache.
pub disable_share_style_cache: bool, pub disable_share_style_cache: bool,
/// Whether to show in stdout style sharing cache stats after a restyle.
pub style_sharing_stats: bool,
/// Translate mouse input into touch events. /// Translate mouse input into touch events.
pub convert_mouse_to_touch: bool, pub convert_mouse_to_touch: bool,
@ -331,6 +337,7 @@ impl DebugOptions {
"paint-flashing" => debug_options.paint_flashing = true, "paint-flashing" => debug_options.paint_flashing = true,
"trace-layout" => debug_options.trace_layout = true, "trace-layout" => debug_options.trace_layout = true,
"disable-share-style-cache" => debug_options.disable_share_style_cache = true, "disable-share-style-cache" => debug_options.disable_share_style_cache = true,
"style-sharing-stats" => debug_options.style_sharing_stats = true,
"convert-mouse-to-touch" => debug_options.convert_mouse_to_touch = true, "convert-mouse-to-touch" => debug_options.convert_mouse_to_touch = true,
"replace-surrogates" => debug_options.replace_surrogates = true, "replace-surrogates" => debug_options.replace_surrogates = true,
"gc-profile" => debug_options.gc_profile = true, "gc-profile" => debug_options.gc_profile = true,
@ -512,6 +519,7 @@ pub fn default_opts() -> Opts {
profile_script_events: false, profile_script_events: false,
profile_heartbeats: false, profile_heartbeats: false,
disable_share_style_cache: false, disable_share_style_cache: false,
style_sharing_stats: false,
convert_mouse_to_touch: false, convert_mouse_to_touch: false,
exit_after_load: false, exit_after_load: false,
no_native_titlebar: false, no_native_titlebar: false,
@ -817,6 +825,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
dump_layer_tree: debug_options.dump_layer_tree, dump_layer_tree: debug_options.dump_layer_tree,
relayout_event: debug_options.relayout_event, relayout_event: debug_options.relayout_event,
disable_share_style_cache: debug_options.disable_share_style_cache, disable_share_style_cache: debug_options.disable_share_style_cache,
style_sharing_stats: debug_options.style_sharing_stats,
convert_mouse_to_touch: debug_options.convert_mouse_to_touch, convert_mouse_to_touch: debug_options.convert_mouse_to_touch,
exit_after_load: opt_match.opt_present("x"), exit_after_load: opt_match.opt_present("x"),
no_native_titlebar: do_not_use_native_titlebar, no_native_titlebar: do_not_use_native_titlebar,

10
ports/cef/Cargo.lock generated
View file

@ -1070,7 +1070,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1", "script_layout_interface 0.0.1",
"script_traits 0.0.1", "script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1783,7 +1783,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1", "script_layout_interface 0.0.1",
"script_traits 0.0.1", "script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1820,7 +1820,7 @@ dependencies = [
"profile_traits 0.0.1", "profile_traits 0.0.1",
"range 0.0.1", "range 0.0.1",
"script_traits 0.0.1", "script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1", "style 0.0.1",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1859,7 +1859,7 @@ dependencies = [
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.8.2" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2122,7 +2122,7 @@ dependencies = [
"plugins 0.0.1", "plugins 0.0.1",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -11,7 +11,7 @@ dependencies = [
"libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1", "style 0.0.1",
"style_traits 0.0.1", "style_traits 0.0.1",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -170,7 +170,7 @@ dependencies = [
"gecko_bindings 0.0.1", "gecko_bindings 0.0.1",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -311,7 +311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.8.2" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -367,7 +367,7 @@ dependencies = [
"ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"style_traits 0.0.1", "style_traits 0.0.1",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -19,7 +19,7 @@ lazy_static = "0.2"
libc = "0.2" libc = "0.2"
log = {version = "0.3.5", features = ["release_max_level_info"]} log = {version = "0.3.5", features = ["release_max_level_info"]}
num_cpus = "0.2.2" num_cpus = "0.2.2"
selectors = "0.8" selectors = "0.9"
style = {path = "../../components/style", features = ["gecko"]} style = {path = "../../components/style", features = ["gecko"]}
style_traits = {path = "../../components/style_traits"} style_traits = {path = "../../components/style_traits"}
url = "1.2" url = "1.2"

View file

@ -14,5 +14,5 @@ cfg-if = "0.1.0"
gecko_bindings = {version = "0.0.1", path = "../gecko_bindings"} gecko_bindings = {version = "0.0.1", path = "../gecko_bindings"}
heapsize = "0.3.5" heapsize = "0.3.5"
libc = "0.2" libc = "0.2"
selectors = "0.8" selectors = "0.9"
serde = "0.8" serde = "0.8"

View file

@ -4,7 +4,7 @@
use context::StandaloneStyleContext; use context::StandaloneStyleContext;
use std::mem; use std::mem;
use style::context::SharedStyleContext; use style::context::{LocalStyleContext, SharedStyleContext, StyleContext};
use style::dom::OpaqueNode; use style::dom::OpaqueNode;
use style::traversal::RestyleResult; use style::traversal::RestyleResult;
use style::traversal::{DomTraversalContext, recalc_style_at}; use style::traversal::{DomTraversalContext, recalc_style_at};
@ -42,4 +42,8 @@ impl<'lc, 'ln> DomTraversalContext<GeckoNode<'ln>> for RecalcStyleOnly<'lc> {
/// We don't use the post-order traversal for anything. /// We don't use the post-order traversal for anything.
fn needs_postorder_traversal(&self) -> bool { false } fn needs_postorder_traversal(&self) -> bool { false }
fn local_context(&self) -> &LocalStyleContext {
self.context.local_context()
}
} }

View file

@ -459,6 +459,12 @@ impl<'le> TElement for GeckoElement<'le> {
} }
} }
impl<'le> PartialEq for GeckoElement<'le> {
fn eq(&self, other: &Self) -> bool {
self.element == other.element
}
}
impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> { impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> {
fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, _hints: &mut V) fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, _hints: &mut V)
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>> where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>

View file

@ -14,7 +14,7 @@ app_units = "0.3"
cssparser = {version = "0.5.7", features = ["heap_size"]} cssparser = {version = "0.5.7", features = ["heap_size"]}
euclid = "0.9" euclid = "0.9"
rustc-serialize = "0.3" rustc-serialize = "0.3"
selectors = {version = "0.8", features = ["heap_size"]} selectors = {version = "0.9", features = ["heap_size"]}
string_cache = {version = "0.2.23", features = ["heap_size"]} string_cache = {version = "0.2.23", features = ["heap_size"]}
style = {path = "../../../components/style"} style = {path = "../../../components/style"}
style_traits = {path = "../../../components/style_traits"} style_traits = {path = "../../../components/style_traits"}

View file

@ -1,3 +0,0 @@
[flexbox_columns.htm]
type: reftest
expected: FAIL

View file

@ -29,7 +29,11 @@
* we're not getting as much test coverage there as we'd like. When we * we're not getting as much test coverage there as we'd like. When we
* implement attribute-based restyle hints, we can stop dirtying the subtree * implement attribute-based restyle hints, we can stop dirtying the subtree
* on attribute modifications, and these tests will start to be more useful. * on attribute modifications, and these tests will start to be more useful.
*
* Also, note that we use window.onload in order to prevent the restyle
* hints to interact with any content appended notifications.
*/ */
window.onload = function() {
window.dummy = 0; window.dummy = 0;
var $ = document.getElementById.bind(document); var $ = document.getElementById.bind(document);
function syncRestyle() { window.dummy += $("fs2").offsetTop; } function syncRestyle() { window.dummy += $("fs2").offsetTop; }
@ -41,5 +45,6 @@
$('cb').checked = true; $('cb').checked = true;
syncRestyle(); syncRestyle();
$('fs2').disabled = true; $('fs2').disabled = true;
}
</script> </script>
</body> </body>