diff --git a/components/servo_arc/lib.rs b/components/servo_arc/lib.rs index 9ed2801ac2c..49a04dea1fc 100644 --- a/components/servo_arc/lib.rs +++ b/components/servo_arc/lib.rs @@ -337,7 +337,7 @@ impl Arc { } #[inline] - fn is_unique(&self) -> bool { + pub fn is_unique(&self) -> bool { // We can use Relaxed here, but the justification is a bit subtle. // // The reason to use Acquire would be to synchronize with other threads diff --git a/components/style/invalidation/media_queries.rs b/components/style/invalidation/media_queries.rs index 0656027555b..9521e3cdc16 100644 --- a/components/style/invalidation/media_queries.rs +++ b/components/style/invalidation/media_queries.rs @@ -53,7 +53,7 @@ impl ToMediaListKey for MediaRule {} /// A struct that holds the result of a media query evaluation pass for the /// media queries that evaluated successfully. -#[derive(Debug)] +#[derive(Debug, PartialEq)] #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct EffectiveMediaQueryResults { diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index cc79d03b0f1..0b5079a0751 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -31,11 +31,20 @@ where } /// A iterator over the stylesheets of a list of entries in the StylesheetSet. -#[derive(Clone)] pub struct StylesheetCollectionIterator<'a, S>(slice::Iter<'a, StylesheetSetEntry>) where S: StylesheetInDocument + PartialEq + 'static; +impl<'a, S> Clone for StylesheetCollectionIterator<'a, S> +where + S: StylesheetInDocument + PartialEq + 'static, +{ + fn clone(&self) -> Self { + StylesheetCollectionIterator(self.0.clone()) + } +} + + impl<'a, S> Iterator for StylesheetCollectionIterator<'a, S> where S: StylesheetInDocument + PartialEq + 'static, @@ -119,11 +128,8 @@ pub struct StylesheetFlusher<'a, S> where S: StylesheetInDocument + PartialEq + 'static, { - origins_dirty: OriginSetIterator, - // NB: Bound to the StylesheetSet lifetime when constructed, see - // StylesheetSet::flush. - collections: *mut PerOrigin>, - current: Option<(Origin, slice::IterMut<'a, StylesheetSetEntry>)>, + origins_dirty: OriginSet, + collections: &'a mut PerOrigin>, origin_data_validity: PerOrigin, author_style_disabled: bool, had_invalidations: bool, @@ -153,9 +159,56 @@ where *self.origin_data_validity.borrow_for_origin(&origin) } + /// Whether the origin data is dirty in any way. + pub fn origin_dirty(&self, origin: Origin) -> bool { + self.origins_dirty.contains(origin.into()) + } + + /// Returns an iterator over the stylesheets of a given origin, assuming all + /// of them will be flushed. + pub fn manual_origin_sheets<'b>(&'b mut self, origin: Origin) -> StylesheetCollectionIterator<'b, S> + where + 'a: 'b + { + debug_assert_eq!(origin, Origin::UserAgent); + + // We could iterate over `origin_sheets(origin)` to ensure state is + // consistent (that the `dirty` member of the Entry is reset to + // `false`). + // + // In practice it doesn't matter for correctness given our use of it + // (that this is UA only). + self.collections.borrow_for_origin(&origin).iter() + } + + /// Returns a flusher for the dirty origin `origin`. + pub fn origin_sheets<'b>(&'b mut self, origin: Origin) -> PerOriginFlusher<'b, S> + where + 'a: 'b + { + let validity = self.origin_validity(origin); + let origin_dirty = self.origins_dirty.contains(origin.into()); + + debug_assert!( + origin_dirty || validity == OriginValidity::Valid, + "origin_data_validity should be a subset of origins_dirty!" + ); + + if self.author_style_disabled && origin == Origin::Author { + return PerOriginFlusher { + iter: [].iter_mut(), + validity, + } + } + PerOriginFlusher { + iter: self.collections.borrow_mut_for_origin(&origin).entries.iter_mut(), + validity, + } + } + /// Returns whether running the whole flushing process would be a no-op. pub fn nothing_to_do(&self) -> bool { - self.origins_dirty.clone().next().is_none() + self.origins_dirty.is_empty() } /// Returns whether any DOM invalidations were processed as a result of the @@ -165,81 +218,44 @@ where } } -#[cfg(debug_assertions)] -impl<'a, S> Drop for StylesheetFlusher<'a, S> +/// A flusher struct for a given origin, that takes care of returning the +/// appropriate stylesheets that need work. +pub struct PerOriginFlusher<'a, S> where - S: StylesheetInDocument + PartialEq + 'static, + S: StylesheetInDocument + PartialEq + 'static { - fn drop(&mut self) { - debug_assert!( - self.origins_dirty.next().is_none(), - "You're supposed to fully consume the flusher" - ); - } + iter: slice::IterMut<'a, StylesheetSetEntry>, + validity: OriginValidity, } -impl<'a, S> Iterator for StylesheetFlusher<'a, S> +impl<'a, S> Iterator for PerOriginFlusher<'a, S> where S: StylesheetInDocument + PartialEq + 'static, { - type Item = (&'a S, Origin, SheetRebuildKind); + type Item = (&'a S, SheetRebuildKind); fn next(&mut self) -> Option { use std::mem; loop { - if self.current.is_none() { - let next_origin = match self.origins_dirty.next() { - Some(o) => o, - None => return None, - }; - - // Should've been cleared already. - debug_assert_eq!( - unsafe { &*self.collections } - .borrow_for_origin(&next_origin) - .data_validity, - OriginValidity::Valid - ); - - self.current = - Some(( - next_origin, - unsafe { &mut *self.collections } - .borrow_mut_for_origin(&next_origin) - .entries - .iter_mut() - )); - } - - let potential_sheet = match self.current.as_mut().unwrap().1.next() { + let potential_sheet = match self.iter.next() { Some(s) => s, - None => { - self.current = None; - continue; - } + None => return None, }; - let origin = self.current.as_ref().unwrap().0; - let dirty = mem::replace(&mut potential_sheet.dirty, false); - if dirty { // If the sheet was dirty, we need to do a full rebuild anyway. - return Some((&potential_sheet.sheet, origin, SheetRebuildKind::Full)) + return Some((&potential_sheet.sheet, SheetRebuildKind::Full)) } - if self.author_style_disabled && matches!(origin, Origin::Author) { - continue; - } - - let rebuild_kind = match self.origin_validity(origin) { + let rebuild_kind = match self.validity { OriginValidity::Valid => continue, OriginValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly, OriginValidity::FullyInvalid => SheetRebuildKind::Full, }; - return Some((&potential_sheet.sheet, origin, rebuild_kind)); + return Some((&potential_sheet.sheet, rebuild_kind)); } } } @@ -501,10 +517,10 @@ where let had_invalidations = self.invalidations.flush(document_element); let origins_dirty = - mem::replace(&mut self.origins_dirty, OriginSet::empty()).iter(); + mem::replace(&mut self.origins_dirty, OriginSet::empty()); let mut origin_data_validity = PerOrigin::::default(); - for origin in origins_dirty.clone() { + for origin in origins_dirty.iter() { let collection = self.collections.borrow_mut_for_origin(&origin); *origin_data_validity.borrow_mut_for_origin(&origin) = mem::replace(&mut collection.data_validity, OriginValidity::Valid); @@ -516,7 +532,6 @@ where had_invalidations, origins_dirty, origin_data_validity, - current: None, } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index ff86d11528e..8dcf9aace05 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -40,6 +40,7 @@ use smallbitvec::SmallBitVec; use smallvec::VecLike; use std::fmt::Debug; use std::ops; +use std::sync::Mutex; use style_traits::viewport::ViewportConstraints; use stylesheet_set::{OriginValidity, SheetRebuildKind, StylesheetSet, StylesheetFlusher}; #[cfg(feature = "gecko")] @@ -61,15 +62,83 @@ pub type StylistSheet = ::stylesheets::DocumentStyleSheet; #[cfg(feature = "gecko")] pub type StylistSheet = ::gecko::data::GeckoStyleSheet; +/// A cache of computed user-agent data, to be shared across documents. +lazy_static! { + static ref UA_CASCADE_DATA_CACHE: Mutex = + Mutex::new(UserAgentCascadeDataCache::new()); +} + +struct UserAgentCascadeDataCache { + entries: Vec>, +} + +impl UserAgentCascadeDataCache { + fn new() -> Self { + Self { + entries: vec![], + } + } + + fn lookup<'a, I, S>( + &'a mut self, + sheets: I, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ) -> Result, FailedAllocationError> + where + I: Iterator + Clone, + S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static, + { + let mut key = EffectiveMediaQueryResults::new(); + for sheet in sheets.clone() { + 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()); + } + } + + let mut new_data = UserAgentCascadeData { + cascade_data: CascadeData::new(), + precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(), + }; + + 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_data = Arc::new(new_data); + + self.entries.push(new_data.clone()); + Ok(new_data) + } + + fn expire_unused(&mut self) { + self.entries.retain(|e| !e.is_unique()) + } +} + type PrecomputedPseudoElementDeclarations = PerPseudoElementMap>; -/// All the computed information for a stylesheet. #[derive(Default)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -struct DocumentCascadeData { - /// Common data for all the origins. - per_origin: PerOrigin, +struct UserAgentCascadeData { + cascade_data: CascadeData, /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// @@ -77,18 +146,98 @@ struct DocumentCascadeData { /// computed values on the fly on layout. /// /// These are only filled from UA stylesheets. - /// - /// FIXME(emilio): Use the rule tree! precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations, } +/// All the computed information for a stylesheet. +#[derive(Default)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +struct DocumentCascadeData { + #[cfg_attr( + feature = "servo", + ignore_heap_size_of = "Arc, owned by UserAgentCascadeDataCache" + )] + user_agent: Arc, + user: CascadeData, + author: CascadeData, + per_origin: PerOrigin<()>, +} + +struct DocumentCascadeDataIter<'a> { + iter: PerOriginIter<'a, ()>, + cascade_data: &'a DocumentCascadeData, +} + +impl<'a> Iterator for DocumentCascadeDataIter<'a> { + type Item = (&'a CascadeData, Origin); + + fn next(&mut self) -> Option { + let (_, origin) = match self.iter.next() { + Some(o) => o, + None => return None, + }; + + Some((self.cascade_data.borrow_for_origin(origin), origin)) + } +} + impl DocumentCascadeData { - fn iter_origins(&self) -> PerOriginIter { - self.per_origin.iter_origins() + fn borrow_for_origin(&self, origin: Origin) -> &CascadeData { + match origin { + Origin::UserAgent => &self.user_agent.cascade_data, + Origin::Author => &self.author, + Origin::User => &self.user, + } } - fn iter_origins_rev(&self) -> PerOriginIter { - self.per_origin.iter_origins_rev() + fn iter_origins(&self) -> DocumentCascadeDataIter { + DocumentCascadeDataIter { + iter: self.per_origin.iter_origins(), + cascade_data: self, + } + } + + fn iter_origins_rev(&self) -> DocumentCascadeDataIter { + DocumentCascadeDataIter { + iter: self.per_origin.iter_origins_rev(), + cascade_data: self, + } + } + + fn rebuild_origin<'a, S>( + device: &Device, + quirks_mode: QuirksMode, + flusher: &mut StylesheetFlusher<'a, S>, + guards: &StylesheetGuards, + origin: Origin, + cascade_data: &mut CascadeData, + ) -> Result<(), FailedAllocationError> + where + S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static, + { + debug_assert_ne!(origin, Origin::UserAgent); + + let validity = flusher.origin_validity(origin); + + match validity { + OriginValidity::Valid => {}, + OriginValidity::CascadeInvalid => cascade_data.clear_cascade_data(), + OriginValidity::FullyInvalid => cascade_data.clear(), + } + + let guard = guards.for_origin(origin); + for (stylesheet, rebuild_kind) in flusher.origin_sheets(origin) { + cascade_data.add_stylesheet( + device, + quirks_mode, + stylesheet, + guard, + rebuild_kind, + /* precomputed_pseudo_element_decls = */ None, + )?; + } + + Ok(()) } /// Rebuild the cascade data for the given document stylesheets, and @@ -98,7 +247,7 @@ impl DocumentCascadeData { &mut self, device: &Device, quirks_mode: QuirksMode, - flusher: StylesheetFlusher<'a, S>, + mut flusher: StylesheetFlusher<'a, S>, guards: &StylesheetGuards, ) -> Result<(), FailedAllocationError> where @@ -106,55 +255,56 @@ impl DocumentCascadeData { { debug_assert!(!flusher.nothing_to_do()); - for (cascade_data, origin) in self.per_origin.iter_mut_origins() { - let validity = flusher.origin_validity(origin); + // First do UA sheets. + { + if flusher.origin_dirty(Origin::UserAgent) { + let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap(); + let origin_sheets = + flusher.manual_origin_sheets(Origin::UserAgent); - if validity == OriginValidity::Valid { - continue; - } - - if origin == Origin::UserAgent { - self.precomputed_pseudo_element_decls.clear(); - } - - if validity == OriginValidity::CascadeInvalid { - cascade_data.clear_cascade_data() - } else { - debug_assert_eq!(validity, OriginValidity::FullyInvalid); - cascade_data.clear(); - } - } - - for (stylesheet, origin, rebuild_kind) in flusher { - let guard = guards.for_origin(origin); - let origin = stylesheet.origin(guard); - self.per_origin - .borrow_mut_for_origin(&origin) - .add_stylesheet( + let ua_cascade_data = ua_cache.lookup( + origin_sheets, device, quirks_mode, - stylesheet, - guard, - rebuild_kind, - &mut self.precomputed_pseudo_element_decls, + guards.ua_or_user )?; + + ua_cache.expire_unused(); + self.user_agent = ua_cascade_data; + } } + // Now do the user sheets. + Self::rebuild_origin( + device, + quirks_mode, + &mut flusher, + guards, + Origin::User, + &mut self.user, + )?; + + // And now the author sheets. + Self::rebuild_origin( + device, + quirks_mode, + &mut flusher, + guards, + Origin::Author, + &mut self.author, + )?; + Ok(()) } /// Measures heap usage. #[cfg(feature = "gecko")] pub fn add_size_of_children(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.per_origin.user_agent.add_size_of_children(ops, sizes); - self.per_origin.user.add_size_of_children(ops, sizes); - self.per_origin.author.add_size_of_children(ops, sizes); + self.user.add_size_of_children(ops, sizes); + self.author.add_size_of_children(ops, sizes); - for elem in self.precomputed_pseudo_element_decls.iter() { - if let Some(ref elem) = *elem { - sizes.mStylistPrecomputedPseudos += elem.shallow_size_of(ops); - } - } + // FIXME(emilio): UA_CASCADE_DATA_CACHE is shared, we should do whatever + // we do for RuleProcessorCache in Gecko. } } @@ -556,7 +706,7 @@ impl Stylist { extra_declarations: Option>, ) -> StrongRuleNode { let mut decl; - let declarations = match self.cascade_data.precomputed_pseudo_element_decls.get(pseudo) { + let declarations = match self.cascade_data.user_agent.precomputed_pseudo_element_decls.get(pseudo) { Some(declarations) => { match extra_declarations { Some(mut extra_decls) => { @@ -975,7 +1125,7 @@ impl Stylist { let guard = guards.for_origin(origin); let origin_cascade_data = - self.cascade_data.per_origin.borrow_for_origin(&origin); + self.cascade_data.borrow_for_origin(origin); let affected_changed = !origin_cascade_data.media_feature_affected_matches( stylesheet, @@ -1034,7 +1184,7 @@ impl Stylist { // nsXBLPrototypeResources::LoadResources() loads Chrome XBL style // sheets under eAuthorSheetFeatures level. - if let Some(map) = self.cascade_data.per_origin.author.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1082,7 +1232,7 @@ impl Stylist { let only_default_rules = rule_inclusion == RuleInclusion::DefaultOnly; // Step 1: Normal user-agent rules. - if let Some(map) = self.cascade_data.per_origin.user_agent.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.user_agent.cascade_data.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1120,7 +1270,7 @@ impl Stylist { // Which may be more what you would probably expect. if rule_hash_target.matches_user_and_author_rules() { // Step 3a: User normal rules. - if let Some(map) = self.cascade_data.per_origin.user.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.user.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1147,7 +1297,7 @@ impl Stylist { // See nsStyleSet::FileRules(). if !cut_off_inheritance { // Step 3c: Author normal rules. - if let Some(map) = self.cascade_data.per_origin.author.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1380,6 +1530,8 @@ pub struct ExtraStyleData { // nsCSSFontFaceRules or nsCSSCounterStyleRules OMT (which we don't). #[cfg(feature = "gecko")] unsafe impl Sync for ExtraStyleData {} +#[cfg(feature = "gecko")] +unsafe impl Send for ExtraStyleData {} #[cfg(feature = "gecko")] impl ExtraStyleData { @@ -1422,7 +1574,7 @@ impl ExtraStyleData { } /// An iterator over the different ExtraStyleData. -pub struct ExtraStyleDataIterator<'a>(PerOriginIter<'a, CascadeData>); +pub struct ExtraStyleDataIterator<'a>(DocumentCascadeDataIter<'a>); impl<'a> Iterator for ExtraStyleDataIterator<'a> { type Item = (&'a ExtraStyleData, Origin); @@ -1712,6 +1864,45 @@ impl CascadeData { } } + /// Collects all the applicable media query results into `results`. + /// + /// This duplicates part of the logic in `add_stylesheet`, which is + /// a bit unfortunate. + /// + /// FIXME(emilio): With a bit of smartness in + /// `media_feature_affected_matches`, we could convert + /// `EffectiveMediaQueryResults` into a vector without too much effort. + fn collect_applicable_media_query_results_into( + device: &Device, + stylesheet: &S, + guard: &SharedRwLockReadGuard, + results: &mut EffectiveMediaQueryResults, + ) + where + S: StylesheetInDocument + ToMediaListKey + 'static, + { + if !stylesheet.enabled() || + !stylesheet.is_effective_for_device(device, guard) { + return; + } + + results.saw_effective(stylesheet); + + for rule in stylesheet.effective_rules(device, guard) { + match *rule { + CssRule::Import(ref lock) => { + let import_rule = lock.read_with(guard); + results.saw_effective(import_rule); + } + CssRule::Media(ref lock) => { + let media_rule = lock.read_with(guard); + results.saw_effective(media_rule); + } + _ => {}, + } + } + } + // Returns Err(..) to signify OOM fn add_stylesheet( &mut self, @@ -1720,7 +1911,7 @@ impl CascadeData { stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, - precomputed_pseudo_element_decls: &mut PrecomputedPseudoElementDeclarations, + mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where S: StylesheetInDocument + ToMediaListKey + 'static, @@ -1756,6 +1947,8 @@ impl CascadeData { } precomputed_pseudo_element_decls + .as_mut() + .expect("Expected precomputed declarations for the UA level") .get_or_insert_with(&pseudo.canonical(), Vec::new) .expect("Unexpected tree pseudo-element?") .push(ApplicableDeclarationBlock::new(