tor-browser

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

stylesheet_set.rs (21525B)


      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 
      7 use crate::derives::*;
      8 use crate::invalidation::stylesheets::{RuleChangeKind, StylesheetInvalidationSet};
      9 use crate::media_queries::Device;
     10 use crate::shared_lock::SharedRwLockReadGuard;
     11 use crate::stylesheets::{
     12    CssRule, CssRuleRef, CustomMediaMap, Origin, OriginSet, PerOrigin, StylesheetInDocument,
     13 };
     14 use std::mem;
     15 
     16 /// Entry for a StylesheetSet.
     17 #[derive(MallocSizeOf)]
     18 struct StylesheetSetEntry<S>
     19 where
     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 
     29 impl<S> StylesheetSetEntry<S>
     30 where
     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)]
     43 pub 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 
     56 impl 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.
     63 pub struct DocumentStylesheetFlusher<'a, S>
     64 where
     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)]
     72 pub 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 
     79 impl 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 
     86 impl<'a, S> DocumentStylesheetFlusher<'a, S>
     87 where
     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.
    105 pub struct SheetCollectionFlusher<'a, S>
    106 where
    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 
    116 impl<'a, S> SheetCollectionFlusher<'a, S>
    117 where
    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 
    138 impl<'a, S> SheetCollectionFlusher<'a, S>
    139 where
    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)]
    172 struct SheetCollection<S>
    173 where
    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 
    196 impl<S> Default for SheetCollection<S>
    197 where
    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 
    209 impl<S> SheetCollection<S>
    210 where
    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))]
    315 pub struct DocumentStylesheetSet<S>
    316 where
    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.
    332 macro_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 
    452 impl<S> DocumentStylesheetSet<S>
    453 where
    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        self.invalidations.clear();
    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)]
    561 pub struct AuthorStylesheetSet<S>
    562 where
    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.
    572 pub struct AuthorStylesheetFlusher<'a, S>
    573 where
    574    S: StylesheetInDocument + PartialEq + 'static,
    575 {
    576    /// The actual flusher for the collection.
    577    pub sheets: SheetCollectionFlusher<'a, S>,
    578 }
    579 
    580 impl<S> AuthorStylesheetSet<S>
    581 where
    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 }