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 rule.path.push_front_scope(owner, end);
989 combined_rules.push(rule);
990 }
991 }
992 }
993 let combined_css = if combined_rules.is_empty() {
994 Css::empty()
995 } else {
996 Css::new(combined_rules)
997 };
998
999 let node_hierarchy_items = fast_dom.node_hierarchy;
1002 let nodes: Vec<Node> = node_hierarchy_items.as_ref()
1003 .iter()
1004 .map(|item| Node {
1005 parent: NodeId::from_usize(item.parent),
1006 previous_sibling: NodeId::from_usize(item.previous_sibling),
1007 next_sibling: NodeId::from_usize(item.next_sibling),
1008 last_child: NodeId::from_usize(item.last_child),
1009 })
1010 .collect();
1011 let node_hierarchy_internal = NodeHierarchy { internal: nodes };
1012
1013 let node_data_vec = fast_dom.node_data.into_library_owned_vec();
1015 let compact_dom = CompactDom {
1016 node_hierarchy: node_hierarchy_internal,
1017 node_data: NodeDataContainer { internal: node_data_vec },
1018 root: NodeId::ZERO,
1019 };
1020
1021 Self::create_from_compact_dom(compact_dom, combined_css, node_hierarchy_items)
1025 }
1026
1027 #[allow(clippy::similar_names)] #[allow(clippy::too_many_lines)] fn create_from_compact_dom(
1033 compact_dom: CompactDom,
1034 mut css: Css,
1035 node_hierarchy: NodeHierarchyItemVec,
1036 ) -> Self {
1037 use crate::dom::EventFilter;
1038
1039 static CASCADE_BREAKDOWN: crate::sync::OnceLock<bool> = crate::sync::OnceLock::new();
1040 let cascade_dbg = *CASCADE_BREAKDOWN.get_or_init(crate::profile::memory_enabled);
1041
1042 let node_count = compact_dom.len();
1043
1044 let non_leaf_nodes = compact_dom
1045 .node_hierarchy
1046 .as_ref()
1047 .get_parents_sorted_by_depth();
1048
1049 let mut styled_nodes = vec![
1050 StyledNode {
1051 styled_node_state: StyledNodeState::new()
1052 };
1053 node_count
1054 ];
1055
1056 let mut css_property_cache = CssPropertyCache::empty(compact_dom.node_data.len());
1057
1058 let html_tree = construct_html_cascade_tree(
1059 &compact_dom.node_hierarchy.as_ref(),
1060 &non_leaf_nodes[..],
1061 &compact_dom.node_data.as_ref(),
1062 );
1063
1064 let non_leaf_nodes = non_leaf_nodes
1065 .iter()
1066 .map(|(depth, node_id)| ParentWithNodeDepth {
1067 depth: *depth,
1068 node_id: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
1069 })
1070 .collect::<Vec<_>>();
1071
1072 let non_leaf_nodes: ParentWithNodeDepthVec = non_leaf_nodes.into();
1073
1074 let _restyle_tag_ids = css_property_cache.restyle(
1075 &mut css,
1076 &compact_dom.node_data.as_ref(),
1077 &node_hierarchy,
1078 &non_leaf_nodes,
1079 &html_tree.as_ref(),
1080 );
1081
1082 css_property_cache.retained_author_css = css;
1087
1088 css_property_cache.apply_ua_css(compact_dom.node_data.as_ref().internal);
1096 css_property_cache.compute_inherited_values(
1097 node_hierarchy.as_container().internal,
1098 compact_dom.node_data.as_ref().internal,
1099 );
1100
1101 let prev_font_hashes: Vec<u64> = css_property_cache.compact_cache
1102 .as_ref()
1103 .map(|c| c.prev_font_hashes.clone())
1104 .unwrap_or_default();
1105 let compact = css_property_cache.build_compact_cache_with_inheritance(
1106 compact_dom.node_data.as_ref().internal,
1107 node_hierarchy.as_container().internal,
1108 &prev_font_hashes,
1109 );
1110 css_property_cache.compact_cache = Some(compact);
1111 let pre_prune = if cascade_dbg {
1112 Some(css_property_cache.memory_breakdown())
1113 } else { None };
1114 css_property_cache.prune_compact_normal_props();
1115 if let Some(pre) = pre_prune {
1116 let post = css_property_cache.memory_breakdown();
1117 #[cfg(feature = "std")]
1118 eprintln!("[PRUNE] css_props {} → {} KiB cascaded {} → {} KiB (saved {} KiB)",
1119 pre.css_props_bytes / 1024, post.css_props_bytes / 1024,
1120 pre.cascaded_props_bytes / 1024, post.cascaded_props_bytes / 1024,
1121 (pre.total_bytes().saturating_sub(post.total_bytes())) / 1024);
1122 #[cfg(not(feature = "std"))]
1123 let _ = post;
1124 }
1125
1126 let tag_ids = css_property_cache.generate_tag_ids(
1127 &compact_dom.node_data.as_ref(),
1128 &node_hierarchy,
1129 );
1130
1131 if cascade_dbg {
1132 let bd = css_property_cache.memory_breakdown();
1133 #[cfg(feature = "std")]
1134 eprintln!("[CASCADE] {} nodes cascaded_props={} KiB css_props={} KiB compact={} KiB computed={} KiB total={} KiB",
1135 node_count,
1136 bd.cascaded_props_bytes / 1024, bd.css_props_bytes / 1024,
1137 bd.compact_cache_bytes / 1024, bd.computed_values_bytes / 1024,
1138 bd.total_bytes() / 1024);
1139 #[cfg(not(feature = "std"))]
1140 let _ = bd;
1141 }
1142
1143 let has_any_callbacks = compact_dom.node_data.as_ref().internal.iter()
1146 .any(|c| !c.get_callbacks().is_empty() || c.get_dataset().is_some());
1147
1148 let (nodes_with_window_callbacks, nodes_with_datasets) = if has_any_callbacks {
1149 let mut win_cbs = Vec::new();
1150 let mut datasets = Vec::new();
1151 for (node_id, c) in compact_dom.node_data.as_ref().internal.iter().enumerate() {
1152 let cbs = c.get_callbacks();
1153 let has_dataset = c.get_dataset().is_some();
1154 if !cbs.is_empty() || has_dataset {
1155 datasets.push(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_id))));
1156 }
1157 for cb in cbs {
1158 if let EventFilter::Window(_) = cb.event {
1159 win_cbs.push(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_id))));
1160 break;
1161 }
1162 }
1163 }
1164 (win_cbs, datasets)
1165 } else {
1166 (Vec::new(), Vec::new())
1167 };
1168 let mut styled_dom = Self {
1169 root: NodeHierarchyItemId::from_crate_internal(Some(compact_dom.root)),
1170 node_hierarchy,
1171 node_data: compact_dom.node_data.internal.into(),
1172 cascade_info: html_tree.internal.into(),
1173 styled_nodes: styled_nodes.into(),
1174 tag_ids_to_node_ids: tag_ids.into(),
1175 nodes_with_window_callbacks: nodes_with_window_callbacks.into(),
1176 nodes_with_datasets: nodes_with_datasets.into(),
1177 non_leaf_nodes,
1178 css_property_cache: CssPropertyCachePtr::new(css_property_cache),
1179 dom_id: DomId::ROOT_ID,
1180 };
1181 #[cfg(feature = "table_layout")]
1182 if let Err(_e) = crate::dom_table::generate_anonymous_table_elements(&mut styled_dom) {
1183 }
1184
1185 styled_dom
1186 }
1187
1188 #[must_use] pub fn create_from_dom(mut dom: Dom) -> Self {
1199 use azul_css::css::Css;
1200
1201 dom.fixup_children_estimated();
1206 let mut next_scope_id = 0usize;
1207 scope_inline_css(&mut dom, &mut next_scope_id);
1208
1209 let mut all_css = Vec::new();
1211 collect_css_from_dom(&dom, &mut all_css);
1212
1213 let mut combined_css = if all_css.is_empty() {
1215 Css::empty()
1216 } else {
1217 let mut combined_rules: Vec<azul_css::css::CssRuleBlock> = Vec::new();
1218 for css in all_css {
1219 combined_rules.extend(css.rules.into_library_owned_vec());
1220 }
1221 Css::new(combined_rules)
1222 };
1223
1224 strip_css_from_dom(&mut dom);
1227
1228 Self::create(&mut dom, combined_css)
1230 }
1231
1232 pub fn append_child(&mut self, other: Self) {
1235 let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1236 let current_root_children_count = self_root_id
1237 .az_children(&self.node_hierarchy.as_container())
1238 .count();
1239 self.append_child_with_index(other, current_root_children_count);
1240 self.finalize_non_leaf_nodes();
1241 }
1242
1243 pub fn append_child_with_index(&mut self, mut other: Self, child_index: usize) {
1246 let self_len = self.node_hierarchy.as_ref().len();
1248 let other_len = other.node_hierarchy.as_ref().len();
1249 let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1250 let other_root_id = other.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1251
1252 other.cascade_info.as_mut()[other_root_id.index()].index_in_parent =
1254 u32::try_from(child_index).unwrap_or(u32::MAX);
1255 other.cascade_info.as_mut()[other_root_id.index()].is_last_child = true;
1256
1257 self.cascade_info.append(&mut other.cascade_info);
1258
1259 for other in other.node_hierarchy.as_mut().iter_mut() {
1261 if other.parent != 0 {
1262 other.parent += self_len;
1263 }
1264 if other.previous_sibling != 0 {
1265 other.previous_sibling += self_len;
1266 }
1267 if other.next_sibling != 0 {
1268 other.next_sibling += self_len;
1269 }
1270 if other.last_child != 0 {
1271 other.last_child += self_len;
1272 }
1273 }
1274
1275 other.node_hierarchy.as_container_mut()[other_root_id].parent =
1276 NodeId::into_raw(&Some(self_root_id));
1277 let current_last_child = self.node_hierarchy.as_container()[self_root_id].last_child_id();
1278 other.node_hierarchy.as_container_mut()[other_root_id].previous_sibling =
1279 NodeId::into_raw(¤t_last_child);
1280 if let Some(current_last) = current_last_child {
1281 if self.node_hierarchy.as_container_mut()[current_last]
1282 .next_sibling_id()
1283 .is_some()
1284 {
1285 self.node_hierarchy.as_container_mut()[current_last].next_sibling +=
1286 other_root_id.index() + other_len;
1287 } else {
1288 self.node_hierarchy.as_container_mut()[current_last].next_sibling =
1289 NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1290 }
1291 }
1292 self.node_hierarchy.as_container_mut()[self_root_id].last_child =
1293 NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1294
1295 self.node_hierarchy.append(&mut other.node_hierarchy);
1296 self.node_data.append(&mut other.node_data);
1297 self.styled_nodes.append(&mut other.styled_nodes);
1298 self.get_css_property_cache_mut()
1299 .append(other.get_css_property_cache_mut());
1300
1301 for tag_id_node_id in &mut other.tag_ids_to_node_ids {
1304 tag_id_node_id.node_id.inner += self_len;
1305 }
1306
1307 self.tag_ids_to_node_ids
1308 .append(&mut other.tag_ids_to_node_ids);
1309
1310 for nid in &mut other.nodes_with_window_callbacks {
1311 nid.inner += self_len;
1312 }
1313 self.nodes_with_window_callbacks
1314 .append(&mut other.nodes_with_window_callbacks);
1315
1316 for nid in &mut other.nodes_with_datasets {
1317 nid.inner += self_len;
1318 }
1319 self.nodes_with_datasets
1320 .append(&mut other.nodes_with_datasets);
1321
1322 if other_len != 1 {
1325 for other_non_leaf_node in &mut other.non_leaf_nodes {
1326 other_non_leaf_node.node_id.inner += self_len;
1327 other_non_leaf_node.depth += 1;
1328 }
1329 self.non_leaf_nodes.append(&mut other.non_leaf_nodes);
1330 }
1332 }
1333
1334 pub fn finalize_non_leaf_nodes(&mut self) {
1337 self.non_leaf_nodes.sort_by(|a, b| a.depth.cmp(&b.depth));
1338 }
1339
1340 #[must_use] pub fn with_child(mut self, other: Self) -> Self {
1342 self.append_child(other);
1343 self
1344 }
1345
1346 pub fn set_context_menu(&mut self, context_menu: Menu) {
1348 if let Some(root_id) = self.root.into_crate_internal() {
1349 self.node_data.as_container_mut()[root_id].set_context_menu(context_menu);
1350 }
1351 }
1352
1353 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
1355 self.set_context_menu(context_menu);
1356 self
1357 }
1358
1359 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
1361 if let Some(root_id) = self.root.into_crate_internal() {
1362 self.node_data.as_container_mut()[root_id].set_menu_bar(menu_bar);
1363 }
1364 }
1365
1366 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
1368 self.set_menu_bar(menu_bar);
1369 self
1370 }
1371
1372 pub fn recompute_inheritance_and_compact_cache(&mut self) {
1387 let prev_font_hashes: Vec<u64> = self.css_property_cache
1395 .downcast_mut()
1396 .compact_cache
1397 .as_ref()
1398 .map(|c| c.prev_font_hashes.clone())
1399 .unwrap_or_default();
1400 let compact = self.css_property_cache
1401 .downcast_mut()
1402 .build_compact_cache_with_inheritance(
1403 self.node_data.as_container().internal,
1404 self.node_hierarchy.as_container().internal,
1405 &prev_font_hashes,
1406 );
1407 self.css_property_cache.downcast_mut().compact_cache = Some(compact);
1408 }
1409
1410 #[allow(clippy::similar_names)] pub fn extend_author_scopes_for_appended(&mut self, new_node: NodeId, parent: NodeId) {
1421 use azul_css::css::CssPathSelector;
1422 let p = parent.index();
1423 let n = new_node.index();
1424 let cache = self.css_property_cache.downcast_mut();
1425 for rule in cache.retained_author_css.rules.as_mut() {
1426 let mut sels = rule.path.selectors.as_ref().to_vec();
1427 let mut changed = false;
1428 for sel in &mut sels {
1429 if let CssPathSelector::Root(range) = sel {
1430 if range.contains(p) && range.end < n {
1431 range.end = n;
1432 changed = true;
1433 }
1434 }
1435 }
1436 if changed {
1437 rule.path.selectors = sels.into();
1438 }
1439 }
1440 }
1441
1442 pub fn restyle_retained(&mut self) {
1447 let css = self
1448 .css_property_cache
1449 .downcast_mut()
1450 .retained_author_css
1451 .clone();
1452 if css.is_empty() {
1453 return;
1454 }
1455 self.restyle(css);
1456 }
1457
1458 pub fn restyle(&mut self, mut css: Css) {
1459 let _stale_tag_ids = self.css_property_cache.downcast_mut().restyle(
1464 &mut css,
1465 &self.node_data.as_container(),
1466 &self.node_hierarchy,
1467 &self.non_leaf_nodes,
1468 &self.cascade_info.as_container(),
1469 );
1470
1471 self.css_property_cache.downcast_mut().retained_author_css = css;
1473
1474 self.css_property_cache
1476 .downcast_mut()
1477 .apply_ua_css(self.node_data.as_container().internal);
1478
1479 self.css_property_cache
1481 .downcast_mut()
1482 .compute_inherited_values(
1483 self.node_hierarchy.as_container().internal,
1484 self.node_data.as_container().internal,
1485 );
1486
1487 let prev_font_hashes: Vec<u64> = self
1493 .css_property_cache
1494 .downcast_mut()
1495 .compact_cache
1496 .as_ref()
1497 .map(|c| c.prev_font_hashes.clone())
1498 .unwrap_or_default();
1499 self.css_property_cache.downcast_mut().compact_cache = None;
1500 let compact = self
1501 .css_property_cache
1502 .downcast_mut()
1503 .build_compact_cache_with_inheritance(
1504 self.node_data.as_container().internal,
1505 self.node_hierarchy.as_container().internal,
1506 &prev_font_hashes,
1507 );
1508 self.css_property_cache.downcast_mut().compact_cache = Some(compact);
1509 self.css_property_cache
1510 .downcast_mut()
1511 .invalidate_resolved_font_sizes();
1512
1513 let new_tag_ids = self.css_property_cache.downcast_mut().generate_tag_ids(
1516 &self.node_data.as_container(),
1517 &self.node_hierarchy,
1518 );
1519 self.tag_ids_to_node_ids = new_tag_ids.into();
1520 }
1521
1522 #[inline]
1524 #[must_use] pub const fn node_count(&self) -> usize {
1525 self.node_data.len()
1526 }
1527
1528 #[inline]
1530 #[must_use] pub fn get_css_property_cache(&self) -> &CssPropertyCache {
1531 &self.css_property_cache.ptr
1532 }
1533
1534 #[inline]
1536 pub fn get_css_property_cache_mut(&mut self) -> &mut CssPropertyCache {
1537 &mut self.css_property_cache.ptr
1538 }
1539
1540 #[inline]
1542 #[must_use] pub fn get_styled_node_state(&self, node_id: &NodeId) -> StyledNodeState {
1543 self.styled_nodes.as_container()[*node_id]
1544 .styled_node_state
1545 }
1546
1547 #[must_use]
1549 pub fn restyle_nodes_hover(
1550 &mut self,
1551 nodes: &[NodeId],
1552 new_hover_state: bool,
1553 ) -> RestyleNodes {
1554 self.restyle_nodes_state(
1555 nodes,
1556 new_hover_state,
1557 |state, val| state.hover = val,
1558 azul_css::dynamic_selector::PseudoStateType::Hover,
1559 )
1560 }
1561
1562 #[must_use]
1564 pub fn restyle_nodes_active(
1565 &mut self,
1566 nodes: &[NodeId],
1567 new_active_state: bool,
1568 ) -> RestyleNodes {
1569 self.restyle_nodes_state(
1570 nodes,
1571 new_active_state,
1572 |state, val| state.active = val,
1573 azul_css::dynamic_selector::PseudoStateType::Active,
1574 )
1575 }
1576
1577 #[must_use]
1579 pub fn restyle_nodes_focus(
1580 &mut self,
1581 nodes: &[NodeId],
1582 new_focus_state: bool,
1583 ) -> RestyleNodes {
1584 self.restyle_nodes_state(
1585 nodes,
1586 new_focus_state,
1587 |state, val| state.focused = val,
1588 azul_css::dynamic_selector::PseudoStateType::Focus,
1589 )
1590 }
1591
1592 fn restyle_nodes_state(
1594 &mut self,
1595 nodes: &[NodeId],
1596 new_state_value: bool,
1597 set_state: impl Fn(&mut StyledNodeState, bool),
1598 pseudo_state_type: azul_css::dynamic_selector::PseudoStateType,
1599 ) -> RestyleNodes {
1600 let node_count = self.node_count();
1605 let nodes: Vec<NodeId> = nodes
1606 .iter()
1607 .copied()
1608 .filter(|nid| nid.index() < node_count)
1609 .collect();
1610
1611 let old_node_states = nodes
1613 .iter()
1614 .map(|nid| {
1615 self.styled_nodes.as_container()[*nid]
1616 .styled_node_state
1617 })
1618 .collect::<Vec<_>>();
1619
1620 for nid in &nodes {
1621 set_state(
1622 &mut self.styled_nodes.as_container_mut()[*nid].styled_node_state,
1623 new_state_value,
1624 );
1625 }
1626
1627 let css_property_cache = self.get_css_property_cache();
1628 let styled_nodes = self.styled_nodes.as_container();
1629 let node_data = self.node_data.as_container();
1630
1631 let v = nodes
1633 .iter()
1634 .zip(old_node_states.iter())
1635 .filter_map(|(node_id, old_node_state)| {
1636 let mut keys_normal: Vec<_> = CssPropertyCache::prop_types_for_state(
1637 css_property_cache.css_props.get_slice(node_id.index()),
1638 pseudo_state_type,
1639 ).collect();
1640 let mut keys_inherited: Vec<_> = CssPropertyCache::prop_types_for_state(
1641 css_property_cache.cascaded_props.get_slice(node_id.index()),
1642 pseudo_state_type,
1643 ).collect();
1644 let keys_inline: Vec<CssPropertyType> = {
1645 use azul_css::dynamic_selector::DynamicSelector;
1646 node_data[*node_id]
1647 .style
1648 .iter_inline_properties()
1649 .filter_map(|(prop, conds)| {
1650 let matches = conds.as_slice().iter().any(|c| {
1651 matches!(c, DynamicSelector::PseudoState(pst) if *pst == pseudo_state_type)
1652 });
1653 if matches {
1654 Some(prop.get_type())
1655 } else {
1656 None
1657 }
1658 })
1659 .collect()
1660 };
1661 let mut keys_inline_ref: Vec<_> = keys_inline.iter().collect();
1662
1663 keys_normal.append(&mut keys_inherited);
1664 keys_normal.append(&mut keys_inline_ref);
1665
1666 let node_properties_that_could_have_changed = keys_normal;
1667
1668 if node_properties_that_could_have_changed.is_empty() {
1669 return None;
1670 }
1671
1672 let new_node_state = &styled_nodes[*node_id].styled_node_state;
1673 let node_data = &node_data[*node_id];
1674
1675 let changes = node_properties_that_could_have_changed
1676 .into_iter()
1677 .filter_map(|prop| {
1678 let old = css_property_cache.get_property_slow(
1680 node_data,
1681 node_id,
1682 old_node_state,
1683 prop,
1684 );
1685 let new = css_property_cache.get_property_slow(
1686 node_data,
1687 node_id,
1688 new_node_state,
1689 prop,
1690 );
1691 if old == new {
1692 None
1693 } else {
1694 Some(ChangedCssProperty {
1695 previous_state: *old_node_state,
1696 previous_prop: old.map_or_else(|| CssProperty::auto(*prop), Clone::clone),
1697 current_state: *new_node_state,
1698 current_prop: new.map_or_else(|| CssProperty::auto(*prop), Clone::clone),
1699 })
1700 }
1701 })
1702 .collect::<Vec<_>>();
1703
1704 if changes.is_empty() {
1705 None
1706 } else {
1707 Some((*node_id, changes))
1708 }
1709 })
1710 .collect::<Vec<_>>();
1711
1712 v.into_iter().collect()
1713 }
1714
1715 #[must_use]
1729 pub fn restyle_on_state_change(
1730 &mut self,
1731 focus_changes: Option<FocusChange>,
1732 hover_changes: Option<HoverChange>,
1733 active_changes: Option<ActiveChange>,
1734 ) -> RestyleResult {
1735
1736 let mut result = RestyleResult {
1738 gpu_only_changes: true,
1739 ..RestyleResult::default()
1740 };
1741
1742 let mut process_changes = |changes: RestyleNodes| {
1744 for (node_id, props) in changes {
1745 for change in &props {
1746 let prop_type = change.current_prop.get_type();
1747
1748 let scope = prop_type.relayout_scope(true);
1755
1756 if scope > result.max_relayout_scope {
1758 result.max_relayout_scope = scope;
1759 }
1760
1761 if scope != RelayoutScope::None {
1763 result.needs_layout = true;
1764 result.gpu_only_changes = false;
1765 }
1766
1767 if !prop_type.is_gpu_only_property() {
1769 result.gpu_only_changes = false;
1770 }
1771
1772 result.needs_display_list = true;
1774 }
1775
1776 result.changed_nodes.entry(node_id).or_default().extend(props);
1777 }
1778 };
1779
1780 if let Some(focus) = focus_changes {
1782 if let Some(old) = focus.lost_focus {
1783 let changes = self.restyle_nodes_focus(&[old], false);
1784 process_changes(changes);
1785 }
1786 if let Some(new) = focus.gained_focus {
1787 let changes = self.restyle_nodes_focus(&[new], true);
1788 process_changes(changes);
1789 }
1790 }
1791
1792 if let Some(hover) = hover_changes {
1794 if !hover.left_nodes.is_empty() {
1795 let changes = self.restyle_nodes_hover(&hover.left_nodes, false);
1796 process_changes(changes);
1797 }
1798 if !hover.entered_nodes.is_empty() {
1799 let changes = self.restyle_nodes_hover(&hover.entered_nodes, true);
1800 process_changes(changes);
1801 }
1802 }
1803
1804 if let Some(active) = active_changes {
1806 if !active.deactivated.is_empty() {
1807 let changes = self.restyle_nodes_active(&active.deactivated, false);
1808 process_changes(changes);
1809 }
1810 if !active.activated.is_empty() {
1811 let changes = self.restyle_nodes_active(&active.activated, true);
1812 process_changes(changes);
1813 }
1814 }
1815
1816 if result.changed_nodes.is_empty() {
1818 result.needs_display_list = false;
1819 result.gpu_only_changes = false;
1820 }
1821
1822 if result.needs_layout {
1824 result.needs_display_list = true;
1825 result.gpu_only_changes = false;
1826 }
1827
1828 result
1829 }
1830
1831 #[must_use]
1842 pub fn restyle_user_property(
1843 &mut self,
1844 node_id: &NodeId,
1845 new_properties: &[CssProperty],
1846 ) -> RestyleNodes {
1847 let mut map = BTreeMap::default();
1848
1849 if new_properties.is_empty() {
1850 return map;
1851 }
1852
1853 let node_count = self.node_data.as_ref().len();
1854 if node_id.index() >= node_count {
1855 return map;
1856 }
1857
1858 let node_data = self.node_data.as_container();
1859 let node_data = &node_data[*node_id];
1860
1861 let node_states = &self.styled_nodes.as_container();
1862 let old_node_state = &node_states[*node_id].styled_node_state;
1863
1864 let changes: Vec<ChangedCssProperty> = {
1865 let css_property_cache = self.get_css_property_cache();
1866
1867 new_properties
1868 .iter()
1869 .filter_map(|new_prop| {
1870 let old_prop = css_property_cache.get_property_slow(
1871 node_data,
1872 node_id,
1873 old_node_state,
1874 &new_prop.get_type(),
1875 );
1876
1877 let old_prop = old_prop.map_or_else(|| CssProperty::auto(new_prop.get_type()), Clone::clone);
1878
1879 if old_prop == *new_prop {
1880 None
1881 } else {
1882 Some(ChangedCssProperty {
1883 previous_state: *old_node_state,
1884 previous_prop: old_prop,
1885 current_state: *old_node_state,
1887 current_prop: new_prop.clone(),
1888 })
1889 }
1890 })
1891 .collect()
1892 };
1893
1894 let css_property_cache_mut = self.get_css_property_cache_mut();
1895
1896 if css_property_cache_mut.user_overridden_properties.len() < node_count {
1901 css_property_cache_mut
1902 .user_overridden_properties
1903 .resize(node_count, Vec::new());
1904 }
1905
1906 for new_prop in new_properties {
1907 let prop_type = new_prop.get_type();
1908 let vec = &mut css_property_cache_mut
1909 .user_overridden_properties[node_id.index()];
1910 if new_prop.is_initial() {
1911 if let Ok(idx) = vec.binary_search_by_key(&prop_type, |(k, _)| *k) {
1913 vec.remove(idx);
1914 }
1915 } else {
1916 match vec.binary_search_by_key(&prop_type, |(k, _)| *k) {
1917 Ok(idx) => vec[idx].1 = new_prop.clone(),
1918 Err(idx) => vec.insert(idx, (prop_type, new_prop.clone())),
1919 }
1920 }
1921 }
1922
1923 if !changes.is_empty() {
1924 map.insert(*node_id, changes);
1925 }
1926
1927 map
1928 }
1929
1930 #[must_use] pub fn get_html_string(&self, custom_head: &str, custom_body: &str, test_mode: bool) -> String {
1940 let css_property_cache = self.get_css_property_cache();
1941
1942 let mut output = String::new();
1943
1944 let mut should_print_close_tag_after_node: BTreeMap<NodeId, Vec<(NodeId, usize)>> = BTreeMap::new();
1946
1947 let should_print_close_tag_debug = self
1948 .non_leaf_nodes
1949 .iter()
1950 .filter_map(|p| {
1951 let parent_node_id = p.node_id.into_crate_internal()?;
1952 let mut total_last_child = None;
1953 recursive_get_last_child(
1954 parent_node_id,
1955 self.node_hierarchy.as_ref(),
1956 &mut total_last_child,
1957 );
1958 let total_last_child = total_last_child?;
1959 Some((parent_node_id, (total_last_child, p.depth)))
1960 })
1961 .collect::<BTreeMap<_, _>>();
1962
1963 for (parent_id, (last_child, parent_depth)) in should_print_close_tag_debug {
1964 should_print_close_tag_after_node
1965 .entry(last_child)
1966 .or_default()
1967 .push((parent_id, parent_depth));
1968 }
1969
1970 let mut all_node_depths = self
1971 .non_leaf_nodes
1972 .iter()
1973 .filter_map(|p| {
1974 let parent_node_id = p.node_id.into_crate_internal()?;
1975 Some((parent_node_id, p.depth))
1976 })
1977 .collect::<BTreeMap<_, _>>();
1978
1979 for (parent_node_id, parent_depth) in self
1980 .non_leaf_nodes
1981 .iter()
1982 .filter_map(|p| Some((p.node_id.into_crate_internal()?, p.depth)))
1983 {
1984 for child_id in parent_node_id.az_children(&self.node_hierarchy.as_container()) {
1985 all_node_depths.insert(child_id, parent_depth + 1);
1986 }
1987 }
1988
1989 for node_id in self.node_hierarchy.as_container().linear_iter() {
1990 let depth = all_node_depths.get(&node_id).copied().unwrap_or(0);
1994
1995 let node_data = &self.node_data.as_container()[node_id];
1996 let node_state = &self.styled_nodes.as_container()[node_id].styled_node_state;
1997 let tabs = String::from(" ").repeat(depth);
1998
1999 output.push_str("\r\n");
2000 output.push_str(&tabs);
2001 output.push_str(&node_data.debug_print_start(css_property_cache, &node_id, node_state));
2002
2003 if let Some(content) = node_data.get_node_type().format().as_ref() {
2004 output.push_str(content);
2005 }
2006
2007 let node_has_children = self.node_hierarchy.as_container()[node_id]
2008 .first_child_id(node_id)
2009 .is_some();
2010 if !node_has_children {
2011 let node_data = &self.node_data.as_container()[node_id];
2012 output.push_str(&node_data.debug_print_end());
2013 }
2014
2015 if let Some(close_tag_vec) = should_print_close_tag_after_node.get(&node_id) {
2016 let mut close_tag_vec = close_tag_vec.clone();
2017 close_tag_vec.sort_by(|a, b| b.1.cmp(&a.1)); for (close_tag_parent_id, close_tag_depth) in close_tag_vec {
2019 let node_data = &self.node_data.as_container()[close_tag_parent_id];
2020 let tabs = String::from(" ").repeat(close_tag_depth);
2021 output.push_str("\r\n");
2022 output.push_str(&tabs);
2023 output.push_str(&node_data.debug_print_end());
2024 }
2025 }
2026 }
2027
2028 if test_mode {
2029 output
2030 } else {
2031 format!(
2032 "
2033 <html>
2034 <head>
2035 <style>* {{ margin:0px; padding:0px; }}</style>
2036 {custom_head}
2037 </head>
2038 {output}
2039 {custom_body}
2040 </html>
2041 "
2042 )
2043 }
2044 }
2045
2046 #[must_use] pub fn get_rects_in_rendering_order(&self) -> ContentGroup {
2048 Self::determine_rendering_order(
2049 self.non_leaf_nodes.as_ref(),
2050 &self.node_hierarchy.as_container(),
2051 &self.styled_nodes.as_container(),
2052 &self.node_data.as_container(),
2053 self.get_css_property_cache(),
2054 )
2055 }
2056
2057 fn determine_rendering_order(
2060 non_leaf_nodes: &[ParentWithNodeDepth],
2061 node_hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2062 styled_nodes: &NodeDataContainerRef<'_, StyledNode>,
2063 node_data_container: &NodeDataContainerRef<'_, NodeData>,
2064 css_property_cache: &CssPropertyCache,
2065 ) -> ContentGroup {
2066 let children_sorted = non_leaf_nodes
2067 .iter()
2068 .filter_map(|parent| {
2069 Some((
2070 parent.node_id,
2071 sort_children_by_position(
2072 parent.node_id.into_crate_internal()?,
2073 node_hierarchy,
2074 styled_nodes,
2075 node_data_container,
2076 css_property_cache,
2077 ),
2078 ))
2079 })
2080 .collect::<Vec<_>>();
2081
2082 let children_sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> =
2083 children_sorted.into_iter().collect();
2084
2085 let mut root_content_group = ContentGroup {
2086 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
2087 children: Vec::new().into(),
2088 };
2089
2090 fill_content_group_children(&mut root_content_group, &children_sorted);
2091
2092 root_content_group
2093 }
2094
2095 #[must_use] pub fn swap_with_default(&mut self) -> Self {
2097 let mut new = Self::default();
2098 core::mem::swap(self, &mut new);
2099 new
2100 }
2101
2102}
2103
2104#[derive(Debug, PartialEq, PartialOrd, Eq)]
2106pub struct CompactDom {
2107 pub node_hierarchy: NodeHierarchy,
2109 pub node_data: NodeDataContainer<NodeData>,
2111 pub root: NodeId,
2113}
2114
2115impl CompactDom {
2116 #[inline]
2118 #[must_use] pub fn len(&self) -> usize {
2119 self.node_hierarchy.as_ref().len()
2120 }
2121
2122 #[inline]
2124 #[must_use] pub fn is_empty(&self) -> bool {
2125 self.node_hierarchy.as_ref().is_empty()
2126 }
2127}
2128
2129impl From<Dom> for CompactDom {
2130 fn from(dom: Dom) -> Self {
2131 convert_dom_into_compact_dom(dom)
2132 }
2133}
2134
2135#[must_use] pub fn convert_dom_into_compact_dom(mut dom: Dom) -> CompactDom {
2137 fn convert_dom_into_compact_dom_internal(
2139 dom: &mut Dom,
2140 node_hierarchy: &mut [Node],
2141 node_data: &mut Vec<NodeData>,
2142 parent_node_id: NodeId,
2143 node: Node,
2144 cur_node_id: &mut usize,
2145 ) {
2146 node_hierarchy[parent_node_id.index()] = node;
2157
2158 let copy = dom.root.copy_special_moving_complex();
2170
2171 node_data[parent_node_id.index()] = copy;
2172
2173 *cur_node_id += 1;
2174
2175 let mut previous_sibling_id = None;
2176 let children_len = dom.children.len();
2177 for (child_index, child_dom) in dom.children.as_mut().iter_mut().enumerate() {
2178 let child_node_id = NodeId::new(*cur_node_id);
2179 let is_last_child = (child_index + 1) == children_len;
2180 let child_dom_is_empty = child_dom.children.is_empty();
2181 let child_node = Node {
2182 parent: Some(parent_node_id),
2183 previous_sibling: previous_sibling_id,
2184 next_sibling: if is_last_child {
2185 None
2186 } else {
2187 Some(child_node_id + child_dom.estimated_total_children + 1)
2188 },
2189 last_child: if child_dom_is_empty {
2190 None
2191 } else {
2192 Some(child_node_id + child_dom.estimated_total_children)
2193 },
2194 };
2195 previous_sibling_id = Some(child_node_id);
2196 convert_dom_into_compact_dom_internal(
2198 child_dom,
2199 node_hierarchy,
2200 node_data,
2201 child_node_id,
2202 child_node,
2203 cur_node_id,
2204 );
2205 }
2206
2207 node_hierarchy[parent_node_id.index()].last_child = previous_sibling_id;
2216 }
2217
2218 let sum_nodes = dom.fixup_children_estimated();
2220
2221 let mut node_hierarchy = vec![Node::ROOT; sum_nodes + 1];
2222 let mut node_data = vec![NodeData::create_div(); sum_nodes + 1];
2223 let mut cur_node_id = 0;
2224
2225 let root_node_id = NodeId::ZERO;
2226 let root_node = Node {
2227 parent: None,
2228 previous_sibling: None,
2229 next_sibling: None,
2230 last_child: if dom.children.is_empty() {
2231 None
2232 } else {
2233 Some(root_node_id + dom.estimated_total_children)
2234 },
2235 };
2236
2237 convert_dom_into_compact_dom_internal(
2238 &mut dom,
2239 &mut node_hierarchy,
2240 &mut node_data,
2241 root_node_id,
2242 root_node,
2243 &mut cur_node_id,
2244 );
2245
2246 CompactDom {
2247 node_hierarchy: NodeHierarchy {
2248 internal: node_hierarchy,
2249 },
2250 node_data: NodeDataContainer {
2251 internal: node_data,
2252 },
2253 root: root_node_id,
2254 }
2255}
2256
2257fn scope_inline_css(dom: &mut Dom, next_id: &mut usize) {
2265 let start = *next_id;
2266 let end = start + dom.estimated_total_children;
2267 for css in dom.css.as_mut().iter_mut() {
2268 for rule in css.rules.as_mut().iter_mut() {
2269 rule.path.push_front_scope(start, end);
2273 }
2274 }
2275 *next_id += 1;
2276 for child in dom.children.as_mut().iter_mut() {
2277 scope_inline_css(child, next_id);
2278 }
2279}
2280
2281fn collect_css_from_dom(dom: &Dom, out: &mut Vec<Css>) {
2285 for child in &dom.children {
2287 collect_css_from_dom(child, out);
2288 }
2289 for css in &dom.css {
2291 out.push(css.clone());
2292 }
2293}
2294
2295fn strip_css_from_dom(dom: &mut Dom) {
2298 dom.css = Vec::new().into();
2299 for child in dom.children.as_mut().iter_mut() {
2300 strip_css_from_dom(child);
2301 }
2302}
2303
2304fn fill_content_group_children(
2305 group: &mut ContentGroup,
2306 children_sorted: &BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>>,
2307) {
2308 if let Some(c) = children_sorted.get(&group.root) {
2309 group.children = c
2311 .iter()
2312 .map(|child| ContentGroup {
2313 root: *child,
2314 children: Vec::new().into(),
2315 })
2316 .collect::<Vec<ContentGroup>>()
2317 .into();
2318
2319 for c in group.children.as_mut() {
2320 fill_content_group_children(c, children_sorted);
2321 }
2322 }
2323}
2324
2325fn sort_children_by_position(
2326 parent: NodeId,
2327 node_hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2328 rectangles: &NodeDataContainerRef<'_, StyledNode>,
2329 node_data_container: &NodeDataContainerRef<'_, NodeData>,
2330 css_property_cache: &CssPropertyCache,
2331) -> Vec<NodeHierarchyItemId> {
2332 use azul_css::props::layout::LayoutPosition::Absolute;
2333
2334 let children_positions = parent
2335 .az_children(node_hierarchy)
2336 .map(|nid| {
2337 let position = css_property_cache
2338 .get_position(
2339 &node_data_container[nid],
2340 &nid,
2341 &rectangles[nid].styled_node_state,
2342 )
2343 .and_then(|p| (*p).get_property_or_default())
2344 .unwrap_or_default();
2345 let id = NodeHierarchyItemId::from_crate_internal(Some(nid));
2346 (id, position)
2347 })
2348 .collect::<Vec<_>>();
2349
2350 let mut not_absolute_children = children_positions
2351 .iter()
2352 .filter_map(|(node_id, position)| {
2353 if *position == Absolute {
2354 None
2355 } else {
2356 Some(*node_id)
2357 }
2358 })
2359 .collect::<Vec<_>>();
2360
2361 let mut absolute_children = children_positions
2362 .iter()
2363 .filter_map(|(node_id, position)| {
2364 if *position == Absolute {
2365 Some(*node_id)
2366 } else {
2367 None
2368 }
2369 })
2370 .collect::<Vec<_>>();
2371
2372 not_absolute_children.append(&mut absolute_children);
2374 not_absolute_children
2375}
2376
2377fn recursive_get_last_child(
2380 node_id: NodeId,
2381 node_hierarchy: &[NodeHierarchyItem],
2382 target: &mut Option<NodeId>,
2383) {
2384 match node_hierarchy[node_id.index()].last_child_id() {
2385 None => (),
2386 Some(s) => {
2387 *target = Some(s);
2388 recursive_get_last_child(s, node_hierarchy, target);
2389 }
2390 }
2391}
2392
2393#[must_use] pub fn is_before_in_document_order(
2407 hierarchy: &NodeHierarchyItemVec,
2408 node_a: NodeId,
2409 node_b: NodeId,
2410) -> bool {
2411 if node_a == node_b {
2412 return false;
2413 }
2414
2415 let hierarchy = hierarchy.as_container();
2416
2417 let path_a = get_path_to_root(&hierarchy, node_a);
2419 let path_b = get_path_to_root(&hierarchy, node_b);
2420
2421 let min_len = path_a.len().min(path_b.len());
2423
2424 for i in 0..min_len {
2425 if path_a[i] != path_b[i] {
2426 let child_towards_a = path_a[i];
2428 let child_towards_b = path_b[i];
2429
2430 return child_towards_a.index() < child_towards_b.index();
2433 }
2434 }
2435
2436 path_a.len() < path_b.len()
2438}
2439
2440fn get_path_to_root(
2442 hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2443 node: NodeId,
2444) -> Vec<NodeId> {
2445 let mut path = Vec::new();
2446 let mut current = Some(node);
2447
2448 while let Some(node_id) = current {
2449 path.push(node_id);
2450 current = hierarchy.get(node_id).and_then(NodeHierarchyItem::parent_id);
2451 }
2452
2453 path.reverse();
2455 path
2456}
2457
2458#[must_use] pub fn collect_nodes_in_document_order(
2471 hierarchy: &NodeHierarchyItemVec,
2472 start_node: NodeId,
2473 end_node: NodeId,
2474) -> Vec<NodeId> {
2475 if start_node == end_node {
2476 return vec![start_node];
2477 }
2478
2479 let hierarchy_container = hierarchy.as_container();
2480 let hierarchy_slice = hierarchy.as_ref();
2481
2482 let mut result = Vec::new();
2483 let mut in_range = false;
2484
2485 let mut stack: Vec<NodeId> = vec![NodeId::ZERO]; while let Some(current) = stack.pop() {
2490 if current == start_node {
2492 in_range = true;
2493 }
2494
2495 if in_range {
2497 result.push(current);
2498 }
2499
2500 if current == end_node {
2502 break;
2503 }
2504
2505 if let Some(item) = hierarchy_container.get(current) {
2508 if let Some(first_child) = item.first_child_id(current) {
2510 let mut children = Vec::new();
2512 let mut child = Some(first_child);
2513 while let Some(child_id) = child {
2514 children.push(child_id);
2515 child = hierarchy_container.get(child_id).and_then(NodeHierarchyItem::next_sibling_id);
2516 }
2517 for child_id in children.into_iter().rev() {
2519 stack.push(child_id);
2520 }
2521 }
2522 }
2523 }
2524
2525 result
2526}
2527
2528#[must_use] pub fn is_layout_equivalent(old: &StyledDom, new: &StyledDom) -> bool {
2542 use crate::dom::NodeType;
2543 use crate::resources::DecodedImage;
2544
2545 let old_nodes = old.node_data.as_ref();
2547 let new_nodes = new.node_data.as_ref();
2548 if old_nodes.len() != new_nodes.len() {
2549 return false;
2550 }
2551
2552 let old_hier = old.node_hierarchy.as_ref();
2554 let new_hier = new.node_hierarchy.as_ref();
2555 if old_hier.len() != new_hier.len() {
2556 return false;
2557 }
2558 if old_hier != new_hier {
2559 return false;
2560 }
2561
2562 for (old_node, new_node) in old_nodes.iter().zip(new_nodes.iter()) {
2564
2565 if core::mem::discriminant(&old_node.node_type)
2567 != core::mem::discriminant(&new_node.node_type)
2568 {
2569 return false;
2570 }
2571
2572 match (&old_node.node_type, &new_node.node_type) {
2574 (NodeType::Image(old_img), NodeType::Image(new_img)) => {
2575 match (old_img.get_data(), new_img.get_data()) {
2576 (DecodedImage::Callback(old_cb), DecodedImage::Callback(new_cb)) => {
2577 if old_cb.callback.cb != new_cb.callback.cb {
2579 return false;
2580 }
2581 if old_cb.refany.get_type_id() != new_cb.refany.get_type_id() {
2583 return false;
2584 }
2585 }
2586 _ => {
2587 if old_img != new_img {
2589 return false;
2590 }
2591 }
2592 }
2593 }
2594 _ => {
2595 if old_node.node_type != new_node.node_type {
2596 return false;
2597 }
2598 }
2599 }
2600
2601 {
2603 use crate::dom::AttributeType;
2604 let old_ids_classes: Vec<_> = old_node.attributes().as_ref().iter()
2605 .filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
2606 .collect();
2607 let new_ids_classes: Vec<_> = new_node.attributes().as_ref().iter()
2608 .filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
2609 .collect();
2610 if old_ids_classes != new_ids_classes {
2611 return false;
2612 }
2613 }
2614
2615 if old_node.style != new_node.style {
2617 return false;
2618 }
2619
2620 let old_cbs = old_node.callbacks.as_ref();
2623 let new_cbs = new_node.callbacks.as_ref();
2624 if old_cbs.len() != new_cbs.len() {
2625 return false;
2626 }
2627 for (old_cb, new_cb) in old_cbs.iter().zip(new_cbs.iter()) {
2628 if old_cb.event != new_cb.event {
2629 return false;
2630 }
2631 }
2632
2633 if old_node.attributes().as_ref() != new_node.attributes().as_ref() {
2635 return false;
2636 }
2637 }
2638
2639 let old_styled = old.styled_nodes.as_ref();
2641 let new_styled = new.styled_nodes.as_ref();
2642 if old_styled.len() != new_styled.len() {
2643 return false;
2644 }
2645 if old_styled != new_styled {
2646 return false;
2647 }
2648
2649 true
2650}
2651
2652#[cfg(test)]
2653mod audit_tests {
2654 use super::*;
2655 use azul_css::props::basic::StyleFontFamily;
2656
2657 fn fam(name: &str) -> StyleFontFamily {
2658 StyleFontFamily::System(name.to_string().into())
2659 }
2660
2661 #[test]
2662 fn style_font_families_hash_is_length_sensitive() {
2663 let a = StyleFontFamiliesHash::new(&[fam("Arial")]);
2666 let a2 = StyleFontFamiliesHash::new(&[fam("Arial")]);
2667 assert_eq!(a, a2, "hash must be deterministic");
2668
2669 let two = StyleFontFamiliesHash::new(&[fam("Arial"), fam("Helvetica")]);
2670 assert_ne!(a, two, "different-length family lists must not collide");
2671
2672 let empty = StyleFontFamiliesHash::new(&[]);
2673 assert_ne!(empty, a);
2674 assert_ne!(empty, two);
2675
2676 let rev = StyleFontFamiliesHash::new(&[fam("Helvetica"), fam("Arial")]);
2678 assert_ne!(two, rev);
2679 }
2680}
2681
2682#[cfg(test)]
2683#[allow(clippy::too_many_lines)]
2684mod autotest_generated {
2685 use azul_css::{
2686 dynamic_selector::PseudoStateFlags,
2687 props::basic::StyleFontFamily,
2688 };
2689
2690 use super::*;
2691
2692 const fn raw_item(parent: usize, prev: usize, next: usize, last: usize) -> NodeHierarchyItem {
2699 NodeHierarchyItem {
2700 parent,
2701 previous_sibling: prev,
2702 next_sibling: next,
2703 last_child: last,
2704 }
2705 }
2706
2707 fn flat_body(n: usize) -> StyledDom {
2710 let children: Vec<Dom> = (0..n).map(|_| Dom::create_div()).collect();
2711 let mut dom = Dom::create_body().with_children(children.into());
2712 StyledDom::create(&mut dom, Css::empty())
2713 }
2714
2715 fn nested_body() -> StyledDom {
2717 let mut dom = Dom::create_body().with_children(
2718 vec![Dom::create_div().with_children(vec![Dom::create_div()].into())].into(),
2719 );
2720 StyledDom::create(&mut dom, Css::empty())
2721 }
2722
2723 fn parse_css(s: &str) -> Css {
2724 azul_css::parser2::new_from_str(s).0
2725 }
2726
2727 fn family(name: &str) -> StyleFontFamily {
2728 StyleFontFamily::System(name.to_string().into())
2729 }
2730
2731 const fn pseudo_flags(all: bool) -> PseudoStateFlags {
2732 PseudoStateFlags {
2733 hover: all,
2734 active: all,
2735 focused: all,
2736 disabled: all,
2737 checked: all,
2738 focus_within: all,
2739 visited: all,
2740 backdrop: all,
2741 dragging: all,
2742 drag_over: all,
2743 }
2744 }
2745
2746 fn empty_menu() -> Menu {
2747 let items: Vec<crate::menu::MenuItem> = Vec::new();
2748 Menu::create(items.into())
2749 }
2750
2751 #[test]
2756 fn restyle_result_default_reports_no_changes() {
2757 let r = RestyleResult::default();
2758 assert!(!r.has_changes());
2759 assert!(!r.needs_layout);
2760 assert!(!r.needs_display_list);
2761 assert!(!r.gpu_only_changes);
2762 assert_eq!(r.max_relayout_scope, RelayoutScope::None);
2763 }
2764
2765 #[test]
2766 fn restyle_result_has_changes_keys_off_node_map_not_property_count() {
2767 let mut r = RestyleResult::default();
2770 r.changed_nodes.insert(NodeId::ZERO, Vec::new());
2771 assert!(r.has_changes());
2772
2773 r.changed_nodes.clear();
2774 assert!(!r.has_changes());
2775 }
2776
2777 #[test]
2778 fn restyle_result_merge_ors_layout_flags_and_ands_gpu_only() {
2779 let mut a = RestyleResult {
2780 needs_layout: false,
2781 needs_display_list: false,
2782 gpu_only_changes: true,
2783 ..RestyleResult::default()
2784 };
2785 let b = RestyleResult {
2786 needs_layout: true,
2787 needs_display_list: true,
2788 gpu_only_changes: true,
2789 ..RestyleResult::default()
2790 };
2791 a.merge(b);
2792 assert!(a.needs_layout, "needs_layout is OR-ed");
2793 assert!(a.needs_display_list, "needs_display_list is OR-ed");
2794 assert!(a.gpu_only_changes, "true && true stays true");
2795
2796 let mut c = RestyleResult {
2798 gpu_only_changes: true,
2799 ..RestyleResult::default()
2800 };
2801 c.merge(RestyleResult {
2802 gpu_only_changes: false,
2803 ..RestyleResult::default()
2804 });
2805 assert!(!c.gpu_only_changes, "gpu_only_changes is AND-ed");
2806 }
2807
2808 #[test]
2809 fn restyle_result_merge_keeps_the_most_expensive_scope() {
2810 let mut low = RestyleResult {
2811 max_relayout_scope: RelayoutScope::None,
2812 ..RestyleResult::default()
2813 };
2814 low.merge(RestyleResult {
2815 max_relayout_scope: RelayoutScope::Full,
2816 ..RestyleResult::default()
2817 });
2818 assert_eq!(low.max_relayout_scope, RelayoutScope::Full);
2819
2820 let mut high = RestyleResult {
2822 max_relayout_scope: RelayoutScope::Full,
2823 ..RestyleResult::default()
2824 };
2825 high.merge(RestyleResult {
2826 max_relayout_scope: RelayoutScope::IfcOnly,
2827 ..RestyleResult::default()
2828 });
2829 assert_eq!(high.max_relayout_scope, RelayoutScope::Full);
2830 }
2831
2832 #[test]
2833 fn restyle_result_merge_of_default_is_not_the_identity_for_gpu_only() {
2834 let mut a = RestyleResult {
2838 gpu_only_changes: true,
2839 ..RestyleResult::default()
2840 };
2841 a.merge(RestyleResult::default());
2842 assert!(!a.gpu_only_changes);
2843 assert!(!a.has_changes());
2844 }
2845
2846 #[test]
2847 fn restyle_result_merge_concatenates_changes_for_the_same_node() {
2848 let prop = |t| ChangedCssProperty {
2849 previous_state: StyledNodeState::new(),
2850 previous_prop: CssProperty::auto(t),
2851 current_state: StyledNodeState::new(),
2852 current_prop: CssProperty::initial(t),
2853 };
2854
2855 let mut a = RestyleResult::default();
2856 a.changed_nodes
2857 .insert(NodeId::ZERO, vec![prop(CssPropertyType::Width)]);
2858
2859 let mut b = RestyleResult::default();
2860 b.changed_nodes
2861 .insert(NodeId::ZERO, vec![prop(CssPropertyType::Height)]);
2862 b.changed_nodes
2863 .insert(NodeId::new(1), vec![prop(CssPropertyType::Opacity)]);
2864
2865 a.merge(b);
2866
2867 assert_eq!(a.changed_nodes.len(), 2);
2868 assert_eq!(
2869 a.changed_nodes[&NodeId::ZERO].len(),
2870 2,
2871 "changes for the same node are appended, not replaced"
2872 );
2873 assert_eq!(a.changed_nodes[&NodeId::new(1)].len(), 1);
2874 assert!(a.has_changes());
2875 }
2876
2877 #[test]
2882 fn styled_node_state_new_is_all_false_and_normal() {
2883 let s = StyledNodeState::new();
2884 assert!(s.is_normal());
2885 assert!(!s.hover);
2886 assert!(!s.active);
2887 assert!(!s.focused);
2888 assert!(!s.disabled);
2889 assert!(!s.checked);
2890 assert!(!s.focus_within);
2891 assert!(!s.visited);
2892 assert!(!s.backdrop);
2893 assert!(!s.dragging);
2894 assert!(!s.drag_over);
2895 assert_eq!(s, StyledNodeState::default());
2896 }
2897
2898 #[test]
2899 fn styled_node_state_has_state_zero_is_always_true() {
2900 assert!(StyledNodeState::new().has_state(0));
2902 assert!(StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true)).has_state(0));
2903 }
2904
2905 #[test]
2906 fn styled_node_state_has_state_maps_every_index_exactly_once() {
2907 let setters: [(u8, fn(&mut StyledNodeState)); 10] = [
2909 (1, |s| s.hover = true),
2910 (2, |s| s.active = true),
2911 (3, |s| s.focused = true),
2912 (4, |s| s.disabled = true),
2913 (5, |s| s.checked = true),
2914 (6, |s| s.focus_within = true),
2915 (7, |s| s.visited = true),
2916 (8, |s| s.backdrop = true),
2917 (9, |s| s.dragging = true),
2918 (10, |s| s.drag_over = true),
2919 ];
2920
2921 for (expected_idx, set) in setters {
2922 let mut s = StyledNodeState::new();
2923 set(&mut s);
2924 assert!(!s.is_normal(), "state {expected_idx} must not be 'normal'");
2925 for idx in 1..=10u8 {
2926 assert_eq!(
2927 s.has_state(idx),
2928 idx == expected_idx,
2929 "state index {idx} misreported for setter {expected_idx}"
2930 );
2931 }
2932 }
2933 }
2934
2935 #[test]
2936 fn styled_node_state_has_state_is_false_for_every_out_of_range_u8() {
2937 let all_on = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true));
2938 for idx in 11..=u8::MAX {
2939 assert!(!StyledNodeState::new().has_state(idx));
2940 assert!(
2941 !all_on.has_state(idx),
2942 "unknown state index {idx} must be inactive even when every flag is set"
2943 );
2944 }
2945 }
2946
2947 #[test]
2948 fn styled_node_state_from_pseudo_state_flags_roundtrips_every_field() {
2949 let all_on = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true));
2950 assert!(!all_on.is_normal());
2951 for idx in 0..=10u8 {
2952 assert!(all_on.has_state(idx), "state {idx} should be active");
2953 }
2954
2955 let all_off = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(false));
2956 assert!(all_off.is_normal());
2957 assert_eq!(all_off, StyledNodeState::new());
2958 }
2959
2960 #[test]
2961 fn styled_node_state_debug_lists_active_states_and_normal_when_empty() {
2962 assert_eq!(format!("{:?}", StyledNodeState::new()), "[\"normal\"]");
2963
2964 let mut s = StyledNodeState::new();
2965 s.hover = true;
2966 s.drag_over = true;
2967 let dbg = format!("{s:?}");
2968 assert!(dbg.contains("hover"), "{dbg}");
2969 assert!(dbg.contains("drag_over"), "{dbg}");
2970 assert!(!dbg.contains("normal"), "{dbg}");
2971 }
2972
2973 #[test]
2978 fn styled_node_vec_empty_container_is_empty_and_get_returns_none() {
2979 let v: StyledNodeVec = Vec::new().into();
2980 let c = v.as_container();
2981 assert_eq!(c.len(), 0);
2982 assert!(c.is_empty());
2983 assert!(c.get(NodeId::ZERO).is_none());
2984 assert!(c.get(NodeId::new(usize::MAX)).is_none());
2985 }
2986
2987 #[test]
2988 fn styled_node_vec_container_mut_writes_are_visible_through_container() {
2989 let mut v: StyledNodeVec = vec![StyledNode::default(), StyledNode::default()].into();
2990 {
2991 let mut c = v.as_container_mut();
2992 c[NodeId::new(1)].styled_node_state.hover = true;
2993 }
2994 let c = v.as_container();
2995 assert_eq!(c.len(), 2);
2996 assert!(!c[NodeId::ZERO].styled_node_state.hover);
2997 assert!(c[NodeId::new(1)].styled_node_state.hover);
2998 assert!(c.get(NodeId::new(2)).is_none());
2999 }
3000
3001 #[test]
3006 fn style_font_family_hash_is_deterministic_and_input_sensitive() {
3007 assert_eq!(
3008 StyleFontFamilyHash::new(&family("Arial")),
3009 StyleFontFamilyHash::new(&family("Arial"))
3010 );
3011 assert_ne!(
3012 StyleFontFamilyHash::new(&family("Arial")),
3013 StyleFontFamilyHash::new(&family("Ariaĺ"))
3014 );
3015 assert_ne!(
3017 StyleFontFamilyHash::new(&StyleFontFamily::System("x".to_string().into())),
3018 StyleFontFamilyHash::new(&StyleFontFamily::File("x".to_string().into()))
3019 );
3020 }
3021
3022 #[test]
3023 fn style_font_family_hash_handles_empty_unicode_and_huge_names() {
3024 let empty = family("");
3025 let unicode = family("🦀 ノート ﷽ عربى");
3026 let huge = family(&"A".repeat(100_000));
3027
3028 assert_eq!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&empty));
3030 assert_eq!(
3031 StyleFontFamilyHash::new(&unicode),
3032 StyleFontFamilyHash::new(&unicode)
3033 );
3034 assert_eq!(StyleFontFamilyHash::new(&huge), StyleFontFamilyHash::new(&huge));
3035 assert_ne!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&unicode));
3036 assert_ne!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&huge));
3037 }
3038
3039 #[test]
3040 fn style_font_families_hash_empty_slice_is_stable_and_distinct() {
3041 let empty = StyleFontFamiliesHash::new(&[]);
3042 assert_eq!(empty, StyleFontFamiliesHash::new(&[]));
3043 assert_ne!(empty, StyleFontFamiliesHash::new(&[family("")]));
3044 }
3045
3046 #[test]
3047 fn style_font_families_hash_scales_to_large_lists_and_is_length_sensitive() {
3048 let big: Vec<StyleFontFamily> = (0..1000).map(|i| family(&format!("font-{i}"))).collect();
3049 let one_shorter = &big[..999];
3050
3051 assert_eq!(
3052 StyleFontFamiliesHash::new(&big),
3053 StyleFontFamiliesHash::new(&big),
3054 "hashing 1000 families must be deterministic"
3055 );
3056 assert_ne!(
3057 StyleFontFamiliesHash::new(&big),
3058 StyleFontFamiliesHash::new(one_shorter),
3059 "the length prefix must separate [0..1000) from [0..999)"
3060 );
3061 }
3062
3063 #[test]
3068 fn node_hierarchy_item_id_none_is_zero() {
3069 assert_eq!(NodeHierarchyItemId::NONE.into_raw(), 0);
3070 assert_eq!(NodeHierarchyItemId::NONE.into_crate_internal(), None);
3071 assert_eq!(NodeHierarchyItemId::from_crate_internal(None).into_raw(), 0);
3072 assert_eq!(NodeHierarchyItemId::from_raw(0).into_crate_internal(), None);
3073 assert_eq!(NodeHierarchyItemId::from_crate_internal(None), NodeHierarchyItemId::NONE);
3074 }
3075
3076 #[test]
3077 fn node_hierarchy_item_id_encode_decode_roundtrip_at_boundaries() {
3078 for idx in [0usize, 1, 2, 1023, usize::MAX / 2, usize::MAX - 1] {
3080 let id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(idx)));
3081 assert_eq!(id.into_raw(), idx + 1, "1-based encoding for {idx}");
3082 assert_eq!(
3083 id.into_crate_internal(),
3084 Some(NodeId::new(idx)),
3085 "decode(encode(x)) == x for {idx}"
3086 );
3087 }
3088 }
3089
3090 #[test]
3091 fn node_hierarchy_item_id_raw_roundtrip_is_identity_even_at_usize_max() {
3092 for raw in [0usize, 1, 2, 7, u32::MAX as usize, usize::MAX] {
3093 let decoded = NodeHierarchyItemId::from_raw(raw).into_crate_internal();
3094 let reencoded = NodeHierarchyItemId::from_crate_internal(decoded).into_raw();
3095 assert_eq!(reencoded, raw, "encode(decode(raw)) must be identity for {raw}");
3096 }
3097 }
3098
3099 #[test]
3100 fn node_hierarchy_item_id_from_raw_decodes_one_based() {
3101 assert_eq!(
3102 NodeHierarchyItemId::from_raw(1).into_crate_internal(),
3103 Some(NodeId::ZERO),
3104 "raw 1 is NodeId(0), NOT NodeId(1)"
3105 );
3106 assert_eq!(
3107 NodeHierarchyItemId::from_raw(usize::MAX).into_crate_internal(),
3108 Some(NodeId::new(usize::MAX - 1))
3109 );
3110 }
3111
3112 #[test]
3113 fn node_hierarchy_item_id_debug_and_display_agree() {
3114 let none = NodeHierarchyItemId::NONE;
3115 assert_eq!(format!("{none:?}"), "None");
3116 assert_eq!(format!("{none}"), format!("{none:?}"));
3117
3118 let some = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(5)));
3119 assert_eq!(format!("{some:?}"), "Some(NodeId(5))");
3120 assert_eq!(format!("{some}"), format!("{some:?}"));
3121
3122 let max = NodeHierarchyItemId::from_raw(usize::MAX);
3124 assert!(!format!("{max:?}").is_empty());
3125 }
3126
3127 #[test]
3128 fn node_hierarchy_item_id_ordering_follows_raw_value() {
3129 let a = NodeHierarchyItemId::from_raw(0);
3130 let b = NodeHierarchyItemId::from_raw(1);
3131 let c = NodeHierarchyItemId::from_raw(usize::MAX);
3132 assert!(a < b);
3133 assert!(b < c);
3134 assert_eq!(a, NodeHierarchyItemId::NONE);
3135 }
3136
3137 #[test]
3138 fn node_hierarchy_item_id_from_impls_match_the_explicit_ones() {
3139 let opt = Some(NodeId::new(41));
3140 let via_from: NodeHierarchyItemId = opt.into();
3141 assert_eq!(via_from, NodeHierarchyItemId::from_crate_internal(opt));
3142
3143 let back: Option<NodeId> = via_from.into();
3144 assert_eq!(back, opt);
3145
3146 let none: NodeHierarchyItemId = None.into();
3147 assert_eq!(none.into_raw(), 0);
3148 }
3149
3150 #[test]
3155 fn node_hierarchy_item_zeroed_has_no_links() {
3156 let z = NodeHierarchyItem::zeroed();
3157 assert_eq!(z.parent_id(), None);
3158 assert_eq!(z.previous_sibling_id(), None);
3159 assert_eq!(z.next_sibling_id(), None);
3160 assert_eq!(z.last_child_id(), None);
3161 assert_eq!(z.first_child_id(NodeId::ZERO), None);
3162 assert_eq!(z.first_child_id(NodeId::new(usize::MAX)), None);
3163 assert_eq!(z, NodeHierarchyItem::from(Node::ROOT));
3164 }
3165
3166 #[test]
3167 fn node_hierarchy_item_getters_decode_the_one_based_fields() {
3168 let item = raw_item(1, 2, 3, 4);
3169 assert_eq!(item.parent_id(), Some(NodeId::new(0)));
3170 assert_eq!(item.previous_sibling_id(), Some(NodeId::new(1)));
3171 assert_eq!(item.next_sibling_id(), Some(NodeId::new(2)));
3172 assert_eq!(item.last_child_id(), Some(NodeId::new(3)));
3173
3174 assert_eq!(item.first_child_id(NodeId::new(7)), Some(NodeId::new(8)));
3176 }
3177
3178 #[test]
3179 fn node_hierarchy_item_getters_at_usize_max_do_not_overflow() {
3180 let item = raw_item(usize::MAX, usize::MAX, usize::MAX, usize::MAX);
3181 assert_eq!(item.parent_id(), Some(NodeId::new(usize::MAX - 1)));
3182 assert_eq!(item.previous_sibling_id(), Some(NodeId::new(usize::MAX - 1)));
3183 assert_eq!(item.next_sibling_id(), Some(NodeId::new(usize::MAX - 1)));
3184 assert_eq!(item.last_child_id(), Some(NodeId::new(usize::MAX - 1)));
3185
3186 assert_eq!(
3189 item.first_child_id(NodeId::new(usize::MAX)),
3190 Some(NodeId::new(usize::MAX)),
3191 "first_child_id must saturate, never wrap to NodeId(0)"
3192 );
3193 }
3194
3195 #[test]
3196 fn node_hierarchy_item_from_node_preserves_every_link() {
3197 let node = Node {
3198 parent: Some(NodeId::new(3)),
3199 previous_sibling: None,
3200 next_sibling: Some(NodeId::new(9)),
3201 last_child: Some(NodeId::new(12)),
3202 };
3203 let item: NodeHierarchyItem = node.into();
3204 assert_eq!(item.parent_id(), node.parent);
3205 assert_eq!(item.previous_sibling_id(), node.previous_sibling);
3206 assert_eq!(item.next_sibling_id(), node.next_sibling);
3207 assert_eq!(item.last_child_id(), node.last_child);
3208 }
3209
3210 #[test]
3215 fn node_hierarchy_item_vec_containers_read_and_write() {
3216 let mut v: NodeHierarchyItemVec = vec![NodeHierarchyItem::zeroed(); 2].into();
3217 {
3218 let mut c = v.as_container_mut();
3219 c[NodeId::new(1)].parent = 1; }
3221 let c = v.as_container();
3222 assert_eq!(c.len(), 2);
3223 assert_eq!(c[NodeId::new(1)].parent_id(), Some(NodeId::ZERO));
3224 assert!(c.get(NodeId::new(2)).is_none());
3225
3226 let empty: NodeHierarchyItemVec = Vec::new().into();
3227 assert!(empty.as_container().is_empty());
3228 }
3229
3230 #[test]
3231 fn subtree_len_counts_descendants_of_a_real_tree() {
3232 let sd = nested_body();
3234 let h = sd.node_hierarchy.as_container();
3235 assert_eq!(h.len(), 3);
3236 assert_eq!(h.subtree_len(NodeId::ZERO), 2, "root has 2 descendants");
3237 assert_eq!(h.subtree_len(NodeId::new(1)), 1);
3238 assert_eq!(h.subtree_len(NodeId::new(2)), 0, "a leaf has no descendants");
3239 }
3240
3241 #[test]
3242 fn subtree_len_saturates_on_a_malformed_backwards_next_sibling() {
3243 let v: NodeHierarchyItemVec = vec![
3246 raw_item(0, 0, 0, 0),
3247 raw_item(0, 0, 0, 0),
3248 raw_item(0, 0, 1, 0),
3249 ]
3250 .into();
3251 let c = v.as_container();
3252 assert_eq!(c.subtree_len(NodeId::new(2)), 0);
3253
3254 let v2: NodeHierarchyItemVec = vec![raw_item(0, 0, 0, 0), raw_item(0, 0, 2, 0)].into();
3256 assert_eq!(v2.as_container().subtree_len(NodeId::new(1)), 0);
3257 }
3258
3259 #[test]
3264 fn memory_report_default_total_is_zero() {
3265 assert_eq!(StyledDomMemoryReport::default().total_bytes(), 0);
3266 }
3267
3268 #[test]
3269 fn memory_report_total_bytes_sums_every_field() {
3270 let r = StyledDomMemoryReport {
3271 node_count: 3,
3272 node_hierarchy_bytes: 1,
3273 node_data_bytes: 2,
3274 styled_nodes_bytes: 4,
3275 cascade_info_bytes: 8,
3276 tag_ids_bytes: 16,
3277 non_leaf_nodes_bytes: 32,
3278 callback_vecs_bytes: 64,
3279 ..StyledDomMemoryReport::default()
3280 };
3281 assert_eq!(r.total_bytes(), 127, "node_count must NOT be part of the sum");
3282
3283 let extreme = StyledDomMemoryReport {
3285 node_data_bytes: usize::MAX,
3286 ..StyledDomMemoryReport::default()
3287 };
3288 assert_eq!(extreme.total_bytes(), usize::MAX);
3289 }
3290
3291 #[test]
3292 fn memory_report_tracks_node_count_and_is_monotonic_in_dom_size() {
3293 let small = flat_body(1).memory_report();
3294 let large = flat_body(50).memory_report();
3295 assert_eq!(small.node_count, 2);
3296 assert_eq!(large.node_count, 51);
3297 assert!(large.total_bytes() > small.total_bytes());
3298 assert!(small.total_bytes() >= small.node_hierarchy_bytes + small.node_data_bytes);
3299
3300 let d = StyledDom::default().memory_report();
3302 assert_eq!(d.node_count, 1);
3303 assert!(d.total_bytes() > 0);
3304 }
3305
3306 #[test]
3311 fn default_styled_dom_is_a_single_rooted_body() {
3312 let sd = StyledDom::default();
3313 assert_eq!(sd.node_count(), 1);
3314 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3315 assert_eq!(sd.node_hierarchy.as_ref().len(), 1);
3316 assert_eq!(sd.styled_nodes.as_ref().len(), 1);
3317 assert_eq!(sd.cascade_info.as_ref().len(), 1);
3318 assert_eq!(sd.non_leaf_nodes.as_ref().len(), 1);
3319 assert_eq!(sd.non_leaf_nodes.as_ref()[0].depth, 0);
3320 assert!(sd.tag_ids_to_node_ids.as_ref().is_empty());
3321 assert!(sd.get_styled_node_state(&NodeId::ZERO).is_normal());
3322 }
3323
3324 #[test]
3325 fn create_empties_the_source_dom() {
3326 let mut dom = Dom::create_body().with_children(vec![Dom::create_div(); 3].into());
3328 let sd = StyledDom::create(&mut dom, Css::empty());
3329 assert_eq!(sd.node_count(), 4);
3330 assert!(
3331 dom.children.as_ref().is_empty(),
3332 "the source Dom must be left empty (it is swapped out, not cloned)"
3333 );
3334 }
3335
3336 #[test]
3337 fn create_keeps_every_parallel_array_the_same_length() {
3338 for n in [0usize, 1, 3, 64] {
3339 let sd = flat_body(n);
3340 let count = sd.node_count();
3341 assert_eq!(count, n + 1);
3342 assert_eq!(sd.node_hierarchy.as_ref().len(), count);
3343 assert_eq!(sd.styled_nodes.as_ref().len(), count);
3344 assert_eq!(sd.cascade_info.as_ref().len(), count);
3345 }
3346 }
3347
3348 #[test]
3349 fn create_survives_malformed_truncated_and_unicode_css() {
3350 let cases: Vec<String> = vec![
3351 String::new(),
3352 "}}}{{{".to_string(),
3353 "div {".to_string(),
3354 "div { color: }".to_string(),
3355 "div { : red; }".to_string(),
3356 "@media".to_string(),
3357 "/* unterminated comment".to_string(),
3358 "div { width: 99999999999999999999999px; }".to_string(),
3359 "div { width: -0px; opacity: 1e400; }".to_string(),
3360 "div { width: NaNpx; height: infpx; }".to_string(),
3361 "* { color: #ZZZZZZ; }".to_string(),
3362 "日本語 { content: \"🦀\"; }".to_string(),
3363 ".\u{202e}rtl { color: red; }".to_string(),
3364 "a".repeat(10_000),
3365 "div { color: red; }".repeat(500),
3366 ];
3367
3368 for case in &cases {
3369 let css = parse_css(case);
3370 let mut dom = Dom::create_body().with_children(vec![Dom::create_div()].into());
3371 let sd = StyledDom::create(&mut dom, css);
3372 assert_eq!(
3373 sd.node_count(),
3374 2,
3375 "CSS must never change the node count; failing input: {case:?}"
3376 );
3377 }
3378 }
3379
3380 #[test]
3381 fn create_handles_deep_and_wide_doms() {
3382 let mut deep = Dom::create_div();
3384 for _ in 0..63 {
3385 deep = Dom::create_div().with_children(vec![deep].into());
3386 }
3387 let mut deep_body = Dom::create_body().with_children(vec![deep].into());
3388 let sd = StyledDom::create(&mut deep_body, Css::empty());
3389 assert_eq!(sd.node_count(), 65);
3390 assert_eq!(
3391 sd.non_leaf_nodes.as_ref().len(),
3392 64,
3393 "every node except the innermost leaf is a parent"
3394 );
3395
3396 let wide = flat_body(1000);
3398 assert_eq!(wide.node_count(), 1001);
3399 assert_eq!(wide.node_hierarchy.as_container().subtree_len(NodeId::ZERO), 1000);
3400 assert_eq!(wide.non_leaf_nodes.as_ref().len(), 1);
3401 }
3402
3403 #[test]
3404 fn create_from_dom_collects_scoped_css_without_changing_the_tree() {
3405 let dom = Dom::create_body().with_children(
3406 vec![
3407 Dom::create_div().with_css("color: red"),
3408 Dom::create_div().with_children(vec![Dom::create_div().with_css("width: 5px")].into()),
3409 ]
3410 .into(),
3411 );
3412 let sd = StyledDom::create_from_dom(dom);
3413 assert_eq!(sd.node_count(), 4);
3414 assert_eq!(sd.node_hierarchy.as_ref().len(), 4);
3415 assert!(sd.get_css_property_cache().compact_cache.is_some());
3416 }
3417
3418 #[test]
3419 fn create_from_dom_on_a_bare_leaf_produces_one_node() {
3420 let sd = StyledDom::create_from_dom(Dom::create_div());
3421 assert_eq!(sd.node_count(), 1);
3422 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3423 }
3424
3425 #[test]
3430 fn append_child_grows_the_node_count_by_the_child_dom_size() {
3431 let mut base = flat_body(2);
3432 base.append_child(flat_body(3));
3433 assert_eq!(base.node_count(), 3 + 4);
3434 assert_eq!(base.node_hierarchy.as_ref().len(), 7);
3435 assert_eq!(base.styled_nodes.as_ref().len(), 7);
3436 assert_eq!(base.cascade_info.as_ref().len(), 7);
3437 }
3438
3439 #[test]
3440 fn append_child_links_the_new_root_as_the_last_sibling() {
3441 let mut base = flat_body(2);
3443 base.append_child(StyledDom::default());
3444
3445 let h = base.node_hierarchy.as_container();
3446 let children: Vec<NodeId> = NodeId::ZERO.az_children(&h).collect();
3447 assert_eq!(
3448 children,
3449 vec![NodeId::new(1), NodeId::new(2), NodeId::new(3)],
3450 "the appended root must become the last direct child"
3451 );
3452 assert_eq!(h[NodeId::new(3)].parent_id(), Some(NodeId::ZERO));
3453 assert_eq!(h[NodeId::new(3)].previous_sibling_id(), Some(NodeId::new(2)));
3454 assert_eq!(h[NodeId::new(3)].next_sibling_id(), None);
3455 }
3456
3457 #[test]
3462 fn append_child_keeps_the_root_children_reachable_for_a_nested_dom() {
3463 let mut base = nested_body(); base.append_child(StyledDom::default());
3465 assert_eq!(base.node_count(), 4);
3466
3467 let h = base.node_hierarchy.as_container();
3468 let children: Vec<NodeId> = NodeId::ZERO.az_children(&h).collect();
3469 assert_eq!(
3470 children,
3471 vec![NodeId::new(1), NodeId::new(3)],
3472 "after append_child the root must have exactly its old child plus the appended root"
3473 );
3474 }
3475
3476 #[test]
3477 fn append_child_with_index_saturates_the_u32_cascade_index() {
3478 for (child_index, expected) in [
3479 (0usize, 0u32),
3480 (7, 7),
3481 (u32::MAX as usize, u32::MAX),
3482 (u32::MAX as usize + 1, u32::MAX),
3483 (usize::MAX, u32::MAX),
3484 ] {
3485 let mut base = flat_body(0); base.append_child_with_index(StyledDom::default(), child_index);
3487
3488 assert_eq!(
3490 base.cascade_info.as_ref()[1].index_in_parent,
3491 expected,
3492 "child_index {child_index} must saturate to {expected}, never wrap"
3493 );
3494 assert!(base.cascade_info.as_ref()[1].is_last_child);
3495 assert_eq!(base.node_count(), 2);
3496 }
3497 }
3498
3499 #[test]
3500 fn finalize_non_leaf_nodes_sorts_by_depth_and_is_idempotent() {
3501 let mut base = flat_body(1);
3502 base.append_child_with_index(flat_body(2), 1);
3503 base.append_child_with_index(flat_body(2), 2);
3504 base.finalize_non_leaf_nodes();
3505
3506 let depths: Vec<usize> = base.non_leaf_nodes.as_ref().iter().map(|p| p.depth).collect();
3507 let mut sorted = depths.clone();
3508 sorted.sort_unstable();
3509 assert_eq!(depths, sorted, "non_leaf_nodes must be depth-ordered");
3510
3511 base.finalize_non_leaf_nodes();
3512 let again: Vec<usize> = base.non_leaf_nodes.as_ref().iter().map(|p| p.depth).collect();
3513 assert_eq!(depths, again, "finalize must be idempotent");
3514 }
3515
3516 #[test]
3517 fn with_child_matches_append_child() {
3518 let mut appended = flat_body(2);
3519 appended.append_child(flat_body(1));
3520
3521 let built = flat_body(2).with_child(flat_body(1));
3522
3523 assert_eq!(built.node_count(), appended.node_count());
3524 assert_eq!(
3525 built.node_hierarchy.as_ref(),
3526 appended.node_hierarchy.as_ref()
3527 );
3528 }
3529
3530 #[test]
3531 fn swap_with_default_returns_the_old_dom_and_resets_self() {
3532 let mut sd = flat_body(3);
3533 let old = sd.swap_with_default();
3534 assert_eq!(old.node_count(), 4);
3535 assert_eq!(sd.node_count(), 1, "self must be left as the default StyledDom");
3536 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3537 }
3538
3539 #[test]
3544 fn context_menu_and_menu_bar_are_stored_on_the_root_node() {
3545 let mut sd = flat_body(1);
3546 assert!(sd.node_data.as_container()[NodeId::ZERO].get_context_menu().is_none());
3547
3548 sd.set_context_menu(empty_menu());
3549 sd.set_menu_bar(empty_menu());
3550
3551 let data = sd.node_data.as_container();
3552 assert!(data[NodeId::ZERO].get_context_menu().is_some());
3553 assert!(data[NodeId::ZERO].get_menu_bar().is_some());
3554
3555 assert!(data[NodeId::new(1)].get_context_menu().is_none());
3557 assert!(data[NodeId::new(1)].get_menu_bar().is_none());
3558 }
3559
3560 #[test]
3561 fn menu_builders_are_equivalent_to_the_setters_and_dont_touch_the_tree() {
3562 let sd = StyledDom::default()
3563 .with_context_menu(empty_menu())
3564 .with_menu_bar(empty_menu());
3565 assert_eq!(sd.node_count(), 1);
3566 let data = sd.node_data.as_container();
3567 assert!(data[NodeId::ZERO].get_context_menu().is_some());
3568 assert!(data[NodeId::ZERO].get_menu_bar().is_some());
3569 }
3570
3571 #[test]
3576 fn restyle_nodes_hover_sets_and_clears_the_state_flag() {
3577 let mut sd = flat_body(2);
3578 let _ = sd.restyle_nodes_hover(&[NodeId::new(1)], true);
3579 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3580 assert!(!sd.get_styled_node_state(&NodeId::new(2)).hover);
3581
3582 let _ = sd.restyle_nodes_hover(&[NodeId::new(1)], false);
3583 assert!(!sd.get_styled_node_state(&NodeId::new(1)).hover);
3584 assert!(sd.get_styled_node_state(&NodeId::new(1)).is_normal());
3585 }
3586
3587 #[test]
3588 fn restyle_nodes_active_and_focus_set_independent_flags() {
3589 let mut sd = flat_body(1);
3590 let _ = sd.restyle_nodes_active(&[NodeId::ZERO], true);
3591 let _ = sd.restyle_nodes_focus(&[NodeId::ZERO], true);
3592
3593 let state = sd.get_styled_node_state(&NodeId::ZERO);
3594 assert!(state.active);
3595 assert!(state.focused);
3596 assert!(!state.hover, "hover must be untouched");
3597 assert!(!state.is_normal());
3598 }
3599
3600 #[test]
3601 fn restyle_nodes_ignores_out_of_range_node_ids_instead_of_panicking() {
3602 let mut sd = flat_body(1); let changed = sd.restyle_nodes_hover(&[NodeId::new(2), NodeId::new(usize::MAX)], true);
3604 assert!(changed.is_empty());
3605 assert!(!sd.get_styled_node_state(&NodeId::ZERO).hover);
3606 assert!(!sd.get_styled_node_state(&NodeId::new(1)).hover);
3607
3608 let _ = sd.restyle_nodes_hover(&[NodeId::new(1), NodeId::new(999)], true);
3610 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3611 }
3612
3613 #[test]
3614 fn restyle_nodes_handles_empty_and_duplicated_input() {
3615 let mut sd = flat_body(1);
3616 assert!(sd.restyle_nodes_focus(&[], true).is_empty());
3617
3618 let _ = sd.restyle_nodes_focus(&[NodeId::ZERO, NodeId::ZERO, NodeId::ZERO], true);
3620 assert!(sd.get_styled_node_state(&NodeId::ZERO).focused);
3621 }
3622
3623 #[test]
3624 #[should_panic(expected = "index out of bounds")]
3625 fn get_styled_node_state_panics_on_an_out_of_range_node_id() {
3626 let sd = flat_body(1);
3629 let _ = sd.get_styled_node_state(&NodeId::new(99));
3630 }
3631
3632 #[test]
3633 fn restyle_on_state_change_with_no_changes_reports_nothing_to_do() {
3634 let mut sd = flat_body(2);
3635 let r = sd.restyle_on_state_change(None, None, None);
3636 assert!(!r.has_changes());
3637 assert!(!r.needs_layout);
3638 assert!(!r.needs_display_list);
3639 assert!(!r.gpu_only_changes);
3640 assert_eq!(r.max_relayout_scope, RelayoutScope::None);
3641 }
3642
3643 #[test]
3644 fn restyle_on_state_change_tolerates_stale_node_ids() {
3645 let mut sd = flat_body(1);
3646 let r = sd.restyle_on_state_change(
3647 Some(FocusChange {
3648 lost_focus: Some(NodeId::new(500)),
3649 gained_focus: Some(NodeId::new(usize::MAX)),
3650 }),
3651 Some(HoverChange {
3652 left_nodes: vec![NodeId::new(700)],
3653 entered_nodes: vec![NodeId::new(800)],
3654 }),
3655 Some(ActiveChange {
3656 deactivated: vec![NodeId::new(900)],
3657 activated: vec![NodeId::new(1000)],
3658 }),
3659 );
3660 assert!(!r.has_changes(), "stale ids must be filtered, not applied");
3661 assert_eq!(sd.node_count(), 2);
3662 }
3663
3664 #[test]
3665 fn restyle_on_state_change_applies_state_to_valid_nodes() {
3666 let mut sd = flat_body(1);
3667 let r = sd.restyle_on_state_change(
3668 None,
3669 Some(HoverChange {
3670 left_nodes: Vec::new(),
3671 entered_nodes: vec![NodeId::new(1)],
3672 }),
3673 None,
3674 );
3675 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3676 assert!(
3677 r.changed_nodes.keys().all(|n| *n == NodeId::new(1)),
3678 "only the node whose state actually changed may be reported"
3679 );
3680 }
3681
3682 #[test]
3683 fn restyle_user_property_rejects_empty_lists_and_stale_nodes() {
3684 let mut sd = flat_body(1);
3685 assert!(sd.restyle_user_property(&NodeId::ZERO, &[]).is_empty());
3686 assert!(
3687 sd.restyle_user_property(
3688 &NodeId::new(50),
3689 &[CssProperty::auto(CssPropertyType::Width)]
3690 )
3691 .is_empty(),
3692 "an out-of-range node id must be a no-op, not a panic"
3693 );
3694 assert!(
3695 sd.get_css_property_cache()
3696 .user_overridden_properties
3697 .iter()
3698 .all(Vec::is_empty),
3699 "a rejected call must not record an override"
3700 );
3701 }
3702
3703 #[test]
3704 fn restyle_user_property_stores_the_override_and_initial_removes_it() {
3705 let mut sd = flat_body(1);
3706 let node = NodeId::ZERO;
3707
3708 let _ = sd.restyle_user_property(&node, &[CssProperty::auto(CssPropertyType::Width)]);
3709 {
3710 let overrides = &sd.get_css_property_cache().user_overridden_properties;
3711 assert_eq!(overrides.len(), sd.node_count(), "table grows to cover the DOM");
3712 assert_eq!(overrides[0].len(), 1);
3713 assert_eq!(overrides[0][0].0, CssPropertyType::Width);
3714 }
3715
3716 let _ = sd.restyle_user_property(&node, &[CssProperty::none(CssPropertyType::Width)]);
3718 assert_eq!(sd.get_css_property_cache().user_overridden_properties[0].len(), 1);
3719
3720 let _ = sd.restyle_user_property(&node, &[CssProperty::initial(CssPropertyType::Width)]);
3722 assert!(sd.get_css_property_cache().user_overridden_properties[0].is_empty());
3723
3724 let _ = sd.restyle_user_property(&node, &[CssProperty::initial(CssPropertyType::Height)]);
3726 assert!(sd.get_css_property_cache().user_overridden_properties[0].is_empty());
3727 }
3728
3729 #[test]
3730 fn restyle_and_recompute_preserve_the_tree_and_rebuild_the_compact_cache() {
3731 let mut sd = flat_body(3);
3732 let before = sd.node_count();
3733
3734 sd.restyle(parse_css("div { color: red; } body > div:hover { color: blue; }"));
3735 assert_eq!(sd.node_count(), before);
3736 assert!(sd.get_css_property_cache().compact_cache.is_some());
3737
3738 sd.restyle(parse_css("}}} div { : ; }"));
3740 assert_eq!(sd.node_count(), before);
3741
3742 sd.recompute_inheritance_and_compact_cache();
3743 assert_eq!(sd.node_count(), before);
3744 assert!(sd.get_css_property_cache().compact_cache.is_some());
3745 }
3746
3747 #[test]
3748 fn get_css_property_cache_mut_sees_the_same_cache_as_the_shared_getter() {
3749 let mut sd = flat_body(1);
3750 let node_count = sd.node_count();
3751 sd.get_css_property_cache_mut()
3752 .user_overridden_properties
3753 .resize(node_count, Vec::new());
3754 assert_eq!(
3755 sd.get_css_property_cache().user_overridden_properties.len(),
3756 node_count
3757 );
3758 }
3759
3760 #[test]
3765 fn get_html_string_test_mode_omits_the_html_wrapper() {
3766 let sd = flat_body(2);
3767 let out = sd.get_html_string("HEAD_MARK", "BODY_MARK", true);
3768 assert!(!out.is_empty());
3769 assert!(!out.contains("HEAD_MARK"), "test_mode must not emit the custom head");
3770 assert!(!out.contains("BODY_MARK"), "test_mode must not emit the custom body");
3771 assert!(!out.contains("<html>"));
3772 }
3773
3774 #[test]
3775 fn get_html_string_embeds_custom_head_and_body_verbatim() {
3776 let sd = flat_body(1);
3777 let head = "🦀 <meta charset=\"utf-8\"> & ünïcödé";
3778 let body = "x".repeat(10_000);
3779 let out = sd.get_html_string(head, &body, false);
3780 assert!(out.contains("<html>"));
3781 assert!(out.contains(head));
3782 assert!(out.contains(&body));
3783 }
3784
3785 #[test]
3786 fn get_html_string_does_not_panic_on_extreme_doms() {
3787 assert!(!StyledDom::default().get_html_string("", "", true).is_empty());
3790 assert!(!flat_body(0).get_html_string("", "", true).is_empty());
3791 assert!(!nested_body().get_html_string("", "", true).is_empty());
3792 assert!(!flat_body(200).get_html_string("", "", true).is_empty());
3793 }
3794
3795 #[test]
3800 fn get_rects_in_rendering_order_is_a_permutation_of_the_children() {
3801 let sd = flat_body(3);
3802 let group = sd.get_rects_in_rendering_order();
3803 assert_eq!(group.root.into_crate_internal(), Some(NodeId::ZERO));
3804
3805 let mut ids: Vec<usize> = group
3806 .children
3807 .as_ref()
3808 .iter()
3809 .filter_map(|c| c.root.into_crate_internal())
3810 .map(|n| n.index())
3811 .collect();
3812 ids.sort_unstable();
3813 assert_eq!(ids, vec![1, 2, 3], "every child appears exactly once");
3814 }
3815
3816 #[test]
3817 fn get_rects_in_rendering_order_nests_grandchildren() {
3818 let sd = nested_body(); let group = sd.get_rects_in_rendering_order();
3820 assert_eq!(group.children.as_ref().len(), 1);
3821
3822 let child = &group.children.as_ref()[0];
3823 assert_eq!(child.root.into_crate_internal(), Some(NodeId::new(1)));
3824 assert_eq!(child.children.as_ref().len(), 1);
3825 assert_eq!(
3826 child.children.as_ref()[0].root.into_crate_internal(),
3827 Some(NodeId::new(2))
3828 );
3829 }
3830
3831 #[test]
3832 fn determine_rendering_order_with_no_parents_yields_a_childless_root() {
3833 let sd = StyledDom::default();
3834 let hierarchy = sd.node_hierarchy.as_container();
3835 let styled = sd.styled_nodes.as_container();
3836 let data = sd.node_data.as_container();
3837
3838 let group = StyledDom::determine_rendering_order(
3839 &[],
3840 &hierarchy,
3841 &styled,
3842 &data,
3843 sd.get_css_property_cache(),
3844 );
3845 assert_eq!(group.root.into_crate_internal(), Some(NodeId::ZERO));
3846 assert!(group.children.as_ref().is_empty());
3847 }
3848
3849 #[test]
3850 fn sort_children_by_position_returns_every_child_of_a_leaf_free_parent() {
3851 let sd = flat_body(3);
3852 let hierarchy = sd.node_hierarchy.as_container();
3853 let styled = sd.styled_nodes.as_container();
3854 let data = sd.node_data.as_container();
3855
3856 let sorted = sort_children_by_position(
3857 NodeId::ZERO,
3858 &hierarchy,
3859 &styled,
3860 &data,
3861 sd.get_css_property_cache(),
3862 );
3863 assert_eq!(sorted.len(), 3);
3864
3865 let leaf = sort_children_by_position(
3867 NodeId::new(3),
3868 &hierarchy,
3869 &styled,
3870 &data,
3871 sd.get_css_property_cache(),
3872 );
3873 assert!(leaf.is_empty());
3874 }
3875
3876 #[test]
3877 fn fill_content_group_children_builds_the_nested_group_tree() {
3878 let id = |i: usize| NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(i)));
3879
3880 let mut sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> = BTreeMap::new();
3881 sorted.insert(id(0), vec![id(1), id(2)]);
3882 sorted.insert(id(1), vec![id(3)]);
3883
3884 let mut group = ContentGroup {
3885 root: id(0),
3886 children: Vec::new().into(),
3887 };
3888 fill_content_group_children(&mut group, &sorted);
3889
3890 assert_eq!(group.children.as_ref().len(), 2);
3891 assert_eq!(group.children.as_ref()[0].root, id(1));
3892 assert_eq!(group.children.as_ref()[0].children.as_ref().len(), 1);
3893 assert_eq!(group.children.as_ref()[0].children.as_ref()[0].root, id(3));
3894 assert!(
3895 group.children.as_ref()[1].children.as_ref().is_empty(),
3896 "a node with no entry in the map is a leaf"
3897 );
3898 }
3899
3900 #[test]
3901 fn fill_content_group_children_leaves_an_unknown_root_untouched() {
3902 let sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> = BTreeMap::new();
3903 let mut group = ContentGroup {
3904 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(9))),
3905 children: Vec::new().into(),
3906 };
3907 fill_content_group_children(&mut group, &sorted);
3908 assert!(group.children.as_ref().is_empty());
3909 }
3910
3911 #[test]
3916 fn recursive_get_last_child_descends_to_the_deepest_last_child() {
3917 let items = vec![
3919 raw_item(0, 0, 0, 2), raw_item(1, 0, 0, 3), raw_item(2, 0, 0, 0), ];
3923
3924 let mut target = None;
3925 recursive_get_last_child(NodeId::ZERO, &items, &mut target);
3926 assert_eq!(target, Some(NodeId::new(2)));
3927 }
3928
3929 #[test]
3930 fn recursive_get_last_child_leaves_the_target_untouched_for_a_leaf() {
3931 let items = vec![raw_item(0, 0, 0, 0)];
3932 let mut target = None;
3933 recursive_get_last_child(NodeId::ZERO, &items, &mut target);
3934 assert_eq!(target, None);
3935
3936 let mut preset = Some(NodeId::new(7));
3938 recursive_get_last_child(NodeId::ZERO, &items, &mut preset);
3939 assert_eq!(preset, Some(NodeId::new(7)));
3940 }
3941
3942 #[test]
3943 fn get_path_to_root_is_root_first_and_tolerates_unknown_nodes() {
3944 let sd = nested_body(); let h = sd.node_hierarchy.as_container();
3946
3947 assert_eq!(get_path_to_root(&h, NodeId::ZERO), vec![NodeId::ZERO]);
3948 assert_eq!(
3949 get_path_to_root(&h, NodeId::new(2)),
3950 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2)]
3951 );
3952
3953 assert_eq!(
3955 get_path_to_root(&h, NodeId::new(9999)),
3956 vec![NodeId::new(9999)]
3957 );
3958 }
3959
3960 #[test]
3965 fn is_before_in_document_order_is_false_for_identical_nodes() {
3966 let sd = flat_body(2);
3967 assert!(!is_before_in_document_order(
3968 &sd.node_hierarchy,
3969 NodeId::new(1),
3970 NodeId::new(1)
3971 ));
3972 }
3973
3974 #[test]
3975 fn is_before_in_document_order_orders_ancestors_and_siblings() {
3976 let sd = flat_body(3); let h = &sd.node_hierarchy;
3978
3979 assert!(is_before_in_document_order(h, NodeId::ZERO, NodeId::new(1)));
3980 assert!(!is_before_in_document_order(h, NodeId::new(1), NodeId::ZERO));
3981 assert!(is_before_in_document_order(h, NodeId::new(1), NodeId::new(3)));
3982 assert!(!is_before_in_document_order(h, NodeId::new(3), NodeId::new(1)));
3983 }
3984
3985 #[test]
3986 fn is_before_in_document_order_is_antisymmetric_across_a_nested_tree() {
3987 let sd = nested_body();
3988 let h = &sd.node_hierarchy;
3989 for a in 0..3 {
3990 for b in 0..3 {
3991 let ab = is_before_in_document_order(h, NodeId::new(a), NodeId::new(b));
3992 let ba = is_before_in_document_order(h, NodeId::new(b), NodeId::new(a));
3993 if a == b {
3994 assert!(!ab && !ba, "a node is never before itself");
3995 } else {
3996 assert_ne!(ab, ba, "exactly one of ({a},{b}) / ({b},{a}) must hold");
3997 }
3998 }
3999 }
4000 }
4001
4002 #[test]
4003 fn is_before_in_document_order_is_deterministic_for_unknown_nodes() {
4004 let sd = flat_body(1);
4005 let h = &sd.node_hierarchy;
4006 assert!(is_before_in_document_order(h, NodeId::ZERO, NodeId::new(usize::MAX)));
4009 assert!(!is_before_in_document_order(h, NodeId::new(usize::MAX), NodeId::ZERO));
4010 }
4011
4012 #[test]
4013 fn collect_nodes_in_document_order_start_equals_end() {
4014 let sd = flat_body(2);
4015 assert_eq!(
4016 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(2), NodeId::new(2)),
4017 vec![NodeId::new(2)]
4018 );
4019 assert_eq!(
4021 collect_nodes_in_document_order(
4022 &sd.node_hierarchy,
4023 NodeId::new(usize::MAX),
4024 NodeId::new(usize::MAX)
4025 ),
4026 vec![NodeId::new(usize::MAX)]
4027 );
4028 }
4029
4030 #[test]
4031 fn collect_nodes_in_document_order_walks_the_tree_in_pre_order() {
4032 let sd = flat_body(3); assert_eq!(
4034 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::ZERO, NodeId::new(3)),
4035 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2), NodeId::new(3)]
4036 );
4037 assert_eq!(
4038 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(1), NodeId::new(2)),
4039 vec![NodeId::new(1), NodeId::new(2)]
4040 );
4041
4042 let nested = nested_body();
4044 assert_eq!(
4045 collect_nodes_in_document_order(&nested.node_hierarchy, NodeId::ZERO, NodeId::new(2)),
4046 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2)]
4047 );
4048 }
4049
4050 #[test]
4051 fn collect_nodes_in_document_order_terminates_when_end_precedes_start() {
4052 let sd = flat_body(3);
4055 let out = collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(2), NodeId::new(1));
4056 assert!(out.is_empty());
4057 }
4058
4059 #[test]
4060 fn collect_nodes_in_document_order_with_an_unreachable_end_stops_at_the_tree_end() {
4061 let sd = flat_body(3);
4062 let out = collect_nodes_in_document_order(
4063 &sd.node_hierarchy,
4064 NodeId::new(1),
4065 NodeId::new(usize::MAX),
4066 );
4067 assert_eq!(
4068 out,
4069 vec![NodeId::new(1), NodeId::new(2), NodeId::new(3)],
4070 "an end node that is never reached must terminate at the end of the traversal"
4071 );
4072 }
4073
4074 #[test]
4079 fn is_layout_equivalent_holds_for_independently_built_identical_doms() {
4080 assert!(is_layout_equivalent(&flat_body(3), &flat_body(3)));
4081 assert!(is_layout_equivalent(
4082 &StyledDom::default(),
4083 &StyledDom::default()
4084 ));
4085 assert!(is_layout_equivalent(&nested_body(), &nested_body()));
4086 }
4087
4088 #[test]
4089 fn is_layout_equivalent_rejects_a_different_node_count() {
4090 assert!(!is_layout_equivalent(&flat_body(3), &flat_body(4)));
4091 assert!(!is_layout_equivalent(&flat_body(0), &flat_body(1)));
4092 }
4093
4094 #[test]
4095 fn is_layout_equivalent_rejects_a_different_structure() {
4096 assert!(!is_layout_equivalent(&nested_body(), &flat_body(2)));
4098 }
4099
4100 #[test]
4101 fn is_layout_equivalent_rejects_a_changed_class() {
4102 let build = |class: &str| {
4103 let mut dom = Dom::create_body().with_children(
4104 vec![Dom::create_div().with_class(class.to_string().into())].into(),
4105 );
4106 StyledDom::create(&mut dom, Css::empty())
4107 };
4108 assert!(is_layout_equivalent(&build("a"), &build("a")));
4109 assert!(!is_layout_equivalent(&build("a"), &build("b")));
4110 }
4111
4112 #[test]
4113 fn is_layout_equivalent_rejects_a_changed_pseudo_state() {
4114 let base = flat_body(2);
4115 let mut hovered = flat_body(2);
4116 let _ = hovered.restyle_nodes_hover(&[NodeId::new(1)], true);
4117 assert!(
4118 !is_layout_equivalent(&base, &hovered),
4119 ":hover changes CSS resolution, so the DOMs are not layout-equivalent"
4120 );
4121 }
4122
4123 #[test]
4128 fn compact_dom_len_and_is_empty() {
4129 let single = convert_dom_into_compact_dom(Dom::create_div());
4130 assert_eq!(single.len(), 1);
4131 assert!(!single.is_empty());
4132
4133 let tree = convert_dom_into_compact_dom(
4134 Dom::create_body().with_children(vec![Dom::create_div(); 4].into()),
4135 );
4136 assert_eq!(tree.len(), 5);
4137 assert!(!tree.is_empty());
4138
4139 let empty = CompactDom {
4141 node_hierarchy: NodeHierarchy {
4142 internal: Vec::new(),
4143 },
4144 node_data: NodeDataContainer {
4145 internal: Vec::new(),
4146 },
4147 root: NodeId::ZERO,
4148 };
4149 assert_eq!(empty.len(), 0);
4150 assert!(empty.is_empty());
4151 }
4152
4153 #[test]
4154 fn convert_dom_into_compact_dom_links_flat_siblings() {
4155 let compact = convert_dom_into_compact_dom(
4156 Dom::create_body().with_children(vec![Dom::create_div(); 3].into()),
4157 );
4158 assert_eq!(compact.len(), 4);
4159 assert_eq!(compact.root, NodeId::ZERO);
4160
4161 let h = compact.node_hierarchy.as_ref();
4162 assert_eq!(h[NodeId::ZERO].parent, None);
4163 assert_eq!(h[NodeId::ZERO].last_child, Some(NodeId::new(3)));
4164
4165 for i in 1..=3usize {
4166 assert_eq!(h[NodeId::new(i)].parent, Some(NodeId::ZERO));
4167 let expected_next = if i == 3 { None } else { Some(NodeId::new(i + 1)) };
4168 assert_eq!(h[NodeId::new(i)].next_sibling, expected_next);
4169 let expected_prev = if i == 1 { None } else { Some(NodeId::new(i - 1)) };
4170 assert_eq!(h[NodeId::new(i)].previous_sibling, expected_prev);
4171 assert_eq!(h[NodeId::new(i)].last_child, None, "the children are leaves");
4172 }
4173 }
4174
4175 #[test]
4182 fn convert_dom_into_compact_dom_last_child_is_the_last_direct_child() {
4183 let sd = nested_body();
4185 let h = sd.node_hierarchy.as_container();
4186
4187 let last_direct_child = NodeId::ZERO.az_children(&h).last();
4188 assert_eq!(last_direct_child, Some(NodeId::new(1)));
4189 assert_eq!(
4190 h[NodeId::ZERO].last_child_id(),
4191 last_direct_child,
4192 "last_child_id() must agree with the forward child iteration"
4193 );
4194 }
4195
4196 #[test]
4197 fn convert_dom_into_compact_dom_handles_an_empty_and_a_deep_tree() {
4198 assert_eq!(convert_dom_into_compact_dom(Dom::create_body()).len(), 1);
4199
4200 let mut deep = Dom::create_div();
4201 for _ in 0..64 {
4202 deep = Dom::create_div().with_children(vec![deep].into());
4203 }
4204 let compact = convert_dom_into_compact_dom(deep);
4205 assert_eq!(compact.len(), 65);
4206 let h = compact.node_hierarchy.as_ref();
4208 for i in 1..65usize {
4209 assert_eq!(h[NodeId::new(i)].parent, Some(NodeId::new(i - 1)));
4210 }
4211 }
4212
4213 #[test]
4218 fn scope_inline_css_advances_next_id_once_per_node() {
4219 let mut dom = Dom::create_body().with_children(
4220 vec![
4221 Dom::create_div().with_children(vec![Dom::create_div()].into()),
4222 Dom::create_div(),
4223 ]
4224 .into(),
4225 );
4226 let _ = dom.fixup_children_estimated();
4227
4228 let mut next = 0usize;
4229 scope_inline_css(&mut dom, &mut next);
4230 assert_eq!(next, 4, "4 nodes → the counter must land on 4 (pre-order ids 0..3)");
4231 }
4232
4233 #[test]
4234 fn scope_inline_css_from_zero_and_from_a_large_offset() {
4235 let mut leaf = Dom::create_div();
4236 let _ = leaf.fixup_children_estimated();
4237 let mut next = 0usize;
4238 scope_inline_css(&mut leaf, &mut next);
4239 assert_eq!(next, 1, "a single leaf consumes exactly one id");
4240
4241 let mut dom = Dom::create_body().with_children(vec![Dom::create_div(); 2].into());
4243 let _ = dom.fixup_children_estimated();
4244 let mut big = 1_000_000usize;
4245 scope_inline_css(&mut dom, &mut big);
4246 assert_eq!(big, 1_000_003);
4247 }
4248
4249 #[test]
4250 fn scope_inline_css_preserves_the_rule_count_of_every_node() {
4251 let mut dom = Dom::create_body()
4252 .with_css("color: red")
4253 .with_children(vec![Dom::create_div().with_css("width: 5px")].into());
4254 let _ = dom.fixup_children_estimated();
4255
4256 let rules_before: usize = dom
4257 .css
4258 .as_ref()
4259 .iter()
4260 .map(|c| c.rules.as_ref().len())
4261 .sum::<usize>()
4262 + dom.children.as_ref()[0]
4263 .css
4264 .as_ref()
4265 .iter()
4266 .map(|c| c.rules.as_ref().len())
4267 .sum::<usize>();
4268 assert!(rules_before > 0, "with_css must produce at least one rule");
4269
4270 let mut next = 0usize;
4271 scope_inline_css(&mut dom, &mut next);
4272
4273 let rules_after: usize = dom
4274 .css
4275 .as_ref()
4276 .iter()
4277 .map(|c| c.rules.as_ref().len())
4278 .sum::<usize>()
4279 + dom.children.as_ref()[0]
4280 .css
4281 .as_ref()
4282 .iter()
4283 .map(|c| c.rules.as_ref().len())
4284 .sum::<usize>();
4285 assert_eq!(
4286 rules_before, rules_after,
4287 "scoping rewrites paths in place; it must not add or drop rules"
4288 );
4289 assert_eq!(next, 2);
4290 }
4291
4292 #[test]
4293 fn collect_css_from_dom_yields_inner_css_before_outer_css() {
4294 let outer = parse_css("div { color: red; } span { color: blue; }");
4295 let inner = parse_css("p { color: green; }");
4296 let outer_rules = outer.rules.as_ref().len();
4297 let inner_rules = inner.rules.as_ref().len();
4298 assert_ne!(
4299 outer_rules, inner_rules,
4300 "the two stylesheets must be distinguishable by rule count"
4301 );
4302
4303 let mut child = Dom::create_div();
4304 child.add_component_css(inner);
4305 let mut dom = Dom::create_body().with_children(vec![child].into());
4306 dom.add_component_css(outer);
4307
4308 let mut out = Vec::new();
4309 collect_css_from_dom(&dom, &mut out);
4310
4311 assert_eq!(out.len(), 2);
4312 assert_eq!(
4313 out[0].rules.as_ref().len(),
4314 inner_rules,
4315 "deeper CSS is collected first (lower cascade priority)"
4316 );
4317 assert_eq!(out[1].rules.as_ref().len(), outer_rules);
4318 }
4319
4320 #[test]
4321 fn collect_css_from_dom_on_a_css_free_tree_appends_nothing() {
4322 let dom = Dom::create_body().with_children(vec![Dom::create_div(); 3].into());
4323 let mut out = Vec::new();
4324 collect_css_from_dom(&dom, &mut out);
4325 assert!(out.is_empty());
4326
4327 let mut prefilled = vec![Css::empty()];
4329 collect_css_from_dom(&dom, &mut prefilled);
4330 assert_eq!(prefilled.len(), 1);
4331 }
4332
4333 #[test]
4334 fn strip_css_from_dom_clears_every_node_recursively() {
4335 let mut dom = Dom::create_body()
4336 .with_css("color: red")
4337 .with_children(
4338 vec![Dom::create_div()
4339 .with_css("width: 5px")
4340 .with_children(vec![Dom::create_div().with_css("height: 5px")].into())]
4341 .into(),
4342 );
4343 assert!(!dom.css.as_ref().is_empty());
4344
4345 strip_css_from_dom(&mut dom);
4346
4347 assert!(dom.css.as_ref().is_empty());
4348 let child = &dom.children.as_ref()[0];
4349 assert!(child.css.as_ref().is_empty());
4350 assert!(child.children.as_ref()[0].css.as_ref().is_empty());
4351
4352 strip_css_from_dom(&mut dom);
4354 assert!(dom.css.as_ref().is_empty());
4355 }
4356}