kozan_core/lifecycle.rs
1//! Document lifecycle — state machine enforcing phase ordering.
2//!
3//! Chrome equivalent: `DocumentLifecycle` in `core/dom/document_lifecycle.h`.
4//!
5//! Phases progress in strict order:
6//! ```text
7//! VisualUpdatePending → InStyleRecalc → StyleClean
8//! → InLayout → LayoutClean
9//! → InPrePaint → PrePaintClean
10//! → InPaint → PaintClean
11//! ```
12//!
13//! Each phase gates the next. You cannot run layout before style is clean,
14//! and you cannot paint before layout is clean.
15
16/// The current lifecycle state of a document.
17///
18/// Chrome: `DocumentLifecycle::LifecycleState`.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
20pub enum LifecycleState {
21 /// Something visual changed — needs work.
22 VisualUpdatePending,
23 /// Style recalc is running.
24 InStyleRecalc,
25 /// Styles computed — ready for layout.
26 StyleClean,
27 /// Layout is running.
28 InLayout,
29 /// Layout complete — ready for paint.
30 LayoutClean,
31 /// Pre-paint (paint property trees) is running.
32 InPrePaint,
33 /// Pre-paint complete.
34 PrePaintClean,
35 /// Paint (display list generation) is running.
36 InPaint,
37 /// All phases complete — everything clean.
38 #[default]
39 PaintClean,
40}
41
42impl LifecycleState {
43 /// Whether this state is "clean" (not in the middle of a phase).
44 #[inline]
45 #[must_use]
46 pub fn is_clean(self) -> bool {
47 matches!(
48 self,
49 Self::StyleClean | Self::LayoutClean | Self::PrePaintClean | Self::PaintClean
50 )
51 }
52
53 /// Whether layout results are up-to-date.
54 #[inline]
55 #[must_use]
56 pub fn is_layout_clean(self) -> bool {
57 self >= Self::LayoutClean
58 }
59
60 /// Whether paint results are up-to-date.
61 #[inline]
62 #[must_use]
63 pub fn is_paint_clean(self) -> bool {
64 self >= Self::PaintClean
65 }
66
67 /// Mark that a visual update is needed (invalidate all phases).
68 ///
69 /// Chrome: `DocumentLifecycle::SetVisualUpdatePending()`.
70 /// Called when DOM changes, style changes, or viewport resizes.
71 #[inline]
72 pub fn invalidate(&mut self) {
73 *self = Self::VisualUpdatePending;
74 }
75
76 /// Whether any work needs to be done.
77 #[inline]
78 #[must_use]
79 pub fn needs_update(self) -> bool {
80 self != Self::PaintClean
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn default_is_clean() {
90 assert_eq!(LifecycleState::default(), LifecycleState::PaintClean);
91 }
92
93 #[test]
94 fn ordering() {
95 assert!(LifecycleState::VisualUpdatePending < LifecycleState::StyleClean);
96 assert!(LifecycleState::StyleClean < LifecycleState::LayoutClean);
97 assert!(LifecycleState::LayoutClean < LifecycleState::PaintClean);
98 }
99
100 #[test]
101 fn invalidate_resets() {
102 let mut state = LifecycleState::PaintClean;
103 assert!(!state.needs_update());
104 state.invalidate();
105 assert!(state.needs_update());
106 assert_eq!(state, LifecycleState::VisualUpdatePending);
107 }
108
109 #[test]
110 fn is_layout_clean_checks() {
111 assert!(!LifecycleState::StyleClean.is_layout_clean());
112 assert!(LifecycleState::LayoutClean.is_layout_clean());
113 assert!(LifecycleState::PaintClean.is_layout_clean());
114 }
115}