style: Cache computed styles objects display: none subtrees

This reuses our existing undisplayed style generation, but in a
per-document rather than per-nsComputedDOMStyle object, which means that
we can avoid re-resolving styles of elements in display: none subtrees
much more often.

This brings the test-case in the bug to par with other browsers or
better, and is much simpler than the initial approach I tried back in
the day.

Differential Revision: https://phabricator.services.mozilla.com/D147547
This commit is contained in:
Emilio Cobos Álvarez 2023-08-15 00:02:36 +02:00 committed by Martin Robinson
parent 39ac4840ee
commit d1aeb3921b
3 changed files with 46 additions and 9 deletions

View file

@ -129,6 +129,12 @@ impl StylesheetInDocument for GeckoStyleSheet {
pub struct PerDocumentStyleDataImpl { pub struct PerDocumentStyleDataImpl {
/// Rule processor. /// Rule processor.
pub stylist: Stylist, pub stylist: Stylist,
/// A cache from element to resolved style.
pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache,
/// The generation for which our cache is valid.
pub undisplayed_style_cache_generation: u64,
} }
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics /// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
@ -143,6 +149,8 @@ impl PerDocumentStyleData {
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
stylist: Stylist::new(device, quirks_mode.into()), stylist: Stylist::new(device, quirks_mode.into()),
undisplayed_style_cache: Default::default(),
undisplayed_style_cache_generation: 0,
})) }))
} }
@ -177,12 +185,6 @@ impl PerDocumentStyleDataImpl {
self.stylist.device().default_computed_values_arc() self.stylist.device().default_computed_values_arc()
} }
/// Returns whether visited styles are enabled.
#[inline]
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.stylist.device().document()) }
}
/// Measure heap usage. /// Measure heap usage.
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.stylist.add_size_of(ops, sizes); self.stylist.add_size_of(ops, sizes);

View file

@ -411,6 +411,11 @@ impl Device {
self.used_font_metrics.load(Ordering::Relaxed) self.used_font_metrics.load(Ordering::Relaxed)
} }
/// Returns whether visited styles are enabled.
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) }
}
/// Returns the device pixel ratio. /// Returns the device pixel ratio.
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> { pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
let pc = match self.pres_context() { let pc = match self.pres_context() {

View file

@ -16,6 +16,13 @@ use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags; use crate::traversal_flags::TraversalFlags;
use selectors::NthIndexCache; use selectors::NthIndexCache;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::HashMap;
/// A cache from element reference to known-valid computed style.
pub type UndisplayedStyleCache = HashMap<
selectors::OpaqueElement,
servo_arc::Arc<crate::properties::ComputedValues>,
>;
/// A per-traversal-level chunk of data. This is sent down by the traversal, and /// A per-traversal-level chunk of data. This is sent down by the traversal, and
/// currently only holds the dom depth for the bloom filter. /// currently only holds the dom depth for the bloom filter.
@ -294,6 +301,7 @@ pub fn resolve_style<E>(
element: E, element: E,
rule_inclusion: RuleInclusion, rule_inclusion: RuleInclusion,
pseudo: Option<&PseudoElement>, pseudo: Option<&PseudoElement>,
mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>,
) -> ElementStyles ) -> ElementStyles
where where
E: TElement, E: TElement,
@ -304,6 +312,11 @@ where
element.borrow_data().map_or(true, |d| !d.has_styles()), element.borrow_data().map_or(true, |d| !d.has_styles()),
"Why are we here?" "Why are we here?"
); );
debug_assert!(
rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(),
"can't use the cache for default styles only"
);
let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();
// Clear the bloom filter, just in case the caller is reusing TLS. // Clear the bloom filter, just in case the caller is reusing TLS.
@ -320,6 +333,12 @@ where
} }
} }
} }
if let Some(ref mut cache) = undisplayed_style_cache {
if let Some(s) = cache.get(&current.opaque()) {
style = Some(s.clone());
break;
}
}
ancestors_requiring_style_resolution.push(current); ancestors_requiring_style_resolution.push(current);
ancestor = current.traversal_parent(); ancestor = current.traversal_parent();
} }
@ -337,7 +356,9 @@ where
} }
ancestor = ancestor.unwrap().traversal_parent(); ancestor = ancestor.unwrap().traversal_parent();
layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone()); layout_parent_style = ancestor.and_then(|a| {
a.borrow_data().map(|data| data.styles.primary().clone())
});
} }
for ancestor in ancestors_requiring_style_resolution.iter().rev() { for ancestor in ancestors_requiring_style_resolution.iter().rev() {
@ -360,18 +381,27 @@ where
layout_parent_style = style.clone(); layout_parent_style = style.clone();
} }
if let Some(ref mut cache) = undisplayed_style_cache {
cache.insert(ancestor.opaque(), style.clone().unwrap());
}
context.thread_local.bloom_filter.push(*ancestor); context.thread_local.bloom_filter.push(*ancestor);
} }
context.thread_local.bloom_filter.assert_complete(element); context.thread_local.bloom_filter.assert_complete(element);
StyleResolverForElement::new( let styles: ElementStyles = StyleResolverForElement::new(
element, element,
context, context,
rule_inclusion, rule_inclusion,
PseudoElementResolution::Force, PseudoElementResolution::Force,
) )
.resolve_style(style.as_deref(), layout_parent_style.as_deref()) .resolve_style(style.as_deref(), layout_parent_style.as_deref())
.into() .into();
if let Some(ref mut cache) = undisplayed_style_cache {
cache.insert(element.opaque(), styles.primary().clone());
}
styles
} }
/// Calculates the style for a single node. /// Calculates the style for a single node.