summaryrefslogtreecommitdiffstats
path: root/servo/components/style/data.rs
blob: 62dff225f8fd596e671196bd2e3f569067719c0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
/* 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 https://mozilla.org/MPL/2.0/. */

//! Per-node data used in style calculation.

use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{SharedStyleContext, StackLimitChecker};
use crate::dom::TElement;
use crate::invalidation::element::invalidator::InvalidationResult;
use crate::invalidation::element::restyle_hints::RestyleHint;
use crate::properties::ComputedValues;
use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT};
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle};
#[cfg(feature = "gecko")]
use malloc_size_of::MallocSizeOfOps;
use selectors::NthIndexCache;
use servo_arc::Arc;
use std::fmt;
use std::mem;
use std::ops::{Deref, DerefMut};

bitflags! {
    /// Various flags stored on ElementData.
    #[derive(Default)]
    pub struct 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)]
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,
}

// 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)]
pub enum 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,
}

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().is_none()
    }

    /// Whether this element uses viewport units.
    pub fn viewport_unit_usage(&self) -> ViewportUnitUsage {
        fn usage_from_flags(flags: ComputedValueFlags) -> ViewportUnitUsage {
            if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES) {
                return ViewportUnitUsage::FromQuery;
            }
            if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) {
                return ViewportUnitUsage::FromDeclaration;
            }
            ViewportUnitUsage::None
        }

        let mut usage = usage_from_flags(self.primary().flags);
        for pseudo_style in self.pseudos.as_array() {
            if let Some(ref pseudo_style) = pseudo_style {
                usage = std::cmp::max(usage, usage_from_flags(pseudo_style.flags));
            }
        }

        usage
    }

    #[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,

    /// 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,
}

// 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)]
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>,
        nth_index_cache: &mut NthIndexCache,
    ) -> InvalidationResult {
        // In animation-only restyle we shouldn't touch snapshot at all.
        if shared_context.traversal_flags.for_animation_only() {
            return InvalidationResult::empty();
        }

        use crate::invalidation::element::invalidator::TreeStyleInvalidator;
        use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor;

        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 mut processor =
            StateAndAttrInvalidationProcessor::new(shared_context, element, self, nth_index_cache);

        let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor);

        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 this element's styles as resolved styles to use for sharing.
    pub fn 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.
    pub fn share_primary_style(&self) -> PrimaryStyle {
        let reused_via_rule_node = self
            .flags
            .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);

        PrimaryStyle {
            style: ResolvedStyle(self.styles.primary().clone()),
            reused_via_rule_node,
        }
    }

    /// Sets a new set of styles, returning the old ones.
    pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
        if new_styles.primary.reused_via_rule_node {
            self.flags
                .insert(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
        } else {
            self.flags
                .remove(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
        }
        mem::replace(&mut self.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.
    pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option<RestyleKind> {
        if shared_context.traversal_flags.for_animation_only() {
            return self.restyle_kind_for_animation(shared_context);
        }

        let style = match self.styles.primary {
            Some(ref s) => s,
            None => return Some(RestyleKind::MatchAndCascade),
        };

        let hint = self.hint;
        if hint.is_empty() {
            return None;
        }

        let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF) ||
            (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style());
        if needs_to_match_self {
            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(),
            ));
        }

        let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) ||
            (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) &&
                style
                    .flags
                    .contains(ComputedValueFlags::INHERITS_RESET_STYLE));
        if needs_to_recascade_self {
            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(),
            "animation traversal doesn't care about unstyled elements"
        );

        // 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;
        if self.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(),
            ));
        }

        let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) ||
            (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) &&
                style
                    .flags
                    .contains(ComputedValueFlags::INHERITS_RESET_STYLE));
        if needs_to_recascade_self {
            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]
    pub fn clear_restyle_state(&mut self) {
        self.hint = RestyleHint::empty();
        self.clear_restyle_flags_and_damage();
    }

    /// Drops restyle flags and damage from the element.
    #[inline]
    pub fn clear_restyle_flags_and_damage(&mut self) {
        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.
    pub fn set_restyled(&mut self) {
        self.flags.insert(ElementDataFlags::WAS_RESTYLED);
        self.flags
            .remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING);
    }

    /// Returns true if this element was restyled.
    #[inline]
    pub fn is_restyle(&self) -> bool {
        self.flags.contains(ElementDataFlags::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(ElementDataFlags::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()
    }

    /// 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.
    pub fn safe_for_cousin_sharing(&self) -> bool {
        if self.flags.intersects(
            ElementDataFlags::TRAVERSED_WITHOUT_STYLING |
                ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
        ) {
            return false;
        }
        if !self
            .styles
            .primary()
            .get_box()
            .clone_container_type()
            .is_normal()
        {
            return false;
        }
        true
    }

    /// 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
    }
}