diff --git a/components/selectors/bloom.rs b/components/selectors/bloom.rs index 0e8290fc8c92..2b96e4f51f87 100644 --- a/components/selectors/bloom.rs +++ b/components/selectors/bloom.rs @@ -2,9 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//! Simple counting bloom filters. +//! Counting and non-counting Bloom filters tuned for use as ancestor filters +//! for selector matching. use fnv::FnvHasher; +use std::fmt::{self, Debug}; use std::hash::{Hash, Hasher}; // The top 8 bits of the 32-bit hash value are not used by the bloom filter. @@ -15,9 +17,17 @@ const KEY_SIZE: usize = 12; const ARRAY_SIZE: usize = 1 << KEY_SIZE; const KEY_MASK: u32 = (1 << KEY_SIZE) - 1; -/// A counting Bloom filter with 8-bit counters. For now we assume -/// that having two hash functions is enough, but we may revisit that -/// decision later. +/// A counting Bloom filter with 8-bit counters. +pub type BloomFilter = CountingBloomFilter; + +/// A non-counting Bloom filter. +/// +/// Effectively a counting Bloom filter with 1-bit counters. +pub type NonCountingBloomFilter = CountingBloomFilter; + +/// A counting Bloom filter with parameterized storage to handle +/// counters of different sizes. For now we assume that having two hash +/// functions is enough, but we may revisit that decision later. /// /// The filter uses an array with 2**KeySize entries. /// @@ -61,58 +71,30 @@ const KEY_MASK: u32 = (1 << KEY_SIZE) - 1; /// Similarly, using a KeySize of 10 would lead to a 4% false /// positive rate for N == 100 and to quite bad false positive /// rates for larger N. -pub struct BloomFilter { - counters: [u8; ARRAY_SIZE], -} - -impl Clone for BloomFilter { - #[inline] - fn clone(&self) -> BloomFilter { - BloomFilter { - counters: self.counters, - } - } +#[derive(Clone)] +pub struct CountingBloomFilter where S: BloomStorage { + storage: S, } -impl BloomFilter { +impl CountingBloomFilter where S: BloomStorage { /// Creates a new bloom filter. #[inline] - pub fn new() -> BloomFilter { - BloomFilter { - counters: [0; ARRAY_SIZE], + pub fn new() -> Self { + CountingBloomFilter { + storage: Default::default(), } } - #[inline] - fn first_slot(&self, hash: u32) -> &u8 { - &self.counters[hash1(hash) as usize] - } - - #[inline] - fn first_mut_slot(&mut self, hash: u32) -> &mut u8 { - &mut self.counters[hash1(hash) as usize] - } - - #[inline] - fn second_slot(&self, hash: u32) -> &u8 { - &self.counters[hash2(hash) as usize] - } - - #[inline] - fn second_mut_slot(&mut self, hash: u32) -> &mut u8 { - &mut self.counters[hash2(hash) as usize] - } - #[inline] pub fn clear(&mut self) { - self.counters = [0; ARRAY_SIZE] + self.storage = Default::default(); } // Slow linear accessor to make sure the bloom filter is zeroed. This should // never be used in release builds. #[cfg(debug_assertions)] pub fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) + self.storage.is_zeroed() } #[cfg(not(debug_assertions))] @@ -122,18 +104,8 @@ impl BloomFilter { #[inline] pub fn insert_hash(&mut self, hash: u32) { - { - let slot1 = self.first_mut_slot(hash); - if !full(slot1) { - *slot1 += 1 - } - } - { - let slot2 = self.second_mut_slot(hash); - if !full(slot2) { - *slot2 += 1 - } - } + self.storage.adjust_first_slot(hash, true); + self.storage.adjust_second_slot(hash, true); } /// Inserts an item into the bloom filter. @@ -144,18 +116,8 @@ impl BloomFilter { #[inline] pub fn remove_hash(&mut self, hash: u32) { - { - let slot1 = self.first_mut_slot(hash); - if !full(slot1) { - *slot1 -= 1 - } - } - { - let slot2 = self.second_mut_slot(hash); - if !full(slot2) { - *slot2 -= 1 - } - } + self.storage.adjust_first_slot(hash, false); + self.storage.adjust_second_slot(hash, false); } /// Removes an item from the bloom filter. @@ -166,7 +128,8 @@ impl BloomFilter { #[inline] pub fn might_contain_hash(&self, hash: u32) -> bool { - *self.first_slot(hash) != 0 && *self.second_slot(hash) != 0 + !self.storage.first_slot_is_empty(hash) && + !self.storage.second_slot_is_empty(hash) } /// Check whether the filter might contain an item. This can @@ -179,9 +142,147 @@ impl BloomFilter { } } -#[inline] -fn full(slot: &u8) -> bool { - *slot == 0xff +impl Debug for CountingBloomFilter where S: BloomStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut slots_used = 0; + for i in 0..ARRAY_SIZE { + if !self.storage.slot_is_empty(i) { + slots_used += 1; + } + } + write!(f, "BloomFilter({}/{})", slots_used, ARRAY_SIZE) + } +} + +pub trait BloomStorage : Clone + Default { + fn slot_is_empty(&self, index: usize) -> bool; + fn adjust_slot(&mut self, index: usize, increment: bool); + fn is_zeroed(&self) -> bool; + + #[inline] + fn first_slot_is_empty(&self, hash: u32) -> bool { + self.slot_is_empty(Self::first_slot_index(hash)) + } + + #[inline] + fn second_slot_is_empty(&self, hash: u32) -> bool { + self.slot_is_empty(Self::second_slot_index(hash)) + } + + #[inline] + fn adjust_first_slot(&mut self, hash: u32, increment: bool) { + self.adjust_slot(Self::first_slot_index(hash), increment) + } + + #[inline] + fn adjust_second_slot(&mut self, hash: u32, increment: bool) { + self.adjust_slot(Self::second_slot_index(hash), increment) + } + + #[inline] + fn first_slot_index(hash: u32) -> usize { + hash1(hash) as usize + } + + #[inline] + fn second_slot_index(hash: u32) -> usize { + hash2(hash) as usize + } +} + +/// Storage class for a CountingBloomFilter that has 8-bit counters. +pub struct BloomStorageU8 { + counters: [u8; ARRAY_SIZE], +} + +impl BloomStorage for BloomStorageU8 { + #[inline] + fn adjust_slot(&mut self, index: usize, increment: bool) { + let slot = &mut self.counters[index]; + if *slot != 0xff { // full + if increment { + *slot += 1; + } else { + *slot -= 1; + } + } + } + + #[inline] + fn slot_is_empty(&self, index: usize) -> bool { + self.counters[index] == 0 + } + + #[inline] + fn is_zeroed(&self) -> bool { + self.counters.iter().all(|x| *x == 0) + } +} + +impl Default for BloomStorageU8 { + fn default() -> Self { + BloomStorageU8 { + counters: [0; ARRAY_SIZE], + } + } +} + +impl Clone for BloomStorageU8 { + fn clone(&self) -> Self { + BloomStorageU8 { + counters: self.counters, + } + } +} + +/// Storage class for a CountingBloomFilter that has 1-bit counters. +pub struct BloomStorageBool { + counters: [u8; ARRAY_SIZE / 8], +} + +impl BloomStorage for BloomStorageBool { + #[inline] + fn adjust_slot(&mut self, index: usize, increment: bool) { + let bit = 1 << (index % 8); + let byte = &mut self.counters[index / 8]; + + // Since we have only one bit for storage, decrementing it + // should never do anything. Assert against an accidental + // decrementing of a bit that was never set. + assert!(increment || (*byte & bit) != 0, + "should not decrement if slot is already false"); + + if increment { + *byte |= bit; + } + } + + #[inline] + fn slot_is_empty(&self, index: usize) -> bool { + let bit = 1 << (index % 8); + (self.counters[index / 8] & bit) == 0 + } + + #[inline] + fn is_zeroed(&self) -> bool { + self.counters.iter().all(|x| *x == 0) + } +} + +impl Default for BloomStorageBool { + fn default() -> Self { + BloomStorageBool { + counters: [0; ARRAY_SIZE / 8], + } + } +} + +impl Clone for BloomStorageBool { + fn clone(&self) -> Self { + BloomStorageBool { + counters: self.counters, + } + } } fn hash(elem: &T) -> u32 { @@ -203,8 +304,16 @@ fn hash2(hash: u32) -> u32 { #[test] fn create_and_insert_some_stuff() { + use std::mem::transmute; + let mut bf = BloomFilter::new(); + // Statically assert that ARRAY_SIZE is a multiple of 8, which + // BloomStorageBool relies on. + unsafe { + transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]); + } + for i in 0_usize .. 1000 { bf.insert(&i); } diff --git a/components/style/animation.rs b/components/style/animation.rs index 69b94edb5808..c1a49fca7a12 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -533,7 +533,7 @@ pub fn maybe_start_animations(context: &SharedStyleContext, continue } - if let Some(ref anim) = context.stylist.animations().get(name) { + if let Some(ref anim) = context.stylist.get_animation(name) { debug!("maybe_start_animations: animation {} found", name); // If this animation doesn't have any keyframe, we can just continue @@ -637,7 +637,7 @@ pub fn update_style_for_animation(context: &SharedStyleContext, KeyframesRunningState::Paused(progress) => started_at + duration * progress, }; - let animation = match context.stylist.animations().get(name) { + let animation = match context.stylist.get_animation(name) { None => { warn!("update_style_for_animation: Animation {:?} not found", name); return; diff --git a/components/style/context.rs b/components/style/context.rs index 7ae849d07285..07c5bb073719 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -388,16 +388,16 @@ impl TraversalStatistics { D: DomTraversal, { let threshold = traversal.shared_context().options.style_statistics_threshold; + let stylist = traversal.shared_context().stylist; self.is_parallel = Some(traversal.is_parallel()); self.is_large = Some(self.elements_traversed as usize >= threshold); self.traversal_time_ms = (time::precise_time_s() - start) * 1000.0; - self.selectors = traversal.shared_context().stylist.num_selectors() as u32; - self.revalidation_selectors = traversal.shared_context().stylist.num_revalidation_selectors() as u32; - self.dependency_selectors = - traversal.shared_context().stylist.invalidation_map().len() as u32; - self.declarations = traversal.shared_context().stylist.num_declarations() as u32; - self.stylist_rebuilds = traversal.shared_context().stylist.num_rebuilds() as u32; + self.selectors = stylist.num_selectors() as u32; + self.revalidation_selectors = stylist.num_revalidation_selectors() as u32; + self.dependency_selectors = stylist.num_invalidations() as u32; + self.declarations = stylist.num_declarations() as u32; + self.stylist_rebuilds = stylist.num_rebuilds() as u32; } /// Returns whether this traversal is 'large' in order to avoid console spam diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index e5c6c63bfcee..81f966a5df32 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -109,7 +109,7 @@ impl SelectorMapEntry for Dependency { /// The same, but for state selectors, which can track more exactly what state /// do they track. -#[derive(Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct StateDependency { /// The other dependency fields. @@ -132,6 +132,7 @@ impl SelectorMapEntry for StateDependency { /// In particular, we want to lookup as few things as possible to get the fewer /// selectors the better, so this looks up by id, class, or looks at the list of /// state/other attribute affecting selectors. +#[derive(Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct InvalidationMap { /// A map from a given class name to all the selectors with that class diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs index 055e3e6f3ac8..46b1d53bc1d1 100644 --- a/components/style/invalidation/element/invalidator.rs +++ b/components/style/invalidation/element/invalidator.rs @@ -213,9 +213,9 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E> invalidates_self: false, }; - collector.collect_dependencies_in_invalidation_map( - shared_context.stylist.invalidation_map(), - ); + shared_context.stylist.each_invalidation_map(|invalidation_map| { + collector.collect_dependencies_in_invalidation_map(invalidation_map); + }); // TODO(emilio): Consider storing dependencies from the UA sheet in // a different map. If we do that, we can skip the stuff on the @@ -223,9 +223,9 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E> // just at that map. let _cut_off_inheritance = self.element.each_xbl_stylist(|stylist| { - collector.collect_dependencies_in_invalidation_map( - stylist.invalidation_map(), - ); + stylist.each_invalidation_map(|invalidation_map| { + collector.collect_dependencies_in_invalidation_map(invalidation_map); + }); }); collector.invalidates_self diff --git a/components/style/stylist.rs b/components/style/stylist.rs index b13fd7376cde..3000cd29e278 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -25,7 +25,7 @@ use rule_tree::{CascadeLevel, RuleTree, StyleSource}; use selector_map::{PrecomputedHashMap, SelectorMap, SelectorMapEntry}; use selector_parser::{SelectorImpl, PerPseudoElementMap, PseudoElement}; use selectors::attr::NamespaceConstraint; -use selectors::bloom::BloomFilter; +use selectors::bloom::{BloomFilter, NonCountingBloomFilter}; use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; use selectors::matching::VisitedHandlingMode; use selectors::parser::{AncestorHashes, Combinator, Component, Selector}; @@ -97,9 +97,6 @@ pub struct Stylist { /// The rule tree, that stores the results of selector matching. rule_tree: RuleTree, - /// A map with all the animations indexed by name. - animations: PrecomputedHashMap, - /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new /// computed values on the fly on layout. @@ -111,52 +108,6 @@ pub struct Stylist { /// style rule appears in a stylesheet, needed to sort them by source order. rules_source_order: u32, - /// The invalidation map for this document. - invalidation_map: InvalidationMap, - - /// The attribute local names that appear in attribute selectors. Used - /// to avoid taking element snapshots when an irrelevant attribute changes. - /// (We don't bother storing the namespace, since namespaced attributes - /// are rare.) - /// - /// FIXME(heycam): This doesn't really need to be a counting Bloom filter. - #[cfg_attr(feature = "servo", ignore_heap_size_of = "just an array")] - attribute_dependencies: BloomFilter, - - /// Whether `"style"` appears in an attribute selector. This is not common, - /// and by tracking this explicitly, we can avoid taking an element snapshot - /// in the common case of style=""` changing due to modifying - /// `element.style`. (We could track this in `attribute_dependencies`, like - /// all other attributes, but we should probably not risk incorrectly - /// returning `true` for `"style"` just due to a hash collision.) - style_attribute_dependency: bool, - - /// The element state bits that are relied on by selectors. Like - /// `attribute_dependencies`, this is used to avoid taking element snapshots - /// when an irrelevant element state bit changes. - state_dependencies: ElementState, - - /// The ids that appear in the rightmost complex selector of selectors (and - /// hence in our selector maps). Used to determine when sharing styles is - /// safe: we disallow style sharing for elements whose id matches this - /// filter, and hence might be in one of our selector maps. - /// - /// FIXME(bz): This doesn't really need to be a counting Blooom filter. - #[cfg_attr(feature = "servo", ignore_heap_size_of = "just an array")] - mapped_ids: BloomFilter, - - /// Selectors that require explicit cache revalidation (i.e. which depend - /// on state that is not otherwise visible to the cache, like attributes or - /// tree-structural state like child index and pseudos). - #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] - selectors_for_cache_revalidation: SelectorMap, - - /// The total number of selectors. - num_selectors: usize, - - /// The total number of declarations. - num_declarations: usize, - /// The total number of times the stylist has been rebuilt. num_rebuilds: usize, } @@ -239,18 +190,9 @@ impl Stylist { effective_media_query_results: EffectiveMediaQueryResults::new(), cascade_data: CascadeData::new(), - animations: Default::default(), precomputed_pseudo_element_decls: PerPseudoElementMap::default(), rules_source_order: 0, rule_tree: RuleTree::new(), - invalidation_map: InvalidationMap::new(), - attribute_dependencies: BloomFilter::new(), - style_attribute_dependency: false, - state_dependencies: ElementState::empty(), - mapped_ids: BloomFilter::new(), - selectors_for_cache_revalidation: SelectorMap::new(), - num_selectors: 0, - num_declarations: 0, num_rebuilds: 0, } @@ -259,12 +201,12 @@ impl Stylist { /// Returns the number of selectors. pub fn num_selectors(&self) -> usize { - self.num_selectors + self.cascade_data.iter_origins().map(|d| d.num_selectors).sum() } /// Returns the number of declarations. pub fn num_declarations(&self) -> usize { - self.num_declarations + self.cascade_data.iter_origins().map(|d| d.num_declarations).sum() } /// Returns the number of times the stylist has been rebuilt. @@ -274,12 +216,27 @@ impl Stylist { /// Returns the number of revalidation_selectors. pub fn num_revalidation_selectors(&self) -> usize { - self.selectors_for_cache_revalidation.len() + self.cascade_data.iter_origins() + .map(|d| d.selectors_for_cache_revalidation.len()).sum() + } + + /// Returns the number of entries in invalidation maps. + pub fn num_invalidations(&self) -> usize { + self.cascade_data.iter_origins() + .map(|d| d.invalidation_map.len()).sum() } - /// Gets a reference to the invalidation map. - pub fn invalidation_map(&self) -> &InvalidationMap { - &self.invalidation_map + /// Invokes `f` with the `InvalidationMap` for each origin. + /// + /// NOTE(heycam) This might be better as an `iter_invalidation_maps`, once + /// we have `impl trait` and can return that easily without bothering to + /// create a whole new iterator type. + pub fn each_invalidation_map(&self, mut f: F) + where F: FnMut(&InvalidationMap) + { + for origin_cascade_data in self.cascade_data.iter_origins() { + f(&origin_cascade_data.invalidation_map) + } } /// Clear the stylist's state, effectively resetting it to more or less @@ -307,18 +264,9 @@ impl Stylist { self.is_device_dirty = true; // preserve current quirks_mode value self.cascade_data.clear(); - self.animations.clear(); // Or set to Default::default()? self.precomputed_pseudo_element_decls.clear(); self.rules_source_order = 0; // We want to keep rule_tree around across stylist rebuilds. - self.invalidation_map.clear(); - self.attribute_dependencies.clear(); - self.style_attribute_dependency = false; - self.state_dependencies = ElementState::empty(); - self.mapped_ids.clear(); - self.selectors_for_cache_revalidation = SelectorMap::new(); - self.num_selectors = 0; - self.num_declarations = 0; // preserve num_rebuilds value, since it should stay across // clear()/rebuild() cycles. } @@ -459,9 +407,10 @@ impl Stylist { match *rule { CssRule::Style(ref locked) => { let style_rule = locked.read_with(&guard); - self.num_declarations += style_rule.block.read_with(&guard).len(); + origin_cascade_data.num_declarations += + style_rule.block.read_with(&guard).len(); for selector in &style_rule.selectors.0 { - self.num_selectors += 1; + origin_cascade_data.num_selectors += 1; let map = match selector.pseudo_element() { Some(pseudo) if pseudo.is_precomputed() => { @@ -506,20 +455,22 @@ impl Stylist { map.insert(rule, self.quirks_mode); - self.invalidation_map.note_selector(selector, self.quirks_mode); + origin_cascade_data + .invalidation_map + .note_selector(selector, self.quirks_mode); let mut visitor = StylistSelectorVisitor { needs_revalidation: false, passed_rightmost_selector: false, - attribute_dependencies: &mut self.attribute_dependencies, - style_attribute_dependency: &mut self.style_attribute_dependency, - state_dependencies: &mut self.state_dependencies, - mapped_ids: &mut self.mapped_ids, + attribute_dependencies: &mut origin_cascade_data.attribute_dependencies, + style_attribute_dependency: &mut origin_cascade_data.style_attribute_dependency, + state_dependencies: &mut origin_cascade_data.state_dependencies, + mapped_ids: &mut origin_cascade_data.mapped_ids, }; selector.visit(&mut visitor); if visitor.needs_revalidation { - self.selectors_for_cache_revalidation.insert( + origin_cascade_data.selectors_for_cache_revalidation.insert( RevalidationSelectorAndHashes::new(selector.clone(), hashes), self.quirks_mode); } @@ -542,14 +493,15 @@ impl Stylist { debug!("Found valid keyframes rule: {:?}", *keyframes_rule); // Don't let a prefixed keyframes animation override a non-prefixed one. - let needs_insertion = keyframes_rule.vendor_prefix.is_none() || - self.animations.get(keyframes_rule.name.as_atom()).map_or(true, |rule| - rule.vendor_prefix.is_some()); + let needs_insertion = + keyframes_rule.vendor_prefix.is_none() || + origin_cascade_data.animations.get(keyframes_rule.name.as_atom()) + .map_or(true, |rule| rule.vendor_prefix.is_some()); if needs_insertion { let animation = KeyframesAnimation::from_keyframes( &keyframes_rule.keyframes, keyframes_rule.vendor_prefix.clone(), guard); debug!("Found valid keyframe animation: {:?}", animation); - self.animations.insert(keyframes_rule.name.as_atom().clone(), animation); + origin_cascade_data.animations.insert(keyframes_rule.name.as_atom().clone(), animation); } } #[cfg(feature = "gecko")] @@ -576,9 +528,16 @@ impl Stylist { // we rebuild. true } else if *local_name == local_name!("style") { - self.style_attribute_dependency + self.cascade_data + .iter_origins() + .any(|d| d.style_attribute_dependency) } else { - self.attribute_dependencies.might_contain_hash(local_name.get_hash()) + self.cascade_data + .iter_origins() + .any(|d| { + d.attribute_dependencies + .might_contain_hash(local_name.get_hash()) + }) } } @@ -590,14 +549,16 @@ impl Stylist { // rules rely on until we rebuild. true } else { - self.state_dependencies.intersects(state) + self.has_state_dependency(state) } } /// Returns whether the given ElementState bit is relied upon by a selector /// of some rule in the stylist. pub fn has_state_dependency(&self, state: ElementState) -> bool { - self.state_dependencies.intersects(state) + self.cascade_data + .iter_origins() + .any(|d| d.state_dependencies.intersects(state)) } /// Computes the style for a given "precomputed" pseudo-element, taking the @@ -1317,7 +1278,9 @@ impl Stylist { /// of our rule maps. #[inline] pub fn may_have_rules_for_id(&self, id: &Atom) -> bool { - self.mapped_ids.might_contain_hash(id.get_hash()) + self.cascade_data + .iter_origins() + .any(|d| d.mapped_ids.might_contain_hash(id.get_hash())) } /// Return whether the device is dirty, that is, whether the screen size or @@ -1327,10 +1290,13 @@ impl Stylist { self.is_device_dirty } - /// Returns the map of registered `@keyframes` animations. + /// Returns the registered `@keyframes` animation for the specified name. #[inline] - pub fn animations(&self) -> &PrecomputedHashMap { - &self.animations + pub fn get_animation(&self, name: &Atom) -> Option<&KeyframesAnimation> { + self.cascade_data + .iter_origins() + .filter_map(|d| d.animations.get(name)) + .next() } /// Computes the match results of a given element against the set of @@ -1354,17 +1320,19 @@ impl Stylist { // the lookups, which means that the bitvecs are comparable. We verify // this in the caller by asserting that the bitvecs are same-length. let mut results = BitVec::new(); - self.selectors_for_cache_revalidation.lookup( - *element, self.quirks_mode, &mut |selector_and_hashes| { - results.push(matches_selector(&selector_and_hashes.selector, - selector_and_hashes.selector_offset, - Some(&selector_and_hashes.hashes), - element, - &mut matching_context, - flags_setter)); - true - } - ); + for origin_cascade_data in self.cascade_data.iter_origins() { + origin_cascade_data.selectors_for_cache_revalidation.lookup( + *element, self.quirks_mode, &mut |selector_and_hashes| { + results.push(matches_selector(&selector_and_hashes.selector, + selector_and_hashes.selector_offset, + Some(&selector_and_hashes.hashes), + element, + &mut matching_context, + flags_setter)); + true + } + ); + } results } @@ -1470,9 +1438,9 @@ struct StylistSelectorVisitor<'a> { passed_rightmost_selector: bool, /// The filter with all the id's getting referenced from rightmost /// selectors. - mapped_ids: &'a mut BloomFilter, + mapped_ids: &'a mut NonCountingBloomFilter, /// The filter with the local names of attributes there are selectors for. - attribute_dependencies: &'a mut BloomFilter, + attribute_dependencies: &'a mut NonCountingBloomFilter, /// Whether there's any attribute selector for the [style] attribute. style_attribute_dependency: &'a mut bool, /// All the states selectors in the page reference. @@ -1635,6 +1603,11 @@ impl CascadeData { } } +/// Iterator over `PerOriginCascadeData`, from highest level (user) to lowest +/// (user agent). +/// +/// We rely on this specific order for correctly looking up animations +/// (prioritizing rules at higher cascade levels), among other things. struct CascadeDataIter<'a> { cascade_data: &'a CascadeData, cur: usize, @@ -1645,9 +1618,9 @@ impl<'a> Iterator for CascadeDataIter<'a> { fn next(&mut self) -> Option<&'a PerOriginCascadeData> { let result = match self.cur { - 0 => &self.cascade_data.user_agent, + 0 => &self.cascade_data.user, 1 => &self.cascade_data.author, - 2 => &self.cascade_data.user, + 2 => &self.cascade_data.user_agent, _ => return None, }; self.cur += 1; @@ -1666,6 +1639,52 @@ struct PerOriginCascadeData { /// Rules from stylesheets at this `CascadeData`'s origin that correspond /// to a given pseudo-element. pseudos_map: PerPseudoElementMap>, + + /// A map with all the animations at this `CascadeData`'s origin, indexed + /// by name. + animations: PrecomputedHashMap, + + /// The invalidation map for the rules at this origin. + invalidation_map: InvalidationMap, + + /// The attribute local names that appear in attribute selectors. Used + /// to avoid taking element snapshots when an irrelevant attribute changes. + /// (We don't bother storing the namespace, since namespaced attributes + /// are rare.) + #[cfg_attr(feature = "servo", ignore_heap_size_of = "just an array")] + attribute_dependencies: NonCountingBloomFilter, + + /// Whether `"style"` appears in an attribute selector. This is not common, + /// and by tracking this explicitly, we can avoid taking an element snapshot + /// in the common case of style=""` changing due to modifying + /// `element.style`. (We could track this in `attribute_dependencies`, like + /// all other attributes, but we should probably not risk incorrectly + /// returning `true` for `"style"` just due to a hash collision.) + style_attribute_dependency: bool, + + /// The element state bits that are relied on by selectors. Like + /// `attribute_dependencies`, this is used to avoid taking element snapshots + /// when an irrelevant element state bit changes. + state_dependencies: ElementState, + + /// The ids that appear in the rightmost complex selector of selectors (and + /// hence in our selector maps). Used to determine when sharing styles is + /// safe: we disallow style sharing for elements whose id matches this + /// filter, and hence might be in one of our selector maps. + #[cfg_attr(feature = "servo", ignore_heap_size_of = "just an array")] + mapped_ids: NonCountingBloomFilter, + + /// Selectors that require explicit cache revalidation (i.e. which depend + /// on state that is not otherwise visible to the cache, like attributes or + /// tree-structural state like child index and pseudos). + #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] + selectors_for_cache_revalidation: SelectorMap, + + /// The total number of selectors. + num_selectors: usize, + + /// The total number of declarations. + num_declarations: usize, } impl PerOriginCascadeData { @@ -1673,6 +1692,15 @@ impl PerOriginCascadeData { Self { element_map: SelectorMap::new(), pseudos_map: PerPseudoElementMap::default(), + animations: Default::default(), + invalidation_map: InvalidationMap::new(), + attribute_dependencies: NonCountingBloomFilter::new(), + style_attribute_dependency: false, + state_dependencies: ElementState::empty(), + mapped_ids: NonCountingBloomFilter::new(), + selectors_for_cache_revalidation: SelectorMap::new(), + num_selectors: 0, + num_declarations: 0, } } @@ -1685,7 +1713,17 @@ impl PerOriginCascadeData { } fn clear(&mut self) { - *self = Self::new(); + self.element_map = SelectorMap::new(); + self.pseudos_map = Default::default(); + self.animations = Default::default(); + self.invalidation_map.clear(); + self.attribute_dependencies.clear(); + self.style_attribute_dependency = false; + self.state_dependencies = ElementState::empty(); + self.mapped_ids.clear(); + self.selectors_for_cache_revalidation = SelectorMap::new(); + self.num_selectors = 0; + self.num_declarations = 0; } fn has_rules_for_pseudo(&self, pseudo: &PseudoElement) -> bool { @@ -1761,8 +1799,8 @@ impl Rule { /// A function to be able to test the revalidation stuff. pub fn needs_revalidation_for_testing(s: &Selector) -> bool { - let mut attribute_dependencies = BloomFilter::new(); - let mut mapped_ids = BloomFilter::new(); + let mut attribute_dependencies = NonCountingBloomFilter::new(); + let mut mapped_ids = NonCountingBloomFilter::new(); let mut style_attribute_dependency = false; let mut state_dependencies = ElementState::empty(); let mut visitor = StylistSelectorVisitor { diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 0f66aefe6df7..ae656c6022c3 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -3297,7 +3297,7 @@ pub extern "C" fn Servo_StyleSet_GetKeyframesForName(raw_data: RawServoStyleSetB let data = PerDocumentStyleData::from_ffi(raw_data).borrow(); let name = unsafe { Atom::from(name.as_ref().unwrap().as_str_unchecked()) }; - let animation = match data.stylist.animations().get(&name) { + let animation = match data.stylist.get_animation(&name) { Some(animation) => animation, None => return false, };