style: Also cache the class list in the CurrentElementInfo.

This patch also removes all notion of style sharing from matching.rs, which is
nice.
This commit is contained in:
Emilio Cobos Álvarez 2017-05-29 22:54:17 +02:00
parent abcc9b301c
commit 03952a0c27
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
5 changed files with 160 additions and 114 deletions

View file

@ -8,7 +8,7 @@
use Atom;
use bit_vec::BitVec;
use cache::{LRUCache, LRUCacheMutIterator};
use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext};
use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles};
use dom::{TElement, SendElement};
use matching::{ChildCascadeRequirement, MatchMethods};
@ -17,6 +17,7 @@ use selectors::bloom::BloomFilter;
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use sink::ForgetfulSink;
use smallvec::SmallVec;
use std::ops::Deref;
use stylist::Stylist;
mod checks;
@ -50,6 +51,23 @@ pub struct CachedStyleSharingData {
}
impl CachedStyleSharingData {
/// Trivially construct an empty `CachedStyleSharingData` with nothing on
/// it.
pub fn new() -> Self {
Self {
class_list: None,
revalidation_match_results: None,
}
}
/// Move the cached data to a new instance, and return it.
pub fn take(&mut self) -> Self {
Self {
class_list: self.class_list.take(),
revalidation_match_results: self.revalidation_match_results.take(),
}
}
/// Get or compute the class-list associated with this element.
pub fn class_list<E>(&mut self, element: E) -> &[Atom]
where E: TElement,
@ -122,6 +140,97 @@ impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<
}
}
/// An element we want to test against the style sharing cache.
pub struct StyleSharingTarget<E: TElement> {
element: E,
cache: CachedStyleSharingData,
}
impl<E: TElement> Deref for StyleSharingTarget<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.element
}
}
impl<E: TElement> StyleSharingTarget<E> {
/// Trivially construct a new StyleSharingTarget to test against the cache.
pub fn new(element: E) -> Self {
Self {
element: element,
cache: CachedStyleSharingData::new(),
}
}
fn class_list(&mut self) -> &[Atom] {
self.cache.class_list(self.element)
}
fn revalidation_match_results(
&mut self,
stylist: &Stylist,
bloom: &BloomFilter,
selector_flags_map: &mut SelectorFlagsMap<E>
) -> &BitVec {
// It's important to set the selector flags. Otherwise, if we succeed in
// sharing the style, we may not set the slow selector flags for the
// right elements (which may not necessarily be |element|), causing
// missed restyles after future DOM mutations.
//
// Gecko's test_bug534804.html exercises this. A minimal testcase is:
// <style> #e:empty + span { ... } </style>
// <span id="e">
// <span></span>
// </span>
// <span></span>
//
// The style sharing cache will get a hit for the second span. When the
// child span is subsequently removed from the DOM, missing selector
// flags would cause us to miss the restyle on the second span.
let element = self.element;
let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| {
element.apply_selector_flags(selector_flags_map, el, flags);
};
self.cache.revalidation_match_results(self.element,
stylist,
bloom,
&mut set_selector_flags)
}
/// Attempts to share a style with another node.
pub fn share_style_if_possible(
mut self,
context: &mut StyleContext<E>,
data: &mut ElementData)
-> StyleSharingResult
{
use std::mem;
let shared_context = &context.shared;
let selector_flags_map = &mut context.thread_local.selector_flags;
let bloom_filter = context.thread_local.bloom_filter.filter();
let result = context.thread_local
.style_sharing_candidate_cache
.share_style_if_possible(shared_context,
selector_flags_map,
bloom_filter,
&mut self,
data);
// FIXME(emilio): Do this in a cleaner way.
mem::swap(&mut self.cache,
&mut context
.thread_local
.current_element_info.as_mut().unwrap()
.cached_style_sharing_data);
result
}
}
/// A cache miss result.
#[derive(Clone, Debug)]
pub enum CacheMiss {
@ -198,7 +307,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
element: &E,
style: &ComputedValues,
relations: StyleRelations,
revalidation_match_results: Option<BitVec>) {
cache: CachedStyleSharingData) {
let parent = match element.parent_element() {
Some(element) => element,
None => {
@ -242,10 +351,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
self.cache.insert(StyleSharingCandidate {
element: unsafe { SendElement::new(*element) },
cache: CachedStyleSharingData {
class_list: None,
revalidation_match_results: revalidation_match_results,
}
cache: cache,
});
}
@ -260,43 +366,39 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
}
/// Attempts to share a style with another node.
///
/// This method is unsafe because it depends on the
/// `style_sharing_candidate_cache` having only live nodes in it, and we
/// have no way to guarantee that at the type system level yet.
pub unsafe fn share_style_if_possible(
fn share_style_if_possible(
&mut self,
shared_context: &SharedStyleContext,
current_element_info: &mut CurrentElementInfo,
selector_flags_map: &mut SelectorFlagsMap<E>,
bloom_filter: &BloomFilter,
element: E,
target: &mut StyleSharingTarget<E>,
data: &mut ElementData
) -> StyleSharingResult {
if shared_context.options.disable_style_sharing_cache {
debug!("{:?} Cannot share style: style sharing cache disabled",
element);
target.element);
return StyleSharingResult::CannotShare
}
if element.parent_element().is_none() {
debug!("{:?} Cannot share style: element has no parent", element);
if target.parent_element().is_none() {
debug!("{:?} Cannot share style: element has no parent",
target.element);
return StyleSharingResult::CannotShare
}
if element.is_native_anonymous() {
debug!("{:?} Cannot share style: NAC", element);
if target.is_native_anonymous() {
debug!("{:?} Cannot share style: NAC", target.element);
return StyleSharingResult::CannotShare;
}
if element.style_attribute().is_some() {
if target.style_attribute().is_some() {
debug!("{:?} Cannot share style: element has style attribute",
element);
target.element);
return StyleSharingResult::CannotShare
}
if element.get_id().is_some() {
debug!("{:?} Cannot share style: element has id", element);
if target.get_id().is_some() {
debug!("{:?} Cannot share style: element has id", target.element);
return StyleSharingResult::CannotShare
}
@ -304,11 +406,10 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
for (i, candidate) in self.iter_mut().enumerate() {
let sharing_result =
Self::test_candidate(
element,
target,
candidate,
&shared_context,
bloom_filter,
current_element_info,
selector_flags_map
);
@ -321,7 +422,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
let old_values = data.get_styles_mut()
.and_then(|s| s.primary.values.take());
let child_cascade_requirement =
element.accumulate_damage(
target.accumulate_damage(
&shared_context,
data.get_restyle_mut(),
old_values.as_ref().map(|v| &**v),
@ -360,7 +461,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
}
}
debug!("{:?} Cannot share style: {} cache entries", element,
debug!("{:?} Cannot share style: {} cache entries", target.element,
self.cache.num_entries());
if should_clear_cache {
@ -370,11 +471,10 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
StyleSharingResult::CannotShare
}
fn test_candidate(element: E,
fn test_candidate(target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>,
shared: &SharedStyleContext,
bloom: &BloomFilter,
info: &mut CurrentElementInfo,
selector_flags_map: &mut SelectorFlagsMap<E>)
-> Result<ComputedStyle, CacheMiss> {
macro_rules! miss {
@ -386,66 +486,66 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
// Check that we have the same parent, or at least the same pointer
// identity for parent computed style. The latter check allows us to
// share style between cousins if the parents shared style.
let parent = element.parent_element();
let parent = target.parent_element();
let candidate_parent = candidate.element.parent_element();
if parent != candidate_parent &&
!checks::same_computed_values(parent, candidate_parent) {
miss!(Parent)
}
if element.is_native_anonymous() {
if target.is_native_anonymous() {
debug_assert!(!candidate.element.is_native_anonymous(),
"Why inserting NAC into the cache?");
miss!(NativeAnonymousContent)
}
if *element.get_local_name() != *candidate.element.get_local_name() {
if *target.get_local_name() != *candidate.element.get_local_name() {
miss!(LocalName)
}
if *element.get_namespace() != *candidate.element.get_namespace() {
if *target.get_namespace() != *candidate.element.get_namespace() {
miss!(Namespace)
}
if element.is_link() != candidate.element.is_link() {
if target.is_link() != candidate.element.is_link() {
miss!(Link)
}
if element.matches_user_and_author_rules() !=
if target.matches_user_and_author_rules() !=
candidate.element.matches_user_and_author_rules() {
miss!(UserAndAuthorRules)
}
if !checks::have_same_state_ignoring_visitedness(element, candidate) {
if !checks::have_same_state_ignoring_visitedness(target.element, candidate) {
miss!(State)
}
if element.get_id() != candidate.element.get_id() {
if target.get_id() != candidate.element.get_id() {
miss!(IdAttr)
}
if element.style_attribute().is_some() {
if target.style_attribute().is_some() {
miss!(StyleAttr)
}
if !checks::have_same_class(element, candidate) {
if !checks::have_same_class(target, candidate) {
miss!(Class)
}
if checks::has_presentational_hints(element) {
if checks::has_presentational_hints(target.element) {
miss!(PresHints)
}
if !checks::revalidate(element, candidate, shared, bloom, info,
if !checks::revalidate(target, candidate, shared, bloom,
selector_flags_map) {
miss!(Revalidation)
}
let data = candidate.element.borrow_data().unwrap();
debug_assert!(element.has_current_styles(&data));
debug_assert!(target.has_current_styles(&data));
debug!("Sharing style between {:?} and {:?}",
element, candidate.element);
target.element, candidate.element);
Ok(data.styles().primary.clone())
}
}