Bug 1381071 - Cache computed styles objects display: none subtrees. r=boris

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 2022-05-30 11:08:15 +00:00
parent 9d20fea826
commit f64de373aa
6 changed files with 111 additions and 40 deletions

View File

@ -3229,9 +3229,6 @@ void RestyleManager::ContentStateChanged(nsIContent* aContent,
}
Element& element = *aContent->AsElement();
if (!element.HasServoData()) {
return;
}
const EventStates kVisitedAndUnvisited =
NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED;
@ -3271,13 +3268,17 @@ void RestyleManager::ContentStateChanged(nsIContent* aContent,
return;
}
ServoElementSnapshot& snapshot = SnapshotFor(element);
EventStates previousState = element.StyleState() ^ aChangedBits;
snapshot.AddState(previousState);
// Assuming we need to invalidate cached style in getComputedStyle for
// undisplayed elements, since we don't know if it is needed.
IncrementUndisplayedRestyleGeneration();
if (!element.HasServoData()) {
return;
}
ServoElementSnapshot& snapshot = SnapshotFor(element);
EventStates previousState = element.StyleState() ^ aChangedBits;
snapshot.AddState(previousState);
}
static inline bool AttributeInfluencesOtherPseudoClassState(

View File

@ -1152,8 +1152,6 @@ already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleLazily(
const Element& aElement, PseudoStyleType aPseudoType,
StyleRuleInclusion aRuleInclusion) {
PreTraverseSync();
MOZ_ASSERT(GetPresContext(),
"For now, no style resolution without a pres context");
MOZ_ASSERT(!StylistNeedsUpdate());
AutoSetInServoTraversal guard(this);
@ -1188,9 +1186,17 @@ already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleLazily(
}
}
return Servo_ResolveStyleLazily(elementForStyleResolution,
pseudoTypeForStyleResolution, aRuleInclusion,
&Snapshots(), mRawSet.get())
nsPresContext* pc = GetPresContext();
MOZ_ASSERT(pc, "For now, no style resolution without a pres context");
auto* restyleManager = pc->RestyleManager();
const bool canUseCache = aRuleInclusion == StyleRuleInclusion::All &&
aElement.OwnerDoc() == mDocument &&
pc->PresShell()->DidInitialize();
return Servo_ResolveStyleLazily(
elementForStyleResolution, pseudoTypeForStyleResolution,
aRuleInclusion, &restyleManager->Snapshots(),
restyleManager->GetUndisplayedRestyleGeneration(), canUseCache,
mRawSet.get())
.Consume();
}

View File

