mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
style: Implement finer-grained stylist rebuilds.
MozReview-Commit-ID: ALsH9kSqHb0 Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
parent
1c67a7dc14
commit
bf45a20760
2 changed files with 193 additions and 57 deletions
|
@ -9,7 +9,7 @@ use invalidation::stylesheets::StylesheetInvalidationSet;
|
|||
use media_queries::Device;
|
||||
use shared_lock::SharedRwLockReadGuard;
|
||||
use std::slice;
|
||||
use stylesheets::{Origin, OriginSet, StylesheetInDocument};
|
||||
use stylesheets::{Origin, OriginSet, PerOrigin, StylesheetInDocument};
|
||||
|
||||
/// Entry for a StylesheetSet. We don't bother creating a constructor, because
|
||||
/// there's no sensible defaults for the member variables.
|
||||
|
@ -19,6 +19,7 @@ where
|
|||
S: StylesheetInDocument + PartialEq + 'static,
|
||||
{
|
||||
sheet: S,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl<S> StylesheetSetEntry<S>
|
||||
|
@ -26,7 +27,7 @@ where
|
|||
S: StylesheetInDocument + PartialEq + 'static,
|
||||
{
|
||||
fn new(sheet: S) -> Self {
|
||||
Self { sheet }
|
||||
Self { sheet, dirty: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +48,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The validity of the data in a given cascade origin.
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
pub enum OriginValidity {
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl Default for OriginValidity {
|
||||
fn default() -> Self {
|
||||
OriginValidity::Valid
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct to iterate over the different stylesheets to be flushed.
|
||||
pub struct StylesheetFlusher<'a, 'b, S>
|
||||
where
|
||||
|
@ -56,15 +79,24 @@ where
|
|||
iter: slice::IterMut<'a, StylesheetSetEntry<S>>,
|
||||
guard: &'a SharedRwLockReadGuard<'b>,
|
||||
origins_dirty: OriginSet,
|
||||
origin_data_validity: PerOrigin<OriginValidity>,
|
||||
author_style_disabled: bool,
|
||||
had_invalidations: bool,
|
||||
}
|
||||
|
||||
/// The type of rebuild that we need to do for a given stylesheet.
|
||||
pub enum SheetRebuildKind {
|
||||
/// For now we only support full rebuilds, in the future we'll implement
|
||||
/// partial rebuilds.
|
||||
/// 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.
|
||||
pub fn rebuild_invalidation(&self) -> bool {
|
||||
matches!(*self, SheetRebuildKind::Full)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, S> StylesheetFlusher<'a, 'b, S>
|
||||
|
@ -72,10 +104,9 @@ where
|
|||
'b: 'a,
|
||||
S: StylesheetInDocument + PartialEq + 'static,
|
||||
{
|
||||
/// The set of origins to fully rebuild, which need to be cleared
|
||||
/// beforehand.
|
||||
pub fn origins_to_fully_rebuild(&self) -> OriginSet {
|
||||
self.origins_dirty
|
||||
/// The data validity for a given origin.
|
||||
pub fn origin_validity(&self, origin: Origin) -> OriginValidity {
|
||||
*self.origin_data_validity.borrow_for_origin(&origin)
|
||||
}
|
||||
|
||||
/// Returns whether running the whole flushing process would be a no-op.
|
||||
|
@ -99,7 +130,7 @@ where
|
|||
fn drop(&mut self) {
|
||||
debug_assert!(
|
||||
self.iter.next().is_none(),
|
||||
"You're supposed to fully consume the flusher",
|
||||
"You're supposed to fully consume the flusher"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -112,12 +143,21 @@ where
|
|||
type Item = (&'a S, SheetRebuildKind);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
use std::mem;
|
||||
|
||||
loop {
|
||||
let potential_sheet = match self.iter.next() {
|
||||
None => return None,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
let dirty = mem::replace(&mut potential_sheet.dirty, false);
|
||||
|
||||
if dirty {
|
||||
// If the sheet was dirty, we need to do a full rebuild anyway.
|
||||
return Some((&potential_sheet.sheet, SheetRebuildKind::Full))
|
||||
}
|
||||
|
||||
let origin = potential_sheet.sheet.contents(self.guard).origin;
|
||||
if !self.origins_dirty.contains(origin.into()) {
|
||||
continue;
|
||||
|
@ -127,7 +167,13 @@ where
|
|||
continue;
|
||||
}
|
||||
|
||||
return Some((&potential_sheet.sheet, SheetRebuildKind::Full))
|
||||
let rebuild_kind = match self.origin_validity(origin) {
|
||||
OriginValidity::Valid => continue,
|
||||
OriginValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly,
|
||||
OriginValidity::FullyInvalid => SheetRebuildKind::Full,
|
||||
};
|
||||
|
||||
return Some((&potential_sheet.sheet, rebuild_kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +197,14 @@ where
|
|||
/// The origins whose stylesheets have changed so far.
|
||||
origins_dirty: OriginSet,
|
||||
|
||||
/// 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
|
||||
/// `OriginValidity::Valid`, if only sheets have been appended into it (in
|
||||
/// which case the existing data is valid, but the origin needs to be
|
||||
/// rebuilt).
|
||||
origin_data_validity: PerOrigin<OriginValidity>,
|
||||
|
||||
/// Has author style been disabled?
|
||||
author_style_disabled: bool,
|
||||
}
|
||||
|
@ -165,6 +219,7 @@ where
|
|||
entries: vec![],
|
||||
invalidations: StylesheetInvalidationSet::new(),
|
||||
origins_dirty: OriginSet::empty(),
|
||||
origin_data_validity: Default::default(),
|
||||
author_style_disabled: false,
|
||||
}
|
||||
}
|
||||
|
@ -201,6 +256,24 @@ where
|
|||
self.origins_dirty |= sheet.contents(guard).origin;
|
||||
}
|
||||
|
||||
fn set_data_validity_at_least(
|
||||
&mut self,
|
||||
origin: Origin,
|
||||
validity: OriginValidity,
|
||||
) {
|
||||
use std::cmp;
|
||||
|
||||
debug_assert!(
|
||||
self.origins_dirty.contains(origin.into()),
|
||||
"data_validity should be a subset of origins_dirty"
|
||||
);
|
||||
|
||||
let existing_validity =
|
||||
self.origin_data_validity.borrow_mut_for_origin(&origin);
|
||||
|
||||
*existing_validity = cmp::max(*existing_validity, validity);
|
||||
}
|
||||
|
||||
/// Appends a new stylesheet to the current set.
|
||||
///
|
||||
/// No device implies not computing invalidations.
|
||||
|
@ -213,6 +286,8 @@ where
|
|||
debug!("StylesheetSet::append_stylesheet");
|
||||
self.remove_stylesheet_if_present(&sheet);
|
||||
self.collect_invalidations_for(device, &sheet, guard);
|
||||
// Appending sheets doesn't alter the validity of the existing data, so
|
||||
// we don't need to change `origin_data_validity` here.
|
||||
self.entries.push(StylesheetSetEntry::new(sheet));
|
||||
}
|
||||
|
||||
|
@ -226,6 +301,11 @@ where
|
|||
debug!("StylesheetSet::prepend_stylesheet");
|
||||
self.remove_stylesheet_if_present(&sheet);
|
||||
self.collect_invalidations_for(device, &sheet, guard);
|
||||
|
||||
// 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(sheet.contents(guard).origin, OriginValidity::CascadeInvalid);
|
||||
|
||||
self.entries.insert(0, StylesheetSetEntry::new(sheet));
|
||||
}
|
||||
|
||||
|
@ -243,6 +323,10 @@ where
|
|||
entry.sheet == before_sheet
|
||||
}).expect("`before_sheet` stylesheet not found");
|
||||
self.collect_invalidations_for(device, &sheet, guard);
|
||||
|
||||
// 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(sheet.contents(guard).origin, OriginValidity::CascadeInvalid);
|
||||
self.entries.insert(index, StylesheetSetEntry::new(sheet));
|
||||
}
|
||||
|
||||
|
@ -255,7 +339,12 @@ where
|
|||
) {
|
||||
debug!("StylesheetSet::remove_stylesheet");
|
||||
self.remove_stylesheet_if_present(&sheet);
|
||||
|
||||
self.collect_invalidations_for(device, &sheet, guard);
|
||||
|
||||
// Removing sheets makes us tear down the whole cascade and invalidation
|
||||
// data.
|
||||
self.set_data_validity_at_least(sheet.contents(guard).origin, OriginValidity::FullyInvalid);
|
||||
}
|
||||
|
||||
/// Notes that the author style has been disabled for this document.
|
||||
|
@ -290,12 +379,15 @@ where
|
|||
|
||||
let had_invalidations = self.invalidations.flush(document_element);
|
||||
let origins_dirty = mem::replace(&mut self.origins_dirty, OriginSet::empty());
|
||||
let origin_data_validity =
|
||||
mem::replace(&mut self.origin_data_validity, Default::default());
|
||||
|
||||
StylesheetFlusher {
|
||||
iter: self.entries.iter_mut(),
|
||||
author_style_disabled: self.author_style_disabled,
|
||||
had_invalidations,
|
||||
origins_dirty,
|
||||
origin_data_validity,
|
||||
guard,
|
||||
}
|
||||
}
|
||||
|
@ -321,5 +413,9 @@ where
|
|||
pub fn force_dirty(&mut self, origins: OriginSet) {
|
||||
self.invalidations.invalidate_fully();
|
||||
self.origins_dirty |= origins;
|
||||
for origin in origins.iter() {
|
||||
// We don't know what happened, assume the worse.
|
||||
self.set_data_validity_at_least(origin, OriginValidity::FullyInvalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ use smallvec::VecLike;
|
|||
use std::fmt::Debug;
|
||||
use std::ops;
|
||||
use style_traits::viewport::ViewportConstraints;
|
||||
use stylesheet_set::{StylesheetSet, StylesheetIterator, StylesheetFlusher};
|
||||
use stylesheet_set::{OriginValidity, SheetRebuildKind, StylesheetSet, StylesheetIterator, StylesheetFlusher};
|
||||
#[cfg(feature = "gecko")]
|
||||
use stylesheets::{CounterStyleRule, FontFaceRule};
|
||||
use stylesheets::{CssRule, StyleRule};
|
||||
|
@ -98,17 +98,26 @@ impl DocumentCascadeData {
|
|||
{
|
||||
debug_assert!(!flusher.nothing_to_do());
|
||||
|
||||
let origins_to_rebuild = flusher.origins_to_fully_rebuild();
|
||||
for (cascade_data, origin) in self.per_origin.iter_mut_origins() {
|
||||
let validity = flusher.origin_validity(origin);
|
||||
|
||||
for origin in origins_to_rebuild.iter() {
|
||||
extra_data.borrow_mut_for_origin(&origin).clear();
|
||||
self.per_origin.borrow_mut_for_origin(&origin).clear();
|
||||
if validity == OriginValidity::Valid {
|
||||
continue;
|
||||
}
|
||||
|
||||
if origins_to_rebuild.contains(Origin::UserAgent.into()) {
|
||||
if origin == Origin::UserAgent {
|
||||
self.precomputed_pseudo_element_decls.clear();
|
||||
}
|
||||
|
||||
extra_data.borrow_mut_for_origin(&origin).clear();
|
||||
if validity == OriginValidity::CascadeInvalid {
|
||||
cascade_data.clear_cascade_data()
|
||||
} else {
|
||||
debug_assert_eq!(validity, OriginValidity::FullyInvalid);
|
||||
cascade_data.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ua_stylesheets) = ua_stylesheets {
|
||||
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
|
||||
let sheet_origin =
|
||||
|
@ -119,16 +128,27 @@ impl DocumentCascadeData {
|
|||
Origin::UserAgent | Origin::User
|
||||
));
|
||||
|
||||
if origins_to_rebuild.contains(sheet_origin.into()) {
|
||||
let validity = flusher.origin_validity(sheet_origin);
|
||||
|
||||
// Servo doesn't support to incrementally mutate UA sheets.
|
||||
debug_assert!(matches!(
|
||||
validity,
|
||||
OriginValidity::Valid | OriginValidity::FullyInvalid
|
||||
));
|
||||
|
||||
if validity == OriginValidity::Valid {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.add_stylesheet(
|
||||
device,
|
||||
quirks_mode,
|
||||
stylesheet,
|
||||
guards.ua_or_user,
|
||||
extra_data,
|
||||
SheetRebuildKind::Full,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if quirks_mode != QuirksMode::NoQuirks {
|
||||
let stylesheet = &ua_stylesheets.quirks_mode_stylesheet;
|
||||
|
@ -140,25 +160,35 @@ impl DocumentCascadeData {
|
|||
Origin::UserAgent | Origin::User
|
||||
));
|
||||
|
||||
if origins_to_rebuild.contains(sheet_origin.into()) {
|
||||
let validity = flusher.origin_validity(sheet_origin);
|
||||
|
||||
// Servo doesn't support to incrementally mutate UA sheets.
|
||||
debug_assert!(matches!(
|
||||
validity,
|
||||
OriginValidity::Valid | OriginValidity::FullyInvalid
|
||||
));
|
||||
|
||||
if validity != OriginValidity::Valid {
|
||||
self.add_stylesheet(
|
||||
device,
|
||||
quirks_mode,
|
||||
&ua_stylesheets.quirks_mode_stylesheet,
|
||||
guards.ua_or_user,
|
||||
extra_data,
|
||||
SheetRebuildKind::Full,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (stylesheet, _rebuild_kind) in flusher {
|
||||
for (stylesheet, rebuild_kind) in flusher {
|
||||
self.add_stylesheet(
|
||||
device,
|
||||
quirks_mode,
|
||||
stylesheet,
|
||||
guards.author,
|
||||
extra_data
|
||||
extra_data,
|
||||
rebuild_kind,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +199,8 @@ impl DocumentCascadeData {
|
|||
quirks_mode: QuirksMode,
|
||||
stylesheet: &S,
|
||||
guard: &SharedRwLockReadGuard,
|
||||
_extra_data: &mut PerOrigin<ExtraStyleData>
|
||||
_extra_data: &mut PerOrigin<ExtraStyleData>,
|
||||
rebuild_kind: SheetRebuildKind,
|
||||
)
|
||||
where
|
||||
S: StylesheetInDocument + ToMediaListKey + 'static,
|
||||
|
@ -183,9 +214,11 @@ impl DocumentCascadeData {
|
|||
let origin_cascade_data =
|
||||
self.per_origin.borrow_mut_for_origin(&origin);
|
||||
|
||||
if rebuild_kind.rebuild_invalidation() {
|
||||
origin_cascade_data
|
||||
.effective_media_query_results
|
||||
.saw_effective(stylesheet);
|
||||
}
|
||||
|
||||
for rule in stylesheet.effective_rules(device, guard) {
|
||||
match *rule {
|
||||
|
@ -239,6 +272,7 @@ impl DocumentCascadeData {
|
|||
|
||||
map.insert(rule, quirks_mode);
|
||||
|
||||
if rebuild_kind.rebuild_invalidation() {
|
||||
origin_cascade_data
|
||||
.invalidation_map
|
||||
.note_selector(selector, quirks_mode);
|
||||
|
@ -260,9 +294,10 @@ impl DocumentCascadeData {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
origin_cascade_data.rules_source_order += 1;
|
||||
}
|
||||
CssRule::Import(ref lock) => {
|
||||
CssRule::Import(ref lock) if rebuild_kind.rebuild_invalidation() => {
|
||||
let import_rule = lock.read_with(guard);
|
||||
origin_cascade_data
|
||||
.effective_media_query_results
|
||||
|
@ -271,7 +306,7 @@ impl DocumentCascadeData {
|
|||
// NOTE: effective_rules visits the inner stylesheet if
|
||||
// appropriate.
|
||||
}
|
||||
CssRule::Media(ref lock) => {
|
||||
CssRule::Media(ref lock) if rebuild_kind.rebuild_invalidation() => {
|
||||
let media_rule = lock.read_with(guard);
|
||||
origin_cascade_data
|
||||
.effective_media_query_results
|
||||
|
@ -1773,20 +1808,25 @@ impl CascadeData {
|
|||
self.pseudos_map.get(pseudo).is_some()
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
/// Clears the cascade data, but not the invalidation data.
|
||||
fn clear_cascade_data(&mut self) {
|
||||
self.element_map.clear();
|
||||
self.pseudos_map.clear();
|
||||
self.animations.clear();
|
||||
self.rules_source_order = 0;
|
||||
self.num_selectors = 0;
|
||||
self.num_declarations = 0;
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.clear_cascade_data();
|
||||
self.effective_media_query_results.clear();
|
||||
self.invalidation_map.clear();
|
||||
self.attribute_dependencies.clear();
|
||||
self.style_attribute_dependency = false;
|
||||
self.state_dependencies = ElementState::empty();
|
||||
self.mapped_ids.clear();
|
||||
self.selectors_for_cache_revalidation.clear();
|
||||
self.effective_media_query_results.clear();
|
||||
self.rules_source_order = 0;
|
||||
self.num_selectors = 0;
|
||||
self.num_declarations = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue