1#![deny(unsafe_code)]
9
10use crate::context::QuirksMode;
11use crate::data::ElementData;
12use crate::derives::*;
13use crate::dom::{TDocument, TElement, TNode};
14use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
15use crate::invalidation::element::restyle_hints::RestyleHint;
16use crate::media_queries::Device;
17use crate::selector_map::PrecomputedHashSet;
18use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap};
19use crate::shared_lock::SharedRwLockReadGuard;
20use crate::simple_buckets_map::SimpleBucketsMap;
21use crate::stylesheets::{
22 CssRule, CssRuleRef, CustomMediaMap, EffectiveRules, EffectiveRulesIterator,
23 StylesheetInDocument,
24};
25use crate::stylist::CascadeDataDifference;
26use crate::values::specified::position::PositionTryFallbacksItem;
27use crate::values::AtomIdent;
28use crate::Atom;
29use crate::LocalName as SelectorLocalName;
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(Debug, Eq, Hash, MallocSizeOf, PartialEq)]
54enum Invalidation {
55 ID(AtomIdent),
57 Class(AtomIdent),
59 LocalName {
61 name: SelectorLocalName,
62 lower_name: 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(ref 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(ref 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 self.process_invalidations_in_subtree(root, snapshots, quirks_mode)
281 }
282
283 #[allow(unsafe_code)]
289 fn process_invalidations_in_subtree<E>(
290 &self,
291 element: E,
292 snapshots: Option<&SnapshotMap>,
293 quirks_mode: QuirksMode,
294 ) -> bool
295 where
296 E: TElement,
297 {
298 debug!("process_invalidations_in_subtree({:?})", element);
299 let mut data = match element.mutate_data() {
300 Some(data) => data,
301 None => return false,
302 };
303
304 if !data.has_styles() {
305 return false;
306 }
307
308 if data.hint.contains_subtree() {
309 debug!(
310 "process_invalidations_in_subtree: {:?} was already invalid",
311 element
312 );
313 return false;
314 }
315
316 let element_wrapper = snapshots.map(|s| ElementWrapper::new(element, s));
317 let snapshot = element_wrapper.as_ref().and_then(|e| e.snapshot());
318
319 match self.invalidation_kind_for(element, snapshot, quirks_mode) {
320 InvalidationKind::None => {},
321 InvalidationKind::Element => {
322 debug!(
323 "process_invalidations_in_subtree: {:?} matched self",
324 element
325 );
326 data.hint.insert(RestyleHint::RESTYLE_SELF);
327 },
328 InvalidationKind::Scope => {
329 debug!(
330 "process_invalidations_in_subtree: {:?} matched subtree",
331 element
332 );
333 data.hint.insert(RestyleHint::restyle_subtree());
334 return true;
335 },
336 }
337
338 let mut any_children_invalid = false;
339
340 for child in element.traversal_children() {
341 let child = match child.as_element() {
342 Some(e) => e,
343 None => continue,
344 };
345
346 any_children_invalid |=
347 self.process_invalidations_in_subtree(child, snapshots, quirks_mode);
348 }
349
350 if any_children_invalid {
351 debug!(
352 "Children of {:?} changed, setting dirty descendants",
353 element
354 );
355 unsafe { element.set_dirty_descendants() }
356 }
357
358 data.hint.contains(RestyleHint::RESTYLE_SELF) || any_children_invalid
359 }
360
361 fn scan_component(
363 component: &Component<SelectorImpl>,
364 invalidation: &mut Option<Invalidation>,
365 ) {
366 match *component {
367 Component::LocalName(LocalName {
368 ref name,
369 ref lower_name,
370 }) => {
371 if invalidation.is_none() {
372 *invalidation = Some(Invalidation::LocalName {
373 name: name.clone(),
374 lower_name: lower_name.clone(),
375 });
376 }
377 },
378 Component::Class(ref class) => {
379 if invalidation.as_ref().map_or(true, |s| !s.is_id_or_class()) {
380 *invalidation = Some(Invalidation::Class(class.clone()));
381 }
382 },
383 Component::ID(ref id) => {
384 if invalidation.as_ref().map_or(true, |s| !s.is_id()) {
385 *invalidation = Some(Invalidation::ID(id.clone()));
386 }
387 },
388 _ => {
389 },
391 }
392 }
393
394 fn collect_invalidations(
409 &mut self,
410 selector: &Selector<SelectorImpl>,
411 quirks_mode: QuirksMode,
412 ) {
413 debug!(
414 "StylesheetInvalidationSet::collect_invalidations({:?})",
415 selector
416 );
417
418 let mut element_invalidation: Option<Invalidation> = None;
419 let mut subtree_invalidation: Option<Invalidation> = None;
420
421 let mut scan_for_element_invalidation = true;
422 let mut scan_for_subtree_invalidation = false;
423
424 let mut iter = selector.iter();
425
426 loop {
427 for component in &mut iter {
428 if scan_for_element_invalidation {
429 Self::scan_component(component, &mut element_invalidation);
430 } else if scan_for_subtree_invalidation {
431 Self::scan_component(component, &mut subtree_invalidation);
432 }
433 }
434 match iter.next_sequence() {
435 None => break,
436 Some(combinator) => {
437 scan_for_subtree_invalidation = combinator.is_ancestor();
438 },
439 }
440 scan_for_element_invalidation = false;
441 }
442
443 if let Some(s) = subtree_invalidation {
444 debug!(" > Found subtree invalidation: {:?}", s);
445 if self.insert_invalidation(s, InvalidationKind::Scope, quirks_mode) {
446 return;
447 }
448 }
449
450 if let Some(s) = element_invalidation {
451 debug!(" > Found element invalidation: {:?}", s);
452 if self.insert_invalidation(s, InvalidationKind::Element, quirks_mode) {
453 return;
454 }
455 }
456
457 debug!(" > Can't handle selector or OOMd, marking fully invalid");
460 self.invalidate_fully()
461 }
462
463 fn insert_invalidation(
464 &mut self,
465 invalidation: Invalidation,
466 kind: InvalidationKind,
467 quirks_mode: QuirksMode,
468 ) -> bool {
469 match invalidation {
470 Invalidation::Class(c) => {
471 let entry = match self.buckets.classes.try_entry(c.0, quirks_mode) {
472 Ok(e) => e,
473 Err(..) => return false,
474 };
475 *entry.or_insert(InvalidationKind::None) |= kind;
476 },
477 Invalidation::ID(i) => {
478 let entry = match self.buckets.ids.try_entry(i.0, quirks_mode) {
479 Ok(e) => e,
480 Err(..) => return false,
481 };
482 *entry.or_insert(InvalidationKind::None) |= kind;
483 },
484 Invalidation::LocalName { name, lower_name } => {
485 let insert_lower = name != lower_name;
486 if self.buckets.local_names.try_reserve(1).is_err() {
487 return false;
488 }
489 let entry = self.buckets.local_names.entry(name);
490 *entry.or_insert(InvalidationKind::None) |= kind;
491 if insert_lower {
492 if self.buckets.local_names.try_reserve(1).is_err() {
493 return false;
494 }
495 let entry = self.buckets.local_names.entry(lower_name);
496 *entry.or_insert(InvalidationKind::None) |= kind;
497 }
498 },
499 }
500
501 true
502 }
503
504 pub fn rule_changed<S>(
506 &mut self,
507 stylesheet: &S,
508 rule: &CssRule,
509 guard: &SharedRwLockReadGuard,
510 device: &Device,
511 quirks_mode: QuirksMode,
512 custom_media: &CustomMediaMap,
513 change_kind: RuleChangeKind,
514 ancestors: &[CssRuleRef],
515 ) where
516 S: StylesheetInDocument,
517 {
518 debug!("StylesheetInvalidationSet::rule_changed");
519 if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, custom_media, guard)
520 {
521 debug!(" > Stylesheet was not effective");
522 return; }
524
525 if ancestors
526 .iter()
527 .any(|r| !EffectiveRules::is_effective(guard, device, quirks_mode, custom_media, r))
528 {
529 debug!(" > Ancestor rules not effective");
530 return;
531 }
532
533 if change_kind == RuleChangeKind::PositionTryDeclarations {
534 match *rule {
537 CssRule::PositionTry(ref pt) => {
538 self.cascade_data_difference
539 .changed_position_try_names
540 .insert(pt.read_with(guard).name.0.clone());
541 },
542 _ => debug_assert!(false, "how did position-try decls change on anything else?"),
543 }
544 return;
545 }
546
547 if self.style_fully_invalid {
548 return;
549 }
550
551 let is_generic_change = change_kind == RuleChangeKind::Generic;
556 self.collect_invalidations_for_rule(
557 rule,
558 guard,
559 device,
560 quirks_mode,
561 is_generic_change,
562 ancestors,
563 );
564 if self.style_fully_invalid {
565 return;
566 }
567
568 if !is_generic_change
569 && !EffectiveRules::is_effective(guard, device, quirks_mode, custom_media, &rule.into())
570 {
571 return;
572 }
573
574 let rules = EffectiveRulesIterator::effective_children(
575 device,
576 quirks_mode,
577 custom_media,
578 guard,
579 rule,
580 );
581 for rule in rules {
582 self.collect_invalidations_for_rule(
583 rule,
584 guard,
585 device,
586 quirks_mode,
587 false,
588 &[],
590 );
591 if self.style_fully_invalid {
592 break;
593 }
594 }
595 }
596
597 fn collect_invalidations_for_rule(
599 &mut self,
600 rule: &CssRule,
601 guard: &SharedRwLockReadGuard,
602 device: &Device,
603 quirks_mode: QuirksMode,
604 is_generic_change: bool,
605 ancestors: &[CssRuleRef],
606 ) {
607 use crate::stylesheets::CssRule::*;
608 debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
609 debug_assert!(!self.style_fully_invalid, "Not worth being here!");
610
611 match *rule {
612 Style(ref lock) => {
613 if is_generic_change {
614 return self.invalidate_fully();
620 }
621
622 let style_rule = lock.read_with(guard);
623 for selector in style_rule.selectors.slice() {
624 self.collect_invalidations(selector, quirks_mode);
625 if self.style_fully_invalid {
626 return;
627 }
628 }
629 },
630 NestedDeclarations(..) => {
631 if ancestors.iter().any(|r| matches!(r, CssRuleRef::Scope(_))) {
632 self.invalidate_fully();
633 }
634 },
635 Namespace(..) => {
636 },
639 LayerStatement(..) => {
640 return self.invalidate_fully();
643 },
644 Document(..) | Import(..) | Media(..) | Supports(..) | Container(..)
645 | LayerBlock(..) | StartingStyle(..) => {
646 },
648 FontFace(..) => {
649 },
652 Page(..) | Margin(..) => {
653 },
656 Keyframes(ref lock) => {
657 if is_generic_change {
658 return self.invalidate_fully();
659 }
660 let keyframes_rule = lock.read_with(guard);
661 if device.animation_name_may_be_referenced(&keyframes_rule.name) {
662 debug!(
663 " > Found @keyframes rule potentially referenced \
664 from the page, marking the whole tree invalid."
665 );
666 self.invalidate_fully();
667 } else {
668 }
670 },
671 CounterStyle(..) | Property(..) | FontFeatureValues(..) | FontPaletteValues(..) => {
672 debug!(" > Found unsupported rule, marking the whole subtree invalid.");
673 self.invalidate_fully();
674 },
675 Scope(..) => {
676 self.invalidate_fully();
679 },
680 PositionTry(..) => {
681 },
684 CustomMedia(..) => {
685 self.invalidate_fully();
690 },
691 }
692 }
693}
694
695pub fn invalidate_position_try<E>(
697 element: E,
698 changed_names: &PrecomputedHashSet<Atom>,
699 invalidate_self: &mut impl FnMut(E, &mut ElementData),
700 invalidated_descendants: &mut impl FnMut(E),
701) -> bool
702where
703 E: TElement,
704{
705 debug_assert!(
706 !changed_names.is_empty(),
707 "Don't call me if there's nothing to do"
708 );
709 let mut data = match element.mutate_data() {
710 Some(data) => data,
711 None => return false,
712 };
713
714 let mut self_invalid = false;
715 let style = data.styles.primary();
716 if style.clone_position().is_absolutely_positioned() {
717 let fallbacks = style.clone_position_try_fallbacks();
718 let referenced = fallbacks.0.iter().any(|f| match f {
719 PositionTryFallbacksItem::IdentAndOrTactic(ident_or_tactic) => {
720 changed_names.contains(&ident_or_tactic.ident.0)
721 },
722 PositionTryFallbacksItem::PositionArea(..) => false,
723 });
724
725 if referenced {
726 self_invalid = true;
727 invalidate_self(element, &mut data);
728 }
729 }
730 let mut any_children_invalid = false;
731 for child in element.traversal_children() {
732 let Some(e) = child.as_element() else {
733 continue;
734 };
735 any_children_invalid |=
736 invalidate_position_try(e, changed_names, invalidate_self, invalidated_descendants);
737 }
738 if any_children_invalid {
739 invalidated_descendants(element);
740 }
741 self_invalid || any_children_invalid
742}