Skip to main content

style/
stylesheet_set.rs

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//! A centralized set of stylesheets for a document.
6
7use crate::derives::*;
8use crate::invalidation::stylesheets::{RuleChangeKind, StylesheetInvalidationSet};
9use crate::media_queries::Device;
10use crate::shared_lock::SharedRwLockReadGuard;
11use crate::stylesheets::{
12    CssRule, CssRuleRef, CustomMediaMap, Origin, OriginSet, PerOrigin, StylesheetInDocument,
13};
14use std::mem;
15
16/// Entry for a StylesheetSet.
17#[derive(MallocSizeOf)]
18struct StylesheetSetEntry<S>
19where
20    S: StylesheetInDocument + PartialEq + 'static,
21{
22    /// The sheet.
23    sheet: S,
24
25    /// Whether this sheet has been part of at least one flush.
26    committed: bool,
27}
28
29impl<S> StylesheetSetEntry<S>
30where
31    S: StylesheetInDocument + PartialEq + 'static,
32{
33    fn new(sheet: S) -> Self {
34        Self {
35            sheet,
36            committed: false,
37        }
38    }
39}
40
41/// The validity of the data in a given cascade origin.
42#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)]
43pub enum DataValidity {
44    /// The origin is clean, all the data already there is valid, though we may
45    /// have new sheets at the end.
46    Valid = 0,
47
48    /// The cascade data is invalid, but not the invalidation data (which is
49    /// order-independent), and thus only the cascade data should be inserted.
50    CascadeInvalid = 1,
51
52    /// Everything needs to be rebuilt.
53    FullyInvalid = 2,
54}
55
56impl Default for DataValidity {
57    fn default() -> Self {
58        DataValidity::Valid
59    }
60}
61
62/// A struct to iterate over the different stylesheets to be flushed.
63pub struct DocumentStylesheetFlusher<'a, S>
64where
65    S: StylesheetInDocument + PartialEq + 'static,
66{
67    collections: &'a mut PerOrigin<SheetCollection<S>>,
68}
69
70/// The type of rebuild that we need to do for a given stylesheet.
71#[derive(Clone, Copy, Debug)]
72pub enum SheetRebuildKind {
73    /// A full rebuild, of both cascade data and invalidation data.
74    Full,
75    /// A partial rebuild, of only the cascade data.
76    CascadeOnly,
77}
78
79impl SheetRebuildKind {
80    /// Whether the stylesheet invalidation data should be rebuilt.
81    pub fn should_rebuild_invalidation(&self) -> bool {
82        matches!(*self, SheetRebuildKind::Full)
83    }
84}
85
86impl<'a, S> DocumentStylesheetFlusher<'a, S>
87where
88    S: StylesheetInDocument + PartialEq + 'static,
89{
90    /// Returns a flusher for `origin`.
91    pub fn flush_origin(&mut self, origin: Origin) -> SheetCollectionFlusher<'_, S> {
92        self.collections.borrow_mut_for_origin(&origin).flush()
93    }
94
95    /// Returns the list of stylesheets for `origin`.
96    ///
97    /// Only used for UA sheets.
98    pub fn origin_sheets(&self, origin: Origin) -> impl Iterator<Item = &S> {
99        self.collections.borrow_for_origin(&origin).iter()
100    }
101}
102
103/// A flusher struct for a given collection, that takes care of returning the
104/// appropriate stylesheets that need work.
105pub struct SheetCollectionFlusher<'a, S>
106where
107    S: StylesheetInDocument + PartialEq + 'static,
108{
109    // TODO: This can be made an iterator again once
110    // https://github.com/rust-lang/rust/pull/82771 lands on stable.
111    entries: &'a mut [StylesheetSetEntry<S>],
112    validity: DataValidity,
113    dirty: bool,
114}
115
116impl<'a, S> SheetCollectionFlusher<'a, S>
117where
118    S: StylesheetInDocument + PartialEq + 'static,
119{
120    /// Whether the collection was originally dirty.
121    #[inline]
122    pub fn dirty(&self) -> bool {
123        self.dirty
124    }
125
126    /// What the state of the sheet data is.
127    #[inline]
128    pub fn data_validity(&self) -> DataValidity {
129        self.validity
130    }
131
132    /// Returns an iterator over the remaining list of sheets to consume.
133    pub fn sheets<'b>(&'b self) -> impl Iterator<Item = &'b S> {
134        self.entries.iter().map(|entry| &entry.sheet)
135    }
136}
137
138impl<'a, S> SheetCollectionFlusher<'a, S>
139where
140    S: StylesheetInDocument + PartialEq + 'static,
141{
142    /// Iterates over all sheets and values that we have to invalidate.
143    ///
144    /// TODO(emilio): This would be nicer as an iterator but we can't do that
145    /// until https://github.com/rust-lang/rust/pull/82771 stabilizes.
146    ///
147    /// Since we don't have a good use-case for partial iteration, this does the
148    /// trick for now.
149    pub fn each(self, mut callback: impl FnMut(usize, &S, SheetRebuildKind) -> bool) {
150        for (index, potential_sheet) in self.entries.iter_mut().enumerate() {
151            let committed = mem::replace(&mut potential_sheet.committed, true);
152            let rebuild_kind = if !committed {
153                // If the sheet was uncommitted, we need to do a full rebuild
154                // anyway.
155                SheetRebuildKind::Full
156            } else {
157                match self.validity {
158                    DataValidity::Valid => continue,
159                    DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly,
160                    DataValidity::FullyInvalid => SheetRebuildKind::Full,
161                }
162            };
163
164            if !callback(index, &potential_sheet.sheet, rebuild_kind) {
165                return;
166            }
167        }
168    }
169}
170
171#[derive(MallocSizeOf)]
172struct SheetCollection<S>
173where
174    S: StylesheetInDocument + PartialEq + 'static,
175{
176    /// The actual list of stylesheets.
177    ///
178    /// This is only a list of top-level stylesheets, and as such it doesn't
179    /// include recursive `@import` rules.
180    entries: Vec<StylesheetSetEntry<S>>,
181
182    /// The validity of the data that was already there for a given origin.
183    ///
184    /// Note that an origin may appear on `origins_dirty`, but still have
185    /// `DataValidity::Valid`, if only sheets have been appended into it (in
186    /// which case the existing data is valid, but the origin needs to be
187    /// rebuilt).
188    data_validity: DataValidity,
189
190    /// Whether anything in the collection has changed. Note that this is
191    /// different from `data_validity`, in the sense that after a sheet append,
192    /// the data validity is still `Valid`, but we need to be marked as dirty.
193    dirty: bool,
194}
195
196impl<S> Default for SheetCollection<S>
197where
198    S: StylesheetInDocument + PartialEq + 'static,
199{
200    fn default() -> Self {
201        Self {
202            entries: vec![],
203            data_validity: DataValidity::Valid,
204            dirty: false,
205        }
206    }
207}
208
209impl<S> SheetCollection<S>
210where
211    S: StylesheetInDocument + PartialEq + 'static,
212{
213    /// Returns the number of stylesheets in the set.
214    fn len(&self) -> usize {
215        self.entries.len()
216    }
217
218    /// Returns the `index`th stylesheet in the set if present.
219    fn get(&self, index: usize) -> Option<&S> {
220        self.entries.get(index).map(|e| &e.sheet)
221    }
222
223    fn find_sheet_index(&self, sheet: &S) -> Option<usize> {
224        let rev_pos = self
225            .entries
226            .iter()
227            .rev()
228            .position(|entry| entry.sheet == *sheet);
229        rev_pos.map(|i| self.entries.len() - i - 1)
230    }
231
232    fn remove(&mut self, sheet: &S) {
233        let index = self.find_sheet_index(sheet);
234        if cfg!(feature = "gecko") && index.is_none() {
235            // FIXME(emilio): Make Gecko's PresShell::AddUserSheet not suck.
236            return;
237        }
238        let sheet = self.entries.remove(index.unwrap());
239        // Removing sheets makes us tear down the whole cascade and invalidation
240        // data, but only if the sheet has been involved in at least one flush.
241        // Checking whether the sheet has been committed allows us to avoid
242        // rebuilding the world when sites quickly append and remove a
243        // stylesheet.
244        //
245        // See bug 1434756.
246        if sheet.committed {
247            self.set_data_validity_at_least(DataValidity::FullyInvalid);
248        } else {
249            self.dirty = true;
250        }
251    }
252
253    fn contains(&self, sheet: &S) -> bool {
254        self.entries.iter().any(|e| e.sheet == *sheet)
255    }
256
257    /// Appends a given sheet into the collection.
258    fn append(&mut self, sheet: S) {
259        debug_assert!(!self.contains(&sheet));
260        self.entries.push(StylesheetSetEntry::new(sheet));
261        // Appending sheets doesn't alter the validity of the existing data, so
262        // we don't need to change `data_validity` here.
263        //
264        // But we need to be marked as dirty, otherwise we'll never add the new
265        // sheet!
266        self.dirty = true;
267    }
268
269    fn insert_before(&mut self, sheet: S, before_sheet: &S) {
270        debug_assert!(!self.contains(&sheet));
271
272        let index = self
273            .find_sheet_index(before_sheet)
274            .expect("`before_sheet` stylesheet not found");
275
276        // Inserting stylesheets somewhere but at the end changes the validity
277        // of the cascade data, but not the invalidation data.
278        self.set_data_validity_at_least(DataValidity::CascadeInvalid);
279        self.entries.insert(index, StylesheetSetEntry::new(sheet));
280    }
281
282    fn set_data_validity_at_least(&mut self, validity: DataValidity) {
283        use std::cmp;
284
285        debug_assert_ne!(validity, DataValidity::Valid);
286
287        self.dirty = true;
288        self.data_validity = cmp::max(validity, self.data_validity);
289    }
290
291    /// Returns an iterator over the current list of stylesheets.
292    fn iter(&self) -> impl Iterator<Item = &S> {
293        self.entries.iter().map(|e| &e.sheet)
294    }
295
296    /// Returns a mutable iterator over the current list of stylesheets.
297    fn iter_mut(&mut self) -> impl Iterator<Item = &mut S> {
298        self.entries.iter_mut().map(|e| &mut e.sheet)
299    }
300
301    fn flush(&mut self) -> SheetCollectionFlusher<'_, S> {
302        let dirty = mem::replace(&mut self.dirty, false);
303        let validity = mem::replace(&mut self.data_validity, DataValidity::Valid);
304
305        SheetCollectionFlusher {
306            entries: &mut self.entries,
307            dirty,
308            validity,
309        }
310    }
311}
312
313/// The set of stylesheets effective for a given document.
314#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
315pub struct DocumentStylesheetSet<S>
316where
317    S: StylesheetInDocument + PartialEq + 'static,
318{
319    /// The collections of sheets per each origin.
320    collections: PerOrigin<SheetCollection<S>>,
321
322    /// The invalidations for stylesheets added or removed from this document.
323    invalidations: StylesheetInvalidationSet,
324}
325
326/// This macro defines methods common to DocumentStylesheetSet and
327/// AuthorStylesheetSet.
328///
329/// We could simplify the setup moving invalidations to SheetCollection, but
330/// that would imply not sharing invalidations across origins of the same
331/// documents, which is slightly annoying.
332macro_rules! sheet_set_methods {
333    ($set_name:expr) => {
334        fn collect_invalidations_for(
335            &mut self,
336            device: Option<&Device>,
337            custom_media: &CustomMediaMap,
338            sheet: &S,
339            guard: &SharedRwLockReadGuard,
340        ) {
341            if let Some(device) = device {
342                self.invalidations
343                    .collect_invalidations_for(device, custom_media, sheet, guard);
344            }
345        }
346
347        /// Appends a new stylesheet to the current set.
348        ///
349        /// No device implies not computing invalidations.
350        pub fn append_stylesheet(
351            &mut self,
352            device: Option<&Device>,
353            custom_media: &CustomMediaMap,
354            sheet: S,
355            guard: &SharedRwLockReadGuard,
356        ) {
357            debug!(concat!($set_name, "::append_stylesheet"));
358            self.collect_invalidations_for(device, custom_media, &sheet, guard);
359            let collection = self.collection_for(&sheet, guard);
360            collection.append(sheet);
361        }
362
363        /// Insert a given stylesheet before another stylesheet in the document.
364        pub fn insert_stylesheet_before(
365            &mut self,
366            device: Option<&Device>,
367            custom_media: &CustomMediaMap,
368            sheet: S,
369            before_sheet: S,
370            guard: &SharedRwLockReadGuard,
371        ) {
372            debug!(concat!($set_name, "::insert_stylesheet_before"));
373            self.collect_invalidations_for(device, custom_media, &sheet, guard);
374
375            let collection = self.collection_for(&sheet, guard);
376            collection.insert_before(sheet, &before_sheet);
377        }
378
379        /// Remove a given stylesheet from the set.
380        pub fn remove_stylesheet(
381            &mut self,
382            device: Option<&Device>,
383            custom_media: &CustomMediaMap,
384            sheet: S,
385            guard: &SharedRwLockReadGuard,
386        ) {
387            debug!(concat!($set_name, "::remove_stylesheet"));
388            self.collect_invalidations_for(device, custom_media, &sheet, guard);
389
390            let collection = self.collection_for(&sheet, guard);
391            collection.remove(&sheet)
392        }
393
394        /// Notify the set that a rule from a given stylesheet has changed
395        /// somehow.
396        pub fn rule_changed(
397            &mut self,
398            device: Option<&Device>,
399            custom_media: &CustomMediaMap,
400            sheet: &S,
401            rule: &CssRule,
402            guard: &SharedRwLockReadGuard,
403            change_kind: RuleChangeKind,
404            ancestors: &[CssRuleRef],
405        ) {
406            if let Some(device) = device {
407                let quirks_mode = device.quirks_mode();
408                self.invalidations.rule_changed(
409                    sheet,
410                    rule,
411                    guard,
412                    device,
413                    quirks_mode,
414                    custom_media,
415                    change_kind,
416                    ancestors,
417                );
418            }
419
420            let validity = match change_kind {
421                // Insertion / Removals need to rebuild both the cascade and
422                // invalidation data. For generic changes this is conservative,
423                // could be optimized on a per-case basis.
424                RuleChangeKind::Generic | RuleChangeKind::Insertion | RuleChangeKind::Removal => {
425                    DataValidity::FullyInvalid
426                },
427                // TODO(emilio): This, in theory, doesn't need to invalidate
428                // style data, if the rule we're modifying is actually in the
429                // CascadeData already.
430                //
431                // But this is actually a bit tricky to prove, because when we
432                // copy-on-write a stylesheet we don't bother doing a rebuild,
433                // so we may still have rules from the original stylesheet
434                // instead of the cloned one that we're modifying. So don't
435                // bother for now and unconditionally rebuild, it's no worse
436                // than what we were already doing anyway.
437                //
438                // Maybe we could record whether we saw a clone in this flush,
439                // and if so do the conservative thing, otherwise just
440                // early-return.
441                RuleChangeKind::PositionTryDeclarations | RuleChangeKind::StyleRuleDeclarations => {
442                    DataValidity::FullyInvalid
443                },
444            };
445
446            let collection = self.collection_for(&sheet, guard);
447            collection.set_data_validity_at_least(validity);
448        }
449    };
450}
451
452impl<S> DocumentStylesheetSet<S>
453where
454    S: StylesheetInDocument + PartialEq + 'static,
455{
456    /// Create a new empty DocumentStylesheetSet.
457    pub fn new() -> Self {
458        Self {
459            collections: Default::default(),
460            invalidations: StylesheetInvalidationSet::new(),
461        }
462    }
463
464    fn collection_for(
465        &mut self,
466        sheet: &S,
467        guard: &SharedRwLockReadGuard,
468    ) -> &mut SheetCollection<S> {
469        let origin = sheet.contents(guard).origin;
470        self.collections.borrow_mut_for_origin(&origin)
471    }
472
473    sheet_set_methods!("DocumentStylesheetSet");
474
475    /// Returns the number of stylesheets in the set.
476    pub fn len(&self) -> usize {
477        self.collections
478            .iter_origins()
479            .fold(0, |s, (item, _)| s + item.len())
480    }
481
482    /// Returns the count of stylesheets for a given origin.
483    #[inline]
484    pub fn sheet_count(&self, origin: Origin) -> usize {
485        self.collections.borrow_for_origin(&origin).len()
486    }
487
488    /// Returns the `index`th stylesheet in the set for the given origin.
489    #[inline]
490    pub fn get(&self, origin: Origin, index: usize) -> Option<&S> {
491        self.collections.borrow_for_origin(&origin).get(index)
492    }
493
494    /// Returns whether the given set has changed from the last flush.
495    pub fn has_changed(&self) -> bool {
496        !self.invalidations.is_empty()
497            || self
498                .collections
499                .iter_origins()
500                .any(|(collection, _)| collection.dirty)
501    }
502
503    /// Flush the current set, unmarking it as dirty, and returns a `DocumentStylesheetFlusher` in
504    /// order to rebuild the stylist and the invalidation set.
505    pub fn flush(&mut self) -> (DocumentStylesheetFlusher<'_, S>, StylesheetInvalidationSet) {
506        debug!("DocumentStylesheetSet::flush");
507        (
508            DocumentStylesheetFlusher {
509                collections: &mut self.collections,
510            },
511            std::mem::take(&mut self.invalidations),
512        )
513    }
514
515    /// Flush stylesheets, but without running any of the invalidation passes.
516    #[cfg(feature = "servo")]
517    pub fn flush_without_invalidation(&mut self) -> OriginSet {
518        debug!("DocumentStylesheetSet::flush_without_invalidation");
519
520        let mut origins = OriginSet::empty();
521        std::mem::take(&mut self.invalidations);
522
523        for (collection, origin) in self.collections.iter_mut_origins() {
524            if collection.flush().dirty() {
525                origins |= origin;
526            }
527        }
528
529        origins
530    }
531
532    /// Return an iterator over the flattened view of all the stylesheets.
533    pub fn iter(&self) -> impl Iterator<Item = (&S, Origin)> {
534        self.collections
535            .iter_origins()
536            .flat_map(|(c, o)| c.iter().map(move |s| (s, o)))
537    }
538
539    /// Return an iterator over the flattened view of all the stylesheets, mutably.
540    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut S, Origin)> {
541        self.collections
542            .iter_mut_origins()
543            .flat_map(|(c, o)| c.iter_mut().map(move |s| (s, o)))
544    }
545
546    /// Mark the stylesheets for the specified origin as dirty, because
547    /// something external may have invalidated it.
548    pub fn force_dirty(&mut self, origins: OriginSet) {
549        self.invalidations.invalidate_fully();
550        for origin in origins.iter_origins() {
551            // We don't know what happened, assume the worse.
552            self.collections
553                .borrow_mut_for_origin(&origin)
554                .set_data_validity_at_least(DataValidity::FullyInvalid);
555        }
556    }
557}
558
559/// The set of stylesheets effective for a given Shadow Root.
560#[derive(MallocSizeOf)]
561pub struct AuthorStylesheetSet<S>
562where
563    S: StylesheetInDocument + PartialEq + 'static,
564{
565    /// The actual style sheets.
566    collection: SheetCollection<S>,
567    /// The set of invalidations scheduled for this collection.
568    invalidations: StylesheetInvalidationSet,
569}
570
571/// A struct to flush an author style sheet collection.
572pub struct AuthorStylesheetFlusher<'a, S>
573where
574    S: StylesheetInDocument + PartialEq + 'static,
575{
576    /// The actual flusher for the collection.
577    pub sheets: SheetCollectionFlusher<'a, S>,
578}
579
580impl<S> AuthorStylesheetSet<S>
581where
582    S: StylesheetInDocument + PartialEq + 'static,
583{
584    /// Create a new empty AuthorStylesheetSet.
585    #[inline]
586    pub fn new() -> Self {
587        Self {
588            collection: Default::default(),
589            invalidations: StylesheetInvalidationSet::new(),
590        }
591    }
592
593    /// Whether anything has changed since the last time this was flushed.
594    pub fn dirty(&self) -> bool {
595        self.collection.dirty
596    }
597
598    /// Whether the collection is empty.
599    pub fn is_empty(&self) -> bool {
600        self.collection.len() == 0
601    }
602
603    /// Returns the `index`th stylesheet in the collection of author styles if present.
604    pub fn get(&self, index: usize) -> Option<&S> {
605        self.collection.get(index)
606    }
607
608    /// Returns the number of author stylesheets.
609    pub fn len(&self) -> usize {
610        self.collection.len()
611    }
612
613    fn collection_for(&mut self, _: &S, _: &SharedRwLockReadGuard) -> &mut SheetCollection<S> {
614        &mut self.collection
615    }
616
617    sheet_set_methods!("AuthorStylesheetSet");
618
619    /// Iterate over the list of stylesheets.
620    pub fn iter(&self) -> impl Iterator<Item = &S> {
621        self.collection.iter()
622    }
623
624    /// Returns a mutable iterator over the current list of stylesheets.
625    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut S> {
626        self.collection.iter_mut()
627    }
628
629    /// Mark the sheet set dirty, as appropriate.
630    pub fn force_dirty(&mut self) {
631        self.invalidations.invalidate_fully();
632        self.collection
633            .set_data_validity_at_least(DataValidity::FullyInvalid);
634    }
635
636    /// Flush the stylesheets for this author set.
637    pub fn flush(&mut self) -> (AuthorStylesheetFlusher<'_, S>, StylesheetInvalidationSet) {
638        (
639            AuthorStylesheetFlusher {
640                sheets: self.collection.flush(),
641            },
642            std::mem::take(&mut self.invalidations),
643        )
644    }
645}