diff --git a/Cargo.lock b/Cargo.lock index 877f67e4dc4..97999fcbb15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2733,6 +2733,7 @@ dependencies = [ "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 10ed72aaea3..6ea87409078 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -25,6 +25,7 @@ testing = [] app_units = "0.4" atomic_refcell = "0.1" bitflags = "0.7" +bit-vec = "0.4.3" byteorder = "1.0" cfg-if = "0.1.0" cssparser = "0.12.1" diff --git a/components/style/context.rs b/components/style/context.rs index 583b5bda187..17a49ad6040 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -7,6 +7,7 @@ use animation::{Animation, PropertyAnimation}; use app_units::Au; +use bit_vec::BitVec; use bloom::StyleBloom; use data::ElementData; use dom::{OpaqueNode, TNode, TElement, SendElement}; @@ -111,6 +112,9 @@ pub struct CurrentElementInfo { element: OpaqueNode, /// Whether the element is being styled for the first time. is_initial_style: bool, + /// Lazy cache of the result of matching the current element against the + /// revalidation selectors. + pub revalidation_match_results: Option, /// A Vec of possibly expired animations. Used only by Servo. #[allow(dead_code)] pub possibly_expired_animations: Vec, @@ -317,6 +321,7 @@ impl ThreadLocalStyleContext { self.current_element_info = Some(CurrentElementInfo { element: element.as_node().opaque(), is_initial_style: !data.has_styles(), + revalidation_match_results: None, possibly_expired_animations: Vec::new(), }); } diff --git a/components/style/lib.rs b/components/style/lib.rs index 171b8081fc2..41d03644acf 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -39,6 +39,7 @@ extern crate app_units; extern crate atomic_refcell; +extern crate bit_vec; #[macro_use] extern crate bitflags; #[allow(unused_extern_crates)] extern crate byteorder; diff --git a/components/style/matching.rs b/components/style/matching.rs index 571a8135276..7375f2f3a2a 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -10,9 +10,10 @@ use Atom; use animation::{self, Animation, PropertyAnimation}; use atomic_refcell::AtomicRefMut; +use bit_vec::BitVec; use cache::{LRUCache, LRUCacheMutIterator}; use cascade_info::CascadeInfo; -use context::{SequentialTask, SharedStyleContext, StyleContext}; +use context::{CurrentElementInfo, SequentialTask, SharedStyleContext, StyleContext}; use data::{ComputedStyle, ElementData, ElementStyles, RestyleData}; use dom::{AnimationRules, SendElement, TElement, TNode}; use font_metrics::FontMetricsProvider; @@ -53,6 +54,8 @@ struct StyleSharingCandidate { element: SendElement, /// The cached class names. class_attributes: Option>, + /// The cached result of matching this entry against the revalidation selectors. + revalidation_match_results: Option, } impl PartialEq> for StyleSharingCandidate { @@ -103,7 +106,9 @@ pub enum CacheMiss { fn element_matches_candidate(element: &E, candidate: &mut StyleSharingCandidate, candidate_element: &E, - shared_context: &SharedStyleContext) + shared: &SharedStyleContext, + bloom: &BloomFilter, + info: &mut CurrentElementInfo) -> Result { macro_rules! miss { ($miss: ident) => { @@ -151,7 +156,7 @@ fn element_matches_candidate(element: &E, miss!(PresHints) } - if !revalidate(element, candidate_element, shared_context) { + if !revalidate(element, candidate, candidate_element, shared, bloom, info) { miss!(Revalidation) } @@ -194,9 +199,37 @@ fn have_same_class(element: &E, #[inline] fn revalidate(element: &E, - candidate: &E, - ctx: &SharedStyleContext) -> bool { - ctx.stylist.revalidate_entry_for_cache(element, candidate) + candidate: &mut StyleSharingCandidate, + candidate_element: &E, + shared: &SharedStyleContext, + bloom: &BloomFilter, + info: &mut CurrentElementInfo) + -> bool { + // NB: We could avoid matching ancestor selectors entirely (rather than + // just depending on the bloom filter), at the expense of some complexity. + // Gecko bug 1354965 tracks this. + // + // We could also be even more careful about only matching the minimal number + // of revalidation selectors until we find a mismatch. Gecko bug 1355668 + // tracks this. + // + // These potential optimizations may not be worth the complexity. + let stylist = &shared.stylist; + + if info.revalidation_match_results.is_none() { + info.revalidation_match_results = + Some(stylist.match_revalidation_selectors(element, bloom)); + } + + if candidate.revalidation_match_results.is_none() { + candidate.revalidation_match_results = + Some(stylist.match_revalidation_selectors(candidate_element, bloom)); + } + + let for_element = info.revalidation_match_results.as_ref().unwrap(); + let for_candidate = candidate.revalidation_match_results.as_ref().unwrap(); + debug_assert!(for_element.len() == for_candidate.len()); + for_element == for_candidate } static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 8; @@ -219,7 +252,8 @@ impl StyleSharingCandidateCache { pub fn insert_if_possible(&mut self, element: &E, style: &Arc, - relations: StyleRelations) { + relations: StyleRelations, + revalidation_match_results: Option) { let parent = match element.parent_element() { Some(element) => element, None => { @@ -252,6 +286,7 @@ impl StyleSharingCandidateCache { self.cache.insert(StyleSharingCandidate { element: unsafe { SendElement::new(*element) }, class_attributes: None, + revalidation_match_results: revalidation_match_results, }); } @@ -632,11 +667,14 @@ trait PrivateMatchMethods: TElement { } fn share_style_with_candidate_if_possible(&self, - shared_context: &SharedStyleContext, - candidate: &mut StyleSharingCandidate) + candidate: &mut StyleSharingCandidate, + shared: &SharedStyleContext, + bloom: &BloomFilter, + info: &mut CurrentElementInfo) -> Result { let candidate_element = *candidate.element; - element_matches_candidate(self, candidate, &candidate_element, shared_context) + element_matches_candidate(self, candidate, &candidate_element, + shared, bloom, info) } } @@ -699,11 +737,23 @@ pub trait MatchMethods : TElement { // If the style is shareable, add it to the LRU cache. if sharing == StyleSharingBehavior::Allow && relations_are_shareable(&primary_relations) { + // If we previously tried to match this element against the cache, + // the revalidation match results will already be cached. Otherwise + // we'll have None, and compute them later on-demand. + // + // If we do have the results, grab them here to satisfy the borrow + // checker. + let revalidation_match_results = context.thread_local + .current_element_info + .as_mut().unwrap() + .revalidation_match_results + .take(); context.thread_local .style_sharing_candidate_cache .insert_if_possible(self, data.styles().primary.values(), - primary_relations); + primary_relations, + revalidation_match_results); } } @@ -951,9 +1001,7 @@ pub trait MatchMethods : TElement { /// live nodes in it, and we have no way to guarantee that at the type /// system level yet. unsafe fn share_style_if_possible(&self, - style_sharing_candidate_cache: - &mut StyleSharingCandidateCache, - shared_context: &SharedStyleContext, + context: &mut StyleContext, data: &mut AtomicRefMut) -> StyleSharingResult { if is_share_style_cache_disabled() { @@ -972,11 +1020,17 @@ pub trait MatchMethods : TElement { return StyleSharingResult::CannotShare } + let cache = &mut context.thread_local.style_sharing_candidate_cache; + let current_element_info = + &mut context.thread_local.current_element_info.as_mut().unwrap(); + let bloom = context.thread_local.bloom_filter.filter(); let mut should_clear_cache = false; - for (i, candidate) in style_sharing_candidate_cache.iter_mut().enumerate() { + for (i, candidate) in cache.iter_mut().enumerate() { let sharing_result = - self.share_style_with_candidate_if_possible(shared_context, - candidate); + self.share_style_with_candidate_if_possible(candidate, + &context.shared, + bloom, + current_element_info); match sharing_result { Ok(shared_style) => { // Yay, cache hit. Share the style. @@ -986,7 +1040,8 @@ pub trait MatchMethods : TElement { let old_values = data.get_styles_mut() .and_then(|s| s.primary.values.take()); if let Some(old) = old_values { - self.accumulate_damage(shared_context, data.restyle_mut(), &old, + self.accumulate_damage(&context.shared, + data.restyle_mut(), &old, shared_style.values(), None); } @@ -1021,7 +1076,7 @@ pub trait MatchMethods : TElement { } } if should_clear_cache { - style_sharing_candidate_cache.clear(); + cache.clear(); } StyleSharingResult::CannotShare diff --git a/components/style/stylist.rs b/components/style/stylist.rs index f108d75fd76..e0c8af2ba8b 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -7,6 +7,7 @@ #![deny(missing_docs)] use {Atom, LocalName}; +use bit_vec::BitVec; use data::ComputedStyle; use dom::{AnimationRules, PresentationalHintsSynthetizer, TElement}; use error_reporting::StdoutErrorReporter; @@ -765,41 +766,30 @@ impl Stylist { self.rule_tree.root() } - /// Returns whether two elements match the same set of revalidation- - /// requiring rules. - /// - /// This is also for the style sharing candidate cache. - pub fn revalidate_entry_for_cache(&self, - element: &E, - candidate: &E) -> bool + /// Computes the match results of a given element against the set of + /// revalidation selectors. + pub fn match_revalidation_selectors(&self, + element: &E, + bloom: &BloomFilter) + -> BitVec where E: TElement, { use selectors::matching::StyleRelations; use selectors::matching::matches_complex_selector; - // TODO(emilio): we can probably do better, the candidate should already - // know what rules it matches. - // - // TODO(emilio): Use the bloom filter, since they contain the element's - // ancestor chain and it's correct for the candidate too. - for ref selector in self.selectors_for_cache_revalidation.iter() { - let element_matches = - matches_complex_selector(&selector.complex_selector, element, - None, &mut StyleRelations::empty(), - &mut |_, _| {}); - let candidate_matches = - matches_complex_selector(&selector.complex_selector, candidate, - None, &mut StyleRelations::empty(), - &mut |_, _| {}); + let len = self.selectors_for_cache_revalidation.len(); + let mut results = BitVec::from_elem(len, false); - if element_matches != candidate_matches { - debug!("match_same_sibling_affecting_rules: Failure due to {:?}", - selector.complex_selector); - return false; - } + for (i, ref selector) in self.selectors_for_cache_revalidation + .iter().enumerate() { + results.set(i, matches_complex_selector(&selector.complex_selector, + element, + Some(bloom), + &mut StyleRelations::empty(), + &mut |_, _| {})); } - true + results } /// Given an element, and a snapshot that represents a previous state of the diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 67828e6a7ec..d0910de2c6d 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -680,15 +680,13 @@ fn compute_style(_traversal: &D, use matching::StyleSharingResult::*; context.thread_local.statistics.elements_styled += 1; - let shared_context = context.shared; let kind = data.restyle_kind(); // First, try the style sharing cache. If we get a match we can skip the rest // of the work. if let MatchAndCascade = kind { let sharing_result = unsafe { - let cache = &mut context.thread_local.style_sharing_candidate_cache; - element.share_style_if_possible(cache, shared_context, &mut data) + element.share_style_if_possible(context, &mut data) }; if let StyleWasShared(index) = sharing_result { context.thread_local.statistics.styles_shared += 1;