1use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
12use core::{
13 fmt,
14 hash::{Hash, Hasher},
15};
16
17use azul_css::{
18 css::Css,
19 props::{
20 basic::{StyleFontFamily, StyleFontFamilyVec, StyleFontSize},
21 property::{
22 BoxDecorationBreakValue, BreakInsideValue, CaretAnimationDurationValue,
23 CaretColorValue, ColumnCountValue, ColumnFillValue, ColumnRuleColorValue,
24 ColumnRuleStyleValue, ColumnRuleWidthValue, ColumnSpanValue, ColumnWidthValue,
25 ContentValue, CounterIncrementValue, CounterResetValue, CssProperty, CssPropertyType,
26 RelayoutScope,
27 FlowFromValue, FlowIntoValue, LayoutAlignContentValue, LayoutAlignItemsValue,
28 LayoutAlignSelfValue, LayoutBorderBottomWidthValue, LayoutBorderLeftWidthValue,
29 LayoutBorderRightWidthValue, LayoutBorderTopWidthValue, LayoutBoxSizingValue,
30 LayoutClearValue, LayoutColumnGapValue, LayoutDisplayValue, LayoutFlexBasisValue,
31 LayoutFlexDirectionValue, LayoutFlexGrowValue, LayoutFlexShrinkValue,
32 LayoutFlexWrapValue, LayoutFloatValue, LayoutGapValue, LayoutGridAutoColumnsValue,
33 LayoutGridAutoFlowValue, LayoutGridAutoRowsValue, LayoutGridColumnValue,
34 LayoutGridRowValue, LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
35 LayoutHeightValue, LayoutInsetBottomValue, LayoutJustifyContentValue,
36 LayoutJustifyItemsValue, LayoutJustifySelfValue, LayoutLeftValue,
37 LayoutMarginBottomValue, LayoutMarginLeftValue, LayoutMarginRightValue,
38 LayoutMarginTopValue, LayoutMaxHeightValue, LayoutMaxWidthValue, LayoutMinHeightValue,
39 LayoutMinWidthValue, LayoutOverflowValue, LayoutPaddingBottomValue,
40 LayoutPaddingLeftValue, LayoutPaddingRightValue, LayoutPaddingTopValue,
41 LayoutPositionValue, LayoutRightValue, LayoutRowGapValue, LayoutScrollbarWidthValue,
42 LayoutTextJustifyValue, LayoutTopValue, LayoutWidthValue, LayoutWritingModeValue,
43 LayoutZIndexValue, OrphansValue, PageBreakValue,
44 SelectionBackgroundColorValue, SelectionColorValue, ShapeImageThresholdValue,
45 ShapeMarginValue, ShapeOutsideValue, StringSetValue, StyleBackfaceVisibilityValue,
46 StyleBackgroundContentVecValue, StyleBackgroundPositionVecValue,
47 StyleBackgroundRepeatVecValue, StyleBackgroundSizeVecValue,
48 StyleBorderBottomColorValue, StyleBorderBottomLeftRadiusValue,
49 StyleBorderBottomRightRadiusValue, StyleBorderBottomStyleValue,
50 StyleBorderLeftColorValue, StyleBorderLeftStyleValue, StyleBorderRightColorValue,
51 StyleBorderRightStyleValue, StyleBorderTopColorValue, StyleBorderTopLeftRadiusValue,
52 StyleBorderTopRightRadiusValue, StyleBorderTopStyleValue, StyleBoxShadowValue,
53 StyleCursorValue, StyleDirectionValue, StyleFilterVecValue, StyleFontFamilyVecValue,
54 StyleFontSizeValue, StyleFontValue, StyleHyphensValue, StyleLetterSpacingValue,
55 StyleLineHeightValue, StyleMixBlendModeValue, StyleOpacityValue,
56 StylePerspectiveOriginValue, StyleScrollbarColorValue, StyleTabSizeValue,
57 StyleTextAlignValue, StyleTextColorValue, StyleTransformOriginValue,
58 StyleTransformVecValue, StyleVisibilityValue, StyleWhiteSpaceValue,
59 StyleWordSpacingValue, WidowsValue,
60 },
61 style::StyleTextColor,
62 },
63 AzString,
64};
65
66use crate::{
67 callbacks::Update,
68 dom::{Dom, DomId, NodeData, NodeDataVec, OptionTabIndex, TabIndex, TagId},
69 events::{RelayoutNodes, RestyleNodes},
70 id::{
71 Node, NodeDataContainer, NodeDataContainerRef, NodeDataContainerRefMut, NodeHierarchy,
72 NodeId,
73 },
74 menu::Menu,
75 prop_cache::{CssPropertyCache, CssPropertyCachePtr},
76 refany::RefAny,
77 resources::{Au, ImageCache, ImageRef, ImmediateFontId, RendererResources},
78 style::{
79 construct_html_cascade_tree, matches_html_element, rule_ends_with, CascadeInfo,
80 CascadeInfoVec,
81 },
82 FastBTreeSet, OrderedMap,
83};
84
85#[repr(C)]
86#[derive(Debug, Clone, PartialEq, Hash, PartialOrd, Eq, Ord)]
87pub struct ChangedCssProperty {
88 pub previous_state: StyledNodeState,
89 pub previous_prop: CssProperty,
90 pub current_state: StyledNodeState,
91 pub current_prop: CssProperty,
92}
93
94impl_option!(
95 ChangedCssProperty,
96 OptionChangedCssProperty,
97 copy = false,
98 [Debug, Clone, PartialEq, Hash, PartialOrd, Eq, Ord]
99);
100
101impl_vec!(ChangedCssProperty, ChangedCssPropertyVec, ChangedCssPropertyVecDestructor, ChangedCssPropertyVecDestructorType, ChangedCssPropertyVecSlice, OptionChangedCssProperty);
102impl_vec_debug!(ChangedCssProperty, ChangedCssPropertyVec);
103impl_vec_partialord!(ChangedCssProperty, ChangedCssPropertyVec);
104impl_vec_clone!(
105 ChangedCssProperty,
106 ChangedCssPropertyVec,
107 ChangedCssPropertyVecDestructor
108);
109impl_vec_partialeq!(ChangedCssProperty, ChangedCssPropertyVec);
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct FocusChange {
114 pub lost_focus: Option<NodeId>,
116 pub gained_focus: Option<NodeId>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct HoverChange {
123 pub left_nodes: Vec<NodeId>,
125 pub entered_nodes: Vec<NodeId>,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct ActiveChange {
132 pub deactivated: Vec<NodeId>,
134 pub activated: Vec<NodeId>,
136}
137
138#[derive(Debug, Clone, Default)]
140pub struct RestyleResult {
141 pub changed_nodes: RestyleNodes,
143 pub needs_layout: bool,
145 pub needs_display_list: bool,
147 pub gpu_only_changes: bool,
150 pub max_relayout_scope: RelayoutScope,
161}
162
163impl RestyleResult {
164 #[must_use] pub fn has_changes(&self) -> bool {
166 !self.changed_nodes.is_empty()
167 }
168
169 pub fn merge(&mut self, other: Self) {
171 for (node_id, changes) in other.changed_nodes {
172 self.changed_nodes.entry(node_id).or_default().extend(changes);
173 }
174 self.needs_layout = self.needs_layout || other.needs_layout;
175 self.needs_display_list = self.needs_display_list || other.needs_display_list;
176 self.gpu_only_changes = self.gpu_only_changes && other.gpu_only_changes;
177 if other.max_relayout_scope > self.max_relayout_scope {
179 self.max_relayout_scope = other.max_relayout_scope;
180 }
181 }
182}
183
184#[repr(C)]
189#[derive(Clone, Copy, PartialEq, Hash, PartialOrd, Eq, Ord, Default)]
190pub struct StyledNodeState {
191 pub hover: bool,
193 pub active: bool,
195 pub focused: bool,
197 pub disabled: bool,
199 pub checked: bool,
201 pub focus_within: bool,
203 pub visited: bool,
205 pub backdrop: bool,
207 pub dragging: bool,
209 pub drag_over: bool,
211}
212
213impl fmt::Debug for StyledNodeState {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 let mut v = Vec::new();
216 if self.hover {
217 v.push("hover");
218 }
219 if self.active {
220 v.push("active");
221 }
222 if self.focused {
223 v.push("focused");
224 }
225 if self.disabled {
226 v.push("disabled");
227 }
228 if self.checked {
229 v.push("checked");
230 }
231 if self.focus_within {
232 v.push("focus_within");
233 }
234 if self.visited {
235 v.push("visited");
236 }
237 if self.backdrop {
238 v.push("backdrop");
239 }
240 if self.dragging {
241 v.push("dragging");
242 }
243 if self.drag_over {
244 v.push("drag_over");
245 }
246 if v.is_empty() {
247 v.push("normal");
248 }
249 write!(f, "{v:?}")
250 }
251}
252
253impl StyledNodeState {
254 #[must_use] pub const fn new() -> Self {
256 Self {
257 hover: false,
258 active: false,
259 focused: false,
260 disabled: false,
261 checked: false,
262 focus_within: false,
263 visited: false,
264 backdrop: false,
265 dragging: false,
266 drag_over: false,
267 }
268 }
269
270 #[must_use] pub const fn has_state(&self, state_type: u8) -> bool {
272 match state_type {
273 0 => true, 1 => self.hover,
275 2 => self.active,
276 3 => self.focused,
277 4 => self.disabled,
278 5 => self.checked,
279 6 => self.focus_within,
280 7 => self.visited,
281 8 => self.backdrop,
282 9 => self.dragging,
283 10 => self.drag_over,
284 _ => false,
285 }
286 }
287
288 #[must_use] pub const fn is_normal(&self) -> bool {
290 !self.hover
291 && !self.active
292 && !self.focused
293 && !self.disabled
294 && !self.checked
295 && !self.focus_within
296 && !self.visited
297 && !self.backdrop
298 && !self.dragging
299 && !self.drag_over
300 }
301
302 #[must_use] pub const fn from_pseudo_state_flags(flags: &azul_css::dynamic_selector::PseudoStateFlags) -> Self {
304 Self {
305 hover: flags.hover,
306 active: flags.active,
307 focused: flags.focused,
308 disabled: flags.disabled,
309 checked: flags.checked,
310 focus_within: flags.focus_within,
311 visited: flags.visited,
312 backdrop: flags.backdrop,
313 dragging: flags.dragging,
314 drag_over: flags.drag_over,
315 }
316 }
317}
318
319#[allow(missing_copy_implementations)]
324#[repr(C)]
325#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd)]
326pub struct StyledNode {
327 pub styled_node_state: StyledNodeState,
329}
330
331impl_option!(
332 StyledNode,
333 OptionStyledNode,
334 copy = false,
335 [Debug, Clone, PartialEq, Eq, PartialOrd]
336);
337
338impl_vec!(StyledNode, StyledNodeVec, StyledNodeVecDestructor, StyledNodeVecDestructorType, StyledNodeVecSlice, OptionStyledNode);
339impl_vec_mut!(StyledNode, StyledNodeVec);
340impl_vec_debug!(StyledNode, StyledNodeVec);
341impl_vec_partialord!(StyledNode, StyledNodeVec);
342impl_vec_clone!(StyledNode, StyledNodeVec, StyledNodeVecDestructor);
343impl_vec_partialeq!(StyledNode, StyledNodeVec);
344
345impl StyledNodeVec {
346 #[must_use] pub fn as_container(&self) -> NodeDataContainerRef<'_, StyledNode> {
348 NodeDataContainerRef {
349 internal: self.as_ref(),
350 }
351 }
352 pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, StyledNode> {
354 NodeDataContainerRefMut {
355 internal: self.as_mut(),
356 }
357 }
358}
359
360#[test]
361#[allow(clippy::used_underscore_binding)] fn test_css_styling_with_nested_divs() {
363 let s = "
364 html, body, p {
365 margin: 0;
366 padding: 0;
367 }
368 #div1 {
369 border: solid black;
370 height: 2in;
371 position: absolute;
372 top: 1in;
373 width: 3in;
374 }
375 div div {
376 background: blue;
377 height: 1in;
378 position: fixed;
379 width: 1in;
380 }
381 ";
382
383 let css = azul_css::parser2::new_from_str(s);
384 let mut _styled_dom = Dom::create_body()
385 .with_children(
386 vec![Dom::create_div()
387 .with_ids_and_classes(
388 vec![crate::dom::IdOrClass::Id("div1".to_string().into())].into(),
389 )
390 .with_children(vec![Dom::create_div()].into())]
391 .into(),
392 );
393 _styled_dom.add_component_css(css.0);
394}
395
396#[test]
404fn test_recompute_preserves_hot_flag_has_background() {
405 use azul_css::compact_cache::HOT_FLAG_HAS_BACKGROUND;
406
407 let css_str = "
408 body { margin: 0; padding: 0; }
409 .painted { background: red; width: 100px; height: 100px; }
410 ";
411 let css = azul_css::parser2::new_from_str(css_str).0;
412
413 let mut dom = Dom::create_body().with_children(
414 vec![Dom::create_div().with_class("painted".to_string().into())].into(),
415 );
416 let mut styled = StyledDom::create(&mut dom, css);
417
418 let any_bg_frame1 = {
420 let cache = styled
421 .css_property_cache
422 .ptr
423 .compact_cache
424 .as_ref()
425 .expect("compact_cache populated by create_from_compact_dom");
426 (0..styled.node_hierarchy.as_ref().len())
427 .any(|i| cache.tier2_cold[i].hot_flags & HOT_FLAG_HAS_BACKGROUND != 0)
428 };
429 assert!(
430 any_bg_frame1,
431 "frame 1: expected HOT_FLAG_HAS_BACKGROUND on the .painted node",
432 );
433
434 styled.recompute_inheritance_and_compact_cache();
438
439 let any_bg_frame2 = {
440 let cache = styled
441 .css_property_cache
442 .ptr
443 .compact_cache
444 .as_ref()
445 .expect("compact_cache rebuilt by recompute_inheritance_and_compact_cache");
446 (0..styled.node_hierarchy.as_ref().len())
447 .any(|i| cache.tier2_cold[i].hot_flags & HOT_FLAG_HAS_BACKGROUND != 0)
448 };
449 assert!(
450 any_bg_frame2,
451 "frame ≥2 after recompute_inheritance_and_compact_cache: \
452 HOT_FLAG_HAS_BACKGROUND disappeared. The recompute path must \
453 use build_compact_cache_with_inheritance (not plain \
454 build_compact_cache) so apply_css_property_to_compact runs and \
455 populates hot_flags for the renderer's negative fast-paths.",
456 );
457}
458
459#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
461pub struct StyleFontFamilyHash(pub u64);
462
463impl ::core::fmt::Debug for StyleFontFamilyHash {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 write!(f, "StyleFontFamilyHash({})", self.0)
466 }
467}
468
469impl StyleFontFamilyHash {
470 #[must_use] pub fn new(family: &StyleFontFamily) -> Self {
472 use core::hash::Hasher;
473 let mut hasher = crate::hash::DefaultHasher::new();
474 family.hash(&mut hasher);
475 Self(hasher.finish())
476 }
477}
478
479#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
481pub struct StyleFontFamiliesHash(pub u64);
482
483impl ::core::fmt::Debug for StyleFontFamiliesHash {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 write!(f, "StyleFontFamiliesHash({})", self.0)
486 }
487}
488
489impl StyleFontFamiliesHash {
490 #[must_use] pub fn new(families: &[StyleFontFamily]) -> Self {
492 use core::hash::Hasher;
493 let mut hasher = crate::hash::DefaultHasher::new();
494 families.len().hash(&mut hasher);
498 for f in families {
499 f.hash(&mut hasher);
500 }
501 Self(hasher.finish())
502 }
503}
504
505#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
547#[repr(C)]
548pub struct NodeHierarchyItemId {
549 inner: usize,
552}
553
554impl fmt::Debug for NodeHierarchyItemId {
555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556 match self.into_crate_internal() {
557 Some(n) => write!(f, "Some(NodeId({n}))"),
558 None => write!(f, "None"),
559 }
560 }
561}
562
563impl fmt::Display for NodeHierarchyItemId {
564 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
565 write!(f, "{self:?}")
566 }
567}
568
569impl NodeHierarchyItemId {
570 pub const NONE: Self = Self { inner: 0 };
572
573 #[inline]
580 #[must_use] pub const fn from_raw(value: usize) -> Self {
581 Self { inner: value }
582 }
583
584 #[inline]
590 #[must_use] pub const fn into_raw(&self) -> usize {
591 self.inner
592 }
593}
594
595impl_option!(
596 NodeHierarchyItemId,
597 OptionNodeHierarchyItemId,
598 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
599);
600
601impl_vec!(NodeHierarchyItemId, NodeHierarchyItemIdVec, NodeHierarchyItemIdVecDestructor, NodeHierarchyItemIdVecDestructorType, NodeHierarchyItemIdVecSlice, OptionNodeHierarchyItemId);
602impl_vec_mut!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
603impl_vec_debug!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
604impl_vec_ord!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
605impl_vec_eq!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
606impl_vec_hash!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
607impl_vec_partialord!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
608impl_vec_clone!(NodeHierarchyItemId, NodeHierarchyItemIdVec, NodeHierarchyItemIdVecDestructor);
609impl_vec_partialeq!(NodeHierarchyItemId, NodeHierarchyItemIdVec);
610
611impl NodeHierarchyItemId {
612 #[inline]
614 #[must_use] pub const fn into_crate_internal(&self) -> Option<NodeId> {
615 NodeId::from_usize(self.inner)
616 }
617
618 #[inline]
620 #[must_use] pub const fn from_crate_internal(t: Option<NodeId>) -> Self {
621 Self {
622 inner: NodeId::into_raw(&t),
623 }
624 }
625}
626
627impl From<Option<NodeId>> for NodeHierarchyItemId {
628 #[inline]
629 fn from(opt: Option<NodeId>) -> Self {
630 Self::from_crate_internal(opt)
631 }
632}
633
634impl From<NodeHierarchyItemId> for Option<NodeId> {
635 #[inline]
636 fn from(id: NodeHierarchyItemId) -> Self {
637 id.into_crate_internal()
638 }
639}
640
641#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
642#[repr(C)]
643pub struct NodeHierarchyItem {
644 pub parent: usize,
645 pub previous_sibling: usize,
646 pub next_sibling: usize,
647 pub last_child: usize,
648}
649
650impl_option!(
651 NodeHierarchyItem,
652 OptionNodeHierarchyItem,
653 [Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
654);
655
656impl NodeHierarchyItem {
657 #[must_use] pub const fn zeroed() -> Self {
659 Self {
660 parent: 0,
661 previous_sibling: 0,
662 next_sibling: 0,
663 last_child: 0,
664 }
665 }
666}
667
668impl From<Node> for NodeHierarchyItem {
669 fn from(node: Node) -> Self {
670 Self {
671 parent: NodeId::into_raw(&node.parent),
672 previous_sibling: NodeId::into_raw(&node.previous_sibling),
673 next_sibling: NodeId::into_raw(&node.next_sibling),
674 last_child: NodeId::into_raw(&node.last_child),
675 }
676 }
677}
678
679impl NodeHierarchyItem {
680 #[must_use] pub const fn parent_id(&self) -> Option<NodeId> {
682 NodeId::from_usize(self.parent)
683 }
684 #[must_use] pub const fn previous_sibling_id(&self) -> Option<NodeId> {
686 NodeId::from_usize(self.previous_sibling)
687 }
688 #[must_use] pub const fn next_sibling_id(&self) -> Option<NodeId> {
690 NodeId::from_usize(self.next_sibling)
691 }
692 #[must_use] pub fn first_child_id(&self, current_node_id: NodeId) -> Option<NodeId> {
694 self.last_child_id().map(|_| current_node_id + 1)
695 }
696 #[must_use] pub const fn last_child_id(&self) -> Option<NodeId> {
698 NodeId::from_usize(self.last_child)
699 }
700}
701
702impl_vec!(NodeHierarchyItem, NodeHierarchyItemVec, NodeHierarchyItemVecDestructor, NodeHierarchyItemVecDestructorType, NodeHierarchyItemVecSlice, OptionNodeHierarchyItem);
703impl_vec_mut!(NodeHierarchyItem, NodeHierarchyItemVec);
704impl_vec_debug!(AzNode, NodeHierarchyItemVec);
705impl_vec_partialord!(AzNode, NodeHierarchyItemVec);
706impl_vec_clone!(
707 NodeHierarchyItem,
708 NodeHierarchyItemVec,
709 NodeHierarchyItemVecDestructor
710);
711impl_vec_partialeq!(AzNode, NodeHierarchyItemVec);
712
713impl NodeHierarchyItemVec {
714 #[must_use] pub fn as_container(&self) -> NodeDataContainerRef<'_, NodeHierarchyItem> {
716 NodeDataContainerRef {
717 internal: self.as_ref(),
718 }
719 }
720 pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, NodeHierarchyItem> {
722 NodeDataContainerRefMut {
723 internal: self.as_mut(),
724 }
725 }
726}
727
728impl NodeDataContainerRef<'_, NodeHierarchyItem> {
729 #[inline]
731 #[must_use] pub fn subtree_len(&self, parent_id: NodeId) -> usize {
732 let self_item_index = parent_id.index();
733 let next_item_index = self[parent_id].next_sibling_id().map_or_else(|| self.len(), |s| s.index());
734 next_item_index.saturating_sub(self_item_index).saturating_sub(1)
737 }
738}
739
740#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
741#[repr(C)]
742pub struct ParentWithNodeDepth {
743 pub depth: usize,
744 pub node_id: NodeHierarchyItemId,
745}
746
747impl_option!(
748 ParentWithNodeDepth,
749 OptionParentWithNodeDepth,
750 [Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
751);
752
753impl fmt::Debug for ParentWithNodeDepth {
754 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
755 write!(
756 f,
757 "{{ depth: {}, node: {:?} }}",
758 self.depth,
759 self.node_id.into_crate_internal()
760 )
761 }
762}
763
764impl_vec!(ParentWithNodeDepth, ParentWithNodeDepthVec, ParentWithNodeDepthVecDestructor, ParentWithNodeDepthVecDestructorType, ParentWithNodeDepthVecSlice, OptionParentWithNodeDepth);
765impl_vec_mut!(ParentWithNodeDepth, ParentWithNodeDepthVec);
766impl_vec_debug!(ParentWithNodeDepth, ParentWithNodeDepthVec);
767impl_vec_partialord!(ParentWithNodeDepth, ParentWithNodeDepthVec);
768impl_vec_clone!(
769 ParentWithNodeDepth,
770 ParentWithNodeDepthVec,
771 ParentWithNodeDepthVecDestructor
772);
773impl_vec_partialeq!(ParentWithNodeDepth, ParentWithNodeDepthVec);
774
775#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
776#[repr(C)]
777pub struct TagIdToNodeIdMapping {
778 pub tag_id: TagId,
780 pub node_id: NodeHierarchyItemId,
782 pub tab_index: OptionTabIndex,
784}
785
786impl_option!(
787 TagIdToNodeIdMapping,
788 OptionTagIdToNodeIdMapping,
789 copy = false,
790 [Debug, Clone, PartialEq, Eq, Ord, PartialOrd]
791);
792
793impl_vec!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec, TagIdToNodeIdMappingVecDestructor, TagIdToNodeIdMappingVecDestructorType, TagIdToNodeIdMappingVecSlice, OptionTagIdToNodeIdMapping);
794impl_vec_mut!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
795impl_vec_debug!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
796impl_vec_partialord!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
797impl_vec_clone!(
798 TagIdToNodeIdMapping,
799 TagIdToNodeIdMappingVec,
800 TagIdToNodeIdMappingVecDestructor
801);
802impl_vec_partialeq!(TagIdToNodeIdMapping, TagIdToNodeIdMappingVec);
803
804#[derive(Debug, Clone, PartialEq, PartialOrd)]
805#[repr(C)]
806pub struct ContentGroup {
807 pub root: NodeHierarchyItemId,
810 pub children: ContentGroupVec,
812}
813
814impl_option!(
815 ContentGroup,
816 OptionContentGroup,
817 copy = false,
818 [Debug, Clone, PartialEq, PartialOrd]
819);
820
821impl_vec!(ContentGroup, ContentGroupVec, ContentGroupVecDestructor, ContentGroupVecDestructorType, ContentGroupVecSlice, OptionContentGroup);
822impl_vec_mut!(ContentGroup, ContentGroupVec);
823impl_vec_debug!(ContentGroup, ContentGroupVec);
824impl_vec_partialord!(ContentGroup, ContentGroupVec);
825impl_vec_clone!(ContentGroup, ContentGroupVec, ContentGroupVecDestructor);
826impl_vec_partialeq!(ContentGroup, ContentGroupVec);
827
828#[derive(Debug, PartialEq, Clone)]
829#[repr(C)]
830pub struct StyledDom {
831 pub root: NodeHierarchyItemId,
832 pub node_hierarchy: NodeHierarchyItemVec,
833 pub node_data: NodeDataVec,
834 pub styled_nodes: StyledNodeVec,
835 pub cascade_info: CascadeInfoVec,
836 pub nodes_with_window_callbacks: NodeHierarchyItemIdVec,
837 pub nodes_with_datasets: NodeHierarchyItemIdVec,
838 pub tag_ids_to_node_ids: TagIdToNodeIdMappingVec,
839 pub non_leaf_nodes: ParentWithNodeDepthVec,
840 pub css_property_cache: CssPropertyCachePtr,
841 pub dom_id: DomId,
843}
844impl_option!(
845 StyledDom,
846 OptionStyledDom,
847 copy = false,
848 [Debug, Clone, PartialEq]
849);
850
851impl Default for StyledDom {
852 fn default() -> Self {
853 let root_node: NodeHierarchyItem = Node::ROOT.into();
854 let root_node_id: NodeHierarchyItemId =
855 NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO));
856 Self {
857 root: root_node_id,
858 node_hierarchy: vec![root_node].into(),
859 node_data: vec![NodeData::create_body()].into(),
860 styled_nodes: vec![StyledNode::default()].into(),
861 cascade_info: vec![CascadeInfo {
862 index_in_parent: 0,
863 is_last_child: true,
864 }]
865 .into(),
866 tag_ids_to_node_ids: Vec::new().into(),
867 non_leaf_nodes: vec![ParentWithNodeDepth {
868 depth: 0,
869 node_id: root_node_id,
870 }]
871 .into(),
872 nodes_with_window_callbacks: Vec::new().into(),
873 nodes_with_datasets: Vec::new().into(),
874 css_property_cache: CssPropertyCachePtr::new(CssPropertyCache::empty(1)),
875 dom_id: DomId::ROOT_ID,
876 }
877 }
878}
879
880#[derive(Debug, Clone, Copy, Default)]
882pub struct StyledDomMemoryReport {
883 pub node_count: usize,
884 pub node_hierarchy_bytes: usize,
885 pub node_data_bytes: usize,
886 pub styled_nodes_bytes: usize,
887 pub cascade_info_bytes: usize,
888 pub tag_ids_bytes: usize,
889 pub non_leaf_nodes_bytes: usize,
890 pub callback_vecs_bytes: usize,
891 pub css_property_cache: crate::prop_cache::CssPropertyCacheBreakdown,
892}
893
894impl StyledDomMemoryReport {
895 #[must_use] pub const fn total_bytes(&self) -> usize {
896 self.node_hierarchy_bytes
897 + self.node_data_bytes
898 + self.styled_nodes_bytes
899 + self.cascade_info_bytes
900 + self.tag_ids_bytes
901 + self.non_leaf_nodes_bytes
902 + self.callback_vecs_bytes
903 + self.css_property_cache.total_bytes()
904 }
905}
906
907impl StyledDom {
908 #[must_use] pub fn memory_report(&self) -> StyledDomMemoryReport {
910 let n = self.node_data.len();
911 StyledDomMemoryReport {
912 node_count: n,
913 node_hierarchy_bytes: size_of_val(self.node_hierarchy.as_ref()),
914 node_data_bytes: {
915 let base = n * size_of::<NodeData>();
916 let mut inner = 0usize;
919 for nd in self.node_data.as_ref() {
920 inner += nd.get_callbacks().len() * 64; inner += nd.style.rules.as_ref().len() * 64;
924 }
925 base + inner
926 },
927 styled_nodes_bytes: n * size_of::<StyledNode>(),
928 cascade_info_bytes: n * size_of::<CascadeInfo>(),
929 tag_ids_bytes: size_of_val(self.tag_ids_to_node_ids.as_ref()),
930 non_leaf_nodes_bytes: size_of_val(self.non_leaf_nodes.as_ref()),
931 callback_vecs_bytes:
932 self.nodes_with_window_callbacks.as_ref().len() * 8
933 + self.nodes_with_datasets.as_ref().len() * 8,
934 css_property_cache: self.css_property_cache.ptr.memory_breakdown(),
935 }
936 }
937
938 pub fn create(dom: &mut Dom, css: Css) -> Self {
945 use core::mem;
946
947 let mut swap_dom = Dom::create_body();
948 mem::swap(dom, &mut swap_dom);
949
950 let compact_dom: CompactDom = swap_dom.into();
951 let node_hierarchy: NodeHierarchyItemVec = compact_dom
952 .node_hierarchy
953 .as_ref()
954 .internal
955 .iter()
956 .map(|i| (*i).into())
957 .collect::<Vec<NodeHierarchyItem>>()
958 .into();
959
960 Self::create_from_compact_dom(compact_dom, css, node_hierarchy)
961 }
962
963 #[must_use] pub fn create_from_fast_dom(fast_dom: crate::dom::FastDom) -> Self {
969 use azul_css::css::Css;
970
971 let mut combined_rules: Vec<azul_css::css::CssRuleBlock> = Vec::new();
977 let css_entries = fast_dom.css.into_library_owned_vec();
978 {
979 let hierarchy = fast_dom.node_hierarchy.as_container();
980 for css_with_id in css_entries {
981 let owner = css_with_id.node_id;
982 let end = if owner < hierarchy.len() {
983 owner + hierarchy.subtree_len(NodeId::new(owner))
984 } else {
985 owner
986 };
987 for mut rule in css_with_id.css.rules.into_library_owned_vec() {
988 let node_only =
993 rule.priority >= azul_css::css::rule_priority::INLINE;
994 rule.path.push_front_scope_for(owner, end, node_only);
995 combined_rules.push(rule);
996 }
997 }
998 }
999 let combined_css = if combined_rules.is_empty() {
1000 Css::empty()
1001 } else {
1002 Css::new(combined_rules)
1003 };
1004
1005 let node_hierarchy_items = fast_dom.node_hierarchy;
1008 let nodes: Vec<Node> = node_hierarchy_items.as_ref()
1009 .iter()
1010 .map(|item| Node {
1011 parent: NodeId::from_usize(item.parent),
1012 previous_sibling: NodeId::from_usize(item.previous_sibling),
1013 next_sibling: NodeId::from_usize(item.next_sibling),
1014 last_child: NodeId::from_usize(item.last_child),
1015 })
1016 .collect();
1017 let node_hierarchy_internal = NodeHierarchy { internal: nodes };
1018
1019 let node_data_vec = fast_dom.node_data.into_library_owned_vec();
1021 let compact_dom = CompactDom {
1022 node_hierarchy: node_hierarchy_internal,
1023 node_data: NodeDataContainer { internal: node_data_vec },
1024 root: NodeId::ZERO,
1025 };
1026
1027 Self::create_from_compact_dom(compact_dom, combined_css, node_hierarchy_items)
1031 }
1032
1033 #[allow(clippy::similar_names)] #[allow(clippy::too_many_lines)] fn create_from_compact_dom(
1039 compact_dom: CompactDom,
1040 mut css: Css,
1041 node_hierarchy: NodeHierarchyItemVec,
1042 ) -> Self {
1043 use crate::dom::EventFilter;
1044
1045 static CASCADE_BREAKDOWN: crate::sync::OnceLock<bool> = crate::sync::OnceLock::new();
1046 let cascade_dbg = *CASCADE_BREAKDOWN.get_or_init(crate::profile::memory_enabled);
1047
1048 let node_count = compact_dom.len();
1049
1050 let non_leaf_nodes = compact_dom
1051 .node_hierarchy
1052 .as_ref()
1053 .get_parents_sorted_by_depth();
1054
1055 let mut styled_nodes = vec![
1056 StyledNode {
1057 styled_node_state: StyledNodeState::new()
1058 };
1059 node_count
1060 ];
1061
1062 let mut css_property_cache = CssPropertyCache::empty(compact_dom.node_data.len());
1063
1064 let html_tree = construct_html_cascade_tree(
1065 &compact_dom.node_hierarchy.as_ref(),
1066 &non_leaf_nodes[..],
1067 &compact_dom.node_data.as_ref(),
1068 );
1069
1070 let non_leaf_nodes = non_leaf_nodes
1071 .iter()
1072 .map(|(depth, node_id)| ParentWithNodeDepth {
1073 depth: *depth,
1074 node_id: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
1075 })
1076 .collect::<Vec<_>>();
1077
1078 let non_leaf_nodes: ParentWithNodeDepthVec = non_leaf_nodes.into();
1079
1080 let _restyle_tag_ids = css_property_cache.restyle(
1081 &mut css,
1082 &compact_dom.node_data.as_ref(),
1083 &node_hierarchy,
1084 &non_leaf_nodes,
1085 &html_tree.as_ref(),
1086 );
1087
1088 css_property_cache.retained_author_css = css;
1093
1094 css_property_cache.apply_ua_css(compact_dom.node_data.as_ref().internal);
1102 css_property_cache.compute_inherited_values(
1103 node_hierarchy.as_container().internal,
1104 compact_dom.node_data.as_ref().internal,
1105 );
1106
1107 let prev_font_hashes: Vec<u64> = css_property_cache.compact_cache
1108 .as_ref()
1109 .map(|c| c.prev_font_hashes.clone())
1110 .unwrap_or_default();
1111 let compact = css_property_cache.build_compact_cache_with_inheritance(
1112 compact_dom.node_data.as_ref().internal,
1113 node_hierarchy.as_container().internal,
1114 &prev_font_hashes,
1115 );
1116 css_property_cache.compact_cache = Some(compact);
1117 let pre_prune = if cascade_dbg {
1118 Some(css_property_cache.memory_breakdown())
1119 } else { None };
1120 css_property_cache.prune_compact_normal_props();
1121 if let Some(pre) = pre_prune {
1122 let post = css_property_cache.memory_breakdown();
1123 #[cfg(feature = "std")]
1124 eprintln!("[PRUNE] css_props {} → {} KiB cascaded {} → {} KiB (saved {} KiB)",
1125 pre.css_props_bytes / 1024, post.css_props_bytes / 1024,
1126 pre.cascaded_props_bytes / 1024, post.cascaded_props_bytes / 1024,
1127 (pre.total_bytes().saturating_sub(post.total_bytes())) / 1024);
1128 #[cfg(not(feature = "std"))]
1129 let _ = post;
1130 }
1131
1132 let tag_ids = css_property_cache.generate_tag_ids(
1133 &compact_dom.node_data.as_ref(),
1134 &node_hierarchy,
1135 );
1136
1137 if cascade_dbg {
1138 let bd = css_property_cache.memory_breakdown();
1139 #[cfg(feature = "std")]
1140 eprintln!("[CASCADE] {} nodes cascaded_props={} KiB css_props={} KiB compact={} KiB computed={} KiB total={} KiB",
1141 node_count,
1142 bd.cascaded_props_bytes / 1024, bd.css_props_bytes / 1024,
1143 bd.compact_cache_bytes / 1024, bd.computed_values_bytes / 1024,
1144 bd.total_bytes() / 1024);
1145 #[cfg(not(feature = "std"))]
1146 let _ = bd;
1147 }
1148
1149 let has_any_callbacks = compact_dom.node_data.as_ref().internal.iter()
1152 .any(|c| !c.get_callbacks().is_empty() || c.get_dataset().is_some());
1153
1154 let (nodes_with_window_callbacks, nodes_with_datasets) = if has_any_callbacks {
1155 let mut win_cbs = Vec::new();
1156 let mut datasets = Vec::new();
1157 for (node_id, c) in compact_dom.node_data.as_ref().internal.iter().enumerate() {
1158 let cbs = c.get_callbacks();
1159 let has_dataset = c.get_dataset().is_some();
1160 if !cbs.is_empty() || has_dataset {
1161 datasets.push(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_id))));
1162 }
1163 for cb in cbs {
1164 if let EventFilter::Window(_) = cb.event {
1165 win_cbs.push(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_id))));
1166 break;
1167 }
1168 }
1169 }
1170 (win_cbs, datasets)
1171 } else {
1172 (Vec::new(), Vec::new())
1173 };
1174 let mut styled_dom = Self {
1175 root: NodeHierarchyItemId::from_crate_internal(Some(compact_dom.root)),
1176 node_hierarchy,
1177 node_data: compact_dom.node_data.internal.into(),
1178 cascade_info: html_tree.internal.into(),
1179 styled_nodes: styled_nodes.into(),
1180 tag_ids_to_node_ids: tag_ids.into(),
1181 nodes_with_window_callbacks: nodes_with_window_callbacks.into(),
1182 nodes_with_datasets: nodes_with_datasets.into(),
1183 non_leaf_nodes,
1184 css_property_cache: CssPropertyCachePtr::new(css_property_cache),
1185 dom_id: DomId::ROOT_ID,
1186 };
1187 #[cfg(feature = "table_layout")]
1188 if let Err(_e) = crate::dom_table::generate_anonymous_table_elements(&mut styled_dom) {
1189 }
1190
1191 styled_dom
1192 }
1193
1194 #[must_use] pub fn create_from_dom(mut dom: Dom) -> Self {
1205 use azul_css::css::Css;
1206
1207 dom.fixup_children_estimated();
1212 let mut next_scope_id = 0usize;
1213 scope_inline_css(&mut dom, &mut next_scope_id);
1214
1215 let mut all_css = Vec::new();
1217 collect_css_from_dom(&dom, &mut all_css);
1218
1219 let mut combined_css = if all_css.is_empty() {
1221 Css::empty()
1222 } else {
1223 let mut combined_rules: Vec<azul_css::css::CssRuleBlock> = Vec::new();
1224 for css in all_css {
1225 combined_rules.extend(css.rules.into_library_owned_vec());
1226 }
1227 Css::new(combined_rules)
1228 };
1229
1230 strip_css_from_dom(&mut dom);
1233
1234 Self::create(&mut dom, combined_css)
1236 }
1237
1238 pub fn append_child(&mut self, other: Self) {
1241 let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1242 let current_root_children_count = self_root_id
1243 .az_children(&self.node_hierarchy.as_container())
1244 .count();
1245 self.append_child_with_index(other, current_root_children_count);
1246 self.finalize_non_leaf_nodes();
1247 }
1248
1249 pub fn append_child_with_index(&mut self, mut other: Self, child_index: usize) {
1252 let self_len = self.node_hierarchy.as_ref().len();
1254 let other_len = other.node_hierarchy.as_ref().len();
1255 let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1256 let other_root_id = other.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1257
1258 other.cascade_info.as_mut()[other_root_id.index()].index_in_parent =
1260 u32::try_from(child_index).unwrap_or(u32::MAX);
1261 other.cascade_info.as_mut()[other_root_id.index()].is_last_child = true;
1262
1263 self.cascade_info.append(&mut other.cascade_info);
1264
1265 for other in other.node_hierarchy.as_mut().iter_mut() {
1267 if other.parent != 0 {
1268 other.parent += self_len;
1269 }
1270 if other.previous_sibling != 0 {
1271 other.previous_sibling += self_len;
1272 }
1273 if other.next_sibling != 0 {
1274 other.next_sibling += self_len;
1275 }
1276 if other.last_child != 0 {
1277 other.last_child += self_len;
1278 }
1279 }
1280
1281 other.node_hierarchy.as_container_mut()[other_root_id].parent =
1282 NodeId::into_raw(&Some(self_root_id));
1283 let current_last_child = self.node_hierarchy.as_container()[self_root_id].last_child_id();
1284 other.node_hierarchy.as_container_mut()[other_root_id].previous_sibling =
1285 NodeId::into_raw(¤t_last_child);
1286 if let Some(current_last) = current_last_child {
1287 if self.node_hierarchy.as_container_mut()[current_last]
1288 .next_sibling_id()
1289 .is_some()
1290 {
1291 self.node_hierarchy.as_container_mut()[current_last].next_sibling +=
1292 other_root_id.index() + other_len;
1293 } else {
1294 self.node_hierarchy.as_container_mut()[current_last].next_sibling =
1295 NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1296 }
1297 }
1298 self.node_hierarchy.as_container_mut()[self_root_id].last_child =
1299 NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1300
1301 self.node_hierarchy.append(&mut other.node_hierarchy);
1302 self.node_data.append(&mut other.node_data);
1303 self.styled_nodes.append(&mut other.styled_nodes);
1304 self.get_css_property_cache_mut()
1305 .append(other.get_css_property_cache_mut());
1306
1307 for tag_id_node_id in &mut other.tag_ids_to_node_ids {
1310 tag_id_node_id.node_id.inner += self_len;
1311 }
1312
1313 self.tag_ids_to_node_ids
1314 .append(&mut other.tag_ids_to_node_ids);
1315
1316 for nid in &mut other.nodes_with_window_callbacks {
1317 nid.inner += self_len;
1318 }
1319 self.nodes_with_window_callbacks
1320 .append(&mut other.nodes_with_window_callbacks);
1321
1322 for nid in &mut other.nodes_with_datasets {
1323 nid.inner += self_len;
1324 }
1325 self.nodes_with_datasets
1326 .append(&mut other.nodes_with_datasets);
1327
1328 if other_len != 1 {
1331 for other_non_leaf_node in &mut other.non_leaf_nodes {
1332 other_non_leaf_node.node_id.inner += self_len;
1333 other_non_leaf_node.depth += 1;
1334 }
1335 self.non_leaf_nodes.append(&mut other.non_leaf_nodes);
1336 }
1338 }
1339
1340 pub fn finalize_non_leaf_nodes(&mut self) {
1343 self.non_leaf_nodes.sort_by(|a, b| a.depth.cmp(&b.depth));
1344 }
1345
1346 #[must_use] pub fn with_child(mut self, other: Self) -> Self {
1348 self.append_child(other);
1349 self
1350 }
1351
1352 pub fn set_context_menu(&mut self, context_menu: Menu) {
1354 if let Some(root_id) = self.root.into_crate_internal() {
1355 self.node_data.as_container_mut()[root_id].set_context_menu(context_menu);
1356 }
1357 }
1358
1359 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
1361 self.set_context_menu(context_menu);
1362 self
1363 }
1364
1365 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
1367 if let Some(root_id) = self.root.into_crate_internal() {
1368 self.node_data.as_container_mut()[root_id].set_menu_bar(menu_bar);
1369 }
1370 }
1371
1372 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
1374 self.set_menu_bar(menu_bar);
1375 self
1376 }
1377
1378 pub fn recompute_inheritance_and_compact_cache(&mut self) {
1393 let prev_font_hashes: Vec<u64> = self.css_property_cache
1401 .downcast_mut()
1402 .compact_cache
1403 .as_ref()
1404 .map(|c| c.prev_font_hashes.clone())
1405 .unwrap_or_default();
1406 let compact = self.css_property_cache
1407 .downcast_mut()
1408 .build_compact_cache_with_inheritance(
1409 self.node_data.as_container().internal,
1410 self.node_hierarchy.as_container().internal,
1411 &prev_font_hashes,
1412 );
1413 self.css_property_cache.downcast_mut().compact_cache = Some(compact);
1414 }
1415
1416 #[allow(clippy::similar_names)] pub fn extend_author_scopes_for_appended(&mut self, new_node: NodeId, parent: NodeId) {
1427 use azul_css::css::CssPathSelector;
1428 let p = parent.index();
1429 let n = new_node.index();
1430 let cache = self.css_property_cache.downcast_mut();
1431 for rule in cache.retained_author_css.rules.as_mut() {
1432 let mut sels = rule.path.selectors.as_ref().to_vec();
1433 let mut changed = false;
1434 for sel in &mut sels {
1435 if let CssPathSelector::Root(range) = sel {
1436 if range.contains(p) && range.end < n {
1437 range.end = n;
1438 changed = true;
1439 }
1440 }
1441 }
1442 if changed {
1443 rule.path.selectors = sels.into();
1444 }
1445 }
1446 }
1447
1448 pub fn restyle_retained(&mut self) {
1453 let css = self
1454 .css_property_cache
1455 .downcast_mut()
1456 .retained_author_css
1457 .clone();
1458 if css.is_empty() {
1459 return;
1460 }
1461 self.restyle(css);
1462 }
1463
1464 pub fn restyle(&mut self, mut css: Css) {
1465 let _stale_tag_ids = self.css_property_cache.downcast_mut().restyle(
1470 &mut css,
1471 &self.node_data.as_container(),
1472 &self.node_hierarchy,
1473 &self.non_leaf_nodes,
1474 &self.cascade_info.as_container(),
1475 );
1476
1477 self.css_property_cache.downcast_mut().retained_author_css = css;
1479
1480 self.css_property_cache
1482 .downcast_mut()
1483 .apply_ua_css(self.node_data.as_container().internal);
1484
1485 self.css_property_cache
1487 .downcast_mut()
1488 .compute_inherited_values(
1489 self.node_hierarchy.as_container().internal,
1490 self.node_data.as_container().internal,
1491 );
1492
1493 let prev_font_hashes: Vec<u64> = self
1499 .css_property_cache
1500 .downcast_mut()
1501 .compact_cache
1502 .as_ref()
1503 .map(|c| c.prev_font_hashes.clone())
1504 .unwrap_or_default();
1505 self.css_property_cache.downcast_mut().compact_cache = None;
1506 let compact = self
1507 .css_property_cache
1508 .downcast_mut()
1509 .build_compact_cache_with_inheritance(
1510 self.node_data.as_container().internal,
1511 self.node_hierarchy.as_container().internal,
1512 &prev_font_hashes,
1513 );
1514 self.css_property_cache.downcast_mut().compact_cache = Some(compact);
1515 self.css_property_cache
1516 .downcast_mut()
1517 .invalidate_resolved_font_sizes();
1518
1519 let new_tag_ids = self.css_property_cache.downcast_mut().generate_tag_ids(
1522 &self.node_data.as_container(),
1523 &self.node_hierarchy,
1524 );
1525 self.tag_ids_to_node_ids = new_tag_ids.into();
1526 }
1527
1528 #[inline]
1530 #[must_use] pub const fn node_count(&self) -> usize {
1531 self.node_data.len()
1532 }
1533
1534 #[inline]
1536 #[must_use] pub fn get_css_property_cache(&self) -> &CssPropertyCache {
1537 &self.css_property_cache.ptr
1538 }
1539
1540 #[inline]
1542 pub fn get_css_property_cache_mut(&mut self) -> &mut CssPropertyCache {
1543 &mut self.css_property_cache.ptr
1544 }
1545
1546 #[inline]
1548 #[must_use] pub fn get_styled_node_state(&self, node_id: &NodeId) -> StyledNodeState {
1549 self.styled_nodes.as_container()[*node_id]
1550 .styled_node_state
1551 }
1552
1553 #[must_use]
1555 pub fn restyle_nodes_hover(
1556 &mut self,
1557 nodes: &[NodeId],
1558 new_hover_state: bool,
1559 ) -> RestyleNodes {
1560 self.restyle_nodes_state(
1561 nodes,
1562 new_hover_state,
1563 |state, val| state.hover = val,
1564 azul_css::dynamic_selector::PseudoStateType::Hover,
1565 )
1566 }
1567
1568 #[must_use]
1570 pub fn restyle_nodes_active(
1571 &mut self,
1572 nodes: &[NodeId],
1573 new_active_state: bool,
1574 ) -> RestyleNodes {
1575 self.restyle_nodes_state(
1576 nodes,
1577 new_active_state,
1578 |state, val| state.active = val,
1579 azul_css::dynamic_selector::PseudoStateType::Active,
1580 )
1581 }
1582
1583 #[must_use]
1585 pub fn restyle_nodes_focus(
1586 &mut self,
1587 nodes: &[NodeId],
1588 new_focus_state: bool,
1589 ) -> RestyleNodes {
1590 self.restyle_nodes_state(
1591 nodes,
1592 new_focus_state,
1593 |state, val| state.focused = val,
1594 azul_css::dynamic_selector::PseudoStateType::Focus,
1595 )
1596 }
1597
1598 fn restyle_nodes_state(
1600 &mut self,
1601 nodes: &[NodeId],
1602 new_state_value: bool,
1603 set_state: impl Fn(&mut StyledNodeState, bool),
1604 pseudo_state_type: azul_css::dynamic_selector::PseudoStateType,
1605 ) -> RestyleNodes {
1606 let node_count = self.node_count();
1611 let nodes: Vec<NodeId> = nodes
1612 .iter()
1613 .copied()
1614 .filter(|nid| nid.index() < node_count)
1615 .collect();
1616
1617 let old_node_states = nodes
1619 .iter()
1620 .map(|nid| {
1621 self.styled_nodes.as_container()[*nid]
1622 .styled_node_state
1623 })
1624 .collect::<Vec<_>>();
1625
1626 for nid in &nodes {
1627 set_state(
1628 &mut self.styled_nodes.as_container_mut()[*nid].styled_node_state,
1629 new_state_value,
1630 );
1631 }
1632
1633 let css_property_cache = self.get_css_property_cache();
1634 let styled_nodes = self.styled_nodes.as_container();
1635 let node_data = self.node_data.as_container();
1636
1637 let v = nodes
1639 .iter()
1640 .zip(old_node_states.iter())
1641 .filter_map(|(node_id, old_node_state)| {
1642 let mut keys_normal: Vec<_> = CssPropertyCache::prop_types_for_state(
1643 css_property_cache.css_props.get_slice(node_id.index()),
1644 pseudo_state_type,
1645 ).collect();
1646 let mut keys_inherited: Vec<_> = CssPropertyCache::prop_types_for_state(
1647 css_property_cache.cascaded_props.get_slice(node_id.index()),
1648 pseudo_state_type,
1649 ).collect();
1650 let keys_inline: Vec<CssPropertyType> = {
1651 use azul_css::dynamic_selector::DynamicSelector;
1652 node_data[*node_id]
1653 .style
1654 .iter_inline_properties()
1655 .filter_map(|(prop, conds)| {
1656 let matches = conds.as_slice().iter().any(|c| {
1657 matches!(c, DynamicSelector::PseudoState(pst) if *pst == pseudo_state_type)
1658 });
1659 if matches {
1660 Some(prop.get_type())
1661 } else {
1662 None
1663 }
1664 })
1665 .collect()
1666 };
1667 let mut keys_inline_ref: Vec<_> = keys_inline.iter().collect();
1668
1669 keys_normal.append(&mut keys_inherited);
1670 keys_normal.append(&mut keys_inline_ref);
1671
1672 let node_properties_that_could_have_changed = keys_normal;
1673
1674 if node_properties_that_could_have_changed.is_empty() {
1675 return None;
1676 }
1677
1678 let new_node_state = &styled_nodes[*node_id].styled_node_state;
1679 let node_data = &node_data[*node_id];
1680
1681 let changes = node_properties_that_could_have_changed
1682 .into_iter()
1683 .filter_map(|prop| {
1684 let old = css_property_cache.get_property_slow(
1686 node_data,
1687 node_id,
1688 old_node_state,
1689 prop,
1690 );
1691 let new = css_property_cache.get_property_slow(
1692 node_data,
1693 node_id,
1694 new_node_state,
1695 prop,
1696 );
1697 if old == new {
1698 None
1699 } else {
1700 Some(ChangedCssProperty {
1701 previous_state: *old_node_state,
1702 previous_prop: old.map_or_else(|| CssProperty::auto(*prop), Clone::clone),
1703 current_state: *new_node_state,
1704 current_prop: new.map_or_else(|| CssProperty::auto(*prop), Clone::clone),
1705 })
1706 }
1707 })
1708 .collect::<Vec<_>>();
1709
1710 if changes.is_empty() {
1711 None
1712 } else {
1713 Some((*node_id, changes))
1714 }
1715 })
1716 .collect::<Vec<_>>();
1717
1718 v.into_iter().collect()
1719 }
1720
1721 #[must_use]
1735 pub fn restyle_on_state_change(
1736 &mut self,
1737 focus_changes: Option<FocusChange>,
1738 hover_changes: Option<HoverChange>,
1739 active_changes: Option<ActiveChange>,
1740 ) -> RestyleResult {
1741
1742 let mut result = RestyleResult {
1744 gpu_only_changes: true,
1745 ..RestyleResult::default()
1746 };
1747
1748 let mut process_changes = |changes: RestyleNodes| {
1750 for (node_id, props) in changes {
1751 for change in &props {
1752 let prop_type = change.current_prop.get_type();
1753
1754 let scope = prop_type.relayout_scope(true);
1761
1762 if scope > result.max_relayout_scope {
1764 result.max_relayout_scope = scope;
1765 }
1766
1767 if scope != RelayoutScope::None {
1769 result.needs_layout = true;
1770 result.gpu_only_changes = false;
1771 }
1772
1773 if !prop_type.is_gpu_only_property() {
1775 result.gpu_only_changes = false;
1776 }
1777
1778 result.needs_display_list = true;
1780 }
1781
1782 result.changed_nodes.entry(node_id).or_default().extend(props);
1783 }
1784 };
1785
1786 if let Some(focus) = focus_changes {
1788 if let Some(old) = focus.lost_focus {
1789 let changes = self.restyle_nodes_focus(&[old], false);
1790 process_changes(changes);
1791 }
1792 if let Some(new) = focus.gained_focus {
1793 let changes = self.restyle_nodes_focus(&[new], true);
1794 process_changes(changes);
1795 }
1796 }
1797
1798 if let Some(hover) = hover_changes {
1800 if !hover.left_nodes.is_empty() {
1801 let changes = self.restyle_nodes_hover(&hover.left_nodes, false);
1802 process_changes(changes);
1803 }
1804 if !hover.entered_nodes.is_empty() {
1805 let changes = self.restyle_nodes_hover(&hover.entered_nodes, true);
1806 process_changes(changes);
1807 }
1808 }
1809
1810 if let Some(active) = active_changes {
1812 if !active.deactivated.is_empty() {
1813 let changes = self.restyle_nodes_active(&active.deactivated, false);
1814 process_changes(changes);
1815 }
1816 if !active.activated.is_empty() {
1817 let changes = self.restyle_nodes_active(&active.activated, true);
1818 process_changes(changes);
1819 }
1820 }
1821
1822 if result.changed_nodes.is_empty() {
1824 result.needs_display_list = false;
1825 result.gpu_only_changes = false;
1826 }
1827
1828 if result.needs_layout {
1830 result.needs_display_list = true;
1831 result.gpu_only_changes = false;
1832 }
1833
1834 result
1835 }
1836
1837 #[must_use]
1848 pub fn restyle_user_property(
1849 &mut self,
1850 node_id: &NodeId,
1851 new_properties: &[CssProperty],
1852 ) -> RestyleNodes {
1853 let mut map = BTreeMap::default();
1854
1855 if new_properties.is_empty() {
1856 return map;
1857 }
1858
1859 let node_count = self.node_data.as_ref().len();
1860 if node_id.index() >= node_count {
1861 return map;
1862 }
1863
1864 let node_data = self.node_data.as_container();
1865 let node_data = &node_data[*node_id];
1866
1867 let node_states = &self.styled_nodes.as_container();
1868 let old_node_state = &node_states[*node_id].styled_node_state;
1869
1870 let changes: Vec<ChangedCssProperty> = {
1871 let css_property_cache = self.get_css_property_cache();
1872
1873 new_properties
1874 .iter()
1875 .filter_map(|new_prop| {
1876 let old_prop = css_property_cache.get_property_slow(
1877 node_data,
1878 node_id,
1879 old_node_state,
1880 &new_prop.get_type(),
1881 );
1882
1883 let old_prop = old_prop.map_or_else(|| CssProperty::auto(new_prop.get_type()), Clone::clone);
1884
1885 if old_prop == *new_prop {
1886 None
1887 } else {
1888 Some(ChangedCssProperty {
1889 previous_state: *old_node_state,
1890 previous_prop: old_prop,
1891 current_state: *old_node_state,
1893 current_prop: new_prop.clone(),
1894 })
1895 }
1896 })
1897 .collect()
1898 };
1899
1900 let css_property_cache_mut = self.get_css_property_cache_mut();
1901
1902 if css_property_cache_mut.user_overridden_properties.len() < node_count {
1907 css_property_cache_mut
1908 .user_overridden_properties
1909 .resize(node_count, Vec::new());
1910 }
1911
1912 for new_prop in new_properties {
1913 let prop_type = new_prop.get_type();
1914 let vec = &mut css_property_cache_mut
1915 .user_overridden_properties[node_id.index()];
1916 if new_prop.is_initial() {
1917 if let Ok(idx) = vec.binary_search_by_key(&prop_type, |(k, _)| *k) {
1919 vec.remove(idx);
1920 }
1921 } else {
1922 match vec.binary_search_by_key(&prop_type, |(k, _)| *k) {
1923 Ok(idx) => vec[idx].1 = new_prop.clone(),
1924 Err(idx) => vec.insert(idx, (prop_type, new_prop.clone())),
1925 }
1926 }
1927 }
1928
1929 if new_properties
1951 .iter()
1952 .any(|p| p.get_type().can_trigger_relayout())
1953 {
1954 self.recompute_inheritance_and_compact_cache();
1955 self.get_css_property_cache_mut()
1956 .invalidate_resolved_font_sizes();
1957 }
1958
1959 if !changes.is_empty() {
1960 map.insert(*node_id, changes);
1961 }
1962
1963 map
1964 }
1965
1966 pub fn set_dynamic_selector_context(
1985 &mut self,
1986 context: azul_css::dynamic_selector::DynamicSelectorContext,
1987 ) {
1988 {
1989 let cache = self.get_css_property_cache_mut();
1990 if cache.dynamic_context.as_deref() == Some(&context) {
1991 return;
1992 }
1993 cache.dynamic_context = Some(Box::new(context));
1994 }
1995 let author_conditional = self
2001 .get_css_property_cache()
2002 .retained_author_css
2003 .rules
2004 .as_ref()
2005 .iter()
2006 .any(|r| !r.conditions.as_ref().is_empty());
2007 if author_conditional {
2008 self.restyle_retained();
2009 }
2010 let needs_rebuild = self
2011 .get_css_property_cache()
2012 .compact_cache
2013 .as_ref()
2014 .is_none_or(|cc| cc.has_dynamic_conditions);
2015 if needs_rebuild {
2016 self.recompute_inheritance_and_compact_cache();
2017 self.get_css_property_cache_mut()
2018 .invalidate_resolved_font_sizes();
2019 let new_tag_ids = self.css_property_cache.downcast_mut().generate_tag_ids(
2020 &self.node_data.as_container(),
2021 &self.node_hierarchy,
2022 );
2023 self.tag_ids_to_node_ids = new_tag_ids.into();
2024 }
2025 }
2026
2027 #[must_use]
2042 pub fn viewport_breakpoints(&self) -> Option<(Vec<f32>, Vec<f32>)> {
2043 let cache = self.get_css_property_cache();
2044 let cc = cache.compact_cache.as_ref()?;
2045 let (mut w, mut h) = cache.retained_author_css.viewport_breakpoints();
2046 w.extend(cc.inline_viewport_w.iter().copied().map(f32::from_bits));
2047 h.extend(cc.inline_viewport_h.iter().copied().map(f32::from_bits));
2048 w.sort_by_key(|v| v.to_bits());
2049 w.dedup_by_key(|v| v.to_bits());
2050 h.sort_by_key(|v| v.to_bits());
2051 h.dedup_by_key(|v| v.to_bits());
2052 Some((w, h))
2053 }
2054
2055 pub fn migrate_user_overrides_from(
2071 &mut self,
2072 old_cache: &CssPropertyCache,
2073 node_moves: &[crate::diff::NodeMove],
2074 ) {
2075 let node_count = self.node_data.as_ref().len();
2076 let mut migrated_any = false;
2077 for m in node_moves {
2078 let Some(old_vec) = old_cache
2079 .user_overridden_properties
2080 .get(m.old_node_id.index())
2081 .filter(|v| !v.is_empty())
2082 else {
2083 continue;
2084 };
2085 let new_idx = m.new_node_id.index();
2086 if new_idx >= node_count {
2087 continue;
2088 }
2089 let old_vec = old_vec.clone();
2090 let cache = self.get_css_property_cache_mut();
2091 if cache.user_overridden_properties.len() < node_count {
2092 cache
2093 .user_overridden_properties
2094 .resize(node_count, Vec::new());
2095 }
2096 cache.user_overridden_properties[new_idx] = old_vec;
2097 migrated_any = true;
2098 }
2099 if migrated_any {
2100 self.recompute_inheritance_and_compact_cache();
2101 self.get_css_property_cache_mut()
2102 .invalidate_resolved_font_sizes();
2103 }
2104 }
2105
2106 #[must_use] pub fn reconstruct_dom_subtree(&self, root: Option<NodeId>) -> Dom {
2125 use crate::dom::NodeData;
2126
2127 let hierarchy = self.node_hierarchy.as_container();
2128 let node_data = self.node_data.as_container();
2129 let root_id = root.unwrap_or(NodeId::ZERO);
2130
2131 let make_dom = |id: NodeId| -> Dom {
2132 Dom {
2133 root: node_data
2134 .get(id)
2135 .cloned()
2136 .unwrap_or_else(NodeData::create_div),
2137 children: Vec::new().into(),
2138 css: Vec::new().into(),
2139 estimated_total_children: 0,
2140 }
2141 };
2142
2143 let mut result_stack: Vec<Dom> = vec![make_dom(root_id)];
2148 let mut visit_stack: Vec<(NodeId, Option<NodeId>)> = vec![(
2149 root_id,
2150 hierarchy
2151 .get(root_id)
2152 .and_then(|n| n.first_child_id(root_id)),
2153 )];
2154
2155 while let Some((node, next_child)) = visit_stack.pop() {
2156 if let Some(child) = next_child {
2157 let sibling = hierarchy
2160 .get(child)
2161 .and_then(NodeHierarchyItem::next_sibling_id);
2162 visit_stack.push((node, sibling));
2163 result_stack.push(make_dom(child));
2164 visit_stack.push((
2165 child,
2166 hierarchy.get(child).and_then(|c| c.first_child_id(child)),
2167 ));
2168 } else {
2169 let Some(finished) = result_stack.pop() else { break };
2170 if let Some(parent) = result_stack.last_mut() {
2171 parent.add_child(finished);
2172 } else {
2173 let mut finished = finished;
2174 let author_css =
2175 self.get_css_property_cache().retained_author_css.clone();
2176 if !author_css.is_empty() {
2177 finished.css = vec![author_css].into();
2178 }
2179 return finished;
2180 }
2181 }
2182 }
2183
2184 Dom::create_div()
2186 }
2187
2188 #[must_use] pub fn get_html_string(&self, custom_head: &str, custom_body: &str, test_mode: bool) -> String {
2198 let css_property_cache = self.get_css_property_cache();
2199
2200 let mut output = String::new();
2201
2202 let mut should_print_close_tag_after_node: BTreeMap<NodeId, Vec<(NodeId, usize)>> = BTreeMap::new();
2204
2205 let should_print_close_tag_debug = self
2206 .non_leaf_nodes
2207 .iter()
2208 .filter_map(|p| {
2209 let parent_node_id = p.node_id.into_crate_internal()?;
2210 let mut total_last_child = None;
2211 recursive_get_last_child(
2212 parent_node_id,
2213 self.node_hierarchy.as_ref(),
2214 &mut total_last_child,
2215 );
2216 let total_last_child = total_last_child?;
2217 Some((parent_node_id, (total_last_child, p.depth)))
2218 })
2219 .collect::<BTreeMap<_, _>>();
2220
2221 for (parent_id, (last_child, parent_depth)) in should_print_close_tag_debug {
2222 should_print_close_tag_after_node
2223 .entry(last_child)
2224 .or_default()
2225 .push((parent_id, parent_depth));
2226 }
2227
2228 let mut all_node_depths = self
2229 .non_leaf_nodes
2230 .iter()
2231 .filter_map(|p| {
2232 let parent_node_id = p.node_id.into_crate_internal()?;
2233 Some((parent_node_id, p.depth))
2234 })
2235 .collect::<BTreeMap<_, _>>();
2236
2237 for (parent_node_id, parent_depth) in self
2238 .non_leaf_nodes
2239 .iter()
2240 .filter_map(|p| Some((p.node_id.into_crate_internal()?, p.depth)))
2241 {
2242 for child_id in parent_node_id.az_children(&self.node_hierarchy.as_container()) {
2243 all_node_depths.insert(child_id, parent_depth + 1);
2244 }
2245 }
2246
2247 for node_id in self.node_hierarchy.as_container().linear_iter() {
2248 let depth = all_node_depths.get(&node_id).copied().unwrap_or(0);
2252
2253 let node_data = &self.node_data.as_container()[node_id];
2254 let node_state = &self.styled_nodes.as_container()[node_id].styled_node_state;
2255 let tabs = String::from(" ").repeat(depth);
2256
2257 output.push_str("\r\n");
2258 output.push_str(&tabs);
2259 output.push_str(&node_data.debug_print_start(css_property_cache, &node_id, node_state));
2260
2261 if let Some(content) = node_data.get_node_type().format().as_ref() {
2262 output.push_str(content);
2263 }
2264
2265 let node_has_children = self.node_hierarchy.as_container()[node_id]
2266 .first_child_id(node_id)
2267 .is_some();
2268 if !node_has_children {
2269 let node_data = &self.node_data.as_container()[node_id];
2270 output.push_str(&node_data.debug_print_end());
2271 }
2272
2273 if let Some(close_tag_vec) = should_print_close_tag_after_node.get(&node_id) {
2274 let mut close_tag_vec = close_tag_vec.clone();
2275 close_tag_vec.sort_by(|a, b| b.1.cmp(&a.1)); for (close_tag_parent_id, close_tag_depth) in close_tag_vec {
2277 let node_data = &self.node_data.as_container()[close_tag_parent_id];
2278 let tabs = String::from(" ").repeat(close_tag_depth);
2279 output.push_str("\r\n");
2280 output.push_str(&tabs);
2281 output.push_str(&node_data.debug_print_end());
2282 }
2283 }
2284 }
2285
2286 if test_mode {
2287 output
2288 } else {
2289 format!(
2290 "
2291 <html>
2292 <head>
2293 <style>* {{ margin:0px; padding:0px; }}</style>
2294 {custom_head}
2295 </head>
2296 {output}
2297 {custom_body}
2298 </html>
2299 "
2300 )
2301 }
2302 }
2303
2304 #[must_use] pub fn get_rects_in_rendering_order(&self) -> ContentGroup {
2306 Self::determine_rendering_order(
2307 self.non_leaf_nodes.as_ref(),
2308 &self.node_hierarchy.as_container(),
2309 &self.styled_nodes.as_container(),
2310 &self.node_data.as_container(),
2311 self.get_css_property_cache(),
2312 )
2313 }
2314
2315 fn determine_rendering_order(
2318 non_leaf_nodes: &[ParentWithNodeDepth],
2319 node_hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2320 styled_nodes: &NodeDataContainerRef<'_, StyledNode>,
2321 node_data_container: &NodeDataContainerRef<'_, NodeData>,
2322 css_property_cache: &CssPropertyCache,
2323 ) -> ContentGroup {
2324 let children_sorted = non_leaf_nodes
2325 .iter()
2326 .filter_map(|parent| {
2327 Some((
2328 parent.node_id,
2329 sort_children_by_position(
2330 parent.node_id.into_crate_internal()?,
2331 node_hierarchy,
2332 styled_nodes,
2333 node_data_container,
2334 css_property_cache,
2335 ),
2336 ))
2337 })
2338 .collect::<Vec<_>>();
2339
2340 let children_sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> =
2341 children_sorted.into_iter().collect();
2342
2343 let mut root_content_group = ContentGroup {
2344 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
2345 children: Vec::new().into(),
2346 };
2347
2348 fill_content_group_children(&mut root_content_group, &children_sorted);
2349
2350 root_content_group
2351 }
2352
2353 #[must_use] pub fn swap_with_default(&mut self) -> Self {
2355 let mut new = Self::default();
2356 core::mem::swap(self, &mut new);
2357 new
2358 }
2359
2360}
2361
2362#[derive(Debug, PartialEq, PartialOrd, Eq)]
2364pub struct CompactDom {
2365 pub node_hierarchy: NodeHierarchy,
2367 pub node_data: NodeDataContainer<NodeData>,
2369 pub root: NodeId,
2371}
2372
2373impl CompactDom {
2374 #[inline]
2376 #[must_use] pub fn len(&self) -> usize {
2377 self.node_hierarchy.as_ref().len()
2378 }
2379
2380 #[inline]
2382 #[must_use] pub fn is_empty(&self) -> bool {
2383 self.node_hierarchy.as_ref().is_empty()
2384 }
2385}
2386
2387impl From<Dom> for CompactDom {
2388 fn from(dom: Dom) -> Self {
2389 convert_dom_into_compact_dom(dom)
2390 }
2391}
2392
2393#[must_use] pub fn convert_dom_into_compact_dom(mut dom: Dom) -> CompactDom {
2395 fn convert_dom_into_compact_dom_internal(
2397 dom: &mut Dom,
2398 node_hierarchy: &mut [Node],
2399 node_data: &mut Vec<NodeData>,
2400 parent_node_id: NodeId,
2401 node: Node,
2402 cur_node_id: &mut usize,
2403 ) {
2404 node_hierarchy[parent_node_id.index()] = node;
2415
2416 let copy = dom.root.copy_special_moving_complex();
2428
2429 node_data[parent_node_id.index()] = copy;
2430
2431 *cur_node_id += 1;
2432
2433 let mut previous_sibling_id = None;
2434 let children_len = dom.children.len();
2435 for (child_index, child_dom) in dom.children.as_mut().iter_mut().enumerate() {
2436 let child_node_id = NodeId::new(*cur_node_id);
2437 let is_last_child = (child_index + 1) == children_len;
2438 let child_dom_is_empty = child_dom.children.is_empty();
2439 let child_node = Node {
2440 parent: Some(parent_node_id),
2441 previous_sibling: previous_sibling_id,
2442 next_sibling: if is_last_child {
2443 None
2444 } else {
2445 Some(child_node_id + child_dom.estimated_total_children + 1)
2446 },
2447 last_child: if child_dom_is_empty {
2448 None
2449 } else {
2450 Some(child_node_id + child_dom.estimated_total_children)
2451 },
2452 };
2453 previous_sibling_id = Some(child_node_id);
2454 convert_dom_into_compact_dom_internal(
2456 child_dom,
2457 node_hierarchy,
2458 node_data,
2459 child_node_id,
2460 child_node,
2461 cur_node_id,
2462 );
2463 }
2464
2465 node_hierarchy[parent_node_id.index()].last_child = previous_sibling_id;
2474 }
2475
2476 let sum_nodes = dom.fixup_children_estimated();
2478
2479 let mut node_hierarchy = vec![Node::ROOT; sum_nodes + 1];
2480 let mut node_data = vec![NodeData::create_div(); sum_nodes + 1];
2481 let mut cur_node_id = 0;
2482
2483 let root_node_id = NodeId::ZERO;
2484 let root_node = Node {
2485 parent: None,
2486 previous_sibling: None,
2487 next_sibling: None,
2488 last_child: if dom.children.is_empty() {
2489 None
2490 } else {
2491 Some(root_node_id + dom.estimated_total_children)
2492 },
2493 };
2494
2495 convert_dom_into_compact_dom_internal(
2496 &mut dom,
2497 &mut node_hierarchy,
2498 &mut node_data,
2499 root_node_id,
2500 root_node,
2501 &mut cur_node_id,
2502 );
2503
2504 CompactDom {
2505 node_hierarchy: NodeHierarchy {
2506 internal: node_hierarchy,
2507 },
2508 node_data: NodeDataContainer {
2509 internal: node_data,
2510 },
2511 root: root_node_id,
2512 }
2513}
2514
2515fn scope_inline_css(dom: &mut Dom, next_id: &mut usize) {
2523 let start = *next_id;
2524 let end = start + dom.estimated_total_children;
2525 for css in dom.css.as_mut().iter_mut() {
2526 for rule in css.rules.as_mut().iter_mut() {
2527 let node_only = rule.priority >= azul_css::css::rule_priority::INLINE;
2534 rule.path.push_front_scope_for(start, end, node_only);
2535 }
2536 }
2537 *next_id += 1;
2538 for child in dom.children.as_mut().iter_mut() {
2539 scope_inline_css(child, next_id);
2540 }
2541}
2542
2543fn collect_css_from_dom(dom: &Dom, out: &mut Vec<Css>) {
2547 for child in &dom.children {
2549 collect_css_from_dom(child, out);
2550 }
2551 for css in &dom.css {
2553 out.push(css.clone());
2554 }
2555}
2556
2557fn strip_css_from_dom(dom: &mut Dom) {
2560 dom.css = Vec::new().into();
2561 for child in dom.children.as_mut().iter_mut() {
2562 strip_css_from_dom(child);
2563 }
2564}
2565
2566fn fill_content_group_children(
2567 group: &mut ContentGroup,
2568 children_sorted: &BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>>,
2569) {
2570 if let Some(c) = children_sorted.get(&group.root) {
2571 group.children = c
2573 .iter()
2574 .map(|child| ContentGroup {
2575 root: *child,
2576 children: Vec::new().into(),
2577 })
2578 .collect::<Vec<ContentGroup>>()
2579 .into();
2580
2581 for c in group.children.as_mut() {
2582 fill_content_group_children(c, children_sorted);
2583 }
2584 }
2585}
2586
2587fn sort_children_by_position(
2588 parent: NodeId,
2589 node_hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2590 rectangles: &NodeDataContainerRef<'_, StyledNode>,
2591 node_data_container: &NodeDataContainerRef<'_, NodeData>,
2592 css_property_cache: &CssPropertyCache,
2593) -> Vec<NodeHierarchyItemId> {
2594 use azul_css::props::layout::LayoutPosition::Absolute;
2595
2596 let children_positions = parent
2597 .az_children(node_hierarchy)
2598 .map(|nid| {
2599 let position = css_property_cache
2600 .get_position(
2601 &node_data_container[nid],
2602 &nid,
2603 &rectangles[nid].styled_node_state,
2604 )
2605 .and_then(|p| (*p).get_property_or_default())
2606 .unwrap_or_default();
2607 let id = NodeHierarchyItemId::from_crate_internal(Some(nid));
2608 (id, position)
2609 })
2610 .collect::<Vec<_>>();
2611
2612 let mut not_absolute_children = children_positions
2613 .iter()
2614 .filter_map(|(node_id, position)| {
2615 if *position == Absolute {
2616 None
2617 } else {
2618 Some(*node_id)
2619 }
2620 })
2621 .collect::<Vec<_>>();
2622
2623 let mut absolute_children = children_positions
2624 .iter()
2625 .filter_map(|(node_id, position)| {
2626 if *position == Absolute {
2627 Some(*node_id)
2628 } else {
2629 None
2630 }
2631 })
2632 .collect::<Vec<_>>();
2633
2634 not_absolute_children.append(&mut absolute_children);
2636 not_absolute_children
2637}
2638
2639fn recursive_get_last_child(
2642 node_id: NodeId,
2643 node_hierarchy: &[NodeHierarchyItem],
2644 target: &mut Option<NodeId>,
2645) {
2646 match node_hierarchy[node_id.index()].last_child_id() {
2647 None => (),
2648 Some(s) => {
2649 *target = Some(s);
2650 recursive_get_last_child(s, node_hierarchy, target);
2651 }
2652 }
2653}
2654
2655#[must_use] pub fn is_before_in_document_order(
2669 hierarchy: &NodeHierarchyItemVec,
2670 node_a: NodeId,
2671 node_b: NodeId,
2672) -> bool {
2673 if node_a == node_b {
2674 return false;
2675 }
2676
2677 let hierarchy = hierarchy.as_container();
2678
2679 let path_a = get_path_to_root(&hierarchy, node_a);
2681 let path_b = get_path_to_root(&hierarchy, node_b);
2682
2683 let min_len = path_a.len().min(path_b.len());
2685
2686 for i in 0..min_len {
2687 if path_a[i] != path_b[i] {
2688 let child_towards_a = path_a[i];
2690 let child_towards_b = path_b[i];
2691
2692 return child_towards_a.index() < child_towards_b.index();
2695 }
2696 }
2697
2698 path_a.len() < path_b.len()
2700}
2701
2702fn get_path_to_root(
2704 hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2705 node: NodeId,
2706) -> Vec<NodeId> {
2707 let mut path = Vec::new();
2708 let mut current = Some(node);
2709
2710 while let Some(node_id) = current {
2711 path.push(node_id);
2712 current = hierarchy.get(node_id).and_then(NodeHierarchyItem::parent_id);
2713 }
2714
2715 path.reverse();
2717 path
2718}
2719
2720#[must_use] pub fn collect_nodes_in_document_order(
2733 hierarchy: &NodeHierarchyItemVec,
2734 start_node: NodeId,
2735 end_node: NodeId,
2736) -> Vec<NodeId> {
2737 if start_node == end_node {
2738 return vec![start_node];
2739 }
2740
2741 let hierarchy_container = hierarchy.as_container();
2742 let hierarchy_slice = hierarchy.as_ref();
2743
2744 let mut result = Vec::new();
2745 let mut in_range = false;
2746
2747 let mut stack: Vec<NodeId> = vec![NodeId::ZERO]; while let Some(current) = stack.pop() {
2752 if current == start_node {
2754 in_range = true;
2755 }
2756
2757 if in_range {
2759 result.push(current);
2760 }
2761
2762 if current == end_node {
2764 break;
2765 }
2766
2767 if let Some(item) = hierarchy_container.get(current) {
2770 if let Some(first_child) = item.first_child_id(current) {
2772 let mut children = Vec::new();
2774 let mut child = Some(first_child);
2775 while let Some(child_id) = child {
2776 children.push(child_id);
2777 child = hierarchy_container.get(child_id).and_then(NodeHierarchyItem::next_sibling_id);
2778 }
2779 for child_id in children.into_iter().rev() {
2781 stack.push(child_id);
2782 }
2783 }
2784 }
2785 }
2786
2787 result
2788}
2789
2790#[must_use] pub fn is_layout_equivalent(old: &StyledDom, new: &StyledDom) -> bool {
2804 use crate::dom::NodeType;
2805 use crate::resources::DecodedImage;
2806
2807 let old_nodes = old.node_data.as_ref();
2809 let new_nodes = new.node_data.as_ref();
2810 if old_nodes.len() != new_nodes.len() {
2811 return false;
2812 }
2813
2814 let old_hier = old.node_hierarchy.as_ref();
2816 let new_hier = new.node_hierarchy.as_ref();
2817 if old_hier.len() != new_hier.len() {
2818 return false;
2819 }
2820 if old_hier != new_hier {
2821 return false;
2822 }
2823
2824 for (old_node, new_node) in old_nodes.iter().zip(new_nodes.iter()) {
2826
2827 if core::mem::discriminant(&old_node.node_type)
2829 != core::mem::discriminant(&new_node.node_type)
2830 {
2831 return false;
2832 }
2833
2834 match (&old_node.node_type, &new_node.node_type) {
2836 (NodeType::Image(old_img), NodeType::Image(new_img)) => {
2837 match (old_img.get_data(), new_img.get_data()) {
2838 (DecodedImage::Callback(old_cb), DecodedImage::Callback(new_cb)) => {
2839 if old_cb.callback.cb != new_cb.callback.cb {
2841 return false;
2842 }
2843 if old_cb.refany.get_type_id() != new_cb.refany.get_type_id() {
2845 return false;
2846 }
2847 }
2848 _ => {
2849 if old_img != new_img {
2851 return false;
2852 }
2853 }
2854 }
2855 }
2856 _ => {
2857 if old_node.node_type != new_node.node_type {
2858 return false;
2859 }
2860 }
2861 }
2862
2863 {
2865 use crate::dom::AttributeType;
2866 let old_ids_classes: Vec<_> = old_node.attributes().as_ref().iter()
2867 .filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
2868 .collect();
2869 let new_ids_classes: Vec<_> = new_node.attributes().as_ref().iter()
2870 .filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
2871 .collect();
2872 if old_ids_classes != new_ids_classes {
2873 return false;
2874 }
2875 }
2876
2877 if old_node.style != new_node.style {
2879 return false;
2880 }
2881
2882 let old_cbs = old_node.callbacks.as_ref();
2885 let new_cbs = new_node.callbacks.as_ref();
2886 if old_cbs.len() != new_cbs.len() {
2887 return false;
2888 }
2889 for (old_cb, new_cb) in old_cbs.iter().zip(new_cbs.iter()) {
2890 if old_cb.event != new_cb.event {
2891 return false;
2892 }
2893 }
2894
2895 if old_node.attributes().as_ref() != new_node.attributes().as_ref() {
2897 return false;
2898 }
2899 }
2900
2901 let old_styled = old.styled_nodes.as_ref();
2903 let new_styled = new.styled_nodes.as_ref();
2904 if old_styled.len() != new_styled.len() {
2905 return false;
2906 }
2907 if old_styled != new_styled {
2908 return false;
2909 }
2910
2911 true
2912}
2913
2914#[cfg(test)]
2915mod audit_tests {
2916 use super::*;
2917 use azul_css::props::basic::StyleFontFamily;
2918
2919 fn fam(name: &str) -> StyleFontFamily {
2920 StyleFontFamily::System(name.to_string().into())
2921 }
2922
2923 #[test]
2924 fn style_font_families_hash_is_length_sensitive() {
2925 let a = StyleFontFamiliesHash::new(&[fam("Arial")]);
2928 let a2 = StyleFontFamiliesHash::new(&[fam("Arial")]);
2929 assert_eq!(a, a2, "hash must be deterministic");
2930
2931 let two = StyleFontFamiliesHash::new(&[fam("Arial"), fam("Helvetica")]);
2932 assert_ne!(a, two, "different-length family lists must not collide");
2933
2934 let empty = StyleFontFamiliesHash::new(&[]);
2935 assert_ne!(empty, a);
2936 assert_ne!(empty, two);
2937
2938 let rev = StyleFontFamiliesHash::new(&[fam("Helvetica"), fam("Arial")]);
2940 assert_ne!(two, rev);
2941 }
2942}
2943
2944#[cfg(test)]
2945#[allow(clippy::too_many_lines)]
2946mod autotest_generated {
2947 use azul_css::{
2948 dynamic_selector::PseudoStateFlags,
2949 props::basic::StyleFontFamily,
2950 };
2951
2952 use super::*;
2953
2954 const fn raw_item(parent: usize, prev: usize, next: usize, last: usize) -> NodeHierarchyItem {
2961 NodeHierarchyItem {
2962 parent,
2963 previous_sibling: prev,
2964 next_sibling: next,
2965 last_child: last,
2966 }
2967 }
2968
2969 fn flat_body(n: usize) -> StyledDom {
2972 let children: Vec<Dom> = (0..n).map(|_| Dom::create_div()).collect();
2973 let mut dom = Dom::create_body().with_children(children.into());
2974 StyledDom::create(&mut dom, Css::empty())
2975 }
2976
2977 fn nested_body() -> StyledDom {
2979 let mut dom = Dom::create_body().with_children(
2980 vec![Dom::create_div().with_children(vec![Dom::create_div()].into())].into(),
2981 );
2982 StyledDom::create(&mut dom, Css::empty())
2983 }
2984
2985 fn parse_css(s: &str) -> Css {
2986 azul_css::parser2::new_from_str(s).0
2987 }
2988
2989 fn family(name: &str) -> StyleFontFamily {
2990 StyleFontFamily::System(name.to_string().into())
2991 }
2992
2993 const fn pseudo_flags(all: bool) -> PseudoStateFlags {
2994 PseudoStateFlags {
2995 hover: all,
2996 active: all,
2997 focused: all,
2998 disabled: all,
2999 checked: all,
3000 focus_within: all,
3001 visited: all,
3002 backdrop: all,
3003 dragging: all,
3004 drag_over: all,
3005 }
3006 }
3007
3008 fn empty_menu() -> Menu {
3009 let items: Vec<crate::menu::MenuItem> = Vec::new();
3010 Menu::create(items.into())
3011 }
3012
3013 #[test]
3018 fn restyle_result_default_reports_no_changes() {
3019 let r = RestyleResult::default();
3020 assert!(!r.has_changes());
3021 assert!(!r.needs_layout);
3022 assert!(!r.needs_display_list);
3023 assert!(!r.gpu_only_changes);
3024 assert_eq!(r.max_relayout_scope, RelayoutScope::None);
3025 }
3026
3027 #[test]
3028 fn restyle_result_has_changes_keys_off_node_map_not_property_count() {
3029 let mut r = RestyleResult::default();
3032 r.changed_nodes.insert(NodeId::ZERO, Vec::new());
3033 assert!(r.has_changes());
3034
3035 r.changed_nodes.clear();
3036 assert!(!r.has_changes());
3037 }
3038
3039 #[test]
3040 fn restyle_result_merge_ors_layout_flags_and_ands_gpu_only() {
3041 let mut a = RestyleResult {
3042 needs_layout: false,
3043 needs_display_list: false,
3044 gpu_only_changes: true,
3045 ..RestyleResult::default()
3046 };
3047 let b = RestyleResult {
3048 needs_layout: true,
3049 needs_display_list: true,
3050 gpu_only_changes: true,
3051 ..RestyleResult::default()
3052 };
3053 a.merge(b);
3054 assert!(a.needs_layout, "needs_layout is OR-ed");
3055 assert!(a.needs_display_list, "needs_display_list is OR-ed");
3056 assert!(a.gpu_only_changes, "true && true stays true");
3057
3058 let mut c = RestyleResult {
3060 gpu_only_changes: true,
3061 ..RestyleResult::default()
3062 };
3063 c.merge(RestyleResult {
3064 gpu_only_changes: false,
3065 ..RestyleResult::default()
3066 });
3067 assert!(!c.gpu_only_changes, "gpu_only_changes is AND-ed");
3068 }
3069
3070 #[test]
3071 fn restyle_result_merge_keeps_the_most_expensive_scope() {
3072 let mut low = RestyleResult {
3073 max_relayout_scope: RelayoutScope::None,
3074 ..RestyleResult::default()
3075 };
3076 low.merge(RestyleResult {
3077 max_relayout_scope: RelayoutScope::Full,
3078 ..RestyleResult::default()
3079 });
3080 assert_eq!(low.max_relayout_scope, RelayoutScope::Full);
3081
3082 let mut high = RestyleResult {
3084 max_relayout_scope: RelayoutScope::Full,
3085 ..RestyleResult::default()
3086 };
3087 high.merge(RestyleResult {
3088 max_relayout_scope: RelayoutScope::IfcOnly,
3089 ..RestyleResult::default()
3090 });
3091 assert_eq!(high.max_relayout_scope, RelayoutScope::Full);
3092 }
3093
3094 #[test]
3095 fn restyle_result_merge_of_default_is_not_the_identity_for_gpu_only() {
3096 let mut a = RestyleResult {
3100 gpu_only_changes: true,
3101 ..RestyleResult::default()
3102 };
3103 a.merge(RestyleResult::default());
3104 assert!(!a.gpu_only_changes);
3105 assert!(!a.has_changes());
3106 }
3107
3108 #[test]
3109 fn restyle_result_merge_concatenates_changes_for_the_same_node() {
3110 let prop = |t| ChangedCssProperty {
3111 previous_state: StyledNodeState::new(),
3112 previous_prop: CssProperty::auto(t),
3113 current_state: StyledNodeState::new(),
3114 current_prop: CssProperty::initial(t),
3115 };
3116
3117 let mut a = RestyleResult::default();
3118 a.changed_nodes
3119 .insert(NodeId::ZERO, vec![prop(CssPropertyType::Width)]);
3120
3121 let mut b = RestyleResult::default();
3122 b.changed_nodes
3123 .insert(NodeId::ZERO, vec![prop(CssPropertyType::Height)]);
3124 b.changed_nodes
3125 .insert(NodeId::new(1), vec![prop(CssPropertyType::Opacity)]);
3126
3127 a.merge(b);
3128
3129 assert_eq!(a.changed_nodes.len(), 2);
3130 assert_eq!(
3131 a.changed_nodes[&NodeId::ZERO].len(),
3132 2,
3133 "changes for the same node are appended, not replaced"
3134 );
3135 assert_eq!(a.changed_nodes[&NodeId::new(1)].len(), 1);
3136 assert!(a.has_changes());
3137 }
3138
3139 #[test]
3144 fn styled_node_state_new_is_all_false_and_normal() {
3145 let s = StyledNodeState::new();
3146 assert!(s.is_normal());
3147 assert!(!s.hover);
3148 assert!(!s.active);
3149 assert!(!s.focused);
3150 assert!(!s.disabled);
3151 assert!(!s.checked);
3152 assert!(!s.focus_within);
3153 assert!(!s.visited);
3154 assert!(!s.backdrop);
3155 assert!(!s.dragging);
3156 assert!(!s.drag_over);
3157 assert_eq!(s, StyledNodeState::default());
3158 }
3159
3160 #[test]
3161 fn styled_node_state_has_state_zero_is_always_true() {
3162 assert!(StyledNodeState::new().has_state(0));
3164 assert!(StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true)).has_state(0));
3165 }
3166
3167 #[test]
3168 fn styled_node_state_has_state_maps_every_index_exactly_once() {
3169 let setters: [(u8, fn(&mut StyledNodeState)); 10] = [
3171 (1, |s| s.hover = true),
3172 (2, |s| s.active = true),
3173 (3, |s| s.focused = true),
3174 (4, |s| s.disabled = true),
3175 (5, |s| s.checked = true),
3176 (6, |s| s.focus_within = true),
3177 (7, |s| s.visited = true),
3178 (8, |s| s.backdrop = true),
3179 (9, |s| s.dragging = true),
3180 (10, |s| s.drag_over = true),
3181 ];
3182
3183 for (expected_idx, set) in setters {
3184 let mut s = StyledNodeState::new();
3185 set(&mut s);
3186 assert!(!s.is_normal(), "state {expected_idx} must not be 'normal'");
3187 for idx in 1..=10u8 {
3188 assert_eq!(
3189 s.has_state(idx),
3190 idx == expected_idx,
3191 "state index {idx} misreported for setter {expected_idx}"
3192 );
3193 }
3194 }
3195 }
3196
3197 #[test]
3198 fn styled_node_state_has_state_is_false_for_every_out_of_range_u8() {
3199 let all_on = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true));
3200 for idx in 11..=u8::MAX {
3201 assert!(!StyledNodeState::new().has_state(idx));
3202 assert!(
3203 !all_on.has_state(idx),
3204 "unknown state index {idx} must be inactive even when every flag is set"
3205 );
3206 }
3207 }
3208
3209 #[test]
3210 fn styled_node_state_from_pseudo_state_flags_roundtrips_every_field() {
3211 let all_on = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true));
3212 assert!(!all_on.is_normal());
3213 for idx in 0..=10u8 {
3214 assert!(all_on.has_state(idx), "state {idx} should be active");
3215 }
3216
3217 let all_off = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(false));
3218 assert!(all_off.is_normal());
3219 assert_eq!(all_off, StyledNodeState::new());
3220 }
3221
3222 #[test]
3223 fn styled_node_state_debug_lists_active_states_and_normal_when_empty() {
3224 assert_eq!(format!("{:?}", StyledNodeState::new()), "[\"normal\"]");
3225
3226 let mut s = StyledNodeState::new();
3227 s.hover = true;
3228 s.drag_over = true;
3229 let dbg = format!("{s:?}");
3230 assert!(dbg.contains("hover"), "{dbg}");
3231 assert!(dbg.contains("drag_over"), "{dbg}");
3232 assert!(!dbg.contains("normal"), "{dbg}");
3233 }
3234
3235 #[test]
3240 fn styled_node_vec_empty_container_is_empty_and_get_returns_none() {
3241 let v: StyledNodeVec = Vec::new().into();
3242 let c = v.as_container();
3243 assert_eq!(c.len(), 0);
3244 assert!(c.is_empty());
3245 assert!(c.get(NodeId::ZERO).is_none());
3246 assert!(c.get(NodeId::new(usize::MAX)).is_none());
3247 }
3248
3249 #[test]
3250 fn styled_node_vec_container_mut_writes_are_visible_through_container() {
3251 let mut v: StyledNodeVec = vec![StyledNode::default(), StyledNode::default()].into();
3252 {
3253 let mut c = v.as_container_mut();
3254 c[NodeId::new(1)].styled_node_state.hover = true;
3255 }
3256 let c = v.as_container();
3257 assert_eq!(c.len(), 2);
3258 assert!(!c[NodeId::ZERO].styled_node_state.hover);
3259 assert!(c[NodeId::new(1)].styled_node_state.hover);
3260 assert!(c.get(NodeId::new(2)).is_none());
3261 }
3262
3263 #[test]
3268 fn style_font_family_hash_is_deterministic_and_input_sensitive() {
3269 assert_eq!(
3270 StyleFontFamilyHash::new(&family("Arial")),
3271 StyleFontFamilyHash::new(&family("Arial"))
3272 );
3273 assert_ne!(
3274 StyleFontFamilyHash::new(&family("Arial")),
3275 StyleFontFamilyHash::new(&family("Ariaĺ"))
3276 );
3277 assert_ne!(
3279 StyleFontFamilyHash::new(&StyleFontFamily::System("x".to_string().into())),
3280 StyleFontFamilyHash::new(&StyleFontFamily::File("x".to_string().into()))
3281 );
3282 }
3283
3284 #[test]
3285 fn style_font_family_hash_handles_empty_unicode_and_huge_names() {
3286 let empty = family("");
3287 let unicode = family("🦀 ノート ﷽ عربى");
3288 let huge = family(&"A".repeat(100_000));
3289
3290 assert_eq!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&empty));
3292 assert_eq!(
3293 StyleFontFamilyHash::new(&unicode),
3294 StyleFontFamilyHash::new(&unicode)
3295 );
3296 assert_eq!(StyleFontFamilyHash::new(&huge), StyleFontFamilyHash::new(&huge));
3297 assert_ne!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&unicode));
3298 assert_ne!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&huge));
3299 }
3300
3301 #[test]
3302 fn style_font_families_hash_empty_slice_is_stable_and_distinct() {
3303 let empty = StyleFontFamiliesHash::new(&[]);
3304 assert_eq!(empty, StyleFontFamiliesHash::new(&[]));
3305 assert_ne!(empty, StyleFontFamiliesHash::new(&[family("")]));
3306 }
3307
3308 #[test]
3309 fn style_font_families_hash_scales_to_large_lists_and_is_length_sensitive() {
3310 let big: Vec<StyleFontFamily> = (0..1000).map(|i| family(&format!("font-{i}"))).collect();
3311 let one_shorter = &big[..999];
3312
3313 assert_eq!(
3314 StyleFontFamiliesHash::new(&big),
3315 StyleFontFamiliesHash::new(&big),
3316 "hashing 1000 families must be deterministic"
3317 );
3318 assert_ne!(
3319 StyleFontFamiliesHash::new(&big),
3320 StyleFontFamiliesHash::new(one_shorter),
3321 "the length prefix must separate [0..1000) from [0..999)"
3322 );
3323 }
3324
3325 #[test]
3330 fn node_hierarchy_item_id_none_is_zero() {
3331 assert_eq!(NodeHierarchyItemId::NONE.into_raw(), 0);
3332 assert_eq!(NodeHierarchyItemId::NONE.into_crate_internal(), None);
3333 assert_eq!(NodeHierarchyItemId::from_crate_internal(None).into_raw(), 0);
3334 assert_eq!(NodeHierarchyItemId::from_raw(0).into_crate_internal(), None);
3335 assert_eq!(NodeHierarchyItemId::from_crate_internal(None), NodeHierarchyItemId::NONE);
3336 }
3337
3338 #[test]
3339 fn node_hierarchy_item_id_encode_decode_roundtrip_at_boundaries() {
3340 for idx in [0usize, 1, 2, 1023, usize::MAX / 2, usize::MAX - 1] {
3342 let id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(idx)));
3343 assert_eq!(id.into_raw(), idx + 1, "1-based encoding for {idx}");
3344 assert_eq!(
3345 id.into_crate_internal(),
3346 Some(NodeId::new(idx)),
3347 "decode(encode(x)) == x for {idx}"
3348 );
3349 }
3350 }
3351
3352 #[test]
3353 fn node_hierarchy_item_id_raw_roundtrip_is_identity_even_at_usize_max() {
3354 for raw in [0usize, 1, 2, 7, u32::MAX as usize, usize::MAX] {
3355 let decoded = NodeHierarchyItemId::from_raw(raw).into_crate_internal();
3356 let reencoded = NodeHierarchyItemId::from_crate_internal(decoded).into_raw();
3357 assert_eq!(reencoded, raw, "encode(decode(raw)) must be identity for {raw}");
3358 }
3359 }
3360
3361 #[test]
3362 fn node_hierarchy_item_id_from_raw_decodes_one_based() {
3363 assert_eq!(
3364 NodeHierarchyItemId::from_raw(1).into_crate_internal(),
3365 Some(NodeId::ZERO),
3366 "raw 1 is NodeId(0), NOT NodeId(1)"
3367 );
3368 assert_eq!(
3369 NodeHierarchyItemId::from_raw(usize::MAX).into_crate_internal(),
3370 Some(NodeId::new(usize::MAX - 1))
3371 );
3372 }
3373
3374 #[test]
3375 fn node_hierarchy_item_id_debug_and_display_agree() {
3376 let none = NodeHierarchyItemId::NONE;
3377 assert_eq!(format!("{none:?}"), "None");
3378 assert_eq!(format!("{none}"), format!("{none:?}"));
3379
3380 let some = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(5)));
3381 assert_eq!(format!("{some:?}"), "Some(NodeId(5))");
3382 assert_eq!(format!("{some}"), format!("{some:?}"));
3383
3384 let max = NodeHierarchyItemId::from_raw(usize::MAX);
3386 assert!(!format!("{max:?}").is_empty());
3387 }
3388
3389 #[test]
3390 fn node_hierarchy_item_id_ordering_follows_raw_value() {
3391 let a = NodeHierarchyItemId::from_raw(0);
3392 let b = NodeHierarchyItemId::from_raw(1);
3393 let c = NodeHierarchyItemId::from_raw(usize::MAX);
3394 assert!(a < b);
3395 assert!(b < c);
3396 assert_eq!(a, NodeHierarchyItemId::NONE);
3397 }
3398
3399 #[test]
3400 fn node_hierarchy_item_id_from_impls_match_the_explicit_ones() {
3401 let opt = Some(NodeId::new(41));
3402 let via_from: NodeHierarchyItemId = opt.into();
3403 assert_eq!(via_from, NodeHierarchyItemId::from_crate_internal(opt));
3404
3405 let back: Option<NodeId> = via_from.into();
3406 assert_eq!(back, opt);
3407
3408 let none: NodeHierarchyItemId = None.into();
3409 assert_eq!(none.into_raw(), 0);
3410 }
3411
3412 #[test]
3417 fn node_hierarchy_item_zeroed_has_no_links() {
3418 let z = NodeHierarchyItem::zeroed();
3419 assert_eq!(z.parent_id(), None);
3420 assert_eq!(z.previous_sibling_id(), None);
3421 assert_eq!(z.next_sibling_id(), None);
3422 assert_eq!(z.last_child_id(), None);
3423 assert_eq!(z.first_child_id(NodeId::ZERO), None);
3424 assert_eq!(z.first_child_id(NodeId::new(usize::MAX)), None);
3425 assert_eq!(z, NodeHierarchyItem::from(Node::ROOT));
3426 }
3427
3428 #[test]
3429 fn node_hierarchy_item_getters_decode_the_one_based_fields() {
3430 let item = raw_item(1, 2, 3, 4);
3431 assert_eq!(item.parent_id(), Some(NodeId::new(0)));
3432 assert_eq!(item.previous_sibling_id(), Some(NodeId::new(1)));
3433 assert_eq!(item.next_sibling_id(), Some(NodeId::new(2)));
3434 assert_eq!(item.last_child_id(), Some(NodeId::new(3)));
3435
3436 assert_eq!(item.first_child_id(NodeId::new(7)), Some(NodeId::new(8)));
3438 }
3439
3440 #[test]
3441 fn node_hierarchy_item_getters_at_usize_max_do_not_overflow() {
3442 let item = raw_item(usize::MAX, usize::MAX, usize::MAX, usize::MAX);
3443 assert_eq!(item.parent_id(), Some(NodeId::new(usize::MAX - 1)));
3444 assert_eq!(item.previous_sibling_id(), Some(NodeId::new(usize::MAX - 1)));
3445 assert_eq!(item.next_sibling_id(), Some(NodeId::new(usize::MAX - 1)));
3446 assert_eq!(item.last_child_id(), Some(NodeId::new(usize::MAX - 1)));
3447
3448 assert_eq!(
3451 item.first_child_id(NodeId::new(usize::MAX)),
3452 Some(NodeId::new(usize::MAX)),
3453 "first_child_id must saturate, never wrap to NodeId(0)"
3454 );
3455 }
3456
3457 #[test]
3458 fn node_hierarchy_item_from_node_preserves_every_link() {
3459 let node = Node {
3460 parent: Some(NodeId::new(3)),
3461 previous_sibling: None,
3462 next_sibling: Some(NodeId::new(9)),
3463 last_child: Some(NodeId::new(12)),
3464 };
3465 let item: NodeHierarchyItem = node.into();
3466 assert_eq!(item.parent_id(), node.parent);
3467 assert_eq!(item.previous_sibling_id(), node.previous_sibling);
3468 assert_eq!(item.next_sibling_id(), node.next_sibling);
3469 assert_eq!(item.last_child_id(), node.last_child);
3470 }
3471
3472 #[test]
3477 fn node_hierarchy_item_vec_containers_read_and_write() {
3478 let mut v: NodeHierarchyItemVec = vec![NodeHierarchyItem::zeroed(); 2].into();
3479 {
3480 let mut c = v.as_container_mut();
3481 c[NodeId::new(1)].parent = 1; }
3483 let c = v.as_container();
3484 assert_eq!(c.len(), 2);
3485 assert_eq!(c[NodeId::new(1)].parent_id(), Some(NodeId::ZERO));
3486 assert!(c.get(NodeId::new(2)).is_none());
3487
3488 let empty: NodeHierarchyItemVec = Vec::new().into();
3489 assert!(empty.as_container().is_empty());
3490 }
3491
3492 #[test]
3493 fn subtree_len_counts_descendants_of_a_real_tree() {
3494 let sd = nested_body();
3496 let h = sd.node_hierarchy.as_container();
3497 assert_eq!(h.len(), 3);
3498 assert_eq!(h.subtree_len(NodeId::ZERO), 2, "root has 2 descendants");
3499 assert_eq!(h.subtree_len(NodeId::new(1)), 1);
3500 assert_eq!(h.subtree_len(NodeId::new(2)), 0, "a leaf has no descendants");
3501 }
3502
3503 #[test]
3504 fn subtree_len_saturates_on_a_malformed_backwards_next_sibling() {
3505 let v: NodeHierarchyItemVec = vec![
3508 raw_item(0, 0, 0, 0),
3509 raw_item(0, 0, 0, 0),
3510 raw_item(0, 0, 1, 0),
3511 ]
3512 .into();
3513 let c = v.as_container();
3514 assert_eq!(c.subtree_len(NodeId::new(2)), 0);
3515
3516 let v2: NodeHierarchyItemVec = vec![raw_item(0, 0, 0, 0), raw_item(0, 0, 2, 0)].into();
3518 assert_eq!(v2.as_container().subtree_len(NodeId::new(1)), 0);
3519 }
3520
3521 #[test]
3526 fn memory_report_default_total_is_zero() {
3527 assert_eq!(StyledDomMemoryReport::default().total_bytes(), 0);
3528 }
3529
3530 #[test]
3531 fn memory_report_total_bytes_sums_every_field() {
3532 let r = StyledDomMemoryReport {
3533 node_count: 3,
3534 node_hierarchy_bytes: 1,
3535 node_data_bytes: 2,
3536 styled_nodes_bytes: 4,
3537 cascade_info_bytes: 8,
3538 tag_ids_bytes: 16,
3539 non_leaf_nodes_bytes: 32,
3540 callback_vecs_bytes: 64,
3541 ..StyledDomMemoryReport::default()
3542 };
3543 assert_eq!(r.total_bytes(), 127, "node_count must NOT be part of the sum");
3544
3545 let extreme = StyledDomMemoryReport {
3547 node_data_bytes: usize::MAX,
3548 ..StyledDomMemoryReport::default()
3549 };
3550 assert_eq!(extreme.total_bytes(), usize::MAX);
3551 }
3552
3553 #[test]
3554 fn memory_report_tracks_node_count_and_is_monotonic_in_dom_size() {
3555 let small = flat_body(1).memory_report();
3556 let large = flat_body(50).memory_report();
3557 assert_eq!(small.node_count, 2);
3558 assert_eq!(large.node_count, 51);
3559 assert!(large.total_bytes() > small.total_bytes());
3560 assert!(small.total_bytes() >= small.node_hierarchy_bytes + small.node_data_bytes);
3561
3562 let d = StyledDom::default().memory_report();
3564 assert_eq!(d.node_count, 1);
3565 assert!(d.total_bytes() > 0);
3566 }
3567
3568 #[test]
3573 fn default_styled_dom_is_a_single_rooted_body() {
3574 let sd = StyledDom::default();
3575 assert_eq!(sd.node_count(), 1);
3576 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3577 assert_eq!(sd.node_hierarchy.as_ref().len(), 1);
3578 assert_eq!(sd.styled_nodes.as_ref().len(), 1);
3579 assert_eq!(sd.cascade_info.as_ref().len(), 1);
3580 assert_eq!(sd.non_leaf_nodes.as_ref().len(), 1);
3581 assert_eq!(sd.non_leaf_nodes.as_ref()[0].depth, 0);
3582 assert!(sd.tag_ids_to_node_ids.as_ref().is_empty());
3583 assert!(sd.get_styled_node_state(&NodeId::ZERO).is_normal());
3584 }
3585
3586 #[test]
3592 fn with_css_on_a_text_node_applies_its_declarations() {
3593 use azul_css::props::basic::color::ColorU;
3594
3595 let cases: &[(&str, ColorU, isize)] = &[
3596 ("font-size: 38px; color: #565656;", ColorU { r: 0x56, g: 0x56, b: 0x56, a: 255 }, 38),
3597 ("font-size: 38px; color: #565656; flex-grow: 0;", ColorU { r: 0x56, g: 0x56, b: 0x56, a: 255 }, 38),
3598 ("font-size: 16px; color: #2b579a; margin-bottom: 12px;", ColorU { r: 0x2b, g: 0x57, b: 0x9a, a: 255 }, 16),
3599 ("font-size: 13px; color: white;", ColorU { r: 255, g: 255, b: 255, a: 255 }, 13),
3600 ];
3601
3602 for (css_str, want_color, want_px) in cases {
3603 let dom = crate::dom::Dom::create_body().with_child(
3604 crate::dom::Dom::create_div()
3605 .with_css("color: #444444; font-size: 10px;")
3606 .with_child(crate::dom::Dom::create_text("X").with_css(css_str)),
3607 );
3608 let styled = StyledDom::create_from_dom(dom);
3611 let cache = styled.get_css_property_cache();
3612 let n = styled.node_data.as_ref().len() - 1;
3613 let node_id = NodeId::new(n);
3614 let node_data = &styled.node_data.as_ref()[n];
3615 assert!(
3616 node_data.is_text_node(),
3617 "fixture: last node must be the text node"
3618 );
3619 let state = &styled.styled_nodes.as_ref()[n].styled_node_state;
3620
3621 let color = cache
3622 .get_text_color(node_data, &node_id, state)
3623 .and_then(|p| p.get_property().copied())
3624 .map(|c| c.inner);
3625 assert_eq!(
3626 color,
3627 Some(*want_color),
3628 "inline color lost on text node for {css_str:?}"
3629 );
3630 let size = cache
3631 .get_font_size(node_data, &node_id, state)
3632 .and_then(|p| p.get_property().copied())
3633 .map(|s| s.inner.to_pixels_internal(16.0, 16.0, 16.0) as isize);
3634 assert_eq!(
3635 size,
3636 Some(*want_px),
3637 "inline font-size lost on text node for {css_str:?}"
3638 );
3639 }
3640
3641 let dom = crate::dom::Dom::create_body().with_child(
3646 crate::dom::Dom::create_div()
3647 .with_css("color: #444444;")
3648 .with_child(crate::dom::Dom::create_text("X")),
3649 );
3650 let styled = StyledDom::create_from_dom(dom);
3651 let cache = styled.get_css_property_cache();
3652 let n = styled.node_data.as_ref().len() - 1;
3653 let node_id = NodeId::new(n);
3654 let node_data = &styled.node_data.as_ref()[n];
3655 let state = &styled.styled_nodes.as_ref()[n].styled_node_state;
3656 assert!(node_data.is_text_node());
3657 assert!(
3658 cache.css_props.get_slice(n).is_empty(),
3659 "an unstyled text node must have no OWN css_props"
3660 );
3661 let inherited = cache
3662 .get_text_color(node_data, &node_id, state)
3663 .and_then(|p| p.get_property().copied())
3664 .map(|c| c.inner);
3665 assert_eq!(
3666 inherited,
3667 Some(ColorU { r: 0x44, g: 0x44, b: 0x44, a: 255 }),
3668 "inheritance must still deliver the parent's color to the text node"
3669 );
3670 }
3671
3672 #[test]
3673 fn create_empties_the_source_dom() {
3674 let mut dom = Dom::create_body().with_children(vec![Dom::create_div(); 3].into());
3676 let sd = StyledDom::create(&mut dom, Css::empty());
3677 assert_eq!(sd.node_count(), 4);
3678 assert!(
3679 dom.children.as_ref().is_empty(),
3680 "the source Dom must be left empty (it is swapped out, not cloned)"
3681 );
3682 }
3683
3684 #[test]
3685 fn create_keeps_every_parallel_array_the_same_length() {
3686 for n in [0usize, 1, 3, 64] {
3687 let sd = flat_body(n);
3688 let count = sd.node_count();
3689 assert_eq!(count, n + 1);
3690 assert_eq!(sd.node_hierarchy.as_ref().len(), count);
3691 assert_eq!(sd.styled_nodes.as_ref().len(), count);
3692 assert_eq!(sd.cascade_info.as_ref().len(), count);
3693 }
3694 }
3695
3696 #[test]
3697 fn create_survives_malformed_truncated_and_unicode_css() {
3698 let cases: Vec<String> = vec![
3699 String::new(),
3700 "}}}{{{".to_string(),
3701 "div {".to_string(),
3702 "div { color: }".to_string(),
3703 "div { : red; }".to_string(),
3704 "@media".to_string(),
3705 "/* unterminated comment".to_string(),
3706 "div { width: 99999999999999999999999px; }".to_string(),
3707 "div { width: -0px; opacity: 1e400; }".to_string(),
3708 "div { width: NaNpx; height: infpx; }".to_string(),
3709 "* { color: #ZZZZZZ; }".to_string(),
3710 "日本語 { content: \"🦀\"; }".to_string(),
3711 ".\u{202e}rtl { color: red; }".to_string(),
3712 "a".repeat(10_000),
3713 "div { color: red; }".repeat(500),
3714 ];
3715
3716 for case in &cases {
3717 let css = parse_css(case);
3718 let mut dom = Dom::create_body().with_children(vec![Dom::create_div()].into());
3719 let sd = StyledDom::create(&mut dom, css);
3720 assert_eq!(
3721 sd.node_count(),
3722 2,
3723 "CSS must never change the node count; failing input: {case:?}"
3724 );
3725 }
3726 }
3727
3728 #[test]
3729 fn create_handles_deep_and_wide_doms() {
3730 let mut deep = Dom::create_div();
3732 for _ in 0..63 {
3733 deep = Dom::create_div().with_children(vec![deep].into());
3734 }
3735 let mut deep_body = Dom::create_body().with_children(vec![deep].into());
3736 let sd = StyledDom::create(&mut deep_body, Css::empty());
3737 assert_eq!(sd.node_count(), 65);
3738 assert_eq!(
3739 sd.non_leaf_nodes.as_ref().len(),
3740 64,
3741 "every node except the innermost leaf is a parent"
3742 );
3743
3744 let wide = flat_body(1000);
3746 assert_eq!(wide.node_count(), 1001);
3747 assert_eq!(wide.node_hierarchy.as_container().subtree_len(NodeId::ZERO), 1000);
3748 assert_eq!(wide.non_leaf_nodes.as_ref().len(), 1);
3749 }
3750
3751 #[test]
3752 fn create_from_dom_collects_scoped_css_without_changing_the_tree() {
3753 let dom = Dom::create_body().with_children(
3754 vec![
3755 Dom::create_div().with_css("color: red"),
3756 Dom::create_div().with_children(vec![Dom::create_div().with_css("width: 5px")].into()),
3757 ]
3758 .into(),
3759 );
3760 let sd = StyledDom::create_from_dom(dom);
3761 assert_eq!(sd.node_count(), 4);
3762 assert_eq!(sd.node_hierarchy.as_ref().len(), 4);
3763 assert!(sd.get_css_property_cache().compact_cache.is_some());
3764 }
3765
3766 #[test]
3767 fn create_from_dom_on_a_bare_leaf_produces_one_node() {
3768 let sd = StyledDom::create_from_dom(Dom::create_div());
3769 assert_eq!(sd.node_count(), 1);
3770 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3771 }
3772
3773 #[test]
3778 fn append_child_grows_the_node_count_by_the_child_dom_size() {
3779 let mut base = flat_body(2);
3780 base.append_child(flat_body(3));
3781 assert_eq!(base.node_count(), 3 + 4);
3782 assert_eq!(base.node_hierarchy.as_ref().len(), 7);
3783 assert_eq!(base.styled_nodes.as_ref().len(), 7);
3784 assert_eq!(base.cascade_info.as_ref().len(), 7);
3785 }
3786
3787 #[test]
3788 fn append_child_links_the_new_root_as_the_last_sibling() {
3789 let mut base = flat_body(2);
3791 base.append_child(StyledDom::default());
3792
3793 let h = base.node_hierarchy.as_container();
3794 let children: Vec<NodeId> = NodeId::ZERO.az_children(&h).collect();
3795 assert_eq!(
3796 children,
3797 vec![NodeId::new(1), NodeId::new(2), NodeId::new(3)],
3798 "the appended root must become the last direct child"
3799 );
3800 assert_eq!(h[NodeId::new(3)].parent_id(), Some(NodeId::ZERO));
3801 assert_eq!(h[NodeId::new(3)].previous_sibling_id(), Some(NodeId::new(2)));
3802 assert_eq!(h[NodeId::new(3)].next_sibling_id(), None);
3803 }
3804
3805 #[test]
3810 fn append_child_keeps_the_root_children_reachable_for_a_nested_dom() {
3811 let mut base = nested_body(); base.append_child(StyledDom::default());
3813 assert_eq!(base.node_count(), 4);
3814
3815 let h = base.node_hierarchy.as_container();
3816 let children: Vec<NodeId> = NodeId::ZERO.az_children(&h).collect();
3817 assert_eq!(
3818 children,
3819 vec![NodeId::new(1), NodeId::new(3)],
3820 "after append_child the root must have exactly its old child plus the appended root"
3821 );
3822 }
3823
3824 #[test]
3825 fn append_child_with_index_saturates_the_u32_cascade_index() {
3826 for (child_index, expected) in [
3827 (0usize, 0u32),
3828 (7, 7),
3829 (u32::MAX as usize, u32::MAX),
3830 (u32::MAX as usize + 1, u32::MAX),
3831 (usize::MAX, u32::MAX),
3832 ] {
3833 let mut base = flat_body(0); base.append_child_with_index(StyledDom::default(), child_index);
3835
3836 assert_eq!(
3838 base.cascade_info.as_ref()[1].index_in_parent,
3839 expected,
3840 "child_index {child_index} must saturate to {expected}, never wrap"
3841 );
3842 assert!(base.cascade_info.as_ref()[1].is_last_child);
3843 assert_eq!(base.node_count(), 2);
3844 }
3845 }
3846
3847 #[test]
3848 fn finalize_non_leaf_nodes_sorts_by_depth_and_is_idempotent() {
3849 let mut base = flat_body(1);
3850 base.append_child_with_index(flat_body(2), 1);
3851 base.append_child_with_index(flat_body(2), 2);
3852 base.finalize_non_leaf_nodes();
3853
3854 let depths: Vec<usize> = base.non_leaf_nodes.as_ref().iter().map(|p| p.depth).collect();
3855 let mut sorted = depths.clone();
3856 sorted.sort_unstable();
3857 assert_eq!(depths, sorted, "non_leaf_nodes must be depth-ordered");
3858
3859 base.finalize_non_leaf_nodes();
3860 let again: Vec<usize> = base.non_leaf_nodes.as_ref().iter().map(|p| p.depth).collect();
3861 assert_eq!(depths, again, "finalize must be idempotent");
3862 }
3863
3864 #[test]
3865 fn with_child_matches_append_child() {
3866 let mut appended = flat_body(2);
3867 appended.append_child(flat_body(1));
3868
3869 let built = flat_body(2).with_child(flat_body(1));
3870
3871 assert_eq!(built.node_count(), appended.node_count());
3872 assert_eq!(
3873 built.node_hierarchy.as_ref(),
3874 appended.node_hierarchy.as_ref()
3875 );
3876 }
3877
3878 #[test]
3879 fn swap_with_default_returns_the_old_dom_and_resets_self() {
3880 let mut sd = flat_body(3);
3881 let old = sd.swap_with_default();
3882 assert_eq!(old.node_count(), 4);
3883 assert_eq!(sd.node_count(), 1, "self must be left as the default StyledDom");
3884 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3885 }
3886
3887 #[test]
3892 fn context_menu_and_menu_bar_are_stored_on_the_root_node() {
3893 let mut sd = flat_body(1);
3894 assert!(sd.node_data.as_container()[NodeId::ZERO].get_context_menu().is_none());
3895
3896 sd.set_context_menu(empty_menu());
3897 sd.set_menu_bar(empty_menu());
3898
3899 let data = sd.node_data.as_container();
3900 assert!(data[NodeId::ZERO].get_context_menu().is_some());
3901 assert!(data[NodeId::ZERO].get_menu_bar().is_some());
3902
3903 assert!(data[NodeId::new(1)].get_context_menu().is_none());
3905 assert!(data[NodeId::new(1)].get_menu_bar().is_none());
3906 }
3907
3908 #[test]
3909 fn menu_builders_are_equivalent_to_the_setters_and_dont_touch_the_tree() {
3910 let sd = StyledDom::default()
3911 .with_context_menu(empty_menu())
3912 .with_menu_bar(empty_menu());
3913 assert_eq!(sd.node_count(), 1);
3914 let data = sd.node_data.as_container();
3915 assert!(data[NodeId::ZERO].get_context_menu().is_some());
3916 assert!(data[NodeId::ZERO].get_menu_bar().is_some());
3917 }
3918
3919 #[test]
3924 fn restyle_nodes_hover_sets_and_clears_the_state_flag() {
3925 let mut sd = flat_body(2);
3926 let _ = sd.restyle_nodes_hover(&[NodeId::new(1)], true);
3927 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3928 assert!(!sd.get_styled_node_state(&NodeId::new(2)).hover);
3929
3930 let _ = sd.restyle_nodes_hover(&[NodeId::new(1)], false);
3931 assert!(!sd.get_styled_node_state(&NodeId::new(1)).hover);
3932 assert!(sd.get_styled_node_state(&NodeId::new(1)).is_normal());
3933 }
3934
3935 #[test]
3936 fn restyle_nodes_active_and_focus_set_independent_flags() {
3937 let mut sd = flat_body(1);
3938 let _ = sd.restyle_nodes_active(&[NodeId::ZERO], true);
3939 let _ = sd.restyle_nodes_focus(&[NodeId::ZERO], true);
3940
3941 let state = sd.get_styled_node_state(&NodeId::ZERO);
3942 assert!(state.active);
3943 assert!(state.focused);
3944 assert!(!state.hover, "hover must be untouched");
3945 assert!(!state.is_normal());
3946 }
3947
3948 #[test]
3949 fn restyle_nodes_ignores_out_of_range_node_ids_instead_of_panicking() {
3950 let mut sd = flat_body(1); let changed = sd.restyle_nodes_hover(&[NodeId::new(2), NodeId::new(usize::MAX)], true);
3952 assert!(changed.is_empty());
3953 assert!(!sd.get_styled_node_state(&NodeId::ZERO).hover);
3954 assert!(!sd.get_styled_node_state(&NodeId::new(1)).hover);
3955
3956 let _ = sd.restyle_nodes_hover(&[NodeId::new(1), NodeId::new(999)], true);
3958 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3959 }
3960
3961 #[test]
3962 fn restyle_nodes_handles_empty_and_duplicated_input() {
3963 let mut sd = flat_body(1);
3964 assert!(sd.restyle_nodes_focus(&[], true).is_empty());
3965
3966 let _ = sd.restyle_nodes_focus(&[NodeId::ZERO, NodeId::ZERO, NodeId::ZERO], true);
3968 assert!(sd.get_styled_node_state(&NodeId::ZERO).focused);
3969 }
3970
3971 #[test]
3972 #[should_panic(expected = "index out of bounds")]
3973 fn get_styled_node_state_panics_on_an_out_of_range_node_id() {
3974 let sd = flat_body(1);
3977 let _ = sd.get_styled_node_state(&NodeId::new(99));
3978 }
3979
3980 #[test]
3981 fn restyle_on_state_change_with_no_changes_reports_nothing_to_do() {
3982 let mut sd = flat_body(2);
3983 let r = sd.restyle_on_state_change(None, None, None);
3984 assert!(!r.has_changes());
3985 assert!(!r.needs_layout);
3986 assert!(!r.needs_display_list);
3987 assert!(!r.gpu_only_changes);
3988 assert_eq!(r.max_relayout_scope, RelayoutScope::None);
3989 }
3990
3991 #[test]
3992 fn restyle_on_state_change_tolerates_stale_node_ids() {
3993 let mut sd = flat_body(1);
3994 let r = sd.restyle_on_state_change(
3995 Some(FocusChange {
3996 lost_focus: Some(NodeId::new(500)),
3997 gained_focus: Some(NodeId::new(usize::MAX)),
3998 }),
3999 Some(HoverChange {
4000 left_nodes: vec![NodeId::new(700)],
4001 entered_nodes: vec![NodeId::new(800)],
4002 }),
4003 Some(ActiveChange {
4004 deactivated: vec![NodeId::new(900)],
4005 activated: vec![NodeId::new(1000)],
4006 }),
4007 );
4008 assert!(!r.has_changes(), "stale ids must be filtered, not applied");
4009 assert_eq!(sd.node_count(), 2);
4010 }
4011
4012 #[test]
4013 fn restyle_on_state_change_applies_state_to_valid_nodes() {
4014 let mut sd = flat_body(1);
4015 let r = sd.restyle_on_state_change(
4016 None,
4017 Some(HoverChange {
4018 left_nodes: Vec::new(),
4019 entered_nodes: vec![NodeId::new(1)],
4020 }),
4021 None,
4022 );
4023 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
4024 assert!(
4025 r.changed_nodes.keys().all(|n| *n == NodeId::new(1)),
4026 "only the node whose state actually changed may be reported"
4027 );
4028 }
4029
4030 #[test]
4039 fn restyle_user_property_rebuilds_the_compact_cache_with_the_patch() {
4040 use azul_css::props::layout::display::LayoutDisplay;
4041 use azul_css::props::property::CssProperty;
4042
4043 let mut sd = flat_body(2);
4044 let node = NodeId::new(1);
4045
4046 assert!(
4048 sd.get_css_property_cache().compact_cache.is_some(),
4049 "fixture should carry a compact cache"
4050 );
4051
4052 let changes = sd.restyle_user_property(
4053 &node,
4054 &[CssProperty::const_display(LayoutDisplay::None)],
4055 );
4056 assert!(!changes.is_empty(), "display default -> none must report a change");
4057
4058 let cc = sd
4059 .get_css_property_cache()
4060 .compact_cache
4061 .as_ref()
4062 .expect("compact cache must be REBUILT by a geometry patch, not dropped");
4063 assert_eq!(
4064 cc.get_display(node.index()),
4065 LayoutDisplay::None,
4066 "the rebuilt compact cache must already reflect the patched value"
4067 );
4068
4069 let _ = sd.restyle_user_property(
4071 &node,
4072 &[CssProperty::const_display(LayoutDisplay::Flex)],
4073 );
4074 let cc = sd
4075 .get_css_property_cache()
4076 .compact_cache
4077 .as_ref()
4078 .expect("second patch keeps the cache present");
4079 assert_eq!(cc.get_display(node.index()), LayoutDisplay::Flex);
4080 }
4081
4082 #[test]
4083 fn restyle_user_property_rejects_empty_lists_and_stale_nodes() {
4084 let mut sd = flat_body(1);
4085 assert!(sd.restyle_user_property(&NodeId::ZERO, &[]).is_empty());
4086 assert!(
4087 sd.restyle_user_property(
4088 &NodeId::new(50),
4089 &[CssProperty::auto(CssPropertyType::Width)]
4090 )
4091 .is_empty(),
4092 "an out-of-range node id must be a no-op, not a panic"
4093 );
4094 assert!(
4095 sd.get_css_property_cache()
4096 .user_overridden_properties
4097 .iter()
4098 .all(Vec::is_empty),
4099 "a rejected call must not record an override"
4100 );
4101 }
4102
4103 #[test]
4104 fn restyle_user_property_stores_the_override_and_initial_removes_it() {
4105 let mut sd = flat_body(1);
4106 let node = NodeId::ZERO;
4107
4108 let _ = sd.restyle_user_property(&node, &[CssProperty::auto(CssPropertyType::Width)]);
4109 {
4110 let overrides = &sd.get_css_property_cache().user_overridden_properties;
4111 assert_eq!(overrides.len(), sd.node_count(), "table grows to cover the DOM");
4112 assert_eq!(overrides[0].len(), 1);
4113 assert_eq!(overrides[0][0].0, CssPropertyType::Width);
4114 }
4115
4116 let _ = sd.restyle_user_property(&node, &[CssProperty::none(CssPropertyType::Width)]);
4118 assert_eq!(sd.get_css_property_cache().user_overridden_properties[0].len(), 1);
4119
4120 let _ = sd.restyle_user_property(&node, &[CssProperty::initial(CssPropertyType::Width)]);
4122 assert!(sd.get_css_property_cache().user_overridden_properties[0].is_empty());
4123
4124 let _ = sd.restyle_user_property(&node, &[CssProperty::initial(CssPropertyType::Height)]);
4126 assert!(sd.get_css_property_cache().user_overridden_properties[0].is_empty());
4127 }
4128
4129 #[test]
4130 fn restyle_and_recompute_preserve_the_tree_and_rebuild_the_compact_cache() {
4131 let mut sd = flat_body(3);
4132 let before = sd.node_count();
4133
4134 sd.restyle(parse_css("div { color: red; } body > div:hover { color: blue; }"));
4135 assert_eq!(sd.node_count(), before);
4136 assert!(sd.get_css_property_cache().compact_cache.is_some());
4137
4138 sd.restyle(parse_css("}}} div { : ; }"));
4140 assert_eq!(sd.node_count(), before);
4141
4142 sd.recompute_inheritance_and_compact_cache();
4143 assert_eq!(sd.node_count(), before);
4144 assert!(sd.get_css_property_cache().compact_cache.is_some());
4145 }
4146
4147 #[test]
4148 fn get_css_property_cache_mut_sees_the_same_cache_as_the_shared_getter() {
4149 let mut sd = flat_body(1);
4150 let node_count = sd.node_count();
4151 sd.get_css_property_cache_mut()
4152 .user_overridden_properties
4153 .resize(node_count, Vec::new());
4154 assert_eq!(
4155 sd.get_css_property_cache().user_overridden_properties.len(),
4156 node_count
4157 );
4158 }
4159
4160 #[test]
4165 fn get_html_string_test_mode_omits_the_html_wrapper() {
4166 let sd = flat_body(2);
4167 let out = sd.get_html_string("HEAD_MARK", "BODY_MARK", true);
4168 assert!(!out.is_empty());
4169 assert!(!out.contains("HEAD_MARK"), "test_mode must not emit the custom head");
4170 assert!(!out.contains("BODY_MARK"), "test_mode must not emit the custom body");
4171 assert!(!out.contains("<html>"));
4172 }
4173
4174 #[test]
4175 fn get_html_string_embeds_custom_head_and_body_verbatim() {
4176 let sd = flat_body(1);
4177 let head = "🦀 <meta charset=\"utf-8\"> & ünïcödé";
4178 let body = "x".repeat(10_000);
4179 let out = sd.get_html_string(head, &body, false);
4180 assert!(out.contains("<html>"));
4181 assert!(out.contains(head));
4182 assert!(out.contains(&body));
4183 }
4184
4185 #[test]
4186 fn get_html_string_does_not_panic_on_extreme_doms() {
4187 assert!(!StyledDom::default().get_html_string("", "", true).is_empty());
4190 assert!(!flat_body(0).get_html_string("", "", true).is_empty());
4191 assert!(!nested_body().get_html_string("", "", true).is_empty());
4192 assert!(!flat_body(200).get_html_string("", "", true).is_empty());
4193 }
4194
4195 #[test]
4200 fn get_rects_in_rendering_order_is_a_permutation_of_the_children() {
4201 let sd = flat_body(3);
4202 let group = sd.get_rects_in_rendering_order();
4203 assert_eq!(group.root.into_crate_internal(), Some(NodeId::ZERO));
4204
4205 let mut ids: Vec<usize> = group
4206 .children
4207 .as_ref()
4208 .iter()
4209 .filter_map(|c| c.root.into_crate_internal())
4210 .map(|n| n.index())
4211 .collect();
4212 ids.sort_unstable();
4213 assert_eq!(ids, vec![1, 2, 3], "every child appears exactly once");
4214 }
4215
4216 #[test]
4217 fn get_rects_in_rendering_order_nests_grandchildren() {
4218 let sd = nested_body(); let group = sd.get_rects_in_rendering_order();
4220 assert_eq!(group.children.as_ref().len(), 1);
4221
4222 let child = &group.children.as_ref()[0];
4223 assert_eq!(child.root.into_crate_internal(), Some(NodeId::new(1)));
4224 assert_eq!(child.children.as_ref().len(), 1);
4225 assert_eq!(
4226 child.children.as_ref()[0].root.into_crate_internal(),
4227 Some(NodeId::new(2))
4228 );
4229 }
4230
4231 #[test]
4232 fn determine_rendering_order_with_no_parents_yields_a_childless_root() {
4233 let sd = StyledDom::default();
4234 let hierarchy = sd.node_hierarchy.as_container();
4235 let styled = sd.styled_nodes.as_container();
4236 let data = sd.node_data.as_container();
4237
4238 let group = StyledDom::determine_rendering_order(
4239 &[],
4240 &hierarchy,
4241 &styled,
4242 &data,
4243 sd.get_css_property_cache(),
4244 );
4245 assert_eq!(group.root.into_crate_internal(), Some(NodeId::ZERO));
4246 assert!(group.children.as_ref().is_empty());
4247 }
4248
4249 #[test]
4250 fn sort_children_by_position_returns_every_child_of_a_leaf_free_parent() {
4251 let sd = flat_body(3);
4252 let hierarchy = sd.node_hierarchy.as_container();
4253 let styled = sd.styled_nodes.as_container();
4254 let data = sd.node_data.as_container();
4255
4256 let sorted = sort_children_by_position(
4257 NodeId::ZERO,
4258 &hierarchy,
4259 &styled,
4260 &data,
4261 sd.get_css_property_cache(),
4262 );
4263 assert_eq!(sorted.len(), 3);
4264
4265 let leaf = sort_children_by_position(
4267 NodeId::new(3),
4268 &hierarchy,
4269 &styled,
4270 &data,
4271 sd.get_css_property_cache(),
4272 );
4273 assert!(leaf.is_empty());
4274 }
4275
4276 #[test]
4277 fn fill_content_group_children_builds_the_nested_group_tree() {
4278 let id = |i: usize| NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(i)));
4279
4280 let mut sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> = BTreeMap::new();
4281 sorted.insert(id(0), vec![id(1), id(2)]);
4282 sorted.insert(id(1), vec![id(3)]);
4283
4284 let mut group = ContentGroup {
4285 root: id(0),
4286 children: Vec::new().into(),
4287 };
4288 fill_content_group_children(&mut group, &sorted);
4289
4290 assert_eq!(group.children.as_ref().len(), 2);
4291 assert_eq!(group.children.as_ref()[0].root, id(1));
4292 assert_eq!(group.children.as_ref()[0].children.as_ref().len(), 1);
4293 assert_eq!(group.children.as_ref()[0].children.as_ref()[0].root, id(3));
4294 assert!(
4295 group.children.as_ref()[1].children.as_ref().is_empty(),
4296 "a node with no entry in the map is a leaf"
4297 );
4298 }
4299
4300 #[test]
4301 fn fill_content_group_children_leaves_an_unknown_root_untouched() {
4302 let sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> = BTreeMap::new();
4303 let mut group = ContentGroup {
4304 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(9))),
4305 children: Vec::new().into(),
4306 };
4307 fill_content_group_children(&mut group, &sorted);
4308 assert!(group.children.as_ref().is_empty());
4309 }
4310
4311 #[test]
4316 fn recursive_get_last_child_descends_to_the_deepest_last_child() {
4317 let items = vec![
4319 raw_item(0, 0, 0, 2), raw_item(1, 0, 0, 3), raw_item(2, 0, 0, 0), ];
4323
4324 let mut target = None;
4325 recursive_get_last_child(NodeId::ZERO, &items, &mut target);
4326 assert_eq!(target, Some(NodeId::new(2)));
4327 }
4328
4329 #[test]
4330 fn recursive_get_last_child_leaves_the_target_untouched_for_a_leaf() {
4331 let items = vec![raw_item(0, 0, 0, 0)];
4332 let mut target = None;
4333 recursive_get_last_child(NodeId::ZERO, &items, &mut target);
4334 assert_eq!(target, None);
4335
4336 let mut preset = Some(NodeId::new(7));
4338 recursive_get_last_child(NodeId::ZERO, &items, &mut preset);
4339 assert_eq!(preset, Some(NodeId::new(7)));
4340 }
4341
4342 #[test]
4343 fn get_path_to_root_is_root_first_and_tolerates_unknown_nodes() {
4344 let sd = nested_body(); let h = sd.node_hierarchy.as_container();
4346
4347 assert_eq!(get_path_to_root(&h, NodeId::ZERO), vec![NodeId::ZERO]);
4348 assert_eq!(
4349 get_path_to_root(&h, NodeId::new(2)),
4350 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2)]
4351 );
4352
4353 assert_eq!(
4355 get_path_to_root(&h, NodeId::new(9999)),
4356 vec![NodeId::new(9999)]
4357 );
4358 }
4359
4360 #[test]
4365 fn is_before_in_document_order_is_false_for_identical_nodes() {
4366 let sd = flat_body(2);
4367 assert!(!is_before_in_document_order(
4368 &sd.node_hierarchy,
4369 NodeId::new(1),
4370 NodeId::new(1)
4371 ));
4372 }
4373
4374 #[test]
4375 fn is_before_in_document_order_orders_ancestors_and_siblings() {
4376 let sd = flat_body(3); let h = &sd.node_hierarchy;
4378
4379 assert!(is_before_in_document_order(h, NodeId::ZERO, NodeId::new(1)));
4380 assert!(!is_before_in_document_order(h, NodeId::new(1), NodeId::ZERO));
4381 assert!(is_before_in_document_order(h, NodeId::new(1), NodeId::new(3)));
4382 assert!(!is_before_in_document_order(h, NodeId::new(3), NodeId::new(1)));
4383 }
4384
4385 #[test]
4386 fn is_before_in_document_order_is_antisymmetric_across_a_nested_tree() {
4387 let sd = nested_body();
4388 let h = &sd.node_hierarchy;
4389 for a in 0..3 {
4390 for b in 0..3 {
4391 let ab = is_before_in_document_order(h, NodeId::new(a), NodeId::new(b));
4392 let ba = is_before_in_document_order(h, NodeId::new(b), NodeId::new(a));
4393 if a == b {
4394 assert!(!ab && !ba, "a node is never before itself");
4395 } else {
4396 assert_ne!(ab, ba, "exactly one of ({a},{b}) / ({b},{a}) must hold");
4397 }
4398 }
4399 }
4400 }
4401
4402 #[test]
4403 fn is_before_in_document_order_is_deterministic_for_unknown_nodes() {
4404 let sd = flat_body(1);
4405 let h = &sd.node_hierarchy;
4406 assert!(is_before_in_document_order(h, NodeId::ZERO, NodeId::new(usize::MAX)));
4409 assert!(!is_before_in_document_order(h, NodeId::new(usize::MAX), NodeId::ZERO));
4410 }
4411
4412 #[test]
4413 fn collect_nodes_in_document_order_start_equals_end() {
4414 let sd = flat_body(2);
4415 assert_eq!(
4416 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(2), NodeId::new(2)),
4417 vec![NodeId::new(2)]
4418 );
4419 assert_eq!(
4421 collect_nodes_in_document_order(
4422 &sd.node_hierarchy,
4423 NodeId::new(usize::MAX),
4424 NodeId::new(usize::MAX)
4425 ),
4426 vec![NodeId::new(usize::MAX)]
4427 );
4428 }
4429
4430 #[test]
4431 fn collect_nodes_in_document_order_walks_the_tree_in_pre_order() {
4432 let sd = flat_body(3); assert_eq!(
4434 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::ZERO, NodeId::new(3)),
4435 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2), NodeId::new(3)]
4436 );
4437 assert_eq!(
4438 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(1), NodeId::new(2)),
4439 vec![NodeId::new(1), NodeId::new(2)]
4440 );
4441
4442 let nested = nested_body();
4444 assert_eq!(
4445 collect_nodes_in_document_order(&nested.node_hierarchy, NodeId::ZERO, NodeId::new(2)),
4446 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2)]
4447 );
4448 }
4449
4450 #[test]
4451 fn collect_nodes_in_document_order_terminates_when_end_precedes_start() {
4452 let sd = flat_body(3);
4455 let out = collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(2), NodeId::new(1));
4456 assert!(out.is_empty());
4457 }
4458
4459 #[test]
4460 fn collect_nodes_in_document_order_with_an_unreachable_end_stops_at_the_tree_end() {
4461 let sd = flat_body(3);
4462 let out = collect_nodes_in_document_order(
4463 &sd.node_hierarchy,
4464 NodeId::new(1),
4465 NodeId::new(usize::MAX),
4466 );
4467 assert_eq!(
4468 out,
4469 vec![NodeId::new(1), NodeId::new(2), NodeId::new(3)],
4470 "an end node that is never reached must terminate at the end of the traversal"
4471 );
4472 }
4473
4474 #[test]
4479 fn is_layout_equivalent_holds_for_independently_built_identical_doms() {
4480 assert!(is_layout_equivalent(&flat_body(3), &flat_body(3)));
4481 assert!(is_layout_equivalent(
4482 &StyledDom::default(),
4483 &StyledDom::default()
4484 ));
4485 assert!(is_layout_equivalent(&nested_body(), &nested_body()));
4486 }
4487
4488 #[test]
4489 fn is_layout_equivalent_rejects_a_different_node_count() {
4490 assert!(!is_layout_equivalent(&flat_body(3), &flat_body(4)));
4491 assert!(!is_layout_equivalent(&flat_body(0), &flat_body(1)));
4492 }
4493
4494 #[test]
4495 fn is_layout_equivalent_rejects_a_different_structure() {
4496 assert!(!is_layout_equivalent(&nested_body(), &flat_body(2)));
4498 }
4499
4500 #[test]
4501 fn is_layout_equivalent_rejects_a_changed_class() {
4502 let build = |class: &str| {
4503 let mut dom = Dom::create_body().with_children(
4504 vec![Dom::create_div().with_class(class.to_string().into())].into(),
4505 );
4506 StyledDom::create(&mut dom, Css::empty())
4507 };
4508 assert!(is_layout_equivalent(&build("a"), &build("a")));
4509 assert!(!is_layout_equivalent(&build("a"), &build("b")));
4510 }
4511
4512 #[test]
4513 fn is_layout_equivalent_rejects_a_changed_pseudo_state() {
4514 let base = flat_body(2);
4515 let mut hovered = flat_body(2);
4516 let _ = hovered.restyle_nodes_hover(&[NodeId::new(1)], true);
4517 assert!(
4518 !is_layout_equivalent(&base, &hovered),
4519 ":hover changes CSS resolution, so the DOMs are not layout-equivalent"
4520 );
4521 }
4522
4523 #[test]
4528 fn compact_dom_len_and_is_empty() {
4529 let single = convert_dom_into_compact_dom(Dom::create_div());
4530 assert_eq!(single.len(), 1);
4531 assert!(!single.is_empty());
4532
4533 let tree = convert_dom_into_compact_dom(
4534 Dom::create_body().with_children(vec![Dom::create_div(); 4].into()),
4535 );
4536 assert_eq!(tree.len(), 5);
4537 assert!(!tree.is_empty());
4538
4539 let empty = CompactDom {
4541 node_hierarchy: NodeHierarchy {
4542 internal: Vec::new(),
4543 },
4544 node_data: NodeDataContainer {
4545 internal: Vec::new(),
4546 },
4547 root: NodeId::ZERO,
4548 };
4549 assert_eq!(empty.len(), 0);
4550 assert!(empty.is_empty());
4551 }
4552
4553 #[test]
4554 fn convert_dom_into_compact_dom_links_flat_siblings() {
4555 let compact = convert_dom_into_compact_dom(
4556 Dom::create_body().with_children(vec![Dom::create_div(); 3].into()),
4557 );
4558 assert_eq!(compact.len(), 4);
4559 assert_eq!(compact.root, NodeId::ZERO);
4560
4561 let h = compact.node_hierarchy.as_ref();
4562 assert_eq!(h[NodeId::ZERO].parent, None);
4563 assert_eq!(h[NodeId::ZERO].last_child, Some(NodeId::new(3)));
4564
4565 for i in 1..=3usize {
4566 assert_eq!(h[NodeId::new(i)].parent, Some(NodeId::ZERO));
4567 let expected_next = if i == 3 { None } else { Some(NodeId::new(i + 1)) };
4568 assert_eq!(h[NodeId::new(i)].next_sibling, expected_next);
4569 let expected_prev = if i == 1 { None } else { Some(NodeId::new(i - 1)) };
4570 assert_eq!(h[NodeId::new(i)].previous_sibling, expected_prev);
4571 assert_eq!(h[NodeId::new(i)].last_child, None, "the children are leaves");
4572 }
4573 }
4574
4575 #[test]
4582 fn convert_dom_into_compact_dom_last_child_is_the_last_direct_child() {
4583 let sd = nested_body();
4585 let h = sd.node_hierarchy.as_container();
4586
4587 let last_direct_child = NodeId::ZERO.az_children(&h).last();
4588 assert_eq!(last_direct_child, Some(NodeId::new(1)));
4589 assert_eq!(
4590 h[NodeId::ZERO].last_child_id(),
4591 last_direct_child,
4592 "last_child_id() must agree with the forward child iteration"
4593 );
4594 }
4595
4596 #[test]
4597 fn convert_dom_into_compact_dom_handles_an_empty_and_a_deep_tree() {
4598 assert_eq!(convert_dom_into_compact_dom(Dom::create_body()).len(), 1);
4599
4600 let mut deep = Dom::create_div();
4601 for _ in 0..64 {
4602 deep = Dom::create_div().with_children(vec![deep].into());
4603 }
4604 let compact = convert_dom_into_compact_dom(deep);
4605 assert_eq!(compact.len(), 65);
4606 let h = compact.node_hierarchy.as_ref();
4608 for i in 1..65usize {
4609 assert_eq!(h[NodeId::new(i)].parent, Some(NodeId::new(i - 1)));
4610 }
4611 }
4612
4613 #[test]
4618 fn scope_inline_css_advances_next_id_once_per_node() {
4619 let mut dom = Dom::create_body().with_children(
4620 vec![
4621 Dom::create_div().with_children(vec![Dom::create_div()].into()),
4622 Dom::create_div(),
4623 ]
4624 .into(),
4625 );
4626 let _ = dom.fixup_children_estimated();
4627
4628 let mut next = 0usize;
4629 scope_inline_css(&mut dom, &mut next);
4630 assert_eq!(next, 4, "4 nodes → the counter must land on 4 (pre-order ids 0..3)");
4631 }
4632
4633 #[test]
4634 fn scope_inline_css_from_zero_and_from_a_large_offset() {
4635 let mut leaf = Dom::create_div();
4636 let _ = leaf.fixup_children_estimated();
4637 let mut next = 0usize;
4638 scope_inline_css(&mut leaf, &mut next);
4639 assert_eq!(next, 1, "a single leaf consumes exactly one id");
4640
4641 let mut dom = Dom::create_body().with_children(vec![Dom::create_div(); 2].into());
4643 let _ = dom.fixup_children_estimated();
4644 let mut big = 1_000_000usize;
4645 scope_inline_css(&mut dom, &mut big);
4646 assert_eq!(big, 1_000_003);
4647 }
4648
4649 #[test]
4650 fn scope_inline_css_preserves_the_rule_count_of_every_node() {
4651 let mut dom = Dom::create_body()
4652 .with_css("color: red")
4653 .with_children(vec![Dom::create_div().with_css("width: 5px")].into());
4654 let _ = dom.fixup_children_estimated();
4655
4656 let rules_before: usize = dom
4657 .css
4658 .as_ref()
4659 .iter()
4660 .map(|c| c.rules.as_ref().len())
4661 .sum::<usize>()
4662 + dom.children.as_ref()[0]
4663 .css
4664 .as_ref()
4665 .iter()
4666 .map(|c| c.rules.as_ref().len())
4667 .sum::<usize>();
4668 assert!(rules_before > 0, "with_css must produce at least one rule");
4669
4670 let mut next = 0usize;
4671 scope_inline_css(&mut dom, &mut next);
4672
4673 let rules_after: usize = dom
4674 .css
4675 .as_ref()
4676 .iter()
4677 .map(|c| c.rules.as_ref().len())
4678 .sum::<usize>()
4679 + dom.children.as_ref()[0]
4680 .css
4681 .as_ref()
4682 .iter()
4683 .map(|c| c.rules.as_ref().len())
4684 .sum::<usize>();
4685 assert_eq!(
4686 rules_before, rules_after,
4687 "scoping rewrites paths in place; it must not add or drop rules"
4688 );
4689 assert_eq!(next, 2);
4690 }
4691
4692 #[test]
4693 fn collect_css_from_dom_yields_inner_css_before_outer_css() {
4694 let outer = parse_css("div { color: red; } span { color: blue; }");
4695 let inner = parse_css("p { color: green; }");
4696 let outer_rules = outer.rules.as_ref().len();
4697 let inner_rules = inner.rules.as_ref().len();
4698 assert_ne!(
4699 outer_rules, inner_rules,
4700 "the two stylesheets must be distinguishable by rule count"
4701 );
4702
4703 let mut child = Dom::create_div();
4704 child.add_component_css(inner);
4705 let mut dom = Dom::create_body().with_children(vec![child].into());
4706 dom.add_component_css(outer);
4707
4708 let mut out = Vec::new();
4709 collect_css_from_dom(&dom, &mut out);
4710
4711 assert_eq!(out.len(), 2);
4712 assert_eq!(
4713 out[0].rules.as_ref().len(),
4714 inner_rules,
4715 "deeper CSS is collected first (lower cascade priority)"
4716 );
4717 assert_eq!(out[1].rules.as_ref().len(), outer_rules);
4718 }
4719
4720 #[test]
4721 fn collect_css_from_dom_on_a_css_free_tree_appends_nothing() {
4722 let dom = Dom::create_body().with_children(vec![Dom::create_div(); 3].into());
4723 let mut out = Vec::new();
4724 collect_css_from_dom(&dom, &mut out);
4725 assert!(out.is_empty());
4726
4727 let mut prefilled = vec![Css::empty()];
4729 collect_css_from_dom(&dom, &mut prefilled);
4730 assert_eq!(prefilled.len(), 1);
4731 }
4732
4733 #[test]
4734 fn strip_css_from_dom_clears_every_node_recursively() {
4735 let mut dom = Dom::create_body()
4736 .with_css("color: red")
4737 .with_children(
4738 vec![Dom::create_div()
4739 .with_css("width: 5px")
4740 .with_children(vec![Dom::create_div().with_css("height: 5px")].into())]
4741 .into(),
4742 );
4743 assert!(!dom.css.as_ref().is_empty());
4744
4745 strip_css_from_dom(&mut dom);
4746
4747 assert!(dom.css.as_ref().is_empty());
4748 let child = &dom.children.as_ref()[0];
4749 assert!(child.css.as_ref().is_empty());
4750 assert!(child.children.as_ref()[0].css.as_ref().is_empty());
4751
4752 strip_css_from_dom(&mut dom);
4754 assert!(dom.css.as_ref().is_empty());
4755 }
4756}