1#![deny(unsafe_code)]
9
10use crate::Atom;
11use crate::LocalName as SelectorLocalName;
12use crate::context::QuirksMode;
13use crate::data::ElementData;
14use crate::derives::*;
15use crate::device::Device;
16use crate::dom::{TDocument, TElement, TNode};
17use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper, Snapshots};
18use crate::invalidation::element::restyle_hints::RestyleHint;
19use crate::selector_map::{MaybeCaseInsensitiveHashMap, PrecomputedHashMap, PrecomputedHashSet};
20use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap};
21use crate::shared_lock::SharedRwLockReadGuard;
22use crate::simple_buckets_map::SimpleBucketsMap;
23use crate::stylesheets::{
24 CssRule, CssRuleRef, CustomMediaMap, EffectiveRules, EffectiveRulesIterator,
25 StylesheetInDocument,
26};
27use crate::stylist::CascadeDataDifference;
28use crate::values::AtomIdent;
29use crate::values::specified::position::PositionTryFallbacksItem;
30use selectors::parser::{Component, LocalName, Selector};
31
32#[repr(u32)]
34#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
35pub enum RuleChangeKind {
36 Generic = 0,
39 Insertion,
41 Removal,
43 StyleRuleDeclarations,
45 PositionTryDeclarations,
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54enum Invalidation<'a> {
55 ID(&'a AtomIdent),
57 Class(&'a AtomIdent),
59 LocalName {
61 name: &'a SelectorLocalName,
62 lower_name: &'a SelectorLocalName,
63 },
64}
65
66impl Invalidation<'_> {
67 fn is_id(&self) -> bool {
68 matches!(*self, Invalidation::ID(..))
69 }
70
71 fn is_id_or_class(&self) -> bool {
72 matches!(*self, Invalidation::ID(..) | Invalidation::Class(..))
73 }
74}
75
76#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)]
79enum InvalidationKind {
80 None = 0,
81 Element,
82 Scope,
83}
84
85impl std::ops::BitOrAssign for InvalidationKind {
86 #[inline]
87 fn bitor_assign(&mut self, other: Self) {
88 *self = std::cmp::max(*self, other);
89 }
90}
91
92impl InvalidationKind {
93 #[inline]
94 fn is_scope(self) -> bool {
95 matches!(self, Self::Scope)
96 }
97
98 #[inline]
99 fn add(&mut self, other: Option<&InvalidationKind>) {
100 if let Some(other) = other {
101 *self |= *other;
102 }
103 }
104}
105
106#[derive(Debug, Default, MallocSizeOf)]
111pub struct StylesheetInvalidationSet {
112 buckets: SimpleBucketsMap<InvalidationKind>,
113 style_fully_invalid: bool,
114 pub cascade_data_difference: CascadeDataDifference,
117}
118
119impl StylesheetInvalidationSet {
120 pub fn new() -> Self {
122 Default::default()
123 }
124
125 pub fn invalidate_fully(&mut self) {
127 debug!("StylesheetInvalidationSet::invalidate_fully");
128 self.buckets.clear();
129 self.style_fully_invalid = true;
130 }
131
132 pub fn collect_invalidations_for<S>(
135 &mut self,
136 device: &Device,
137 custom_media: &CustomMediaMap,
138 stylesheet: &S,
139 guard: &SharedRwLockReadGuard,
140 ) where
141 S: StylesheetInDocument,
142 {
143 debug!("StylesheetInvalidationSet::collect_invalidations_for");
144 if self.style_fully_invalid {
145 debug!(" > Fully invalid already");
146 return;
147 }
148
149 if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, custom_media, guard)
150 {
151 debug!(" > Stylesheet was not effective");
152 return; }
154
155 let quirks_mode = device.quirks_mode();
156 for rule in stylesheet
157 .contents(guard)
158 .effective_rules(device, custom_media, guard)
159 {
160 self.collect_invalidations_for_rule(
161 rule,
162 guard,
163 device,
164 quirks_mode,
165 false,
166 &[],
169 );
170 if self.style_fully_invalid {
171 break;
172 }
173 }
174
175 debug!(
176 " > resulting class invalidations: {:?}",
177 self.buckets.classes
178 );
179 debug!(" > resulting id invalidations: {:?}", self.buckets.ids);
180 debug!(
181 " > resulting local name invalidations: {:?}",
182 self.buckets.local_names
183 );
184 debug!(" > style_fully_invalid: {}", self.style_fully_invalid);
185 }
186
187 pub fn is_empty(&self) -> bool {
189 !self.style_fully_invalid
190 && self.buckets.is_empty()
191 && self.cascade_data_difference.is_empty()
192 }
193
194 fn invalidation_kind_for<E>(
195 &self,
196 element: E,
197 snapshot: Option<&Snapshot>,
198 quirks_mode: QuirksMode,
199 ) -> InvalidationKind
200 where
201 E: TElement,
202 {
203 debug_assert!(!self.style_fully_invalid);
204
205 let mut kind = InvalidationKind::None;
206
207 if !self.buckets.classes.is_empty() {
208 element.each_class(|c| {
209 kind.add(self.buckets.classes.get(c, quirks_mode));
210 });
211
212 if kind.is_scope() {
213 return kind;
214 }
215
216 if let Some(snapshot) = snapshot {
217 snapshot.each_class(|c| {
218 kind.add(self.buckets.classes.get(c, quirks_mode));
219 });
220
221 if kind.is_scope() {
222 return kind;
223 }
224 }
225 }
226
227 if !self.buckets.ids.is_empty() {
228 if let Some(id) = element.id() {
229 kind.add(self.buckets.ids.get(id, quirks_mode));
230 if kind.is_scope() {
231 return kind;
232 }
233 }
234
235 if let Some(old_id) = snapshot.and_then(|s| s.id_attr()) {
236 kind.add(self.buckets.ids.get(old_id, quirks_mode));
237 if kind.is_scope() {
238 return kind;
239 }
240 }
241 }
242
243 if !self.buckets.local_names.is_empty() {
244 kind.add(self.buckets.local_names.get(element.local_name()));
245 }
246
247 kind
248 }
249
250 pub fn process_style<E>(&self, root: E, snapshots: Option<&SnapshotMap>) -> bool
253 where
254 E: TElement,
255 {
256 debug!(
257 "StylesheetInvalidationSet::process_style({root:?}, snapshots: {})",
258 snapshots.is_some()
259 );
260
261 {
262 let mut data = match root.mutate_data() {
263 Some(data) => data,
264 None => return false,
265 };
266
267 if self.style_fully_invalid {
268 debug!("process_invalidations: fully_invalid({:?})", root);
269 data.hint.insert(RestyleHint::restyle_subtree());
270 return true;
271 }
272 }
273
274 if self.buckets.is_empty() {
275 debug!("process_invalidations: empty invalidation set");
276 return false;
277 }
278
279 let quirks_mode = root.as_node().owner_doc().quirks_mode();
280 let snapshots = snapshots.map(Snapshots::new);
281 self.process_invalidations_in_subtree(root, snapshots.as_ref(), quirks_mode)
282 }
283
284 #[allow(unsafe_code)]
290 fn process_invalidations_in_subtree<E>(
291 &self,
292 element: E,
293 snapshots: Option<&Snapshots>,
294 quirks_mode: QuirksMode,
295 ) -> bool
296 where
297 E: TElement,
298 {
299 debug!("process_invalidations_in_subtree({:?})", element);
300 let mut data = match element.mutate_data() {
301 Some(data) => data,
302 None => return false,
303 };
304
305 if !data.has_styles() {
306 return false;
307 }
308
309 if data.hint.contains_subtree() {
310 debug!(
311 "process_invalidations_in_subtree: {:?} was already invalid",
312 element
313 );
314 return false;
315 }
316
317 let element_wrapper = snapshots.map(|s| ElementWrapper::new(element, s));
318 let snapshot = element_wrapper.as_ref().and_then(|e| e.snapshot());
319
320 match self.invalidation_kind_for(element, snapshot, quirks_mode) {
321 InvalidationKind::None => {},
322 InvalidationKind::Element => {
323 debug!(
324 "process_invalidations_in_subtree: {:?} matched self",
325 element
326 );
327 data.hint.insert(RestyleHint::RESTYLE_SELF);
328 },
329 InvalidationKind::Scope => {
330 debug!(
331 "process_invalidations_in_subtree: {:?} matched subtree",
332 element
333 );
334 data.hint.insert(RestyleHint::restyle_subtree());
335 return true;
336 },
337 }
338
339 let mut any_children_invalid = false;
340
341 for child in element.traversal_children() {
342 let child = match child.as_element() {
343 Some(e) => e,
344 None => continue,
345 };
346
347 any_children_invalid |=
348 self.process_invalidations_in_subtree(child, snapshots, quirks_mode);
349 }
350
351 if any_children_invalid {
352 debug!(
353 "Children of {:?} changed, setting dirty descendants",
354 element
355 );
356 unsafe { element.set_dirty_descendants() }
357 }
358
359 data.hint.contains(RestyleHint::RESTYLE_SELF) || any_children_invalid
360 }
361
362 fn scan_component<'a>(
364 component: &'a Component<SelectorImpl>,
365 invalidation: &mut Option<Invalidation<'a>>,
366 ) {
367 match *component {
368 Component::LocalName(LocalName {
369 ref name,
370 ref lower_name,
371 }) => {
372 if invalidation.is_none() {
373 *invalidation = Some(Invalidation::LocalName { name, lower_name });
374 }
375 },
376 Component::Class(ref class) => {
377 if invalidation.is_none_or(|s| !s.is_id_or_class()) {
378 *invalidation = Some(Invalidation::Class(class));
379 }
380 },
381 Component::ID(ref id) if invalidation.is_none_or(|s| !s.is_id()) => {
382 *invalidation = Some(Invalidation::ID(id));
383 },
384 _ => {
385 },
387 }
388 }
389
390 fn collect_invalidations(
405 &mut self,
406 selector: &Selector<SelectorImpl>,
407 quirks_mode: QuirksMode,
408 ) {
409 debug!(
410 "StylesheetInvalidationSet::collect_invalidations({:?})",
411 selector
412 );
413
414 let mut element_invalidation: Option<Invalidation> = None;
415 let mut subtree_invalidation: Option<Invalidation> = None;
416
417 let mut scan_for_element_invalidation = true;
418 let mut scan_for_subtree_invalidation = false;
419
420 let mut iter = selector.iter();
421
422 loop {
423 for component in &mut iter {
424 if scan_for_element_invalidation {
425 Self::scan_component(component, &mut element_invalidation);
426 } else if scan_for_subtree_invalidation {
427 Self::scan_component(component, &mut subtree_invalidation);
428 }
429 }
430 match iter.next_sequence() {
431 None => break,
432 Some(combinator) => {
433 scan_for_subtree_invalidation = combinator.is_ancestor();
434 },
435 }
436 scan_for_element_invalidation = false;
437 }
438
439 if let Some(s) = subtree_invalidation {
440 debug!(" > Found subtree invalidation: {:?}", s);
441 if self.insert_invalidation(s, InvalidationKind::Scope, quirks_mode) {
442 return;
443 }
444 }
445
446 if let Some(s) = element_invalidation {
447 debug!(" > Found element invalidation: {:?}", s);
448 if self.insert_invalidation(s, InvalidationKind::Element, quirks_mode) {
449 return;
450 }
451 }
452
453 debug!(" > Can't handle selector or OOMd, marking fully invalid");
456 self.invalidate_fully()
457 }
458
459 fn insert_invalidation(
460 &mut self,
461 invalidation: Invalidation,
462 kind: InvalidationKind,
463 quirks_mode: QuirksMode,
464 ) -> bool {
465 fn insert_atom(
466 map: &mut MaybeCaseInsensitiveHashMap<Atom, InvalidationKind>,
467 key: &AtomIdent,
468 kind: InvalidationKind,
469 quirks_mode: QuirksMode,
470 ) -> bool {
471 match map.try_get_or_insert_with(&key.0, quirks_mode, || InvalidationKind::None) {
472 Ok(existing) => {
473 *existing |= kind;
474 true
475 },
476 Err(..) => false,
477 }
478 }
479
480 fn insert_local_name(
481 map: &mut PrecomputedHashMap<SelectorLocalName, InvalidationKind>,
482 key: &SelectorLocalName,
483 kind: InvalidationKind,
484 ) -> bool {
485 if map.try_reserve(1).is_err() {
486 return false;
487 }
488 *map.entry_ref(key).or_insert(InvalidationKind::None) |= kind;
489 true
490 }
491
492 match invalidation {
493 Invalidation::Class(c) => insert_atom(&mut self.buckets.classes, c, kind, quirks_mode),
494 Invalidation::ID(i) => insert_atom(&mut self.buckets.ids, i, kind, quirks_mode),
495 Invalidation::LocalName { name, lower_name } => {
496 insert_local_name(&mut self.buckets.local_names, name, kind)
497 && (name == lower_name
498 || insert_local_name(&mut self.buckets.local_names, lower_name, kind))
499 },
500 }
501 }
502
503 pub fn rule_changed<S>(
505 &mut self,
506 stylesheet: &S,
507 rule: &CssRule,
508 guard: &SharedRwLockReadGuard,
509 device: &Device,
510 quirks_mode: QuirksMode,
511 custom_media: &CustomMediaMap,
512 change_kind: RuleChangeKind,
513 ancestors: &[CssRuleRef],
514 ) where
515 S: StylesheetInDocument,
516 {
517 debug!("StylesheetInvalidationSet::rule_changed");
518 if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, custom_media, guard)
519 {
520 debug!(" > Stylesheet was not effective");
521 return; }
523
524 if ancestors
525 .iter()
526 .any(|r| !EffectiveRules::is_effective(guard, device, quirks_mode, custom_media, r))
527 {
528 debug!(" > Ancestor rules not effective");
529 return;
530 }
531
532 if change_kind == RuleChangeKind::PositionTryDeclarations {
533 match *rule {
536 CssRule::PositionTry(ref pt) => {
537 self.cascade_data_difference
538 .changed_position_try_names
539 .insert(pt.read_with(guard).name.0.clone());
540 },
541 _ => debug_assert!(false, "how did position-try decls change on anything else?"),
542 }
543 return;
544 }
545
546 if self.style_fully_invalid {
547 return;
548 }
549
550 let is_generic_change = change_kind == RuleChangeKind::Generic;
555 self.collect_invalidations_for_rule(
556 rule,
557 guard,
558 device,
559 quirks_mode,
560 is_generic_change,
561 ancestors,
562 );
563 if self.style_fully_invalid {
564 return;
565 }
566
567 if !is_generic_change
568 && !EffectiveRules::is_effective(guard, device, quirks_mode, custom_media, &rule.into())
569 {
570 return;
571 }
572
573 let rules = EffectiveRulesIterator::effective_children(
574 device,
575 quirks_mode,
576 custom_media,
577 guard,
578 rule,
579 );
580 for rule in rules {
581 self.collect_invalidations_for_rule(
582 rule,
583 guard,
584 device,
585 quirks_mode,
586 false,
587 &[],
589 );
590 if self.style_fully_invalid {
591 break;
592 }
593 }
594 }
595
596 fn collect_invalidations_for_rule(
598 &mut self,
599 rule: &CssRule,
600 guard: &SharedRwLockReadGuard,
601 device: &Device,
602 quirks_mode: QuirksMode,
603 is_generic_change: bool,
604 ancestors: &[CssRuleRef],
605 ) {
606 use crate::stylesheets::CssRule::*;
607 debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
608 debug_assert!(!self.style_fully_invalid, "Not worth being here!");
609
610 match *rule {
611 Style(ref lock) => {
612 if is_generic_change {
613 return self.invalidate_fully();
619 }
620
621 let style_rule = lock.read_with(guard);
622 for selector in style_rule.selectors.slice() {
623 self.collect_invalidations(selector, quirks_mode);
624 if self.style_fully_invalid {
625 return;
626 }
627 }
628 },
629 NestedDeclarations(..) => {
630 if ancestors.iter().any(|r| matches!(r, CssRuleRef::Scope(_))) {
631 self.invalidate_fully();
632 }
633 },
634 Namespace(..) => {
635 },
638 LayerStatement(..) => {
639 self.invalidate_fully()
642 },
643 Document(..) | Import(..) | Media(..) | Supports(..) | Container(..)
644 | LayerBlock(..) | StartingStyle(..) | AppearanceBase(..) => {
645 },
647 FontFace(..) => {
648 },
651 Page(..) | Margin(..) => {
652 },
655 Keyframes(ref lock) => {
656 if is_generic_change {
657 return self.invalidate_fully();
658 }
659 let keyframes_rule = lock.read_with(guard);
660 if device.animation_name_may_be_referenced(&keyframes_rule.name) {
661 debug!(
662 " > Found @keyframes rule potentially referenced \
663 from the page, marking the whole tree invalid."
664 );
665 self.invalidate_fully();
666 } else {
667 }
669 },
670 CounterStyle(..) | Property(..) | FontFeatureValues(..) | FontPaletteValues(..) => {
671 debug!(" > Found unsupported rule, marking the whole subtree invalid.");
672 self.invalidate_fully();
673 },
674 Scope(..) => {
675 self.invalidate_fully();
678 },
679 PositionTry(..) => {
680 },
683 ViewTransition(..) => {
684 },
686 CustomMedia(..) => {
687 self.invalidate_fully();
692 },
693 }
694 }
695}
696
697pub fn invalidate_position_try<E>(
699 element: E,
700 changed_names: &PrecomputedHashSet<Atom>,
701 invalidate_self: &mut impl FnMut(E, &mut ElementData),
702 invalidated_descendants: &mut impl FnMut(E),
703) -> bool
704where
705 E: TElement,
706{
707 debug_assert!(
708 !changed_names.is_empty(),
709 "Don't call me if there's nothing to do"
710 );
711 let mut data = match element.mutate_data() {
712 Some(data) => data,
713 None => return false,
714 };
715
716 let mut self_invalid = false;
717 let style = data.styles.primary();
718 if style.get_box().get_position().is_absolutely_positioned() {
719 let fallbacks = style.get_position_try_fallbacks();
720 let referenced = fallbacks.value.0.iter().any(|f| match f {
721 PositionTryFallbacksItem::IdentAndOrTactic(ident_or_tactic) => {
722 changed_names.contains(&ident_or_tactic.ident.0)
723 },
724 PositionTryFallbacksItem::PositionArea(..) => false,
725 });
726
727 if referenced {
728 self_invalid = true;
729 invalidate_self(element, &mut data);
730 }
731 }
732 let mut any_children_invalid = false;
733 for child in element.traversal_children() {
734 let Some(e) = child.as_element() else {
735 continue;
736 };
737 any_children_invalid |=
738 invalidate_position_try(e, changed_names, invalidate_self, invalidated_descendants);
739 }
740 if any_children_invalid {
741 invalidated_descendants(element);
742 }
743 self_invalid || any_children_invalid
744}