tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

data.rs (20795B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 //! Per-node data used in style calculation.
      6 
      7 use crate::computed_value_flags::ComputedValueFlags;
      8 use crate::context::{SharedStyleContext, StackLimitChecker};
      9 use crate::dom::TElement;
     10 use crate::invalidation::element::invalidator::InvalidationResult;
     11 use crate::invalidation::element::restyle_hints::RestyleHint;
     12 use crate::properties::ComputedValues;
     13 use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT};
     14 use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle};
     15 #[cfg(feature = "gecko")]
     16 use malloc_size_of::MallocSizeOfOps;
     17 use selectors::matching::SelectorCaches;
     18 use servo_arc::Arc;
     19 use std::fmt;
     20 use std::mem;
     21 use std::ops::{Deref, DerefMut};
     22 
     23 bitflags! {
     24    /// Various flags stored on ElementData.
     25    #[derive(Debug, Default)]
     26    pub struct ElementDataFlags: u8 {
     27        /// Whether the styles changed for this restyle.
     28        const WAS_RESTYLED = 1 << 0;
     29        /// Whether the last traversal of this element did not do
     30        /// any style computation. This is not true during the initial
     31        /// styling pass, nor is it true when we restyle (in which case
     32        /// WAS_RESTYLED is set).
     33        ///
     34        /// This bit always corresponds to the last time the element was
     35        /// traversed, so each traversal simply updates it with the appropriate
     36        /// value.
     37        const TRAVERSED_WITHOUT_STYLING = 1 << 1;
     38 
     39        /// Whether the primary style of this element data was reused from
     40        /// another element via a rule node comparison. This allows us to
     41        /// differentiate between elements that shared styles because they met
     42        /// all the criteria of the style sharing cache, compared to elements
     43        /// that reused style structs via rule node identity.
     44        ///
     45        /// The former gives us stronger transitive guarantees that allows us to
     46        /// apply the style sharing cache to cousins.
     47        const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2;
     48 
     49        /// Whether this element may have matched rules inside @starting-style.
     50        const MAY_HAVE_STARTING_STYLE = 1 << 3;
     51    }
     52 }
     53 
     54 /// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements.
     55 ///
     56 /// We use an Arc so that sharing these styles via the style sharing cache does
     57 /// not require duplicate allocations. We leverage the copy-on-write semantics of
     58 /// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations)
     59 /// in servo_arc.
     60 #[derive(Clone, Debug, Default)]
     61 pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>);
     62 
     63 #[derive(Default)]
     64 struct EagerPseudoArray(EagerPseudoArrayInner);
     65 type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT];
     66 
     67 impl Deref for EagerPseudoArray {
     68    type Target = EagerPseudoArrayInner;
     69    fn deref(&self) -> &Self::Target {
     70        &self.0
     71    }
     72 }
     73 
     74 impl DerefMut for EagerPseudoArray {
     75    fn deref_mut(&mut self) -> &mut Self::Target {
     76        &mut self.0
     77    }
     78 }
     79 
     80 // Manually implement `Clone` here because the derived impl of `Clone` for
     81 // array types assumes the value inside is `Copy`.
     82 impl Clone for EagerPseudoArray {
     83    fn clone(&self) -> Self {
     84        let mut clone = Self::default();
     85        for i in 0..EAGER_PSEUDO_COUNT {
     86            clone[i] = self.0[i].clone();
     87        }
     88        clone
     89    }
     90 }
     91 
     92 // Override Debug to print which pseudos we have, and substitute the rule node
     93 // for the much-more-verbose ComputedValues stringification.
     94 impl fmt::Debug for EagerPseudoArray {
     95    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     96        write!(f, "EagerPseudoArray {{ ")?;
     97        for i in 0..EAGER_PSEUDO_COUNT {
     98            if let Some(ref values) = self[i] {
     99                write!(
    100                    f,
    101                    "{:?}: {:?}, ",
    102                    PseudoElement::from_eager_index(i),
    103                    &values.rules
    104                )?;
    105            }
    106        }
    107        write!(f, "}}")
    108    }
    109 }
    110 
    111 // Can't use [None; EAGER_PSEUDO_COUNT] here because it complains
    112 // about Copy not being implemented for our Arc type.
    113 #[cfg(feature = "gecko")]
    114 const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None];
    115 #[cfg(feature = "servo")]
    116 const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None];
    117 
    118 impl EagerPseudoStyles {
    119    /// Returns whether there are any pseudo styles.
    120    pub fn is_empty(&self) -> bool {
    121        self.0.is_none()
    122    }
    123 
    124    /// Grabs a reference to the list of styles, if they exist.
    125    pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> {
    126        match self.0 {
    127            None => None,
    128            Some(ref x) => Some(&x.0),
    129        }
    130    }
    131 
    132    /// Grabs a reference to the list of styles or a list of None if
    133    /// there are no styles to be had.
    134    pub fn as_array(&self) -> &EagerPseudoArrayInner {
    135        self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY)
    136    }
    137 
    138    /// Returns a reference to the style for a given eager pseudo, if it exists.
    139    pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
    140        debug_assert!(pseudo.is_eager());
    141        self.0
    142            .as_ref()
    143            .and_then(|p| p[pseudo.eager_index()].as_ref())
    144    }
    145 
    146    /// Sets the style for the eager pseudo.
    147    pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
    148        if self.0.is_none() {
    149            self.0 = Some(Arc::new(Default::default()));
    150        }
    151        let arr = Arc::make_mut(self.0.as_mut().unwrap());
    152        arr[pseudo.eager_index()] = Some(value);
    153    }
    154 }
    155 
    156 /// The styles associated with a node, including the styles for any
    157 /// pseudo-elements.
    158 #[derive(Clone, Default)]
    159 pub struct ElementStyles {
    160    /// The element's style.
    161    pub primary: Option<Arc<ComputedValues>>,
    162    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
    163    pub pseudos: EagerPseudoStyles,
    164 }
    165 
    166 // There's one of these per rendered elements so it better be small.
    167 size_of_test!(ElementStyles, 16);
    168 
    169 /// Information on how this element uses viewport units.
    170 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
    171 pub enum ViewportUnitUsage {
    172    /// No viewport units are used.
    173    None = 0,
    174    /// There are viewport units used from regular style rules (which means we
    175    /// should re-cascade).
    176    FromDeclaration,
    177    /// There are viewport units used from container queries (which means we
    178    /// need to re-selector-match).
    179    FromQuery,
    180 }
    181 
    182 impl ElementStyles {
    183    /// Returns the primary style.
    184    pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
    185        self.primary.as_ref()
    186    }
    187 
    188    /// Returns the primary style.  Panic if no style available.
    189    pub fn primary(&self) -> &Arc<ComputedValues> {
    190        self.primary.as_ref().unwrap()
    191    }
    192 
    193    /// Whether this element `display` value is `none`.
    194    pub fn is_display_none(&self) -> bool {
    195        self.primary().get_box().clone_display().is_none()
    196    }
    197 
    198    /// Whether this element uses viewport units.
    199    pub fn viewport_unit_usage(&self) -> ViewportUnitUsage {
    200        fn usage_from_flags(flags: ComputedValueFlags) -> ViewportUnitUsage {
    201            if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES) {
    202                return ViewportUnitUsage::FromQuery;
    203            }
    204            if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) {
    205                return ViewportUnitUsage::FromDeclaration;
    206            }
    207            ViewportUnitUsage::None
    208        }
    209 
    210        let mut usage = usage_from_flags(self.primary().flags);
    211        for pseudo_style in self.pseudos.as_array() {
    212            if let Some(ref pseudo_style) = pseudo_style {
    213                usage = std::cmp::max(usage, usage_from_flags(pseudo_style.flags));
    214            }
    215        }
    216 
    217        usage
    218    }
    219 
    220    #[cfg(feature = "gecko")]
    221    fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize {
    222        // As the method name suggests, we don't measures the ComputedValues
    223        // here, because they are measured on the C++ side.
    224 
    225        // XXX: measure the EagerPseudoArray itself, but not the ComputedValues
    226        // within it.
    227 
    228        0
    229    }
    230 }
    231 
    232 // We manually implement Debug for ElementStyles so that we can avoid the
    233 // verbose stringification of every property in the ComputedValues. We
    234 // substitute the rule node instead.
    235 impl fmt::Debug for ElementStyles {
    236    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    237        write!(
    238            f,
    239            "ElementStyles {{ primary: {:?}, pseudos: {:?} }}",
    240            self.primary.as_ref().map(|x| &x.rules),
    241            self.pseudos
    242        )
    243    }
    244 }
    245 
    246 /// Style system data associated with an Element.
    247 ///
    248 /// In Gecko, this hangs directly off the Element. Servo, this is embedded
    249 /// inside of layout data, which itself hangs directly off the Element. In
    250 /// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
    251 #[derive(Debug, Default)]
    252 pub struct ElementData {
    253    /// The styles for the element and its pseudo-elements.
    254    pub styles: ElementStyles,
    255 
    256    /// The restyle damage, indicating what kind of layout changes are required
    257    /// afte restyling.
    258    pub damage: RestyleDamage,
    259 
    260    /// The restyle hint, which indicates whether selectors need to be rematched
    261    /// for this element, its children, and its descendants.
    262    pub hint: RestyleHint,
    263 
    264    /// Flags.
    265    pub flags: ElementDataFlags,
    266 }
    267 
    268 // There's one of these per rendered elements so it better be small.
    269 size_of_test!(ElementData, 24);
    270 
    271 /// The kind of restyle that a single element should do.
    272 #[derive(Debug)]
    273 pub enum RestyleKind {
    274    /// We need to run selector matching plus re-cascade, that is, a full
    275    /// restyle.
    276    MatchAndCascade,
    277    /// We need to recascade with some replacement rule, such as the style
    278    /// attribute, or animation rules.
    279    CascadeWithReplacements(RestyleHint),
    280    /// We only need to recascade, for example, because only inherited
    281    /// properties in the parent changed.
    282    CascadeOnly,
    283 }
    284 
    285 impl ElementData {
    286    /// Invalidates style for this element, its descendants, and later siblings,
    287    /// based on the snapshot of the element that we took when attributes or
    288    /// state changed.
    289    pub fn invalidate_style_if_needed<'a, E: TElement>(
    290        &mut self,
    291        element: E,
    292        shared_context: &SharedStyleContext,
    293        stack_limit_checker: Option<&StackLimitChecker>,
    294        selector_caches: &'a mut SelectorCaches,
    295    ) -> InvalidationResult {
    296        // In animation-only restyle we shouldn't touch snapshot at all.
    297        if shared_context.traversal_flags.for_animation_only() {
    298            return InvalidationResult::empty();
    299        }
    300 
    301        use crate::invalidation::element::invalidator::TreeStyleInvalidator;
    302        use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor;
    303 
    304        debug!(
    305            "invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
    306             handled_snapshot: {}, pseudo: {:?}",
    307            element,
    308            shared_context.traversal_flags,
    309            element.has_snapshot(),
    310            element.handled_snapshot(),
    311            element.implemented_pseudo_element()
    312        );
    313 
    314        if !element.has_snapshot() || element.handled_snapshot() {
    315            return InvalidationResult::empty();
    316        }
    317 
    318        let mut processor =
    319            StateAndAttrInvalidationProcessor::new(shared_context, element, self, selector_caches);
    320 
    321        let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor);
    322 
    323        let result = invalidator.invalidate();
    324 
    325        unsafe { element.set_handled_snapshot() }
    326        debug_assert!(element.handled_snapshot());
    327 
    328        result
    329    }
    330 
    331    /// Returns true if this element has styles.
    332    #[inline]
    333    pub fn has_styles(&self) -> bool {
    334        self.styles.primary.is_some()
    335    }
    336 
    337    /// Returns this element's styles as resolved styles to use for sharing.
    338    pub fn share_styles(&self) -> ResolvedElementStyles {
    339        ResolvedElementStyles {
    340            primary: self.share_primary_style(),
    341            pseudos: self.styles.pseudos.clone(),
    342        }
    343    }
    344 
    345    /// Returns this element's primary style as a resolved style to use for sharing.
    346    pub fn share_primary_style(&self) -> PrimaryStyle {
    347        let reused_via_rule_node = self
    348            .flags
    349            .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
    350        let may_have_starting_style = self
    351            .flags
    352            .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE);
    353 
    354        PrimaryStyle {
    355            style: ResolvedStyle(self.styles.primary().clone()),
    356            reused_via_rule_node,
    357            may_have_starting_style,
    358        }
    359    }
    360 
    361    /// Sets a new set of styles, returning the old ones.
    362    pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
    363        self.flags.set(
    364            ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
    365            new_styles.primary.reused_via_rule_node,
    366        );
    367        self.flags.set(
    368            ElementDataFlags::MAY_HAVE_STARTING_STYLE,
    369            new_styles.primary.may_have_starting_style,
    370        );
    371 
    372        mem::replace(&mut self.styles, new_styles.into())
    373    }
    374 
    375    /// Returns the kind of restyling that we're going to need to do on this
    376    /// element, based of the stored restyle hint.
    377    pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option<RestyleKind> {
    378        let style = match self.styles.primary {
    379            Some(ref s) => s,
    380            None => return Some(RestyleKind::MatchAndCascade),
    381        };
    382 
    383        if shared_context.traversal_flags.for_animation_only() {
    384            return self.restyle_kind_for_animation(shared_context);
    385        }
    386 
    387        let hint = self.hint;
    388        if hint.is_empty() {
    389            return None;
    390        }
    391 
    392        let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF)
    393            || (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style());
    394        if needs_to_match_self {
    395            return Some(RestyleKind::MatchAndCascade);
    396        }
    397 
    398        if hint.has_replacements() {
    399            debug_assert!(
    400                !hint.has_animation_hint(),
    401                "Animation only restyle hint should have already processed"
    402            );
    403            return Some(RestyleKind::CascadeWithReplacements(
    404                hint & RestyleHint::replacements(),
    405            ));
    406        }
    407 
    408        let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF)
    409            || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE)
    410                && style
    411                    .flags
    412                    .contains(ComputedValueFlags::INHERITS_RESET_STYLE));
    413        if needs_to_recascade_self {
    414            return Some(RestyleKind::CascadeOnly);
    415        }
    416 
    417        None
    418    }
    419 
    420    /// Returns the kind of restyling for animation-only restyle.
    421    fn restyle_kind_for_animation(
    422        &self,
    423        shared_context: &SharedStyleContext,
    424    ) -> Option<RestyleKind> {
    425        debug_assert!(shared_context.traversal_flags.for_animation_only());
    426        debug_assert!(self.has_styles());
    427 
    428        // FIXME: We should ideally restyle here, but it is a hack to work around our weird
    429        // animation-only traversal stuff: If we're display: none and the rules we could
    430        // match could change, we consider our style up-to-date. This is because re-cascading with
    431        // and old style doesn't guarantee returning the correct animation style (that's
    432        // bug 1393323). So if our display changed, and it changed from display: none, we would
    433        // incorrectly forget about it and wouldn't be able to correctly style our descendants
    434        // later.
    435        // XXX Figure out if this still makes sense.
    436        let hint = self.hint;
    437        if self.styles.is_display_none() && hint.intersects(RestyleHint::RESTYLE_SELF) {
    438            return None;
    439        }
    440 
    441        let style = self.styles.primary();
    442        // Return either CascadeWithReplacements or CascadeOnly in case of animation-only restyle.
    443        // I.e. animation-only restyle never does selector matching.
    444        if hint.has_animation_hint() {
    445            return Some(RestyleKind::CascadeWithReplacements(
    446                hint & RestyleHint::for_animations(),
    447            ));
    448        }
    449 
    450        let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF)
    451            || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE)
    452                && style
    453                    .flags
    454                    .contains(ComputedValueFlags::INHERITS_RESET_STYLE));
    455        if needs_to_recascade_self {
    456            return Some(RestyleKind::CascadeOnly);
    457        }
    458        return None;
    459    }
    460 
    461    /// Drops any restyle state from the element.
    462    ///
    463    /// FIXME(bholley): The only caller of this should probably just assert that the hint is empty
    464    /// and call clear_flags_and_damage().
    465    #[inline]
    466    pub fn clear_restyle_state(&mut self) {
    467        self.hint = RestyleHint::empty();
    468        self.clear_restyle_flags_and_damage();
    469    }
    470 
    471    /// Drops restyle flags and damage from the element.
    472    #[inline]
    473    pub fn clear_restyle_flags_and_damage(&mut self) {
    474        self.damage = RestyleDamage::empty();
    475        self.flags.remove(ElementDataFlags::WAS_RESTYLED);
    476    }
    477 
    478    /// Mark this element as restyled, which is useful to know whether we need
    479    /// to do a post-traversal.
    480    pub fn set_restyled(&mut self) {
    481        self.flags.insert(ElementDataFlags::WAS_RESTYLED);
    482        self.flags
    483            .remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
    484    }
    485 
    486    /// Returns true if this element was restyled.
    487    #[inline]
    488    pub fn is_restyle(&self) -> bool {
    489        self.flags.contains(ElementDataFlags::WAS_RESTYLED)
    490    }
    491 
    492    /// Mark that we traversed this element without computing any style for it.
    493    pub fn set_traversed_without_styling(&mut self) {
    494        self.flags
    495            .insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
    496    }
    497 
    498    /// Returns whether this element has been part of a restyle.
    499    #[inline]
    500    pub fn contains_restyle_data(&self) -> bool {
    501        self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
    502    }
    503 
    504    /// Returns whether it is safe to perform cousin sharing based on the ComputedValues
    505    /// identity of the primary style in this ElementData. There are a few subtle things
    506    /// to check.
    507    ///
    508    /// First, if a parent element was already styled and we traversed past it without
    509    /// restyling it, that may be because our clever invalidation logic was able to prove
    510    /// that the styles of that element would remain unchanged despite changes to the id
    511    /// or class attributes. However, style sharing relies on the strong guarantee that all
    512    /// the classes and ids up the respective parent chains are identical. As such, if we
    513    /// skipped styling for one (or both) of the parents on this traversal, we can't share
    514    /// styles across cousins. Note that this is a somewhat conservative check. We could
    515    /// tighten it by having the invalidation logic explicitly flag elements for which it
    516    /// ellided styling.
    517    ///
    518    /// Second, we want to only consider elements whose ComputedValues match due to a hit
    519    /// in the style sharing cache, rather than due to the rule-node-based reuse that
    520    /// happens later in the styling pipeline. The former gives us the stronger guarantees
    521    /// we need for style sharing, the latter does not.
    522    pub fn safe_for_cousin_sharing(&self) -> bool {
    523        if self.flags.intersects(
    524            ElementDataFlags::TRAVERSED_WITHOUT_STYLING
    525                | ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
    526        ) {
    527            return false;
    528        }
    529        if !self
    530            .styles
    531            .primary()
    532            .get_box()
    533            .clone_container_type()
    534            .is_normal()
    535        {
    536            return false;
    537        }
    538        true
    539    }
    540 
    541    /// Measures memory usage.
    542    #[cfg(feature = "gecko")]
    543    pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize {
    544        let n = self.styles.size_of_excluding_cvs(ops);
    545 
    546        // We may measure more fields in the future if DMD says it's worth it.
    547 
    548        n
    549    }
    550 
    551    /// Returns true if this element data may need to compute the starting style for CSS
    552    /// transitions.
    553    #[inline]
    554    pub fn may_have_starting_style(&self) -> bool {
    555        self.flags
    556            .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE)
    557    }
    558 }