aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/invalidation/stylesheets.rs
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2024-02-27 23:39:06 +0800
committerGitHub <noreply@github.com>2024-02-27 15:39:06 +0000
commitfaf754dfa655f0b9a28f62bc47a78fbf78ebcaf4 (patch)
tree4725e1446680d036797b1fc258733ae6b2c9f354 /components/style/invalidation/stylesheets.rs
parentb07505417e629bbb081be9683630f2d7a5f50544 (diff)
downloadservo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.tar.gz
servo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.zip
Move Stylo to its own repo (#31350)
* Remove packages that were moved to external repo * Add workspace dependencies pointing to 2023-06-14 branch * Fix servo-tidy.toml errors * Update commit to include #31346 * Update commit to include servo/stylo#2 * Move css-properties.json lookup to target/doc/stylo * Remove dependency on vendored mako in favour of pypi dependency This also removes etc/ci/generate_workflow.py, which has been unused since at least 9e71bd6a7010d6e5723831696ae0ebe26b47682f. * Add temporary code to debug Windows test failures * Fix failures on Windows due to custom target dir * Update commit to include servo/stylo#3 * Fix license in tests/unit/style/build.rs * Document how to build with local Stylo in Cargo.toml
Diffstat (limited to 'components/style/invalidation/stylesheets.rs')
-rw-r--r--components/style/invalidation/stylesheets.rs655
1 files changed, 0 insertions, 655 deletions
diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs
deleted file mode 100644
index 6ae67452dbd..00000000000
--- a/components/style/invalidation/stylesheets.rs
+++ /dev/null
@@ -1,655 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-
-//! A collection of invalidations due to changes in which stylesheets affect a
-//! document.
-
-#![deny(unsafe_code)]
-
-use crate::context::QuirksMode;
-use crate::dom::{TDocument, TElement, TNode};
-use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
-use crate::invalidation::element::restyle_hints::RestyleHint;
-use crate::media_queries::Device;
-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::values::AtomIdent;
-use crate::LocalName as SelectorLocalName;
-use crate::{Atom, ShrinkIfNeeded};
-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
-/// StylesheetInvalidationSet's maps.
-#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
-enum Invalidation {
- /// An element with a given id.
- ID(AtomIdent),
- /// An element with a given class name.
- Class(AtomIdent),
- /// An element with a given local name.
- LocalName {
- name: SelectorLocalName,
- lower_name: SelectorLocalName,
- },
-}
-
-impl Invalidation {
- fn is_id(&self) -> bool {
- matches!(*self, Invalidation::ID(..))
- }
-
- fn is_id_or_class(&self) -> bool {
- matches!(*self, Invalidation::ID(..) | Invalidation::Class(..))
- }
-}
-
-/// Whether we should invalidate just the element, or the whole subtree within
-/// it.
-#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)]
-enum InvalidationKind {
- None = 0,
- Element,
- Scope,
-}
-
-impl std::ops::BitOrAssign for InvalidationKind {
- #[inline]
- fn bitor_assign(&mut self, other: Self) {
- *self = std::cmp::max(*self, other);
- }
-}
-
-impl InvalidationKind {
- #[inline]
- fn is_scope(self) -> bool {
- matches!(self, Self::Scope)
- }
-
- #[inline]
- fn add(&mut self, other: Option<&InvalidationKind>) {
- if let Some(other) = other {
- *self |= *other;
- }
- }
-}
-
-/// A set of invalidations due to stylesheet additions.
-///
-/// TODO(emilio): We might be able to do the same analysis for media query
-/// changes too (or even selector changes?).
-#[derive(Debug, Default, MallocSizeOf)]
-pub struct StylesheetInvalidationSet {
- classes: MaybeCaseInsensitiveHashMap<Atom, InvalidationKind>,
- ids: MaybeCaseInsensitiveHashMap<Atom, InvalidationKind>,
- local_names: PrecomputedHashMap<SelectorLocalName, InvalidationKind>,
- fully_invalid: bool,
-}
-
-impl StylesheetInvalidationSet {
- /// Create an empty `StylesheetInvalidationSet`.
- pub fn new() -> Self {
- Default::default()
- }
-
- /// Mark the DOM tree styles' as fully invalid.
- pub fn invalidate_fully(&mut self) {
- debug!("StylesheetInvalidationSet::invalidate_fully");
- self.clear();
- self.fully_invalid = true;
- }
-
- fn shrink_if_needed(&mut self) {
- if self.fully_invalid {
- return;
- }
- self.classes.shrink_if_needed();
- self.ids.shrink_if_needed();
- self.local_names.shrink_if_needed();
- }
-
- /// Analyze the given stylesheet, and collect invalidations from their
- /// rules, in order to avoid doing a full restyle when we style the document
- /// next time.
- pub fn collect_invalidations_for<S>(
- &mut self,
- device: &Device,
- stylesheet: &S,
- guard: &SharedRwLockReadGuard,
- ) where
- S: StylesheetInDocument,
- {
- debug!("StylesheetInvalidationSet::collect_invalidations_for");
- if self.fully_invalid {
- debug!(" > Fully invalid already");
- return;
- }
-
- if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) {
- debug!(" > Stylesheet was not effective");
- return; // Nothing to do here.
- }
-
- let quirks_mode = device.quirks_mode();
- for rule in stylesheet.effective_rules(device, guard) {
- self.collect_invalidations_for_rule(rule, guard, device, quirks_mode);
- if self.fully_invalid {
- break;
- }
- }
-
- self.shrink_if_needed();
-
- debug!(" > resulting class invalidations: {:?}", self.classes);
- debug!(" > resulting id invalidations: {:?}", self.ids);
- debug!(
- " > resulting local name invalidations: {:?}",
- self.local_names
- );
- debug!(" > fully_invalid: {}", self.fully_invalid);
- }
-
- /// Clears the invalidation set, invalidating elements as needed if
- /// `document_element` is provided.
- ///
- /// Returns true if any invalidations ocurred.
- pub fn flush<E>(&mut self, document_element: Option<E>, snapshots: Option<&SnapshotMap>) -> bool
- where
- E: TElement,
- {
- debug!(
- "Stylist::flush({:?}, snapshots: {})",
- document_element,
- snapshots.is_some()
- );
- let have_invalidations = match document_element {
- Some(e) => self.process_invalidations(e, snapshots),
- None => false,
- };
- self.clear();
- have_invalidations
- }
-
- /// 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() &&
- self.local_names.is_empty()
- }
-
- fn invalidation_kind_for<E>(
- &self,
- element: E,
- snapshot: Option<&Snapshot>,
- quirks_mode: QuirksMode,
- ) -> InvalidationKind
- where
- E: TElement,
- {
- debug_assert!(!self.fully_invalid);
-
- let mut kind = InvalidationKind::None;
-
- if !self.classes.is_empty() {
- element.each_class(|c| {
- kind.add(self.classes.get(c, quirks_mode));
- });
-
- if kind.is_scope() {
- return kind;
- }
-
- if let Some(snapshot) = snapshot {
- snapshot.each_class(|c| {
- kind.add(self.classes.get(c, quirks_mode));
- });
-
- if kind.is_scope() {
- return kind;
- }
- }
- }
-
- if !self.ids.is_empty() {
- if let Some(ref id) = element.id() {
- kind.add(self.ids.get(id, quirks_mode));
- if kind.is_scope() {
- return kind;
- }
- }
-
- if let Some(ref old_id) = snapshot.and_then(|s| s.id_attr()) {
- kind.add(self.ids.get(old_id, quirks_mode));
- if kind.is_scope() {
- return kind;
- }
- }
- }
-
- if !self.local_names.is_empty() {
- kind.add(self.local_names.get(element.local_name()));
- }
-
- kind
- }
-
- /// Clears the invalidation set without processing.
- pub fn clear(&mut self) {
- self.classes.clear();
- self.ids.clear();
- self.local_names.clear();
- self.fully_invalid = false;
- debug_assert!(self.is_empty());
- }
-
- fn process_invalidations<E>(&self, element: E, snapshots: Option<&SnapshotMap>) -> bool
- where
- E: TElement,
- {
- debug!("Stylist::process_invalidations({:?}, {:?})", element, self);
-
- {
- let mut data = match element.mutate_data() {
- Some(data) => data,
- None => return false,
- };
-
- if self.fully_invalid {
- debug!("process_invalidations: fully_invalid({:?})", element);
- data.hint.insert(RestyleHint::restyle_subtree());
- return true;
- }
- }
-
- if self.is_empty() {
- debug!("process_invalidations: empty invalidation set");
- return false;
- }
-
- let quirks_mode = element.as_node().owner_doc().quirks_mode();
- self.process_invalidations_in_subtree(element, snapshots, quirks_mode)
- }
-
- /// Process style invalidations in a given subtree. This traverses the
- /// subtree looking for elements that match the invalidations in our hash
- /// map members.
- ///
- /// Returns whether it invalidated at least one element's style.
- #[allow(unsafe_code)]
- fn process_invalidations_in_subtree<E>(
- &self,
- element: E,
- snapshots: Option<&SnapshotMap>,
- quirks_mode: QuirksMode,
- ) -> bool
- where
- E: TElement,
- {
- debug!("process_invalidations_in_subtree({:?})", element);
- let mut data = match element.mutate_data() {
- Some(data) => data,
- None => return false,
- };
-
- if !data.has_styles() {
- return false;
- }
-
- if data.hint.contains_subtree() {
- debug!(
- "process_invalidations_in_subtree: {:?} was already invalid",
- element
- );
- return false;
- }
-
- let element_wrapper = snapshots.map(|s| ElementWrapper::new(element, s));
- let snapshot = element_wrapper.as_ref().and_then(|e| e.snapshot());
-
- match self.invalidation_kind_for(element, snapshot, quirks_mode) {
- InvalidationKind::None => {},
- InvalidationKind::Element => {
- debug!(
- "process_invalidations_in_subtree: {:?} matched self",
- element
- );
- data.hint.insert(RestyleHint::RESTYLE_SELF);
- },
- InvalidationKind::Scope => {
- debug!(
- "process_invalidations_in_subtree: {:?} matched subtree",
- element
- );
- data.hint.insert(RestyleHint::restyle_subtree());
- return true;
- },
- }
-
- let mut any_children_invalid = false;
-
- for child in element.traversal_children() {
- let child = match child.as_element() {
- Some(e) => e,
- None => continue,
- };
-
- any_children_invalid |=
- self.process_invalidations_in_subtree(child, snapshots, quirks_mode);
- }
-
- if any_children_invalid {
- debug!(
- "Children of {:?} changed, setting dirty descendants",
- element
- );
- unsafe { element.set_dirty_descendants() }
- }
-
- data.hint.contains(RestyleHint::RESTYLE_SELF) || any_children_invalid
- }
-
- /// TODO(emilio): Reuse the bucket stuff from selectormap? That handles
- /// :is() / :where() etc.
- fn scan_component(
- component: &Component<SelectorImpl>,
- invalidation: &mut Option<Invalidation>,
- ) {
- match *component {
- Component::LocalName(LocalName {
- ref name,
- ref lower_name,
- }) => {
- if invalidation.is_none() {
- *invalidation = Some(Invalidation::LocalName {
- name: name.clone(),
- lower_name: lower_name.clone(),
- });
- }
- },
- Component::Class(ref class) => {
- if invalidation.as_ref().map_or(true, |s| !s.is_id_or_class()) {
- *invalidation = Some(Invalidation::Class(class.clone()));
- }
- },
- Component::ID(ref id) => {
- if invalidation.as_ref().map_or(true, |s| !s.is_id()) {
- *invalidation = Some(Invalidation::ID(id.clone()));
- }
- },
- _ => {
- // Ignore everything else, at least for now.
- },
- }
- }
-
- /// Collect invalidations for a given selector.
- ///
- /// We look at the outermost local name, class, or ID selector to the left
- /// of an ancestor combinator, in order to restyle only a given subtree.
- ///
- /// If the selector has no ancestor combinator, then we do the same for
- /// the only sequence it has, but record it as an element invalidation
- /// instead of a subtree invalidation.
- ///
- /// We prefer IDs to classs, and classes to local names, on the basis
- /// that the former should be more specific than the latter. We also
- /// prefer to generate subtree invalidations for the outermost part
- /// of the selector, to reduce the amount of traversal we need to do
- /// when flushing invalidations.
- fn collect_invalidations(
- &mut self,
- selector: &Selector<SelectorImpl>,
- quirks_mode: QuirksMode,
- ) {
- debug!(
- "StylesheetInvalidationSet::collect_invalidations({:?})",
- selector
- );
-
- let mut element_invalidation: Option<Invalidation> = None;
- let mut subtree_invalidation: Option<Invalidation> = None;
-
- let mut scan_for_element_invalidation = true;
- let mut scan_for_subtree_invalidation = false;
-
- let mut iter = selector.iter();
-
- loop {
- for component in &mut iter {
- if scan_for_element_invalidation {
- Self::scan_component(component, &mut element_invalidation);
- } else if scan_for_subtree_invalidation {
- Self::scan_component(component, &mut subtree_invalidation);
- }
- }
- match iter.next_sequence() {
- None => break,
- Some(combinator) => {
- scan_for_subtree_invalidation = combinator.is_ancestor();
- },
- }
- scan_for_element_invalidation = false;
- }
-
- if let Some(s) = subtree_invalidation {
- debug!(" > Found subtree invalidation: {:?}", s);
- if self.insert_invalidation(s, InvalidationKind::Scope, quirks_mode) {
- return;
- }
- }
-
- if let Some(s) = element_invalidation {
- debug!(" > Found element invalidation: {:?}", s);
- if self.insert_invalidation(s, InvalidationKind::Element, quirks_mode) {
- return;
- }
- }
-
- // The selector was of a form that we can't handle. Any element could
- // match it, so let's just bail out.
- debug!(" > Can't handle selector or OOMd, marking fully invalid");
- self.invalidate_fully()
- }
-
- fn insert_invalidation(
- &mut self,
- invalidation: Invalidation,
- kind: InvalidationKind,
- quirks_mode: QuirksMode,
- ) -> bool {
- match invalidation {
- Invalidation::Class(c) => {
- let entry = match self.classes.try_entry(c.0, quirks_mode) {
- Ok(e) => e,
- Err(..) => return false,
- };
- *entry.or_insert(InvalidationKind::None) |= kind;
- },
- Invalidation::ID(i) => {
- let entry = match self.ids.try_entry(i.0, quirks_mode) {
- Ok(e) => e,
- Err(..) => return false,
- };
- *entry.or_insert(InvalidationKind::None) |= kind;
- },
- Invalidation::LocalName { name, lower_name } => {
- let insert_lower = name != lower_name;
- if self.local_names.try_reserve(1).is_err() {
- return false;
- }
- let entry = self.local_names.entry(name);
- *entry.or_insert(InvalidationKind::None) |= kind;
- if insert_lower {
- if self.local_names.try_reserve(1).is_err() {
- return false;
- }
- let entry = self.local_names.entry(lower_name);
- *entry.or_insert(InvalidationKind::None) |= kind;
- }
- },
- }
-
- 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.
- },
- LayerStatement(..) => {
- // Layer statement insertions might alter styling order, so we need to always
- // invalidate fully.
- return self.invalidate_fully();
- },
- CounterStyle(..) |
- Page(..) |
- Property(..) |
- FontFeatureValues(..) |
- FontPaletteValues(..) |
- FontFace(..) |
- Keyframes(..) |
- Container(..) |
- 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(..) | LayerBlock(..) => {
- 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);
- if self.fully_invalid {
- break;
- }
- }
- },
- }
- }
-
- /// Collects invalidations for a given CSS rule.
- fn collect_invalidations_for_rule(
- &mut self,
- rule: &CssRule,
- guard: &SharedRwLockReadGuard,
- device: &Device,
- quirks_mode: QuirksMode,
- ) {
- use crate::stylesheets::CssRule::*;
- debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
- debug_assert!(!self.fully_invalid, "Not worth to be here!");
-
- match *rule {
- Style(ref lock) => {
- let style_rule = lock.read_with(guard);
- for selector in &style_rule.selectors.0 {
- self.collect_invalidations(selector, quirks_mode);
- if self.fully_invalid {
- return;
- }
- }
- },
- Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) |
- Container(..) | LayerStatement(..) | LayerBlock(..) => {
- // Do nothing, relevant nested rules are visited as part of the
- // iteration.
- },
- FontFace(..) => {
- // Do nothing, @font-face doesn't affect computed style
- // information. We'll restyle when the font face loads, if
- // needed.
- },
- Keyframes(ref lock) => {
- let keyframes_rule = lock.read_with(guard);
- if device.animation_name_may_be_referenced(&keyframes_rule.name) {
- debug!(
- " > Found @keyframes rule potentially referenced \
- from the page, marking the whole tree invalid."
- );
- self.fully_invalid = true;
- } else {
- // Do nothing, this animation can't affect the style of
- // existing elements.
- }
- },
- CounterStyle(..) |
- Page(..) |
- Property(..) |
- FontFeatureValues(..) |
- FontPaletteValues(..) => {
- debug!(" > Found unsupported rule, marking the whole subtree invalid.");
-
- // TODO(emilio): Can we do better here?
- //
- // At least in `@page`, we could check the relevant media, I
- // guess.
- self.fully_invalid = true;
- },
- }
- }
-}