/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A centralized set of stylesheets for a document.
/// A iterator over the stylesheets of a list of entries in the StylesheetSet. pubstruct StylesheetCollectionIterator<'a, S>(slice::Iter<'a, StylesheetSetEntry<S>>) where
S: StylesheetInDocument + PartialEq + 'static;
/// The validity of the data in a given cascade origin. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)] pubenum DataValidity { /// The origin is clean, all the data already there is valid, though we may /// have new sheets at the end.
Valid = 0,
/// The cascade data is invalid, but not the invalidation data (which is /// order-independent), and thus only the cascade data should be inserted.
CascadeInvalid = 1,
/// Everything needs to be rebuilt.
FullyInvalid = 2,
}
/// A struct to iterate over the different stylesheets to be flushed. pubstruct DocumentStylesheetFlusher<'a, S> where
S: StylesheetInDocument + PartialEq + 'static,
{
collections: &'a mut PerOrigin<SheetCollection<S>>,
had_invalidations: bool,
}
/// The type of rebuild that we need to do for a given stylesheet. #[derive(Clone, Copy, Debug)] pubenum SheetRebuildKind { /// A full rebuild, of both cascade data and invalidation data.
Full, /// A partial rebuild, of only the cascade data.
CascadeOnly,
}
impl SheetRebuildKind { /// Whether the stylesheet invalidation data should be rebuilt. pubfn should_rebuild_invalidation(&self) -> bool {
matches!(*self, SheetRebuildKind::Full)
}
}
impl<'a, S> DocumentStylesheetFlusher<'a, S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// Returns a flusher for `origin`. pubfn flush_origin(&mutself, origin: Origin) -> SheetCollectionFlusher<S> { self.collections.borrow_mut_for_origin(&origin).flush()
}
/// Returns the list of stylesheets for `origin`. /// /// Only used for UA sheets. pubfn origin_sheets(&mutself, origin: Origin) -> StylesheetCollectionIterator<S> { self.collections.borrow_mut_for_origin(&origin).iter()
}
/// Returns whether any DOM invalidations were processed as a result of the /// stylesheet flush. #[inline] pubfn had_invalidations(&self) -> bool { self.had_invalidations
}
}
/// A flusher struct for a given collection, that takes care of returning the /// appropriate stylesheets that need work. pubstruct SheetCollectionFlusher<'a, S> where
S: StylesheetInDocument + PartialEq + 'static,
{ // TODO: This can be made an iterator again once // https://github.com/rust-lang/rust/pull/82771 lands on stable.
entries: &'a mut [StylesheetSetEntry<S>],
validity: DataValidity,
dirty: bool,
}
impl<'a, S> SheetCollectionFlusher<'a, S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// Whether the collection was originally dirty. #[inline] pubfn dirty(&self) -> bool { self.dirty
}
/// What the state of the sheet data is. #[inline] pubfn data_validity(&self) -> DataValidity { self.validity
}
/// Returns an iterator over the remaining list of sheets to consume. pubfn sheets<'b>(&'b self) -> impl Iterator<Item = &'b S> { self.entries.iter().map(|entry| &entry.sheet)
}
}
impl<'a, S> SheetCollectionFlusher<'a, S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// Iterates over all sheets and values that we have to invalidate. /// /// TODO(emilio): This would be nicer as an iterator but we can't do that /// until https://github.com/rust-lang/rust/pull/82771 stabilizes. /// /// Since we don't have a good use-case for partial iteration, this does the /// trick for now. pubfn each(self, mut callback: impl FnMut(usize, &S, SheetRebuildKind) -> bool) { for (index, potential_sheet) inself.entries.iter_mut().enumerate() { let committed = mem::replace(&mut potential_sheet.committed, true); let rebuild_kind = if !committed { // If the sheet was uncommitted, we need to do a full rebuild // anyway.
SheetRebuildKind::Full
} else { matchself.validity {
DataValidity::Valid => continue,
DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly,
DataValidity::FullyInvalid => SheetRebuildKind::Full,
}
};
if !callback(index, &potential_sheet.sheet, rebuild_kind) { return;
}
}
}
}
#[derive(MallocSizeOf)] struct SheetCollection<S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// The actual list of stylesheets. /// /// This is only a list of top-level stylesheets, and as such it doesn't /// include recursive `@import` rules.
entries: Vec<StylesheetSetEntry<S>>,
/// The validity of the data that was already there for a given origin. /// /// Note that an origin may appear on `origins_dirty`, but still have /// `DataValidity::Valid`, if only sheets have been appended into it (in /// which case the existing data is valid, but the origin needs to be /// rebuilt).
data_validity: DataValidity,
/// Whether anything in the collection has changed. Note that this is /// different from `data_validity`, in the sense that after a sheet append, /// the data validity is still `Valid`, but we need to be marked as dirty.
dirty: bool,
}
impl<S> SheetCollection<S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// Returns the number of stylesheets in the set. fn len(&self) -> usize { self.entries.len()
}
/// Returns the `index`th stylesheet in the set if present. fn get(&self, index: usize) -> Option<&S> { self.entries.get(index).map(|e| &e.sheet)
}
fn remove(&mutself, sheet: &S) { let index = self.entries.iter().position(|entry| entry.sheet == *sheet); if cfg!(feature = "gecko") && index.is_none() { // FIXME(emilio): Make Gecko's PresShell::AddUserSheet not suck. return;
} let sheet = self.entries.remove(index.unwrap()); // Removing sheets makes us tear down the whole cascade and invalidation // data, but only if the sheet has been involved in at least one flush. // Checking whether the sheet has been committed allows us to avoid // rebuilding the world when sites quickly append and remove a // stylesheet. // // See bug 1434756. if sheet.committed { self.set_data_validity_at_least(DataValidity::FullyInvalid);
} else { self.dirty = true;
}
}
/// Appends a given sheet into the collection. fn append(&mutself, sheet: S) {
debug_assert!(!self.contains(&sheet)); self.entries.push(StylesheetSetEntry::new(sheet)); // Appending sheets doesn't alter the validity of the existing data, so // we don't need to change `data_validity` here. // // But we need to be marked as dirty, otherwise we'll never add the new // sheet! self.dirty = true;
}
let index = self
.entries
.iter()
.position(|entry| entry.sheet == *before_sheet)
.expect("`before_sheet` stylesheet not found");
// Inserting stylesheets somewhere but at the end changes the validity // of the cascade data, but not the invalidation data. self.set_data_validity_at_least(DataValidity::CascadeInvalid); self.entries.insert(index, StylesheetSetEntry::new(sheet));
}
fn set_data_validity_at_least(&mutself, validity: DataValidity) { use std::cmp;
/// Returns an iterator over the current list of stylesheets. fn iter(&self) -> StylesheetCollectionIterator<S> {
StylesheetCollectionIterator(self.entries.iter())
}
fn flush(&mutself) -> SheetCollectionFlusher<S> { let dirty = mem::replace(&mutself.dirty, false); let validity = mem::replace(&mutself.data_validity, DataValidity::Valid);
/// The set of stylesheets effective for a given document. #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pubstruct DocumentStylesheetSet<S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// The collections of sheets per each origin.
collections: PerOrigin<SheetCollection<S>>,
/// The invalidations for stylesheets added or removed from this document.
invalidations: StylesheetInvalidationSet,
}
/// This macro defines methods common to DocumentStylesheetSet and /// AuthorStylesheetSet. /// /// We could simplify the setup moving invalidations to SheetCollection, but /// that would imply not sharing invalidations across origins of the same /// documents, which is slightly annoying.
macro_rules! sheet_set_methods {
($set_name:expr) => { fn collect_invalidations_for(
&mutself,
device: Option<&Device>,
sheet: &S,
guard: &SharedRwLockReadGuard,
) { iflet Some(device) = device { self.invalidations
.collect_invalidations_for(device, sheet, guard);
}
}
/// Appends a new stylesheet to the current set. /// /// No device implies not computing invalidations. pubfn append_stylesheet(
&mutself,
device: Option<&Device>,
sheet: S,
guard: &SharedRwLockReadGuard,
) {
debug!(concat!($set_name, "::append_stylesheet")); self.collect_invalidations_for(device, &sheet, guard); let collection = self.collection_for(&sheet);
collection.append(sheet);
}
/// Insert a given stylesheet before another stylesheet in the document. pubfn insert_stylesheet_before(
&mutself,
device: Option<&Device>,
sheet: S,
before_sheet: S,
guard: &SharedRwLockReadGuard,
) {
debug!(concat!($set_name, "::insert_stylesheet_before")); self.collect_invalidations_for(device, &sheet, guard);
let collection = self.collection_for(&sheet);
collection.insert_before(sheet, &before_sheet);
}
/// Remove a given stylesheet from the set. pubfn remove_stylesheet(
&mutself,
device: Option<&Device>,
sheet: S,
guard: &SharedRwLockReadGuard,
) {
debug!(concat!($set_name, "::remove_stylesheet")); self.collect_invalidations_for(device, &sheet, guard);
let collection = self.collection_for(&sheet);
collection.remove(&sheet)
}
/// Notify the set that a rule from a given stylesheet has changed /// somehow. pubfn rule_changed(
&mutself,
device: Option<&Device>,
sheet: &S,
rule: &CssRule,
guard: &SharedRwLockReadGuard,
change_kind: RuleChangeKind,
) { iflet Some(device) = device { let quirks_mode = device.quirks_mode(); self.invalidations.rule_changed(
sheet,
rule,
guard,
device,
quirks_mode,
change_kind,
);
}
let validity = match change_kind { // Insertion / Removals need to rebuild both the cascade and // invalidation data. For generic changes this is conservative, // could be optimized on a per-case basis.
RuleChangeKind::Generic | RuleChangeKind::Insertion | RuleChangeKind::Removal => {
DataValidity::FullyInvalid
}, // TODO(emilio): This, in theory, doesn't need to invalidate // style data, if the rule we're modifying is actually in the // CascadeData already. // // But this is actually a bit tricky to prove, because when we // copy-on-write a stylesheet we don't bother doing a rebuild, // so we may still have rules from the original stylesheet // instead of the cloned one that we're modifying. So don't // bother for now and unconditionally rebuild, it's no worse // than what we were already doing anyway. // // Maybe we could record whether we saw a clone in this flush, // and if so do the conservative thing, otherwise just // early-return.
RuleChangeKind::StyleRuleDeclarations => DataValidity::FullyInvalid,
};
let collection = self.collection_for(&sheet);
collection.set_data_validity_at_least(validity);
}
};
}
impl<S> DocumentStylesheetSet<S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// Create a new empty DocumentStylesheetSet. pubfn new() -> Self { Self {
collections: Default::default(),
invalidations: StylesheetInvalidationSet::new(),
}
}
/// Returns the number of stylesheets in the set. pubfn len(&self) -> usize { self.collections
.iter_origins()
.fold(0, |s, (item, _)| s + item.len())
}
/// Returns the count of stylesheets for a given origin. #[inline] pubfn sheet_count(&self, origin: Origin) -> usize { self.collections.borrow_for_origin(&origin).len()
}
/// Returns the `index`th stylesheet in the set for the given origin. #[inline] pubfn get(&self, origin: Origin, index: usize) -> Option<&S> { self.collections.borrow_for_origin(&origin).get(index)
}
/// Returns whether the given set has changed from the last flush. pubfn has_changed(&self) -> bool {
!self.invalidations.is_empty() || self.collections
.iter_origins()
.any(|(collection, _)| collection.dirty)
}
/// Flush the current set, unmarking it as dirty, and returns a /// `DocumentStylesheetFlusher` in order to rebuild the stylist. pubfn flush<E>(
&mutself,
document_element: Option<E>,
snapshots: Option<&SnapshotMap>,
) -> DocumentStylesheetFlusher<S> where
E: TElement,
{
debug!("DocumentStylesheetSet::flush");
let had_invalidations = self.invalidations.flush(document_element, snapshots);
/// Flush stylesheets, but without running any of the invalidation passes. #[cfg(feature = "servo")] pubfn flush_without_invalidation(&mutself) -> OriginSet {
debug!("DocumentStylesheetSet::flush_without_invalidation");
for (collection, origin) inself.collections.iter_mut_origins() { if collection.flush().dirty() {
origins |= origin;
}
}
origins
}
/// Return an iterator over the flattened view of all the stylesheets. pubfn iter(&self) -> StylesheetIterator<S> {
StylesheetIterator {
origins: OriginSet::all().iter_origins(),
collections: &self.collections,
current: None,
}
}
/// Mark the stylesheets for the specified origin as dirty, because /// something external may have invalidated it. pubfn force_dirty(&mutself, origins: OriginSet) { self.invalidations.invalidate_fully(); for origin in origins.iter_origins() { // We don't know what happened, assume the worse. self.collections
.borrow_mut_for_origin(&origin)
.set_data_validity_at_least(DataValidity::FullyInvalid);
}
}
}
/// The set of stylesheets effective for a given Shadow Root. #[derive(MallocSizeOf)] pubstruct AuthorStylesheetSet<S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// The actual style sheets.
collection: SheetCollection<S>, /// The set of invalidations scheduled for this collection.
invalidations: StylesheetInvalidationSet,
}
/// A struct to flush an author style sheet collection. pubstruct AuthorStylesheetFlusher<'a, S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// The actual flusher for the collection. pub sheets: SheetCollectionFlusher<'a, S>, /// Whether any sheet invalidation matched. pub had_invalidations: bool,
}
impl<S> AuthorStylesheetSet<S> where
S: StylesheetInDocument + PartialEq + 'static,
{ /// Create a new empty AuthorStylesheetSet. #[inline] pubfn new() -> Self { Self {
collection: Default::default(),
invalidations: StylesheetInvalidationSet::new(),
}
}
/// Whether anything has changed since the last time this was flushed. pubfn dirty(&self) -> bool { self.collection.dirty
}
/// Whether the collection is empty. pubfn is_empty(&self) -> bool { self.collection.len() == 0
}
/// Returns the `index`th stylesheet in the collection of author styles if present. pubfn get(&self, index: usize) -> Option<&S> { self.collection.get(index)
}
/// Returns the number of author stylesheets. pubfn len(&self) -> usize { self.collection.len()
}
/// Iterate over the list of stylesheets. pubfn iter(&self) -> StylesheetCollectionIterator<S> { self.collection.iter()
}
/// Mark the sheet set dirty, as appropriate. pubfn force_dirty(&mutself) { self.invalidations.invalidate_fully(); self.collection
.set_data_validity_at_least(DataValidity::FullyInvalid);
}
/// Flush the stylesheets for this author set. /// /// `host` is the root of the affected subtree, like the shadow host, for /// example. pubfn flush<E>(
&mutself,
host: Option<E>,
snapshots: Option<&SnapshotMap>,
) -> AuthorStylesheetFlusher<S> where
E: TElement,
{ let had_invalidations = self.invalidations.flush(host, snapshots);
AuthorStylesheetFlusher {
sheets: self.collection.flush(),
had_invalidations,
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.