style/invalidation/element/
state_and_attributes.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! An invalidation processor for style changes due to state and attribute
6//! changes.
7
8use crate::context::SharedStyleContext;
9use crate::data::ElementData;
10use crate::dom::{TElement, TNode};
11use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
12use crate::invalidation::element::invalidation_map::*;
13use crate::invalidation::element::invalidator::{
14    DescendantInvalidationLists, InvalidationVector, SiblingTraversalMap,
15};
16use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
17use crate::invalidation::element::restyle_hints::RestyleHint;
18use crate::selector_map::SelectorMap;
19use crate::selector_parser::Snapshot;
20use crate::stylesheets::origin::OriginSet;
21use crate::values::AtomIdent;
22use crate::{Atom, WeakAtom};
23use dom::ElementState;
24use selectors::attr::CaseSensitivity;
25use selectors::kleene_value::KleeneValue;
26use selectors::matching::{
27    matches_selector_kleene, IncludeStartingStyle, MatchingContext, MatchingForInvalidation,
28    MatchingMode, NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode,
29};
30use selectors::OpaqueElement;
31use smallvec::SmallVec;
32
33/// The collector implementation.
34struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
35where
36    E: TElement,
37{
38    element: E,
39    wrapper: ElementWrapper<'b, E>,
40    snapshot: &'a Snapshot,
41    matching_context: &'a mut MatchingContext<'b, E::Impl>,
42    lookup_element: E,
43    removed_id: Option<&'a WeakAtom>,
44    added_id: Option<&'a WeakAtom>,
45    classes_removed: &'a SmallVec<[Atom; 8]>,
46    classes_added: &'a SmallVec<[Atom; 8]>,
47    custom_states_removed: &'a SmallVec<[AtomIdent; 8]>,
48    custom_states_added: &'a SmallVec<[AtomIdent; 8]>,
49    state_changes: ElementState,
50    descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
51    sibling_invalidations: &'a mut InvalidationVector<'selectors>,
52    invalidates_self: bool,
53}
54
55/// An invalidation processor for style changes due to state and attribute
56/// changes.
57pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> {
58    shared_context: &'a SharedStyleContext<'b>,
59    element: E,
60    data: &'a mut ElementData,
61    matching_context: MatchingContext<'a, E::Impl>,
62    traversal_map: SiblingTraversalMap<E>,
63}
64
65impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> {
66    /// Creates a new StateAndAttrInvalidationProcessor.
67    pub fn new(
68        shared_context: &'a SharedStyleContext<'b>,
69        element: E,
70        data: &'a mut ElementData,
71        selector_caches: &'a mut SelectorCaches,
72    ) -> Self {
73        let matching_context = MatchingContext::new_for_visited(
74            MatchingMode::Normal,
75            None,
76            selector_caches,
77            VisitedHandlingMode::AllLinksVisitedAndUnvisited,
78            IncludeStartingStyle::No,
79            shared_context.quirks_mode(),
80            NeedsSelectorFlags::No,
81            MatchingForInvalidation::Yes,
82        );
83
84        Self {
85            shared_context,
86            element,
87            data,
88            matching_context,
89            traversal_map: SiblingTraversalMap::default(),
90        }
91    }
92}
93
94/// Checks a dependency against a given element and wrapper, to see if something
95/// changed.
96pub fn check_dependency<E, W>(
97    dependency: &Dependency,
98    element: &E,
99    wrapper: &W,
100    context: &mut MatchingContext<'_, E::Impl>,
101    scope: Option<OpaqueElement>,
102) -> bool
103where
104    E: TElement,
105    W: selectors::Element<Impl = E::Impl>,
106{
107    context.for_invalidation_comparison(|context| {
108        context.nest_for_scope_condition(scope, |context|{
109
110            let matches_now = matches_selector_kleene(
111                &dependency.selector,
112                dependency.selector_offset,
113                None,
114                element,
115                context,
116            );
117
118            // If the previous dependency was a scope dependency (i.e. by `scope` is set),
119            // possible change in scope element is encapsulated in `:scope`, whose
120            // matching value will not change. We instead check that the change in scope
121            // element can propagate (i.e. This selector matches).
122            if scope.is_some() && matches_now != KleeneValue::False {
123                return true;
124            }
125
126            let matched_then = matches_selector_kleene(
127                &dependency.selector,
128                dependency.selector_offset,
129                None,
130                wrapper,
131                context,
132            );
133
134            matched_then != matches_now || matches_now == KleeneValue::Unknown
135        })
136    })
137}
138
139/// Whether we should process the descendants of a given element for style
140/// invalidation.
141pub fn should_process_descendants(data: &ElementData) -> bool {
142    !data.styles.is_display_none() && !data.hint.contains(RestyleHint::RESTYLE_DESCENDANTS)
143}
144
145/// Propagates the bits after invalidating a descendant child.
146pub fn propagate_dirty_bit_up_to<E>(ancestor: E, child: E)
147where
148    E: TElement,
149{
150    // The child may not be a flattened tree child of the current element,
151    // but may be arbitrarily deep.
152    //
153    // Since we keep the traversal flags in terms of the flattened tree,
154    // we need to propagate it as appropriate.
155    let mut current = child.traversal_parent();
156    while let Some(parent) = current.take() {
157        unsafe { parent.set_dirty_descendants() };
158        current = parent.traversal_parent();
159
160        if parent == ancestor {
161            return;
162        }
163    }
164    debug_assert!(
165        false,
166        "Should've found {:?} as an ancestor of {:?}",
167        ancestor, child
168    );
169}
170
171/// Propagates the bits after invalidating a descendant child, if needed.
172pub fn invalidated_descendants<E>(element: E, child: E)
173where
174    E: TElement,
175{
176    if !child.has_data() {
177        return;
178    }
179    propagate_dirty_bit_up_to(element, child)
180}
181
182/// Sets the appropriate restyle hint after invalidating the style of a given
183/// element.
184pub fn invalidated_self<E>(element: E) -> bool
185where
186    E: TElement,
187{
188    let mut data = match element.mutate_data() {
189        Some(data) => data,
190        None => return false,
191    };
192    data.hint.insert(RestyleHint::RESTYLE_SELF);
193    true
194}
195
196/// Sets the appropriate hint after invalidating the style of a sibling.
197pub fn invalidated_sibling<E>(element: E, of: E)
198where
199    E: TElement,
200{
201    debug_assert_eq!(
202        element.as_node().parent_node(),
203        of.as_node().parent_node(),
204        "Should be siblings"
205    );
206    if !invalidated_self(element) {
207        return;
208    }
209    if element.traversal_parent() != of.traversal_parent() {
210        let parent = element.as_node().parent_element_or_host();
211        debug_assert!(
212            parent.is_some(),
213            "How can we have siblings without parent nodes?"
214        );
215        if let Some(e) = parent {
216            propagate_dirty_bit_up_to(e, element)
217        }
218    }
219}
220
221impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'a, 'a, E>
222    for StateAndAttrInvalidationProcessor<'a, 'b, E>
223where
224    E: TElement,
225{
226    /// We need to invalidate style on pseudo-elements, in order to process
227    /// changes that could otherwise end up in ::before or ::after content being
228    /// generated, and invalidate lazy pseudo caches.
229    fn invalidates_on_pseudo_element(&self) -> bool {
230        true
231    }
232
233    fn check_outer_dependency(&mut self, dependency: &Dependency, element: E, scope: Option<OpaqueElement>) -> bool {
234        // We cannot assert about `element` having a snapshot here (in fact it
235        // most likely won't), because it may be an arbitrary descendant or
236        // later-sibling of the element we started invalidating with.
237        let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map);
238        check_dependency(dependency, &element, &wrapper, &mut self.matching_context, scope)
239    }
240
241    fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
242        &mut self.matching_context
243    }
244
245    fn sibling_traversal_map(&self) -> &SiblingTraversalMap<E> {
246        &self.traversal_map
247    }
248
249    fn collect_invalidations(
250        &mut self,
251        element: E,
252        _self_invalidations: &mut InvalidationVector<'a>,
253        descendant_invalidations: &mut DescendantInvalidationLists<'a>,
254        sibling_invalidations: &mut InvalidationVector<'a>,
255    ) -> bool {
256        debug_assert_eq!(element, self.element);
257        debug_assert!(element.has_snapshot(), "Why bothering?");
258
259        let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map);
260
261        let state_changes = wrapper.state_changes();
262        let Some(snapshot) = wrapper.snapshot() else {
263            return false;
264        };
265
266        if !snapshot.has_attrs() && !snapshot.has_custom_states() && state_changes.is_empty() {
267            return false;
268        }
269
270        let mut classes_removed = SmallVec::<[Atom; 8]>::new();
271        let mut classes_added = SmallVec::<[Atom; 8]>::new();
272        if snapshot.class_changed() {
273            // TODO(emilio): Do this more efficiently!
274            snapshot.each_class(|c| {
275                if !element.has_class(c, CaseSensitivity::CaseSensitive) {
276                    classes_removed.push(c.0.clone())
277                }
278            });
279
280            element.each_class(|c| {
281                if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
282                    classes_added.push(c.0.clone())
283                }
284            })
285        }
286
287        let mut custom_states_removed = SmallVec::<[AtomIdent; 8]>::new();
288        let mut custom_states_added = SmallVec::<[AtomIdent; 8]>::new();
289        if snapshot.has_custom_states() {
290            snapshot.each_custom_state(|s| {
291                if !element.has_custom_state(s) {
292                    custom_states_removed.push(s.clone())
293                }
294            });
295            element.each_custom_state(|s| {
296                if !snapshot.has_custom_state(s) {
297                    custom_states_added.push(s.clone())
298                }
299            })
300        }
301
302        let mut id_removed = None;
303        let mut id_added = None;
304        if snapshot.id_changed() {
305            let old_id = snapshot.id_attr();
306            let current_id = element.id();
307
308            if old_id != current_id {
309                id_removed = old_id;
310                id_added = current_id;
311            }
312        }
313
314        if log_enabled!(::log::Level::Debug) {
315            debug!("Collecting changes for: {:?}", element);
316            if !state_changes.is_empty() {
317                debug!(" > state: {:?}", state_changes);
318            }
319            if snapshot.id_changed() {
320                debug!(" > id changed: +{:?} -{:?}", id_added, id_removed);
321            }
322            if snapshot.class_changed() {
323                debug!(
324                    " > class changed: +{:?} -{:?}",
325                    classes_added, classes_removed
326                );
327            }
328            let mut attributes_changed = false;
329            snapshot.each_attr_changed(|_| {
330                attributes_changed = true;
331            });
332            if attributes_changed {
333                debug!(
334                    " > attributes changed, old: {}",
335                    snapshot.debug_list_attributes()
336                )
337            }
338        }
339
340        let lookup_element = if element.implemented_pseudo_element().is_some() {
341            element.pseudo_element_originating_element().unwrap()
342        } else {
343            element
344        };
345
346        let mut shadow_rule_datas = SmallVec::<[_; 3]>::new();
347        let matches_document_author_rules =
348            element.each_applicable_non_document_style_rule_data(|data, host| {
349                shadow_rule_datas.push((data, host.opaque()))
350            });
351
352        let invalidated_self = {
353            let mut collector = Collector {
354                wrapper,
355                lookup_element,
356                state_changes,
357                element,
358                snapshot: &snapshot,
359                matching_context: &mut self.matching_context,
360                removed_id: id_removed,
361                added_id: id_added,
362                classes_removed: &classes_removed,
363                classes_added: &classes_added,
364                custom_states_removed: &custom_states_removed,
365                custom_states_added: &custom_states_added,
366                descendant_invalidations,
367                sibling_invalidations,
368                invalidates_self: false,
369            };
370
371            let document_origins = if !matches_document_author_rules {
372                OriginSet::ORIGIN_USER_AGENT | OriginSet::ORIGIN_USER
373            } else {
374                OriginSet::all()
375            };
376
377            for (cascade_data, origin) in self.shared_context.stylist.iter_origins() {
378                if document_origins.contains(origin.into()) {
379                    collector
380                        .collect_dependencies_in_invalidation_map(cascade_data.invalidation_map());
381                }
382            }
383
384            for &(ref data, ref host) in &shadow_rule_datas {
385                collector.matching_context.current_host = Some(host.clone());
386                collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
387            }
388
389            collector.invalidates_self
390        };
391
392        // If we generated a ton of descendant invalidations, it's probably not
393        // worth to go ahead and try to process them.
394        //
395        // Just restyle the descendants directly.
396        //
397        // This number is completely made-up, but the page that made us add this
398        // code generated 1960+ invalidations (bug 1420741).
399        //
400        // We don't look at slotted_descendants because those don't propagate
401        // down more than one level anyway.
402        if descendant_invalidations.dom_descendants.len() > 150 {
403            self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
404        }
405
406        if invalidated_self {
407            self.data.hint.insert(RestyleHint::RESTYLE_SELF);
408        }
409
410        invalidated_self
411    }
412
413    fn should_process_descendants(&mut self, element: E) -> bool {
414        if element == self.element {
415            return should_process_descendants(&self.data);
416        }
417
418        match element.borrow_data() {
419            Some(d) => should_process_descendants(&d),
420            None => return false,
421        }
422    }
423
424    fn recursion_limit_exceeded(&mut self, element: E) {
425        if element == self.element {
426            self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
427            return;
428        }
429
430        if let Some(mut data) = element.mutate_data() {
431            data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
432        }
433    }
434
435    fn invalidated_descendants(&mut self, element: E, child: E) {
436        invalidated_descendants(element, child)
437    }
438
439    fn invalidated_self(&mut self, element: E) {
440        debug_assert_ne!(element, self.element);
441        invalidated_self(element);
442    }
443
444    fn invalidated_sibling(&mut self, element: E, of: E) {
445        debug_assert_ne!(element, self.element);
446        invalidated_sibling(element, of);
447    }
448}
449
450impl<'a, 'b, 'selectors, E> Collector<'a, 'b, 'selectors, E>
451where
452    E: TElement,
453    'selectors: 'a,
454{
455    fn collect_dependencies_in_invalidation_map(&mut self, map: &'selectors InvalidationMap) {
456        let quirks_mode = self.matching_context.quirks_mode();
457        let removed_id = self.removed_id;
458        if let Some(ref id) = removed_id {
459            if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
460                for dep in deps {
461                    self.scan_dependency(dep);
462                }
463            }
464        }
465
466        let added_id = self.added_id;
467        if let Some(ref id) = added_id {
468            if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
469                for dep in deps {
470                    self.scan_dependency(dep);
471                }
472            }
473        }
474
475        for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
476            if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
477                for dep in deps {
478                    self.scan_dependency(dep);
479                }
480            }
481        }
482
483        for state in self
484            .custom_states_added
485            .iter()
486            .chain(self.custom_states_removed.iter())
487        {
488            if let Some(deps) = map.custom_state_affecting_selectors.get(state) {
489                for dep in deps {
490                    self.scan_dependency(dep);
491                }
492            }
493        }
494
495        self.snapshot.each_attr_changed(|attribute| {
496            if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) {
497                for dep in deps {
498                    self.scan_dependency(dep);
499                }
500            }
501        });
502
503        self.collect_state_dependencies(&map.state_affecting_selectors)
504    }
505
506    fn collect_state_dependencies(&mut self, map: &'selectors SelectorMap<StateDependency>) {
507        if self.state_changes.is_empty() {
508            return;
509        }
510        map.lookup_with_additional(
511            self.lookup_element,
512            self.matching_context.quirks_mode(),
513            self.removed_id,
514            self.classes_removed,
515            self.state_changes,
516            |dependency| {
517                if !dependency.state.intersects(self.state_changes) {
518                    return true;
519                }
520                self.scan_dependency(&dependency.dep);
521                true
522            },
523        );
524    }
525
526    /// Check whether a dependency should be taken into account.
527    #[inline]
528    fn check_dependency(&mut self, dependency: &Dependency) -> bool {
529        check_dependency(
530            dependency,
531            &self.element,
532            &self.wrapper,
533            &mut self.matching_context,
534            None,
535        )
536    }
537
538    fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
539        debug_assert!(
540            matches!(
541                dependency.invalidation_kind(),
542                DependencyInvalidationKind::Normal(_) | DependencyInvalidationKind::Scope(_)
543            ),
544            "Found unexpected dependency invalidation kind"
545        );
546        debug!(
547            "TreeStyleInvalidator::scan_dependency({:?}, {:?})",
548            self.element, dependency
549        );
550
551        if !self.dependency_may_be_relevant(dependency) {
552            return;
553        }
554
555        if self.check_dependency(dependency) || matches!(
556                dependency.invalidation_kind(),
557                DependencyInvalidationKind::Scope(_)
558            )
559        {
560            return self.note_dependency(dependency);
561        }
562    }
563
564    fn note_dependency(&mut self, dependency: &'selectors Dependency) {
565        debug_assert!(self.dependency_may_be_relevant(dependency));
566        let invalidation_kind = dependency.invalidation_kind();
567        if matches!(invalidation_kind, DependencyInvalidationKind::Normal(NormalDependencyInvalidationKind::Element)) {
568            if let Some(ref next) = dependency.next {
569                // We know something changed in the inner selector, go outwards
570                // now.
571                self.scan_dependency(&next.as_ref().slice()[0]);
572            } else {
573                self.invalidates_self = true;
574            }
575            return;
576        }
577
578        if matches!(invalidation_kind, DependencyInvalidationKind::Scope(_)){
579            if dependency.selector_offset == 0 {
580                if let Some(ref next) = dependency.next{
581                    for dep in next.as_ref().slice(){
582                        self.scan_dependency(dep);
583                    }
584                }
585            } else {
586                let invalidation =
587                Invalidation::new(&dependency, self.matching_context.current_host.clone(), Some(self.element.opaque()));
588
589                self.invalidates_self |= push_invalidation(
590                    invalidation,
591                    invalidation_kind,
592                    self.descendant_invalidations,
593                    self.sibling_invalidations,
594                );
595            }
596            return;
597        }
598
599        debug_assert_ne!(dependency.selector_offset, 0);
600        debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
601
602        let invalidation =
603            Invalidation::new(&dependency, self.matching_context.current_host.clone(), self.matching_context.scope_element.clone());
604
605        self.invalidates_self |= push_invalidation(
606            invalidation,
607            invalidation_kind,
608            self.descendant_invalidations,
609            self.sibling_invalidations,
610        );
611    }
612
613    /// Returns whether `dependency` may cause us to invalidate the style of
614    /// more elements than what we've already invalidated.
615    fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool {
616        match dependency.invalidation_kind() {
617            DependencyInvalidationKind::FullSelector |
618            DependencyInvalidationKind::Relative(_) => unreachable!(),
619            DependencyInvalidationKind::Scope(_) => true,
620            DependencyInvalidationKind::Normal(kind) => {
621                match kind {
622                    NormalDependencyInvalidationKind::Element => !self.invalidates_self,
623                    NormalDependencyInvalidationKind::SlottedElements => {
624                        self.element.is_html_slot_element()
625                    },
626                    NormalDependencyInvalidationKind::Parts => self.element.shadow_root().is_some(),
627                    NormalDependencyInvalidationKind::ElementAndDescendants |
628                    NormalDependencyInvalidationKind::Siblings |
629                    NormalDependencyInvalidationKind::Descendants => true,
630                }
631            },
632        }
633    }
634}
635
636pub(crate) fn push_invalidation<'a>(
637    invalidation: Invalidation<'a>,
638    invalidation_kind: DependencyInvalidationKind,
639    descendant_invalidations: &mut DescendantInvalidationLists<'a>,
640    sibling_invalidations: &mut InvalidationVector<'a>,
641) -> bool {
642    match invalidation_kind {
643        DependencyInvalidationKind::FullSelector => unreachable!(),
644        DependencyInvalidationKind::Relative(_) => unreachable!(),
645        DependencyInvalidationKind::Scope(_) => {
646            descendant_invalidations.dom_descendants.push(invalidation);
647            true
648        },
649        DependencyInvalidationKind::Normal(kind) => {
650            match kind {
651                NormalDependencyInvalidationKind::Element => unreachable!(),
652                NormalDependencyInvalidationKind::ElementAndDescendants => {
653                    descendant_invalidations.dom_descendants.push(invalidation);
654                    true
655                },
656                NormalDependencyInvalidationKind::Descendants => {
657                    descendant_invalidations.dom_descendants.push(invalidation);
658                    false
659                },
660                NormalDependencyInvalidationKind::Siblings => {
661                    sibling_invalidations.push(invalidation);
662                    false
663                },
664                NormalDependencyInvalidationKind::Parts => {
665                    descendant_invalidations.parts.push(invalidation);
666                    false
667                },
668                NormalDependencyInvalidationKind::SlottedElements => {
669                    descendant_invalidations
670                    .slotted_descendants
671                    .push(invalidation);
672                false
673                },
674            }
675        }
676    }
677}
678
679pub(crate) fn dependency_may_be_relevant<E: TElement>(
680    dependency: &Dependency,
681    element: &E,
682    already_invalidated_self: bool,
683) -> bool {
684    match dependency.invalidation_kind() {
685        DependencyInvalidationKind::FullSelector => unreachable!(),
686        DependencyInvalidationKind::Relative(_) => unreachable!(),
687        DependencyInvalidationKind::Scope(_) => true,
688        DependencyInvalidationKind::Normal(kind) => {
689            match kind {
690                NormalDependencyInvalidationKind::Element => !already_invalidated_self,
691                NormalDependencyInvalidationKind::SlottedElements => element.is_html_slot_element(),
692                NormalDependencyInvalidationKind::Parts => element.shadow_root().is_some(),
693                NormalDependencyInvalidationKind::ElementAndDescendants |
694                NormalDependencyInvalidationKind::Siblings |
695                NormalDependencyInvalidationKind::Descendants => true,
696            }
697        },
698    }
699}