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
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,
dirty: bool,
}
@ -204,32 +206,42 @@ where
pub fn data_validity(&self) -> DataValidity {
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
S: StylesheetInDocument + PartialEq + 'static,
{
type Item = (&'a S, SheetRebuildKind);
fn next(&mut self) -> Option<Self::Item> {
loop {
let potential_sheet = self.iter.next()?;
/// Iterates over all sheets and values that we have to invalidate.
///
/// TODO(emilio): This would be nicer as an iterator but we can't do that
/// until https://github.com/rust-lang/rust/pull/82771 stabilizes.
///
/// 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);
if !committed {
let rebuild_kind = if !committed {
// If the sheet was uncommitted, we need to do a full rebuild
// anyway.
return Some((&potential_sheet.sheet, SheetRebuildKind::Full));
}
let rebuild_kind = match self.validity {
DataValidity::Valid => continue,
DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly,
DataValidity::FullyInvalid => SheetRebuildKind::Full,
SheetRebuildKind::Full
} else {
match self.validity {
DataValidity::Valid => continue,
DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly,
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);
SheetCollectionFlusher {
iter: self.entries.iter_mut(),
entries: &mut self.entries,
dirty,
validity,
}

View file

@ -60,17 +60,33 @@ pub type StylistSheet = crate::stylesheets::DocumentStyleSheet;
#[cfg(feature = "gecko")]
pub type StylistSheet = crate::gecko::data::GeckoStyleSheet;
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());
trait CascadeDataCacheEntry : Sized {
/// Returns a reference to the cascade data.
fn cascade_data(&self) -> &CascadeData;
/// 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 {
entries: Vec<Arc<UserAgentCascadeData>>,
struct CascadeDataCache<Entry> {
entries: Vec<Arc<Entry>>,
}
impl UserAgentCascadeDataCache {
impl<Entry> CascadeDataCache<Entry>
where
Entry: CascadeDataCacheEntry,
{
fn new() -> Self {
Self { entries: vec![] }
}
@ -79,53 +95,50 @@ impl UserAgentCascadeDataCache {
self.entries.len()
}
// FIXME(emilio): This may need to be keyed on quirks-mode too, though there
// aren't class / id selectors on those sheets, usually, so it's probably
// ok...
fn lookup<'a, I, S>(
// FIXME(emilio): This may need to be keyed on quirks-mode too, though for
// UA sheets there aren't class / id selectors on those sheets, usually, so
// it's probably ok... For the other cache the quirks mode shouldn't differ
// so also should be fine.
fn lookup<'a, S>(
&'a mut self,
sheets: I,
device: &Device,
quirks_mode: QuirksMode,
collection: SheetCollectionFlusher<S>,
guard: &SharedRwLockReadGuard,
) -> Result<Arc<UserAgentCascadeData>, FailedAllocationError>
old_entry: &Entry,
) -> Result<Option<Arc<Entry>>, FailedAllocationError>
where
I: Iterator<Item = &'a S> + Clone,
S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static,
{
debug!("StyleSheetCache::lookup({})", self.len());
if !collection.dirty() {
return Ok(None);
}
let mut key = EffectiveMediaQueryResults::new();
debug!("UserAgentCascadeDataCache::lookup({:?})", device);
for sheet in sheets.clone() {
for sheet in collection.sheets() {
CascadeData::collect_applicable_media_query_results_into(device, sheet, guard, &mut key)
}
for entry in &self.entries {
if entry.cascade_data.effective_media_query_results == key {
return Ok(entry.clone());
if entry.cascade_data().effective_media_query_results == key {
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");
for sheet in sheets {
new_data.cascade_data.add_stylesheet(
device,
quirks_mode,
sheet,
guard,
SheetRebuildKind::Full,
Some(&mut new_data.precomputed_pseudo_element_decls),
)?;
}
let new_entry = Entry::rebuild(
device,
quirks_mode,
collection,
guard,
old_entry,
)?;
let new_data = Arc::new(new_data);
self.entries.push(new_data.clone());
Ok(new_data)
self.entries.push(new_entry.clone());
Ok(Some(new_entry))
}
/// 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
/// URL references), and thus we don't want to drop them while locking the
/// 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();
for i in (0..self.entries.len()).rev() {
// is_unique() returns false for static references, but we never
@ -148,7 +161,7 @@ impl UserAgentCascadeDataCache {
unused
}
fn take_all(&mut self) -> Vec<Arc<UserAgentCascadeData>> {
fn take_all(&mut self) -> Vec<Arc<Entry>> {
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);
}
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>>;
#[derive(Default)]
@ -188,14 +253,6 @@ struct UserAgentCascadeData {
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
/// document.
#[derive(Default)]
@ -266,15 +323,23 @@ impl DocumentCascadeData {
{
// First do UA sheets.
{
if flusher.flush_origin(Origin::UserAgent).dirty() {
let origin_sheets = flusher.origin_sheets(Origin::UserAgent);
let _unused_cascade_datas = {
let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap();
self.user_agent =
ua_cache.lookup(origin_sheets, device, quirks_mode, guards.ua_or_user)?;
debug!("User agent data cache size {:?}", ua_cache.len());
ua_cache.take_unused()
};
let origin_flusher = flusher.flush_origin(Origin::UserAgent);
if origin_flusher.dirty() {
let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap();
let new_data = ua_cache.lookup(
device,
quirks_mode,
origin_flusher,
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(),
}
for (stylesheet, rebuild_kind) in collection {
self.add_stylesheet(
let mut result = Ok(());
collection.each(|stylesheet, rebuild_kind| {
result = self.add_stylesheet(
device,
quirks_mode,
stylesheet,
guard,
rebuild_kind,
/* precomputed_pseudo_element_decls = */ None,
)?;
}
);
result.is_ok()
});
Ok(())
result
}
/// Returns the invalidation map.