From 11153c63fa9d908355573722614a5bb8f23dac9a Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 16 May 2023 09:47:54 +0200 Subject: [PATCH] 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 --- components/style/stylesheet_set.rs | 48 +++++--- components/style/stylist.rs | 188 ++++++++++++++++++++--------- 2 files changed, 158 insertions(+), 78 deletions(-) diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index 9e858a4566c..d392d1795ec 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -184,7 +184,9 @@ pub struct SheetCollectionFlusher<'a, S> where S: StylesheetInDocument + PartialEq + 'static, { - iter: slice::IterMut<'a, StylesheetSetEntry>, + // TODO: This can be made an iterator again once + // https://github.com/rust-lang/rust/pull/82771 lands on stable. + entries: &'a mut [StylesheetSetEntry], 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 { + 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 { - 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, } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 5cb40b968eb..e2392cf5742 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -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 = - 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( + device: &Device, + quirks_mode: QuirksMode, + collection: SheetCollectionFlusher, + guard: &SharedRwLockReadGuard, + old_entry: &Self, + ) -> Result, 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>, +struct CascadeDataCache { + entries: Vec>, } -impl UserAgentCascadeDataCache { +impl CascadeDataCache +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, guard: &SharedRwLockReadGuard, - ) -> Result, FailedAllocationError> + old_entry: &Entry, + ) -> Result>, FailedAllocationError> where - I: Iterator + 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; 3]> { + fn take_unused(&mut self) -> SmallVec<[Arc; 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> { + fn take_all(&mut self) -> Vec> { 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 = + Mutex::new(UserAgentCascadeDataCache::new()); +} + +impl CascadeDataCacheEntry for UserAgentCascadeData { + fn cascade_data(&self) -> &CascadeData { + &self.cascade_data + } + + fn rebuild( + device: &Device, + quirks_mode: QuirksMode, + collection: SheetCollectionFlusher, + guard: &SharedRwLockReadGuard, + _old: &Self, + ) -> Result, 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; + type PrecomputedPseudoElementDeclarations = PerPseudoElementMap>; #[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.