diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/style/invalidation/stylesheets.rs | 88 | ||||
-rw-r--r-- | components/style/stylesheet_set.rs | 55 | ||||
-rw-r--r-- | components/style/stylesheets/mod.rs | 2 | ||||
-rw-r--r-- | components/style/stylesheets/rules_iterator.rs | 187 | ||||
-rw-r--r-- | components/style/stylesheets/stylesheet.rs | 2 | ||||
-rw-r--r-- | components/style/stylist.rs | 13 |
6 files changed, 273 insertions, 74 deletions
diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 18d24a28a78..b5868883248 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -16,10 +16,26 @@ use crate::selector_map::{MaybeCaseInsensitiveHashMap, PrecomputedHashMap}; use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap}; use crate::shared_lock::SharedRwLockReadGuard; use crate::stylesheets::{CssRule, StylesheetInDocument}; +use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator}; use crate::Atom; use crate::LocalName as SelectorLocalName; use selectors::parser::{Component, LocalName, Selector}; +/// The kind of change that happened for a given rule. +#[repr(u32)] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub enum RuleChangeKind { + /// The rule was inserted. + Insertion, + /// The rule was removed. + Removal, + /// Some change in the rule which we don't know about, and could have made + /// the rule change in any way. + Generic, + /// A change in the declarations of a style rule. + StyleRuleDeclarations, +} + /// A style sheet invalidation represents a kind of element or subtree that may /// need to be restyled. Whether it represents a whole subtree or just a single /// element is determined by the given InvalidationKind in @@ -162,7 +178,8 @@ impl StylesheetInvalidationSet { have_invalidations } - fn is_empty(&self) -> bool { + /// Returns whether there's no invalidation to process. + pub fn is_empty(&self) -> bool { !self.fully_invalid && self.classes.is_empty() && self.ids.is_empty() && @@ -484,6 +501,75 @@ impl StylesheetInvalidationSet { true } + /// Collects invalidations for a given CSS rule, if not fully invalid + /// already. + /// + /// TODO(emilio): we can't check whether the rule is inside a non-effective + /// subtree, we potentially could do that. + pub fn rule_changed<S>( + &mut self, + stylesheet: &S, + rule: &CssRule, + guard: &SharedRwLockReadGuard, + device: &Device, + quirks_mode: QuirksMode, + change_kind: RuleChangeKind, + ) where + S: StylesheetInDocument, + { + use crate::stylesheets::CssRule::*; + + debug!("StylesheetInvalidationSet::rule_changed"); + if self.fully_invalid { + return; + } + + if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { + debug!(" > Stylesheet was not effective"); + return; // Nothing to do here. + } + + let is_generic_change = change_kind == RuleChangeKind::Generic; + + match *rule { + Namespace(..) => { + // It's not clear what handling changes for this correctly would + // look like. + }, + CounterStyle(..) | + Page(..) | + Viewport(..) | + FontFeatureValues(..) | + FontFace(..) | + Keyframes(..) | + Style(..) => { + if is_generic_change { + // TODO(emilio): We need to do this for selector / keyframe + // name / font-face changes, because we don't have the old + // selector / name. If we distinguish those changes + // specially, then we can at least use this invalidation for + // style declaration changes. + return self.invalidate_fully(); + } + + self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) + }, + Document(..) | Import(..) | Media(..) | Supports(..) => { + if !is_generic_change && + !EffectiveRules::is_effective(guard, device, quirks_mode, rule) + { + return; + } + + let rules = + EffectiveRulesIterator::effective_children(device, quirks_mode, guard, rule); + for rule in rules { + self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) + } + }, + } + } + /// Collects invalidations for a given CSS rule. fn collect_invalidations_for_rule( &mut self, diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index a9cd39eef20..93f4b986052 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -5,11 +5,11 @@ //! A centralized set of stylesheets for a document. use crate::dom::TElement; -use crate::invalidation::stylesheets::StylesheetInvalidationSet; +use crate::invalidation::stylesheets::{StylesheetInvalidationSet, RuleChangeKind}; use crate::media_queries::Device; use crate::selector_parser::SnapshotMap; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{Origin, OriginSet, OriginSetIterator, PerOrigin, StylesheetInDocument}; +use crate::stylesheets::{CssRule, Origin, OriginSet, OriginSetIterator, PerOrigin, StylesheetInDocument}; use std::{mem, slice}; /// Entry for a StylesheetSet. @@ -438,6 +438,56 @@ macro_rules! sheet_set_methods { let collection = self.collection_for(&sheet, guard); collection.remove(&sheet) } + + /// Notify the set that a rule from a given stylesheet has changed + /// somehow. + pub fn rule_changed( + &mut self, + device: Option<&Device>, + sheet: &S, + rule: &CssRule, + guard: &SharedRwLockReadGuard, + change_kind: RuleChangeKind, + ) { + if let Some(device) = device { + let quirks_mode = sheet.quirks_mode(guard); + 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, guard); + collection.set_data_validity_at_least(validity); + } }; } @@ -485,6 +535,7 @@ where /// Returns whether the given set has changed from the last flush. pub fn has_changed(&self) -> bool { + !self.invalidations.is_empty() || self.collections .iter_origins() .any(|(collection, _)| collection.dirty) diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 12ba46b3e06..f83595dacef 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -56,7 +56,7 @@ pub use self::page_rule::PageRule; pub use self::rule_list::{CssRules, CssRulesHelpers}; pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser}; pub use self::rules_iterator::{AllRules, EffectiveRules}; -pub use self::rules_iterator::{NestedRuleIterationCondition, RulesIterator}; +pub use self::rules_iterator::{NestedRuleIterationCondition, EffectiveRulesIterator, RulesIterator}; pub use self::style_rule::StyleRule; pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index 866879ef30e..1fa25837a8c 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -35,15 +35,15 @@ where device: &'a Device, quirks_mode: QuirksMode, guard: &'a SharedRwLockReadGuard<'b>, - rules: &'a [CssRule], + rules: slice::Iter<'a, CssRule>, ) -> Self { let mut stack = SmallVec::new(); - stack.push(rules.iter()); + stack.push(rules); Self { - device: device, - quirks_mode: quirks_mode, - guard: guard, - stack: stack, + device, + quirks_mode, + guard, + stack, _phantom: ::std::marker::PhantomData, } } @@ -54,6 +54,61 @@ where } } +fn children_of_rule<'a, C>( + rule: &'a CssRule, + device: &'a Device, + quirks_mode: QuirksMode, + guard: &'a SharedRwLockReadGuard<'_>, + effective: &mut bool, +) -> slice::Iter<'a, CssRule> +where + C: NestedRuleIterationCondition + 'static, +{ + *effective = true; + match *rule { + CssRule::Namespace(_) | + CssRule::Style(_) | + CssRule::FontFace(_) | + CssRule::CounterStyle(_) | + CssRule::Viewport(_) | + CssRule::Keyframes(_) | + CssRule::Page(_) | + CssRule::FontFeatureValues(_) => [].iter(), + CssRule::Import(ref import_rule) => { + let import_rule = import_rule.read_with(guard); + if !C::process_import(guard, device, quirks_mode, import_rule) { + *effective = false; + return [].iter(); + } + import_rule.stylesheet.rules(guard).iter() + }, + CssRule::Document(ref doc_rule) => { + let doc_rule = doc_rule.read_with(guard); + if !C::process_document(guard, device, quirks_mode, doc_rule) { + *effective = false; + return [].iter(); + } + doc_rule.rules.read_with(guard).0.iter() + }, + CssRule::Media(ref lock) => { + let media_rule = lock.read_with(guard); + if !C::process_media(guard, device, quirks_mode, media_rule) { + *effective = false; + return [].iter(); + } + media_rule.rules.read_with(guard).0.iter() + }, + CssRule::Supports(ref lock) => { + let supports_rule = lock.read_with(guard); + if !C::process_supports(guard, device, quirks_mode, supports_rule) { + *effective = false; + return [].iter(); + } + supports_rule.rules.read_with(guard).0.iter() + }, + } +} + impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C> where 'b: 'a, @@ -62,78 +117,28 @@ where type Item = &'a CssRule; fn next(&mut self) -> Option<Self::Item> { - let mut nested_iter_finished = false; while !self.stack.is_empty() { - if nested_iter_finished { - self.stack.pop(); - nested_iter_finished = false; - continue; - } - - let rule; - let sub_iter = { + let rule = { let nested_iter = self.stack.last_mut().unwrap(); - rule = match nested_iter.next() { + match nested_iter.next() { Some(r) => r, None => { - nested_iter_finished = true; + self.stack.pop(); continue; }, - }; - - match *rule { - CssRule::Namespace(_) | - CssRule::Style(_) | - CssRule::FontFace(_) | - CssRule::CounterStyle(_) | - CssRule::Viewport(_) | - CssRule::Keyframes(_) | - CssRule::Page(_) | - CssRule::FontFeatureValues(_) => return Some(rule), - CssRule::Import(ref import_rule) => { - let import_rule = import_rule.read_with(self.guard); - if !C::process_import( - self.guard, - self.device, - self.quirks_mode, - import_rule, - ) { - continue; - } - import_rule.stylesheet.rules(self.guard).iter() - }, - CssRule::Document(ref doc_rule) => { - let doc_rule = doc_rule.read_with(self.guard); - if !C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) - { - continue; - } - doc_rule.rules.read_with(self.guard).0.iter() - }, - CssRule::Media(ref lock) => { - let media_rule = lock.read_with(self.guard); - if !C::process_media(self.guard, self.device, self.quirks_mode, media_rule) - { - continue; - } - media_rule.rules.read_with(self.guard).0.iter() - }, - CssRule::Supports(ref lock) => { - let supports_rule = lock.read_with(self.guard); - if !C::process_supports( - self.guard, - self.device, - self.quirks_mode, - supports_rule, - ) { - continue; - } - supports_rule.rules.read_with(self.guard).0.iter() - }, } }; - self.stack.push(sub_iter); + let mut effective = true; + let children = children_of_rule::<C>(rule, self.device, self.quirks_mode, self.guard, &mut effective); + if !effective { + continue; + } + + if !children.as_slice().is_empty() { + self.stack.push(children); + } + return Some(rule); } @@ -180,6 +185,36 @@ pub trait NestedRuleIterationCondition { /// A struct that represents the condition that a rule applies to the document. pub struct EffectiveRules; +impl EffectiveRules { + /// Returns whether a given rule is effective. + pub fn is_effective( + guard: &SharedRwLockReadGuard, + device: &Device, + quirks_mode: QuirksMode, + rule: &CssRule, + ) -> bool { + match *rule { + CssRule::Import(ref import_rule) => { + let import_rule = import_rule.read_with(guard); + Self::process_import(guard, device, quirks_mode, import_rule) + }, + CssRule::Document(ref doc_rule) => { + let doc_rule = doc_rule.read_with(guard); + Self::process_document(guard, device, quirks_mode, doc_rule) + }, + CssRule::Media(ref lock) => { + let media_rule = lock.read_with(guard); + Self::process_media(guard, device, quirks_mode, media_rule) + }, + CssRule::Supports(ref lock) => { + let supports_rule = lock.read_with(guard); + Self::process_supports(guard, device, quirks_mode, supports_rule) + }, + _ => true, + } + } +} + impl NestedRuleIterationCondition for EffectiveRules { fn process_import( guard: &SharedRwLockReadGuard, @@ -260,3 +295,17 @@ impl NestedRuleIterationCondition for AllRules { /// /// NOTE: This iterator recurses into `@import` rules. pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>; + +impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { + /// Returns an iterator over the effective children of a rule, even if + /// `rule` itself is not effective. + pub fn effective_children( + device: &'a Device, + quirks_mode: QuirksMode, + guard: &'a SharedRwLockReadGuard<'b>, + rule: &'a CssRule, + ) -> Self { + let children = children_of_rule::<AllRules>(rule, device, quirks_mode, guard, &mut false); + EffectiveRulesIterator::new(device, quirks_mode, guard, children) + } +} diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 6679f587a41..e5b0c073f2f 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -243,7 +243,7 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { where C: NestedRuleIterationCondition, { - RulesIterator::new(device, self.quirks_mode(guard), guard, self.rules(guard)) + RulesIterator::new(device, self.quirks_mode(guard), guard, self.rules(guard).iter()) } /// Returns whether the style-sheet applies for the current device. diff --git a/components/style/stylist.rs b/components/style/stylist.rs index fc363b2ce68..8cf9efb3927 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -13,6 +13,7 @@ use crate::font_metrics::FontMetricsProvider; use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion}; use crate::invalidation::element::invalidation_map::InvalidationMap; use crate::invalidation::media_queries::{EffectiveMediaQueryResults, ToMediaListKey}; +use crate::invalidation::stylesheets::RuleChangeKind; use crate::media_queries::Device; use crate::properties::{self, CascadeMode, ComputedValues}; use crate::properties::{AnimationDeclarations, PropertyDeclarationBlock}; @@ -605,6 +606,18 @@ impl Stylist { .remove_stylesheet(Some(&self.device), sheet, guard) } + /// Notify of a change of a given rule. + pub fn rule_changed( + &mut self, + sheet: &StylistSheet, + rule: &CssRule, + guard: &SharedRwLockReadGuard, + change_kind: RuleChangeKind, + ) { + self.stylesheets + .rule_changed(Some(&self.device), sheet, rule, guard, change_kind) + } + /// Appends a new stylesheet to the current set. #[inline] pub fn sheet_count(&self, origin: Origin) -> usize { |