style: Make the cascade data cache generic

We're going to use it both for UA sheets and for author styles in Shadow
DOM.

Differential Revision: https://phabricator.services.mozilla.com/D107265
This commit is contained in:
Oriol Brufau 2023-05-16 09:47:54 +02:00
parent b38517757f
commit 11153c63fa
2 changed files with 158 additions and 78 deletions

View file

@ -184,7 +184,9 @@ pub struct SheetCollectionFlusher<'a, S>
where where
S: StylesheetInDocument + PartialEq + 'static, S: StylesheetInDocument + PartialEq + 'static,
{ {
iter: slice::IterMut<'a, StylesheetSetEntry<S>>, // TODO: This can be made an iterator again once
// https://github.com/rust-lang/rust/pull/82771 lands on stable.
entries: &'a mut [StylesheetSetEntry<S>],
validity: DataValidity, validity: DataValidity,
dirty: bool, dirty: bool,
} }
@ -204,32 +206,42 @@ where
pub fn data_validity(&self) -> DataValidity { pub fn data_validity(&self) -> DataValidity {
self.validity self.validity
} }
/// Returns an iterator over the remaining list of sheets to consume.
pub fn sheets<'b>(&'b self) -> impl Iterator<Item = &'b S> {
self.entries.iter().map(|entry| &entry.sheet)
}
} }
impl<'a, S> Iterator for SheetCollectionFlusher<'a, S> impl<'a, S> SheetCollectionFlusher<'a, S>
where where
S: StylesheetInDocument + PartialEq + 'static, S: StylesheetInDocument + PartialEq + 'static,
{ {
type Item = (&'a S, SheetRebuildKind); /// Iterates over all sheets and values that we have to invalidate.
///
fn next(&mut self) -> Option<Self::Item> { /// TODO(emilio): This would be nicer as an iterator but we can't do that
loop { /// until https://github.com/rust-lang/rust/pull/82771 stabilizes.
let potential_sheet = self.iter.next()?; ///
/// Since we don't have a good use-case for partial iteration, this does the
/// trick for now.
pub fn each(self, mut callback: impl FnMut(&S, SheetRebuildKind) -> bool) {
for potential_sheet in self.entries.iter_mut() {
let committed = mem::replace(&mut potential_sheet.committed, true); let committed = mem::replace(&mut potential_sheet.committed, true);
if !committed { let rebuild_kind = if !committed {
// If the sheet was uncommitted, we need to do a full rebuild // If the sheet was uncommitted, we need to do a full rebuild
// anyway. // anyway.
return Some((&potential_sheet.sheet, SheetRebuildKind::Full)); SheetRebuildKind::Full
} } else {
match self.validity {
let rebuild_kind = match self.validity { DataValidity::Valid => continue,
DataValidity::Valid => continue, DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly,
DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly, DataValidity::FullyInvalid => SheetRebuildKind::Full,
DataValidity::FullyInvalid => SheetRebuildKind::Full, }
}; };
return Some((&potential_sheet.sheet, rebuild_kind)); if !callback(&potential_sheet.sheet, rebuild_kind) {
return;
}
} }
} }
} }
@ -357,7 +369,7 @@ where
let validity = mem::replace(&mut self.data_validity, DataValidity::Valid); let validity = mem::replace(&mut self.data_validity, DataValidity::Valid);
SheetCollectionFlusher { SheetCollectionFlusher {
iter: self.entries.iter_mut(), entries: &mut self.entries,
dirty, dirty,
validity, validity,
} }

View file

@ -60,17 +60,33 @@ pub type StylistSheet = crate::stylesheets::DocumentStyleSheet;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub type StylistSheet = crate::gecko::data::GeckoStyleSheet; pub type StylistSheet = crate::gecko::data::GeckoStyleSheet;
lazy_static! { trait CascadeDataCacheEntry : Sized {
/// A cache of computed user-agent data, to be shared across documents. /// Returns a reference to the cascade data.
static ref UA_CASCADE_DATA_CACHE: Mutex<UserAgentCascadeDataCache> = fn cascade_data(&self) -> &CascadeData;
Mutex::new(UserAgentCascadeDataCache::new()); /// Rebuilds the cascade data for the new stylesheet collection. The
/// collection is guaranteed to be dirty.
fn rebuild<S>(
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
old_entry: &Self,
) -> Result<Arc<Self>, FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static;
/// Measures heap memory usage.
#[cfg(feature = "gecko")]
fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes);
} }
struct UserAgentCascadeDataCache { struct CascadeDataCache<Entry> {
entries: Vec<Arc<UserAgentCascadeData>>, entries: Vec<Arc<Entry>>,
} }
impl UserAgentCascadeDataCache { impl<Entry> CascadeDataCache<Entry>
where
Entry: CascadeDataCacheEntry,
{
fn new() -> Self { fn new() -> Self {
Self { entries: vec![] } Self { entries: vec![] }
} }
@ -79,53 +95,50 @@ impl UserAgentCascadeDataCache {
self.entries.len() self.entries.len()
} }
// FIXME(emilio): This may need to be keyed on quirks-mode too, though there // FIXME(emilio): This may need to be keyed on quirks-mode too, though for
// aren't class / id selectors on those sheets, usually, so it's probably // UA sheets there aren't class / id selectors on those sheets, usually, so
// ok... // it's probably ok... For the other cache the quirks mode shouldn't differ
fn lookup<'a, I, S>( // so also should be fine.
fn lookup<'a, S>(
&'a mut self, &'a mut self,
sheets: I,
device: &Device, device: &Device,
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard, guard: &SharedRwLockReadGuard,
) -> Result<Arc<UserAgentCascadeData>, FailedAllocationError> old_entry: &Entry,
) -> Result<Option<Arc<Entry>>, FailedAllocationError>
where where
I: Iterator<Item = &'a S> + Clone,
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static, S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static,
{ {
debug!("StyleSheetCache::lookup({})", self.len());
if !collection.dirty() {
return Ok(None);
}
let mut key = EffectiveMediaQueryResults::new(); let mut key = EffectiveMediaQueryResults::new();
debug!("UserAgentCascadeDataCache::lookup({:?})", device); for sheet in collection.sheets() {
for sheet in sheets.clone() {
CascadeData::collect_applicable_media_query_results_into(device, sheet, guard, &mut key) CascadeData::collect_applicable_media_query_results_into(device, sheet, guard, &mut key)
} }
for entry in &self.entries { for entry in &self.entries {
if entry.cascade_data.effective_media_query_results == key { if entry.cascade_data().effective_media_query_results == key {
return Ok(entry.clone()); return Ok(Some(entry.clone()));
} }
} }
let mut new_data = UserAgentCascadeData {
cascade_data: CascadeData::new(),
precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(),
};
debug!("> Picking the slow path"); debug!("> Picking the slow path");
for sheet in sheets { let new_entry = Entry::rebuild(
new_data.cascade_data.add_stylesheet( device,
device, quirks_mode,
quirks_mode, collection,
sheet, guard,
guard, old_entry,
SheetRebuildKind::Full, )?;
Some(&mut new_data.precomputed_pseudo_element_decls),
)?;
}
let new_data = Arc::new(new_data); self.entries.push(new_entry.clone());
self.entries.push(new_data.clone()); Ok(Some(new_entry))
Ok(new_data)
} }
/// Returns all the cascade datas that are not being used (that is, that are /// Returns all the cascade datas that are not being used (that is, that are
@ -135,7 +148,7 @@ impl UserAgentCascadeDataCache {
/// keep alive some other documents (like the SVG documents kept alive by /// keep alive some other documents (like the SVG documents kept alive by
/// URL references), and thus we don't want to drop them while locking the /// URL references), and thus we don't want to drop them while locking the
/// cache to not deadlock. /// cache to not deadlock.
fn take_unused(&mut self) -> SmallVec<[Arc<UserAgentCascadeData>; 3]> { fn take_unused(&mut self) -> SmallVec<[Arc<Entry>; 3]> {
let mut unused = SmallVec::new(); let mut unused = SmallVec::new();
for i in (0..self.entries.len()).rev() { for i in (0..self.entries.len()).rev() {
// is_unique() returns false for static references, but we never // is_unique() returns false for static references, but we never
@ -148,7 +161,7 @@ impl UserAgentCascadeDataCache {
unused unused
} }
fn take_all(&mut self) -> Vec<Arc<UserAgentCascadeData>> { fn take_all(&mut self) -> Vec<Arc<Entry>> {
mem::replace(&mut self.entries, Vec::new()) mem::replace(&mut self.entries, Vec::new())
} }
@ -173,6 +186,58 @@ pub fn add_size_of_ua_cache(ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSet
.add_size_of(ops, sizes); .add_size_of(ops, sizes);
} }
lazy_static! {
/// A cache of computed user-agent data, to be shared across documents.
static ref UA_CASCADE_DATA_CACHE: Mutex<UserAgentCascadeDataCache> =
Mutex::new(UserAgentCascadeDataCache::new());
}
impl CascadeDataCacheEntry for UserAgentCascadeData {
fn cascade_data(&self) -> &CascadeData {
&self.cascade_data
}
fn rebuild<S>(
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
_old: &Self,
) -> Result<Arc<Self>, FailedAllocationError>
where
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static
{
// TODO: Maybe we should support incremental rebuilds, though they seem
// uncommon and rebuild() doesn't deal with
// precomputed_pseudo_element_decls for now so...
let mut new_data = Self {
cascade_data: CascadeData::new(),
precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(),
};
for sheet in collection.sheets() {
new_data.cascade_data.add_stylesheet(
device,
quirks_mode,
sheet,
guard,
SheetRebuildKind::Full,
Some(&mut new_data.precomputed_pseudo_element_decls),
)?;
}
Ok(Arc::new(new_data))
}
#[cfg(feature = "gecko")]
fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.cascade_data.add_size_of(ops, sizes);
sizes.mPrecomputedPseudos += self.precomputed_pseudo_element_decls.size_of(ops);
}
}
type UserAgentCascadeDataCache = CascadeDataCache<UserAgentCascadeData>;
type PrecomputedPseudoElementDeclarations = PerPseudoElementMap<Vec<ApplicableDeclarationBlock>>; type PrecomputedPseudoElementDeclarations = PerPseudoElementMap<Vec<ApplicableDeclarationBlock>>;
#[derive(Default)] #[derive(Default)]
@ -188,14 +253,6 @@ struct UserAgentCascadeData {
precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations, precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations,
} }
impl UserAgentCascadeData {
#[cfg(feature = "gecko")]
fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.cascade_data.add_size_of(ops, sizes);
sizes.mPrecomputedPseudos += self.precomputed_pseudo_element_decls.size_of(ops);
}
}
/// All the computed information for all the stylesheets that apply to the /// All the computed information for all the stylesheets that apply to the
/// document. /// document.
#[derive(Default)] #[derive(Default)]
@ -266,15 +323,23 @@ impl DocumentCascadeData {
{ {
// First do UA sheets. // First do UA sheets.
{ {
if flusher.flush_origin(Origin::UserAgent).dirty() { let origin_flusher = flusher.flush_origin(Origin::UserAgent);
let origin_sheets = flusher.origin_sheets(Origin::UserAgent); if origin_flusher.dirty() {
let _unused_cascade_datas = { let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap();
let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap(); let new_data = ua_cache.lookup(
self.user_agent = device,
ua_cache.lookup(origin_sheets, device, quirks_mode, guards.ua_or_user)?; quirks_mode,
debug!("User agent data cache size {:?}", ua_cache.len()); origin_flusher,
ua_cache.take_unused() guards.ua_or_user,
}; &self.user_agent,
)?;
if let Some(new_data) = new_data {
self.user_agent = new_data;
}
let _unused_entries = ua_cache.take_unused();
// See the comments in take_unused() as for why the following
// line.
std::mem::drop(ua_cache);
} }
} }
@ -1836,18 +1901,21 @@ impl CascadeData {
DataValidity::FullyInvalid => self.clear(), DataValidity::FullyInvalid => self.clear(),
} }
for (stylesheet, rebuild_kind) in collection { let mut result = Ok(());
self.add_stylesheet(
collection.each(|stylesheet, rebuild_kind| {
result = self.add_stylesheet(
device, device,
quirks_mode, quirks_mode,
stylesheet, stylesheet,
guard, guard,
rebuild_kind, rebuild_kind,
/* precomputed_pseudo_element_decls = */ None, /* precomputed_pseudo_element_decls = */ None,
)?; );
} result.is_ok()
});
Ok(()) result
} }
/// Returns the invalidation map. /// Returns the invalidation map.