style: Share CascadeData instances across ShadowRoots

This should be both a memory and speed win for pages using a lot of
Shadow DOM.

In order to make the cache properly work we need to start keying media query
results on the actual StyleSheetContents, as that's what we share on Gecko, but
that should all be fine.

Differential Revision: https://phabricator.services.mozilla.com/D107266
This commit is contained in:
Oriol Brufau 2023-05-16 09:51:46 +02:00
parent 11153c63fa
commit 060d74ba3b
11 changed files with 175 additions and 178 deletions

View file

@ -11,7 +11,7 @@ use crate::element_state::{DocumentState, ElementState};
#[cfg(feature = "gecko")]
use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion};
use crate::invalidation::element::invalidation_map::InvalidationMap;
use crate::invalidation::media_queries::{EffectiveMediaQueryResults, ToMediaListKey};
use crate::invalidation::media_queries::EffectiveMediaQueryResults;
use crate::invalidation::stylesheets::RuleChangeKind;
use crate::media_queries::Device;
use crate::properties::{self, CascadeMode, ComputedValues};
@ -73,7 +73,7 @@ trait CascadeDataCacheEntry : Sized {
old_entry: &Self,
) -> Result<Arc<Self>, FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static;
S: StylesheetInDocument + PartialEq + 'static;
/// Measures heap memory usage.
#[cfg(feature = "gecko")]
fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes);
@ -108,7 +108,7 @@ where
old_entry: &Entry,
) -> Result<Option<Arc<Entry>>, FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static,
S: StylesheetInDocument + PartialEq + 'static,
{
debug!("StyleSheetCache::lookup({})", self.len());
@ -122,9 +122,23 @@ where
}
for entry in &self.entries {
if entry.cascade_data().effective_media_query_results == key {
return Ok(Some(entry.clone()));
if std::ptr::eq(&**entry, old_entry) {
// Avoid reusing our old entry (this can happen if we get
// invalidated due to CSSOM mutations and our old stylesheet
// contents were already unique, for example). This old entry
// will be pruned from the cache with take_unused() afterwards.
continue;
}
if entry.cascade_data().effective_media_query_results != key {
continue;
}
if log_enabled!(log::Level::Debug) {
debug!("cache hit for:");
for sheet in collection.sheets() {
debug!(" > {:?}", sheet);
}
}
return Ok(Some(entry.clone()));
}
debug!("> Picking the slow path");
@ -205,7 +219,7 @@ impl CascadeDataCacheEntry for UserAgentCascadeData {
_old: &Self,
) -> Result<Arc<Self>, FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static
S: StylesheetInDocument + PartialEq + 'static
{
// TODO: Maybe we should support incremental rebuilds, though they seem
// uncommon and rebuild() doesn't deal with
@ -319,11 +333,13 @@ impl DocumentCascadeData {
guards: &StylesheetGuards,
) -> Result<(), FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static,
S: StylesheetInDocument + PartialEq + 'static,
{
// First do UA sheets.
{
let origin_flusher = flusher.flush_origin(Origin::UserAgent);
// Dirty check is just a minor optimization (no need to grab the
// lock if nothing has changed).
if origin_flusher.dirty() {
let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap();
let new_data = ua_cache.lookup(
@ -436,6 +452,9 @@ pub struct Stylist {
/// The list of stylesheets.
stylesheets: StylistStylesheetSet,
/// A cache of CascadeDatas for AuthorStylesheetSets (i.e., shadow DOM).
author_data_cache: CascadeDataCache<CascadeData>,
/// If true, the quirks-mode stylesheet is applied.
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "defined in selectors")]
quirks_mode: QuirksMode,
@ -487,6 +506,7 @@ impl Stylist {
device,
quirks_mode,
stylesheets: StylistStylesheetSet::new(),
author_data_cache: CascadeDataCache::new(),
cascade_data: Default::default(),
author_styles_enabled: AuthorStylesEnabled::Yes,
rule_tree: RuleTree::new(),
@ -512,6 +532,31 @@ impl Stylist {
self.cascade_data.iter_origins()
}
/// Does what the name says, to prevent author_data_cache to grow without
/// bound.
pub fn remove_unique_author_data_cache_entries(&mut self) {
self.author_data_cache.take_unused();
}
/// Rebuilds (if needed) the CascadeData given a sheet collection.
pub fn rebuild_author_data<S>(
&mut self,
old_data: &CascadeData,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
) -> Result<Option<Arc<CascadeData>>, FailedAllocationError>
where
S: StylesheetInDocument + PartialEq + 'static,
{
self.author_data_cache.lookup(
&self.device,
self.quirks_mode,
collection,
guard,
old_data,
)
}
/// Iterate over the extra data in origin order.
#[inline]
pub fn iter_extra_data_origins(&self) -> ExtraStyleDataIterator {
@ -1420,6 +1465,7 @@ impl Stylist {
#[cfg(feature = "gecko")]
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.cascade_data.add_size_of(ops, sizes);
self.author_data_cache.add_size_of(ops, sizes);
sizes.mRuleTree += self.rule_tree.size_of(ops);
// We may measure other fields in the future if DMD says it's worth it.
@ -1433,7 +1479,7 @@ impl Stylist {
/// This struct holds data which users of Stylist may want to extract
/// from stylesheets which can be done at the same time as updating.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct ExtraStyleData {
/// A list of effective font-face rules and their origin.
@ -1453,11 +1499,6 @@ pub struct ExtraStyleData {
pub pages: Vec<Arc<Locked<PageRule>>>,
}
#[cfg(feature = "gecko")]
unsafe impl Sync for ExtraStyleData {}
#[cfg(feature = "gecko")]
unsafe impl Send for ExtraStyleData {}
#[cfg(feature = "gecko")]
impl ExtraStyleData {
/// Add the given @font-face rule.
@ -1698,7 +1739,7 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> {
}
/// A set of rules for element and pseudo-elements.
#[derive(Debug, Default, MallocSizeOf)]
#[derive(Clone, Debug, Default, MallocSizeOf)]
struct GenericElementAndPseudoRules<Map> {
/// Rules from stylesheets at this `CascadeData`'s origin.
element_map: Map,
@ -1777,7 +1818,7 @@ impl PartElementAndPseudoRules {
///
/// FIXME(emilio): Consider renaming and splitting in `CascadeData` and
/// `InvalidationData`? That'd make `clear_cascade_data()` clearer.
#[derive(Debug, MallocSizeOf)]
#[derive(Debug, Clone, MallocSizeOf)]
pub struct CascadeData {
/// The data coming from normal style rules that apply to elements at this
/// cascade level.
@ -1887,7 +1928,7 @@ impl CascadeData {
guard: &SharedRwLockReadGuard,
) -> Result<(), FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static,
S: StylesheetInDocument + PartialEq + 'static,
{
if !collection.dirty() {
return Ok(());
@ -1990,14 +2031,14 @@ impl CascadeData {
guard: &SharedRwLockReadGuard,
results: &mut EffectiveMediaQueryResults,
) where
S: StylesheetInDocument + ToMediaListKey + 'static,
S: StylesheetInDocument + 'static,
{
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) {
return;
}
debug!(" + {:?}", stylesheet);
results.saw_effective(stylesheet);
results.saw_effective(stylesheet.contents());
for rule in stylesheet.effective_rules(device, guard) {
match *rule {
@ -2027,16 +2068,17 @@ impl CascadeData {
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
) -> Result<(), FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + 'static,
S: StylesheetInDocument + 'static,
{
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) {
return Ok(());
}
let origin = stylesheet.origin(guard);
let contents = stylesheet.contents();
let origin = contents.origin;
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results.saw_effective(stylesheet);
self.effective_media_query_results.saw_effective(contents);
}
for rule in stylesheet.effective_rules(device, guard) {
@ -2211,13 +2253,13 @@ impl CascadeData {
quirks_mode: QuirksMode,
) -> bool
where
S: StylesheetInDocument + ToMediaListKey + 'static,
S: StylesheetInDocument + 'static,
{
use crate::invalidation::media_queries::PotentiallyEffectiveMediaRules;
let effective_now = stylesheet.is_effective_for_device(device, guard);
let effective_then = self.effective_media_query_results.was_effective(stylesheet);
let effective_then = self.effective_media_query_results.was_effective(stylesheet.contents());
if effective_now != effective_then {
debug!(
@ -2252,9 +2294,10 @@ impl CascadeData {
},
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
let effective_now = import_rule
.stylesheet
.is_effective_for_device(&device, guard);
let effective_now = match import_rule.stylesheet.media(guard) {
Some(m) => m.evaluate(device, quirks_mode),
None => true,
};
let effective_then = self
.effective_media_query_results
.was_effective(import_rule);
@ -2326,8 +2369,33 @@ impl CascadeData {
self.selectors_for_cache_revalidation.clear();
self.effective_media_query_results.clear();
}
}
impl CascadeDataCacheEntry for CascadeData {
fn cascade_data(&self) -> &CascadeData {
self
}
fn rebuild<S>(
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old: &Self,
) -> Result<Arc<Self>, FailedAllocationError>
where
S: StylesheetInDocument + PartialEq + 'static
{
debug_assert!(collection.dirty(), "We surely need to do something?");
// If we're doing a full rebuild anyways, don't bother cloning the data.
let mut updatable_entry = match collection.data_validity() {
DataValidity::Valid | DataValidity::CascadeInvalid => old.clone(),
DataValidity::FullyInvalid => Self::new(),
};
updatable_entry.rebuild(device, quirks_mode, collection, guard)?;
Ok(Arc::new(updatable_entry))
}
/// Measures heap usage.
#[cfg(feature = "gecko")]
fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.normal_rules.add_size_of(ops, sizes);