mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 11:25:00 +00:00
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:
parent
9d20fea826
commit
f64de373aa
@ -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(
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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(¤t.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.
|
||||
|
@ -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 },
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user