@ -129,6 +129,12 @@ impl StylesheetInDocument for GeckoStyleSheet {
pub struct PerDocumentStyleDataImpl {
/// Rule processor.
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
@ -143,6 +149,8 @@ impl PerDocumentStyleData {
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
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()
}
/// 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.
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.stylist.add_size_of(ops, sizes);

View File

@ -339,6 +339,11 @@ impl Device {
self.used_dynamic_viewport_size.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.
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
let pc = match self.pres_context() {

View File

@ -16,6 +16,13 @@ use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags;
use selectors::NthIndexCache;
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
/// currently only holds the dom depth for the bloom filter.
@ -294,6 +301,7 @@ pub fn resolve_style<E>(
element: E,
rule_inclusion: RuleInclusion,
pseudo: Option<&PseudoElement>,
mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>,
) -> ElementStyles
where
E: TElement,
@ -304,6 +312,11 @@ where
element.borrow_data().map_or(true, |d| !d.has_styles()),
"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();
// 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);
ancestor = current.traversal_parent();
}
@ -337,7 +356,9 @@ where
}
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() {
@ -360,18 +381,27 @@ where
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.assert_complete(element);
StyleResolverForElement::new(
let styles: ElementStyles = StyleResolverForElement::new(
element,
context,
rule_inclusion,
PseudoElementResolution::Force,
)
.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.

View File

@ -220,13 +220,13 @@ fn is_in_servo_traversal() -> bool {
fn create_shared_context<'a>(
global_style_data: &GlobalStyleData,
guard: &'a SharedRwLockReadGuard,
per_doc_data: &'a PerDocumentStyleDataImpl,
stylist: &'a Stylist,
traversal_flags: TraversalFlags,
snapshot_map: &'a ServoElementSnapshotTable,
) -> SharedStyleContext<'a> {
SharedStyleContext {
stylist: &per_doc_data.stylist,
visited_styles_enabled: per_doc_data.visited_styles_enabled(),
stylist: &stylist,
visited_styles_enabled: stylist.device().visited_styles_enabled(),
options: global_style_data.options.clone(),
guards: StylesheetGuards::same(guard),
current_time_for_animations: 0.0, // Unused for Gecko, at least for now.
@ -246,7 +246,7 @@ fn traverse_subtree(
let shared_style_context = create_shared_context(
&global_style_data,
&guard,
&per_doc_data,
&per_doc_data.stylist,
traversal_flags,
snapshots,
);
@ -1174,7 +1174,7 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(
let shared = create_shared_context(
&global_style_data,
&guard,
&doc_data,
&doc_data.stylist,
TraversalFlags::empty(),
unsafe { &*snapshots },
);
@ -1225,7 +1225,7 @@ pub extern "C" fn Servo_StyleSet_GetComputedValuesByAddingAnimation(
let shared = create_shared_context(
&global_style_data,
&guard,
&doc_data,
&doc_data.stylist,
TraversalFlags::empty(),
unsafe { &*snapshots },
);
@ -3911,7 +3911,7 @@ pub extern "C" fn Servo_ResolvePseudoStyle(
RuleInclusion::All,
&data.styles,
inherited_style,
&*doc_data,
&doc_data.stylist,
is_probe,
/* matching_func = */ None,
);
@ -3984,7 +3984,7 @@ pub extern "C" fn Servo_ComputedValues_ResolveXULTreePseudoStyle(
RuleInclusion::All,
&data.styles,
Some(inherited_style),
&*doc_data,
&doc_data.stylist,
/* is_probe = */ false,
Some(&matching_fn),
)
@ -4010,7 +4010,7 @@ fn get_pseudo_style(
rule_inclusion: RuleInclusion,
styles: &ElementStyles,
inherited_styles: Option<&ComputedValues>,
doc_data: &PerDocumentStyleDataImpl,
stylist: &Stylist,
is_probe: bool,
matching_func: Option<&dyn Fn(&PseudoElement) -> bool>,
) -> Option<Arc<ComputedValues>> {
@ -4028,7 +4028,7 @@ fn get_pseudo_style(
let guards = StylesheetGuards::same(guard);
let metrics = get_metrics_provider_for_product();
let inputs = CascadeInputs::new_from_style(pseudo_styles);
doc_data.stylist.compute_pseudo_element_style_with_inputs(
stylist.compute_pseudo_element_style_with_inputs(
inputs,
pseudo,
&guards,
@ -4071,13 +4071,13 @@ fn get_pseudo_style(
ptr::eq(inherited_styles.unwrap(), &**styles.primary())
);
let base = if pseudo.inherits_from_default_values() {
doc_data.default_computed_values()
stylist.device().default_computed_values_arc()
} else {
styles.primary()
};
let guards = StylesheetGuards::same(guard);
let metrics = get_metrics_provider_for_product();
doc_data.stylist.lazily_compute_pseudo_element_style(
stylist.lazily_compute_pseudo_element_style(
&guards,
element,
&pseudo,
@ -4096,7 +4096,7 @@ fn get_pseudo_style(
Some(style.unwrap_or_else(|| {
StyleBuilder::for_inheritance(
doc_data.stylist.device(),
stylist.device(),
Some(styles.primary()),
Some(pseudo),
)
@ -4279,6 +4279,7 @@ pub extern "C" fn Servo_StyleSet_Init(doc: &structs::Document) -> *mut RawServoS
pub extern "C" fn Servo_StyleSet_RebuildCachedData(raw_data: &RawServoStyleSet) {
let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
data.stylist.device_mut().rebuild_cached_data();
data.undisplayed_style_cache.clear();
}
#[no_mangle]
@ -5715,16 +5716,27 @@ pub extern "C" fn Servo_ResolveStyleLazily(
pseudo_type: PseudoStyleType,
rule_inclusion: StyleRuleInclusion,
snapshots: *const ServoElementSnapshotTable,
cache_generation: u64,
can_use_cache: bool,
raw_data: &RawServoStyleSet,
) -> Strong<ComputedValues> {
use selectors::Element;
debug_assert!(!snapshots.is_null());
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let element = GeckoElement(element);
let doc_data = PerDocumentStyleData::from_ffi(raw_data);
let data = doc_data.borrow();
let mut data = doc_data.borrow_mut();
let mut data = &mut *data;
let rule_inclusion = RuleInclusion::from(rule_inclusion);
let pseudo = PseudoElement::from_pseudo_type(pseudo_type);
if cache_generation != data.undisplayed_style_cache_generation {
data.undisplayed_style_cache.clear();
data.undisplayed_style_cache_generation = cache_generation;
}
let stylist = &data.stylist;
let finish = |styles: &ElementStyles, is_probe: bool| -> Option<Arc<ComputedValues>> {
match pseudo {
Some(ref pseudo) => {
@ -5735,7 +5747,7 @@ pub extern "C" fn Servo_ResolveStyleLazily(
rule_inclusion,
styles,
/* inherited_styles = */ None,
&*data,
&stylist,
is_probe,
/* matching_func = */ None,
)
@ -5753,7 +5765,7 @@ pub extern "C" fn Servo_ResolveStyleLazily(
// not be in the `ElementData`, given they may exist but not be applicable
// to generate an actual pseudo-element (like, having a `content: none`).
if rule_inclusion == RuleInclusion::All {
let styles = element.mutate_data().and_then(|d| {
let styles = element.borrow_data().and_then(|d| {
if d.has_styles() {
finish(&d.styles, is_before_or_after)
} else {
@ -5763,13 +5775,18 @@ pub extern "C" fn Servo_ResolveStyleLazily(
if let Some(result) = styles {
return result.into();
}
if pseudo.is_none() && can_use_cache {
if let Some(style) = data.undisplayed_style_cache.get(&element.opaque()) {
return style.clone().into();
}
}
}
// We don't have the style ready. Go ahead and compute it as necessary.
let shared = create_shared_context(
&global_style_data,
&guard,
&data,
&stylist,
TraversalFlags::empty(),
unsafe { &*snapshots },
);
@ -5779,7 +5796,17 @@ pub extern "C" fn Servo_ResolveStyleLazily(
thread_local: &mut tlc,
};
let styles = resolve_style(&mut context, element, rule_inclusion, pseudo.as_ref());
let styles = resolve_style(
&mut context,
element,
rule_inclusion,
pseudo.as_ref(),
if can_use_cache {
Some(&mut data.undisplayed_style_cache)
} else {
None
},
);
finish(&styles, /* is_probe = */ false)
.expect("We're not probing, so we should always get a style back")
@ -6678,7 +6705,7 @@ pub extern "C" fn Servo_ProcessInvalidations(
let shared_style_context = create_shared_context(
&global_style_data,
&guard,
&per_doc_data,
&per_doc_data.stylist,
TraversalFlags::empty(),
unsafe { &*snapshots },
);