mirror of
https://github.com/servo/servo.git
synced 2025-07-16 03:43:38 +01:00
This patch makes the MallocSizeOf stuff in Stylo work more like the HeapSizeOf stuff already in Servo, except better. In particular, it adds deriving support for MallocSizeOf, which will make it easier to improve coverage. The patch does the following. - Combines servo/components/style/stylesheets/memory.rs and the heapsize crate into a new crate, malloc_size_of. - Forks the heapsize_derive crate, calling it malloc_size_of, so that MallocSizeOf can be derived. - Both the new crates have MIT/Apache licenses, like heapsize, in case they are incorporated into heapsize in the future. - Renames the methods within MallocSizeOf and the related traits so they are more concise. - Removes MallocSizeOfWithGuard. - Adds `derive(MallocSizeOf)` to a lot of types, in some cases replacing an equivalent or almost-equivalent hand-written implementation. - Adds stuff so that Rc/Arc can be handled properly.
463 lines
16 KiB
Rust
463 lines
16 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
//! Per-node data used in style calculation.
|
|
|
|
use context::{SharedStyleContext, StackLimitChecker};
|
|
use dom::TElement;
|
|
use invalidation::element::invalidator::InvalidationResult;
|
|
use invalidation::element::restyle_hints::RestyleHint;
|
|
#[cfg(feature = "gecko")]
|
|
use malloc_size_of::MallocSizeOfOps;
|
|
use properties::ComputedValues;
|
|
use properties::longhands::display::computed_value as display;
|
|
use rule_tree::StrongRuleNode;
|
|
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
|
|
use servo_arc::Arc;
|
|
use shared_lock::StylesheetGuards;
|
|
use std::fmt;
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
bitflags! {
|
|
flags RestyleFlags: u8 {
|
|
/// Whether the styles changed for this restyle.
|
|
const WAS_RESTYLED = 1 << 0,
|
|
/// Whether the last traversal of this element did not do
|
|
/// any style computation. This is not true during the initial
|
|
/// styling pass, nor is it true when we restyle (in which case
|
|
/// WAS_RESTYLED is set).
|
|
///
|
|
/// This bit always corresponds to the last time the element was
|
|
/// traversed, so each traversal simply updates it with the appropriate
|
|
/// value.
|
|
const TRAVERSED_WITHOUT_STYLING = 1 << 1,
|
|
/// Whether we reframed/reconstructed any ancestor or self.
|
|
const ANCESTOR_WAS_RECONSTRUCTED = 1 << 2,
|
|
}
|
|
}
|
|
|
|
/// Transient data used by the restyle algorithm. This structure is instantiated
|
|
/// either before or during restyle traversal, and is cleared at the end of node
|
|
/// processing.
|
|
#[derive(Debug)]
|
|
pub struct RestyleData {
|
|
/// The restyle hint, which indicates whether selectors need to be rematched
|
|
/// for this element, its children, and its descendants.
|
|
pub hint: RestyleHint,
|
|
|
|
/// A few flags to have in mind.
|
|
flags: RestyleFlags,
|
|
|
|
/// The restyle damage, indicating what kind of layout changes are required
|
|
/// afte restyling.
|
|
pub damage: RestyleDamage,
|
|
}
|
|
|
|
impl Default for RestyleData {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl RestyleData {
|
|
fn new() -> Self {
|
|
Self {
|
|
hint: RestyleHint::empty(),
|
|
flags: RestyleFlags::empty(),
|
|
damage: RestyleDamage::empty(),
|
|
}
|
|
}
|
|
|
|
/// Clear all the restyle state associated with this element.
|
|
///
|
|
/// FIXME(bholley): The only caller of this should probably just assert that
|
|
/// the hint is empty and call clear_flags_and_damage().
|
|
#[inline]
|
|
fn clear_restyle_state(&mut self) {
|
|
self.clear_restyle_flags_and_damage();
|
|
self.hint = RestyleHint::empty();
|
|
}
|
|
|
|
/// Clear restyle flags and damage.
|
|
///
|
|
/// Note that we don't touch the TRAVERSED_WITHOUT_STYLING bit, which gets
|
|
/// set to the correct value on each traversal. There's no reason anyone
|
|
/// needs to clear it, and clearing it accidentally mid-traversal could
|
|
/// cause incorrect style sharing behavior.
|
|
#[inline]
|
|
fn clear_restyle_flags_and_damage(&mut self) {
|
|
self.damage = RestyleDamage::empty();
|
|
self.flags = self.flags & TRAVERSED_WITHOUT_STYLING;
|
|
}
|
|
|
|
/// Returns whether this element or any ancestor is going to be
|
|
/// reconstructed.
|
|
pub fn reconstructed_self_or_ancestor(&self) -> bool {
|
|
self.reconstructed_ancestor() || self.reconstructed_self()
|
|
}
|
|
|
|
/// Returns whether this element is going to be reconstructed.
|
|
pub fn reconstructed_self(&self) -> bool {
|
|
self.damage.contains(RestyleDamage::reconstruct())
|
|
}
|
|
|
|
/// Returns whether any ancestor of this element is going to be
|
|
/// reconstructed.
|
|
fn reconstructed_ancestor(&self) -> bool {
|
|
self.flags.contains(ANCESTOR_WAS_RECONSTRUCTED)
|
|
}
|
|
|
|
/// Sets the flag that tells us whether we've reconstructed an ancestor.
|
|
pub fn set_reconstructed_ancestor(&mut self, reconstructed: bool) {
|
|
if reconstructed {
|
|
// If it weren't for animation-only traversals, we could assert
|
|
// `!self.reconstructed_ancestor()` here.
|
|
self.flags.insert(ANCESTOR_WAS_RECONSTRUCTED);
|
|
} else {
|
|
self.flags.remove(ANCESTOR_WAS_RECONSTRUCTED);
|
|
}
|
|
}
|
|
|
|
/// Mark this element as restyled, which is useful to know whether we need
|
|
/// to do a post-traversal.
|
|
pub fn set_restyled(&mut self) {
|
|
self.flags.insert(WAS_RESTYLED);
|
|
self.flags.remove(TRAVERSED_WITHOUT_STYLING);
|
|
}
|
|
|
|
/// Returns true if this element was restyled.
|
|
#[inline]
|
|
pub fn is_restyle(&self) -> bool {
|
|
self.flags.contains(WAS_RESTYLED)
|
|
}
|
|
|
|
/// Mark that we traversed this element without computing any style for it.
|
|
pub fn set_traversed_without_styling(&mut self) {
|
|
self.flags.insert(TRAVERSED_WITHOUT_STYLING);
|
|
}
|
|
|
|
/// Returns whether the element was traversed without computing any style for
|
|
/// it.
|
|
pub fn traversed_without_styling(&self) -> bool {
|
|
self.flags.contains(TRAVERSED_WITHOUT_STYLING)
|
|
}
|
|
|
|
/// Returns whether this element has been part of a restyle.
|
|
#[inline]
|
|
pub fn contains_restyle_data(&self) -> bool {
|
|
self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
|
|
}
|
|
}
|
|
|
|
/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements.
|
|
///
|
|
/// We use an Arc so that sharing these styles via the style sharing cache does
|
|
/// not require duplicate allocations. We leverage the copy-on-write semantics of
|
|
/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations)
|
|
/// in servo_arc.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>);
|
|
|
|
#[derive(Default)]
|
|
struct EagerPseudoArray(EagerPseudoArrayInner);
|
|
type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT];
|
|
|
|
impl Deref for EagerPseudoArray {
|
|
type Target = EagerPseudoArrayInner;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for EagerPseudoArray {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
// Manually implement `Clone` here because the derived impl of `Clone` for
|
|
// array types assumes the value inside is `Copy`.
|
|
impl Clone for EagerPseudoArray {
|
|
fn clone(&self) -> Self {
|
|
let mut clone = Self::default();
|
|
for i in 0..EAGER_PSEUDO_COUNT {
|
|
clone[i] = self.0[i].clone();
|
|
}
|
|
clone
|
|
}
|
|
}
|
|
|
|
// Override Debug to print which pseudos we have, and substitute the rule node
|
|
// for the much-more-verbose ComputedValues stringification.
|
|
impl fmt::Debug for EagerPseudoArray {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "EagerPseudoArray {{ ")?;
|
|
for i in 0..EAGER_PSEUDO_COUNT {
|
|
if let Some(ref values) = self[i] {
|
|
write!(f, "{:?}: {:?}, ", PseudoElement::from_eager_index(i), &values.rules)?;
|
|
}
|
|
}
|
|
write!(f, "}}")
|
|
}
|
|
}
|
|
|
|
// Can't use [None; EAGER_PSEUDO_COUNT] here because it complains
|
|
// about Copy not being implemented for our Arc type.
|
|
#[cfg(feature = "gecko")]
|
|
const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None];
|
|
#[cfg(feature = "servo")]
|
|
const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None];
|
|
|
|
impl EagerPseudoStyles {
|
|
/// Returns whether there are any pseudo styles.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.0.is_none()
|
|
}
|
|
|
|
/// Grabs a reference to the list of styles, if they exist.
|
|
pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> {
|
|
match self.0 {
|
|
None => None,
|
|
Some(ref x) => Some(&x.0),
|
|
}
|
|
}
|
|
|
|
/// Grabs a reference to the list of styles or a list of None if
|
|
/// there are no styles to be had.
|
|
pub fn as_array(&self) -> &EagerPseudoArrayInner {
|
|
self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY)
|
|
}
|
|
|
|
/// Returns a reference to the style for a given eager pseudo, if it exists.
|
|
pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
|
|
debug_assert!(pseudo.is_eager());
|
|
self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
|
|
}
|
|
|
|
/// Sets the style for the eager pseudo.
|
|
pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
|
|
if self.0.is_none() {
|
|
self.0 = Some(Arc::new(Default::default()));
|
|
}
|
|
let arr = Arc::make_mut(self.0.as_mut().unwrap());
|
|
arr[pseudo.eager_index()] = Some(value);
|
|
}
|
|
}
|
|
|
|
/// The styles associated with a node, including the styles for any
|
|
/// pseudo-elements.
|
|
#[derive(Clone, Default)]
|
|
pub struct ElementStyles {
|
|
/// The element's style.
|
|
pub primary: Option<Arc<ComputedValues>>,
|
|
/// A list of the styles for the element's eagerly-cascaded pseudo-elements.
|
|
pub pseudos: EagerPseudoStyles,
|
|
}
|
|
|
|
impl ElementStyles {
|
|
/// Returns the primary style.
|
|
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
|
|
self.primary.as_ref()
|
|
}
|
|
|
|
/// Returns the primary style. Panic if no style available.
|
|
pub fn primary(&self) -> &Arc<ComputedValues> {
|
|
self.primary.as_ref().unwrap()
|
|
}
|
|
|
|
/// Whether this element `display` value is `none`.
|
|
pub fn is_display_none(&self) -> bool {
|
|
self.primary().get_box().clone_display() == display::T::none
|
|
}
|
|
|
|
#[cfg(feature = "gecko")]
|
|
fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize {
|
|
// As the method name suggests, we don't measures the ComputedValues
|
|
// here, because they are measured on the C++ side.
|
|
|
|
// XXX: measure the EagerPseudoArray itself, but not the ComputedValues
|
|
// within it.
|
|
|
|
0
|
|
}
|
|
}
|
|
|
|
// We manually implement Debug for ElementStyles so that we can avoid the
|
|
// verbose stringification of every property in the ComputedValues. We
|
|
// substitute the rule node instead.
|
|
impl fmt::Debug for ElementStyles {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "ElementStyles {{ primary: {:?}, pseudos: {:?} }}",
|
|
self.primary.as_ref().map(|x| &x.rules), self.pseudos)
|
|
}
|
|
}
|
|
|
|
/// Style system data associated with an Element.
|
|
///
|
|
/// In Gecko, this hangs directly off the Element. Servo, this is embedded
|
|
/// inside of layout data, which itself hangs directly off the Element. In
|
|
/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
|
|
#[derive(Debug, Default)]
|
|
pub struct ElementData {
|
|
/// The styles for the element and its pseudo-elements.
|
|
pub styles: ElementStyles,
|
|
|
|
/// Restyle state.
|
|
pub restyle: RestyleData,
|
|
}
|
|
|
|
/// The kind of restyle that a single element should do.
|
|
#[derive(Debug)]
|
|
pub enum RestyleKind {
|
|
/// We need to run selector matching plus re-cascade, that is, a full
|
|
/// restyle.
|
|
MatchAndCascade,
|
|
/// We need to recascade with some replacement rule, such as the style
|
|
/// attribute, or animation rules.
|
|
CascadeWithReplacements(RestyleHint),
|
|
/// We only need to recascade, for example, because only inherited
|
|
/// properties in the parent changed.
|
|
CascadeOnly,
|
|
}
|
|
|
|
impl ElementData {
|
|
/// Invalidates style for this element, its descendants, and later siblings,
|
|
/// based on the snapshot of the element that we took when attributes or
|
|
/// state changed.
|
|
pub fn invalidate_style_if_needed<'a, E: TElement>(
|
|
&mut self,
|
|
element: E,
|
|
shared_context: &SharedStyleContext,
|
|
stack_limit_checker: Option<&StackLimitChecker>,
|
|
) -> InvalidationResult {
|
|
// In animation-only restyle we shouldn't touch snapshot at all.
|
|
if shared_context.traversal_flags.for_animation_only() {
|
|
return InvalidationResult::empty();
|
|
}
|
|
|
|
use invalidation::element::invalidator::TreeStyleInvalidator;
|
|
|
|
debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
|
|
handled_snapshot: {}, pseudo: {:?}",
|
|
element,
|
|
shared_context.traversal_flags,
|
|
element.has_snapshot(),
|
|
element.handled_snapshot(),
|
|
element.implemented_pseudo_element());
|
|
|
|
if !element.has_snapshot() || element.handled_snapshot() {
|
|
return InvalidationResult::empty();
|
|
}
|
|
|
|
let invalidator = TreeStyleInvalidator::new(
|
|
element,
|
|
Some(self),
|
|
shared_context,
|
|
stack_limit_checker,
|
|
);
|
|
let result = invalidator.invalidate();
|
|
unsafe { element.set_handled_snapshot() }
|
|
debug_assert!(element.handled_snapshot());
|
|
result
|
|
}
|
|
|
|
/// Returns true if this element has styles.
|
|
#[inline]
|
|
pub fn has_styles(&self) -> bool {
|
|
self.styles.primary.is_some()
|
|
}
|
|
|
|
/// Returns the kind of restyling that we're going to need to do on this
|
|
/// element, based of the stored restyle hint.
|
|
pub fn restyle_kind(
|
|
&self,
|
|
shared_context: &SharedStyleContext
|
|
) -> RestyleKind {
|
|
if shared_context.traversal_flags.for_animation_only() {
|
|
return self.restyle_kind_for_animation(shared_context);
|
|
}
|
|
|
|
if !self.has_styles() {
|
|
return RestyleKind::MatchAndCascade;
|
|
}
|
|
|
|
let hint = self.restyle.hint;
|
|
if hint.match_self() {
|
|
return RestyleKind::MatchAndCascade;
|
|
}
|
|
|
|
if hint.has_replacements() {
|
|
debug_assert!(!hint.has_animation_hint(),
|
|
"Animation only restyle hint should have already processed");
|
|
return RestyleKind::CascadeWithReplacements(hint & RestyleHint::replacements());
|
|
}
|
|
|
|
debug_assert!(hint.has_recascade_self(),
|
|
"We definitely need to do something: {:?}!", hint);
|
|
return RestyleKind::CascadeOnly;
|
|
}
|
|
|
|
/// Returns the kind of restyling for animation-only restyle.
|
|
fn restyle_kind_for_animation(
|
|
&self,
|
|
shared_context: &SharedStyleContext,
|
|
) -> RestyleKind {
|
|
debug_assert!(shared_context.traversal_flags.for_animation_only());
|
|
debug_assert!(self.has_styles(),
|
|
"Unstyled element shouldn't be traversed during \
|
|
animation-only traversal");
|
|
|
|
// return either CascadeWithReplacements or CascadeOnly in case of
|
|
// animation-only restyle. I.e. animation-only restyle never does
|
|
// selector matching.
|
|
let hint = self.restyle.hint;
|
|
if hint.has_animation_hint() {
|
|
return RestyleKind::CascadeWithReplacements(hint & RestyleHint::for_animations());
|
|
}
|
|
|
|
return RestyleKind::CascadeOnly;
|
|
}
|
|
|
|
/// Return true if important rules are different.
|
|
/// We use this to make sure the cascade of off-main thread animations is correct.
|
|
/// Note: Ignore custom properties for now because we only support opacity and transform
|
|
/// properties for animations running on compositor. Actually, we only care about opacity
|
|
/// and transform for now, but it's fine to compare all properties and let the user
|
|
/// the check which properties do they want.
|
|
/// If it costs too much, get_properties_overriding_animations() should return a set
|
|
/// containing only opacity and transform properties.
|
|
pub fn important_rules_are_different(
|
|
&self,
|
|
rules: &StrongRuleNode,
|
|
guards: &StylesheetGuards
|
|
) -> bool {
|
|
debug_assert!(self.has_styles());
|
|
let (important_rules, _custom) =
|
|
self.styles.primary().rules().get_properties_overriding_animations(&guards);
|
|
let (other_important_rules, _custom) = rules.get_properties_overriding_animations(&guards);
|
|
important_rules != other_important_rules
|
|
}
|
|
|
|
/// Drops any restyle state from the element.
|
|
#[inline]
|
|
pub fn clear_restyle_state(&mut self) {
|
|
self.restyle.clear_restyle_state();
|
|
}
|
|
|
|
/// Drops restyle flags and damage from the element.
|
|
#[inline]
|
|
pub fn clear_restyle_flags_and_damage(&mut self) {
|
|
self.restyle.clear_restyle_flags_and_damage();
|
|
}
|
|
|
|
/// Measures memory usage.
|
|
#[cfg(feature = "gecko")]
|
|
pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize {
|
|
let n = self.styles.size_of_excluding_cvs(ops);
|
|
|
|
// We may measure more fields in the future if DMD says it's worth it.
|
|
|
|
n
|
|
}
|
|
}
|