style: Invalidate for CSSOM changes in a more fine-grained way.

Also, for changes in CSS declarations, like changing
cssRules[i].style.color or something, we end up avoiding a lot of the
work we were doing.

This page still trips us in the sense that they add a stylesheet, then
call getBoundingClientRect(), then insert more rules in the stylesheet,
which causes us to rebuild a lot of the cascade data.

We could try to detect appends to the last stylesheet on the list or
something I guess, and avoid rebuilding the cascade data in some cases.

Depends on D85615

Differential Revision: https://phabricator.services.mozilla.com/D85616
This commit is contained in:
Emilio Cobos Álvarez 2020-08-10 18:00:44 +00:00
parent dfa715a8d8
commit ca7e1ecfd8
6 changed files with 273 additions and 74 deletions

View file

@ -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,