/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Per-node data used in style calculation.
usecrate::computed_value_flags::ComputedValueFlags; usecrate::context::{SharedStyleContext, StackLimitChecker}; usecrate::dom::TElement; usecrate::invalidation::element::invalidator::InvalidationResult; usecrate::invalidation::element::restyle_hints::RestyleHint; usecrate::properties::ComputedValues; usecrate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT}; usecrate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle}; usecrate::values::specified::TreeCountingFunction; #[cfg(feature = "gecko")] use malloc_size_of::MallocSizeOfOps; use selectors::matching::SelectorCaches; use servo_arc::Arc; use std::ops::{Deref, DerefMut}; use std::{fmt, mem};
#[cfg(debug_assertions)] use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
bitflags! { /// Various flags stored on ElementData. #[derive(Debug, Default)] pubstruct ElementDataFlags: 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 the primary style of this element data was reused from /// another element via a rule node comparison. This allows us to /// differentiate between elements that shared styles because they met /// all the criteria of the style sharing cache, compared to elements /// that reused style structs via rule node identity. /// /// The former gives us stronger transitive guarantees that allows us to /// apply the style sharing cache to cousins. const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2;
}
}
/// 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)] pubstruct 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
}
}
// 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 { letmut clone = Self::default(); for i in0..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 in0..EAGER_PSEUDO_COUNT { iflet 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. const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None];
impl EagerPseudoStyles { /// Returns whether there are any pseudo styles. pubfn is_empty(&self) -> bool { self.0.is_none()
}
/// Grabs a reference to the list of styles, if they exist. pubfn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> { matchself.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. pubfn 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. pubfn 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. pubfn set(&mutself, pseudo: &PseudoElement, value: Arc<ComputedValues>) { ifself.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)] pubstruct 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,
}
// There's one of these per rendered elements so it better be small.
size_of_test!(ElementStyles, 16);
/// Information on how this element uses viewport units. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pubenum ViewportUnitUsage { /// No viewport units are used.
None = 0, /// There are viewport units used from regular style rules (which means we /// should re-cascade).
FromDeclaration, /// There are viewport units used from container queries (which means we /// need to re-selector-match).
FromQuery,
}
#[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)] pubstruct ElementData { /// The styles for the element and its pseudo-elements. pub styles: ElementStyles,
/// The restyle damage, indicating what kind of layout changes are required /// afte restyling. pub damage: RestyleDamage,
/// The restyle hint, which indicates whether selectors need to be rematched /// for this element, its children, and its descendants. pub hint: RestyleHint,
/// Flags. pub flags: ElementDataFlags,
}
/// A struct that wraps ElementData, giving it the ability of doing thread-safety checks. #[derive(Debug, Default)] pubstruct ElementDataWrapper {
inner: std::cell::UnsafeCell<ElementData>, /// Implements optional (debug_assertions-only) thread-safety checking. #[cfg(debug_assertions)]
refcell: AtomicRefCell<()>,
}
/// A read-only reference to ElementData. #[derive(Debug)] pubstruct ElementDataMut<'a> {
v: &'a mut ElementData, #[cfg(debug_assertions)]
_borrow: AtomicRefMut<'a, ()>,
}
/// A mutable reference to ElementData. #[derive(Debug)] pubstruct ElementDataRef<'a> {
v: &'a ElementData, #[cfg(debug_assertions)]
_borrow: AtomicRef<'a, ()>,
}
impl ElementDataWrapper { /// Gets a non-exclusive reference to this ElementData. #[inline(always)] pubfn borrow(&self) -> ElementDataRef<'_> { #[cfg(debug_assertions)] let borrow = self.refcell.borrow();
ElementDataRef {
v: unsafe { &*self.inner.get() }, #[cfg(debug_assertions)]
_borrow: borrow,
}
}
/// Gets an exclusive reference to this ElementData. #[inline(always)] pubfn borrow_mut(&self) -> ElementDataMut<'_> { #[cfg(debug_assertions)] let borrow = self.refcell.borrow_mut();
ElementDataMut {
v: unsafe { &mut *self.inner.get() }, #[cfg(debug_assertions)]
_borrow: borrow,
}
}
}
impl<'a> Deref for ElementDataRef<'a> { type Target = ElementData; #[inline] fn deref(&self) -> &Self::Target {
&*self.v
}
}
impl<'a> Deref for ElementDataMut<'a> { type Target = ElementData; #[inline] fn deref(&self) -> &Self::Target {
&*self.v
}
}
// There's one of these per rendered elements so it better be small.
size_of_test!(ElementData, 24);
/// The kind of restyle that a single element should do. #[derive(Debug)] pubenum 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. pubfn invalidate_style_if_needed<'a, E: TElement>(
&mutself,
element: E,
shared_context: &SharedStyleContext,
stack_limit_checker: Option<&StackLimitChecker>,
selector_caches: &'a mut SelectorCaches,
) -> InvalidationResult { // In animation-only restyle we shouldn't touch snapshot at all. if shared_context.traversal_flags.for_animation_only() { return InvalidationResult::empty();
}
/// Returns true if this element has styles. #[inline] pubfn has_styles(&self) -> bool { self.styles.primary.is_some()
}
/// Returns this element's styles as resolved styles to use for sharing. pubfn share_styles(&self) -> ResolvedElementStyles {
ResolvedElementStyles {
primary: self.share_primary_style(),
pseudos: self.styles.pseudos.clone(),
}
}
/// Returns this element's primary style as a resolved style to use for sharing. pubfn share_primary_style(&self) -> PrimaryStyle { let reused_via_rule_node = self
.flags
.contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
/// Return a copy of the element's primary style as a resolved style with the /// given flags. pubfn clone_style_with_flags(&self, flags: ComputedValueFlags) -> ResolvedStyle { let primary_style = self.styles.primary(); // We are only using this pseudo to find the correct pseudo type so it // does not matter it technically belongs to a different style. let pseudo = primary_style.pseudo();
ResolvedStyle(
primary_style
.deref()
.clone_with_flags(flags, pseudo.as_ref()),
)
}
/// Sets a new set of styles, returning the old ones. pubfn set_styles(&mutself, new_styles: ResolvedElementStyles) -> ElementStyles { self.flags.set(
ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
new_styles.primary.reused_via_rule_node,
);
mem::replace(&mutself.styles, new_styles.into())
}
/// Returns the kind of restyling that we're going to need to do on this /// element, based of the stored restyle hint. pubfn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option<RestyleKind> { let style = matchself.styles.primary {
Some(ref s) => s,
None => return Some(RestyleKind::MatchAndCascade),
};
if shared_context.traversal_flags.for_animation_only() { returnself.restyle_kind_for_animation(shared_context);
}
let hint = self.hint; if hint.is_empty() { return None;
}
if needs_to_match_self(hint, style) { return Some(RestyleKind::MatchAndCascade);
}
if hint.has_replacements() {
debug_assert!(
!hint.has_animation_hint(), "Animation only restyle hint should have already processed"
); return Some(RestyleKind::CascadeWithReplacements(
hint & RestyleHint::replacements(),
));
}
if needs_to_recascade_self(hint, style) { return Some(RestyleKind::CascadeOnly);
}
None
}
/// Returns the kind of restyling for animation-only restyle. fn restyle_kind_for_animation(
&self,
shared_context: &SharedStyleContext,
) -> Option<RestyleKind> {
debug_assert!(shared_context.traversal_flags.for_animation_only());
debug_assert!(self.has_styles());
// FIXME: We should ideally restyle here, but it is a hack to work around our weird // animation-only traversal stuff: If we're display: none and the rules we could // match could change, we consider our style up-to-date. This is because re-cascading with // and old style doesn't guarantee returning the correct animation style (that's // bug 1393323). So if our display changed, and it changed from display: none, we would // incorrectly forget about it and wouldn't be able to correctly style our descendants // later. // XXX Figure out if this still makes sense. let hint = self.hint; ifself.styles.is_display_none() && hint.intersects(RestyleHint::RESTYLE_SELF) { return None;
}
let style = self.styles.primary(); // Return either CascadeWithReplacements or CascadeOnly in case of animation-only restyle. // I.e. animation-only restyle never does selector matching. if hint.has_animation_hint() { return Some(RestyleKind::CascadeWithReplacements(
hint & RestyleHint::for_animations(),
));
}
if needs_to_recascade_self(hint, style) { return Some(RestyleKind::CascadeOnly);
} return None;
}
/// Drops any restyle state from the element. /// /// FIXME(bholley): The only caller of this should probably just assert that the hint is empty /// and call clear_flags_and_damage(). #[inline] pubfn clear_restyle_state(&mutself) { self.hint = RestyleHint::empty(); self.clear_restyle_flags_and_damage();
}
/// Drops restyle flags and damage from the element. #[inline] pubfn clear_restyle_flags_and_damage(&mutself) { self.damage = RestyleDamage::empty(); self.flags.remove(ElementDataFlags::WAS_RESTYLED);
}
/// Mark this element as restyled, which is useful to know whether we need /// to do a post-traversal. pubfn set_restyled(&mutself) { self.flags.insert(ElementDataFlags::WAS_RESTYLED); self.flags
.remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
}
/// Returns true if this element was restyled. #[inline] pubfn is_restyle(&self) -> bool { self.flags.contains(ElementDataFlags::WAS_RESTYLED)
}
/// Mark that we traversed this element without computing any style for it. pubfn set_traversed_without_styling(&mutself) { self.flags
.insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
}
/// Returns whether this element has been part of a restyle. #[inline] pubfn contains_restyle_data(&self) -> bool { self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
}
/// Returns whether it is safe to perform cousin sharing based on the ComputedValues /// identity of the primary style in this ElementData. There are a few subtle things /// to check. /// /// First, if a parent element was already styled and we traversed past it without /// restyling it, that may be because our clever invalidation logic was able to prove /// that the styles of that element would remain unchanged despite changes to the id /// or class attributes. However, style sharing relies on the strong guarantee that all /// the classes and ids up the respective parent chains are identical. As such, if we /// skipped styling for one (or both) of the parents on this traversal, we can't share /// styles across cousins. Note that this is a somewhat conservative check. We could /// tighten it by having the invalidation logic explicitly flag elements for which it /// ellided styling. /// /// Second, we want to only consider elements whose ComputedValues match due to a hit /// in the style sharing cache, rather than due to the rule-node-based reuse that /// happens later in the styling pipeline. The former gives us the stronger guarantees /// we need for style sharing, the latter does not. pubfn safe_for_cousin_sharing(&self) -> bool { ifself.flags.intersects(
ElementDataFlags::TRAVERSED_WITHOUT_STYLING
| ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
) { returnfalse;
} if !self
.styles
.primary()
.get_box()
.clone_container_type()
.is_normal()
{ returnfalse;
} true
}
/// Measures memory usage. #[cfg(feature = "gecko")] pubfn 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.
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.