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 drop(css);
1086
1087 css_property_cache.apply_ua_css(compact_dom.node_data.as_ref().internal);
1095 css_property_cache.compute_inherited_values(
1096 node_hierarchy.as_container().internal,
1097 compact_dom.node_data.as_ref().internal,
1098 );
1099
1100 let prev_font_hashes: Vec<u64> = css_property_cache.compact_cache
1101 .as_ref()
1102 .map(|c| c.prev_font_hashes.clone())
1103 .unwrap_or_default();
1104 let compact = css_property_cache.build_compact_cache_with_inheritance(
1105 compact_dom.node_data.as_ref().internal,
1106 node_hierarchy.as_container().internal,
1107 &prev_font_hashes,
1108 );
1109 css_property_cache.compact_cache = Some(compact);
1110 let pre_prune = if cascade_dbg {
1111 Some(css_property_cache.memory_breakdown())
1112 } else { None };
1113 css_property_cache.prune_compact_normal_props();
1114 if let Some(pre) = pre_prune {
1115 let post = css_property_cache.memory_breakdown();
1116 #[cfg(feature = "std")]
1117 eprintln!("[PRUNE] css_props {} → {} KiB cascaded {} → {} KiB (saved {} KiB)",
1118 pre.css_props_bytes / 1024, post.css_props_bytes / 1024,
1119 pre.cascaded_props_bytes / 1024, post.cascaded_props_bytes / 1024,
1120 (pre.total_bytes().saturating_sub(post.total_bytes())) / 1024);
1121 #[cfg(not(feature = "std"))]
1122 let _ = post;
1123 }
1124
1125 let tag_ids = css_property_cache.generate_tag_ids(
1126 &compact_dom.node_data.as_ref(),
1127 &node_hierarchy,
1128 );
1129
1130 if cascade_dbg {
1131 let bd = css_property_cache.memory_breakdown();
1132 #[cfg(feature = "std")]
1133 eprintln!("[CASCADE] {} nodes cascaded_props={} KiB css_props={} KiB compact={} KiB computed={} KiB total={} KiB",
1134 node_count,
1135 bd.cascaded_props_bytes / 1024, bd.css_props_bytes / 1024,
1136 bd.compact_cache_bytes / 1024, bd.computed_values_bytes / 1024,
1137 bd.total_bytes() / 1024);
1138 #[cfg(not(feature = "std"))]
1139 let _ = bd;
1140 }
1141
1142 let has_any_callbacks = compact_dom.node_data.as_ref().internal.iter()
1145 .any(|c| !c.get_callbacks().is_empty() || c.get_dataset().is_some());
1146
1147 let (nodes_with_window_callbacks, nodes_with_datasets) = if has_any_callbacks {
1148 let mut win_cbs = Vec::new();
1149 let mut datasets = Vec::new();
1150 for (node_id, c) in compact_dom.node_data.as_ref().internal.iter().enumerate() {
1151 let cbs = c.get_callbacks();
1152 let has_dataset = c.get_dataset().is_some();
1153 if !cbs.is_empty() || has_dataset {
1154 datasets.push(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_id))));
1155 }
1156 for cb in cbs {
1157 if let EventFilter::Window(_) = cb.event {
1158 win_cbs.push(NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_id))));
1159 break;
1160 }
1161 }
1162 }
1163 (win_cbs, datasets)
1164 } else {
1165 (Vec::new(), Vec::new())
1166 };
1167 let mut styled_dom = Self {
1168 root: NodeHierarchyItemId::from_crate_internal(Some(compact_dom.root)),
1169 node_hierarchy,
1170 node_data: compact_dom.node_data.internal.into(),
1171 cascade_info: html_tree.internal.into(),
1172 styled_nodes: styled_nodes.into(),
1173 tag_ids_to_node_ids: tag_ids.into(),
1174 nodes_with_window_callbacks: nodes_with_window_callbacks.into(),
1175 nodes_with_datasets: nodes_with_datasets.into(),
1176 non_leaf_nodes,
1177 css_property_cache: CssPropertyCachePtr::new(css_property_cache),
1178 dom_id: DomId::ROOT_ID,
1179 };
1180 #[cfg(feature = "table_layout")]
1181 if let Err(_e) = crate::dom_table::generate_anonymous_table_elements(&mut styled_dom) {
1182 }
1183
1184 styled_dom
1185 }
1186
1187 #[must_use] pub fn create_from_dom(mut dom: Dom) -> Self {
1198 use azul_css::css::Css;
1199
1200 dom.fixup_children_estimated();
1205 let mut next_scope_id = 0usize;
1206 scope_inline_css(&mut dom, &mut next_scope_id);
1207
1208 let mut all_css = Vec::new();
1210 collect_css_from_dom(&dom, &mut all_css);
1211
1212 let mut combined_css = if all_css.is_empty() {
1214 Css::empty()
1215 } else {
1216 let mut combined_rules: Vec<azul_css::css::CssRuleBlock> = Vec::new();
1217 for css in all_css {
1218 combined_rules.extend(css.rules.into_library_owned_vec());
1219 }
1220 Css::new(combined_rules)
1221 };
1222
1223 strip_css_from_dom(&mut dom);
1226
1227 Self::create(&mut dom, combined_css)
1229 }
1230
1231 pub fn append_child(&mut self, other: Self) {
1234 let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1235 let current_root_children_count = self_root_id
1236 .az_children(&self.node_hierarchy.as_container())
1237 .count();
1238 self.append_child_with_index(other, current_root_children_count);
1239 self.finalize_non_leaf_nodes();
1240 }
1241
1242 pub fn append_child_with_index(&mut self, mut other: Self, child_index: usize) {
1245 let self_len = self.node_hierarchy.as_ref().len();
1247 let other_len = other.node_hierarchy.as_ref().len();
1248 let self_root_id = self.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1249 let other_root_id = other.root.into_crate_internal().unwrap_or(NodeId::ZERO);
1250
1251 other.cascade_info.as_mut()[other_root_id.index()].index_in_parent =
1253 u32::try_from(child_index).unwrap_or(u32::MAX);
1254 other.cascade_info.as_mut()[other_root_id.index()].is_last_child = true;
1255
1256 self.cascade_info.append(&mut other.cascade_info);
1257
1258 for other in other.node_hierarchy.as_mut().iter_mut() {
1260 if other.parent != 0 {
1261 other.parent += self_len;
1262 }
1263 if other.previous_sibling != 0 {
1264 other.previous_sibling += self_len;
1265 }
1266 if other.next_sibling != 0 {
1267 other.next_sibling += self_len;
1268 }
1269 if other.last_child != 0 {
1270 other.last_child += self_len;
1271 }
1272 }
1273
1274 other.node_hierarchy.as_container_mut()[other_root_id].parent =
1275 NodeId::into_raw(&Some(self_root_id));
1276 let current_last_child = self.node_hierarchy.as_container()[self_root_id].last_child_id();
1277 other.node_hierarchy.as_container_mut()[other_root_id].previous_sibling =
1278 NodeId::into_raw(¤t_last_child);
1279 if let Some(current_last) = current_last_child {
1280 if self.node_hierarchy.as_container_mut()[current_last]
1281 .next_sibling_id()
1282 .is_some()
1283 {
1284 self.node_hierarchy.as_container_mut()[current_last].next_sibling +=
1285 other_root_id.index() + other_len;
1286 } else {
1287 self.node_hierarchy.as_container_mut()[current_last].next_sibling =
1288 NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1289 }
1290 }
1291 self.node_hierarchy.as_container_mut()[self_root_id].last_child =
1292 NodeId::into_raw(&Some(NodeId::new(self_len + other_root_id.index())));
1293
1294 self.node_hierarchy.append(&mut other.node_hierarchy);
1295 self.node_data.append(&mut other.node_data);
1296 self.styled_nodes.append(&mut other.styled_nodes);
1297 self.get_css_property_cache_mut()
1298 .append(other.get_css_property_cache_mut());
1299
1300 for tag_id_node_id in &mut other.tag_ids_to_node_ids {
1303 tag_id_node_id.node_id.inner += self_len;
1304 }
1305
1306 self.tag_ids_to_node_ids
1307 .append(&mut other.tag_ids_to_node_ids);
1308
1309 for nid in &mut other.nodes_with_window_callbacks {
1310 nid.inner += self_len;
1311 }
1312 self.nodes_with_window_callbacks
1313 .append(&mut other.nodes_with_window_callbacks);
1314
1315 for nid in &mut other.nodes_with_datasets {
1316 nid.inner += self_len;
1317 }
1318 self.nodes_with_datasets
1319 .append(&mut other.nodes_with_datasets);
1320
1321 if other_len != 1 {
1324 for other_non_leaf_node in &mut other.non_leaf_nodes {
1325 other_non_leaf_node.node_id.inner += self_len;
1326 other_non_leaf_node.depth += 1;
1327 }
1328 self.non_leaf_nodes.append(&mut other.non_leaf_nodes);
1329 }
1331 }
1332
1333 pub fn finalize_non_leaf_nodes(&mut self) {
1336 self.non_leaf_nodes.sort_by(|a, b| a.depth.cmp(&b.depth));
1337 }
1338
1339 #[must_use] pub fn with_child(mut self, other: Self) -> Self {
1341 self.append_child(other);
1342 self
1343 }
1344
1345 pub fn set_context_menu(&mut self, context_menu: Menu) {
1347 if let Some(root_id) = self.root.into_crate_internal() {
1348 self.node_data.as_container_mut()[root_id].set_context_menu(context_menu);
1349 }
1350 }
1351
1352 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
1354 self.set_context_menu(context_menu);
1355 self
1356 }
1357
1358 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
1360 if let Some(root_id) = self.root.into_crate_internal() {
1361 self.node_data.as_container_mut()[root_id].set_menu_bar(menu_bar);
1362 }
1363 }
1364
1365 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
1367 self.set_menu_bar(menu_bar);
1368 self
1369 }
1370
1371 pub fn recompute_inheritance_and_compact_cache(&mut self) {
1386 let prev_font_hashes: Vec<u64> = self.css_property_cache
1394 .downcast_mut()
1395 .compact_cache
1396 .as_ref()
1397 .map(|c| c.prev_font_hashes.clone())
1398 .unwrap_or_default();
1399 let compact = self.css_property_cache
1400 .downcast_mut()
1401 .build_compact_cache_with_inheritance(
1402 self.node_data.as_container().internal,
1403 self.node_hierarchy.as_container().internal,
1404 &prev_font_hashes,
1405 );
1406 self.css_property_cache.downcast_mut().compact_cache = Some(compact);
1407 }
1408
1409 pub fn restyle(&mut self, mut css: Css) {
1411 let _stale_tag_ids = self.css_property_cache.downcast_mut().restyle(
1416 &mut css,
1417 &self.node_data.as_container(),
1418 &self.node_hierarchy,
1419 &self.non_leaf_nodes,
1420 &self.cascade_info.as_container(),
1421 );
1422
1423 self.css_property_cache
1425 .downcast_mut()
1426 .apply_ua_css(self.node_data.as_container().internal);
1427
1428 self.css_property_cache
1430 .downcast_mut()
1431 .compute_inherited_values(
1432 self.node_hierarchy.as_container().internal,
1433 self.node_data.as_container().internal,
1434 );
1435
1436 let prev_font_hashes: Vec<u64> = self
1442 .css_property_cache
1443 .downcast_mut()
1444 .compact_cache
1445 .as_ref()
1446 .map(|c| c.prev_font_hashes.clone())
1447 .unwrap_or_default();
1448 self.css_property_cache.downcast_mut().compact_cache = None;
1449 let compact = self
1450 .css_property_cache
1451 .downcast_mut()
1452 .build_compact_cache_with_inheritance(
1453 self.node_data.as_container().internal,
1454 self.node_hierarchy.as_container().internal,
1455 &prev_font_hashes,
1456 );
1457 self.css_property_cache.downcast_mut().compact_cache = Some(compact);
1458 self.css_property_cache
1459 .downcast_mut()
1460 .invalidate_resolved_font_sizes();
1461
1462 let new_tag_ids = self.css_property_cache.downcast_mut().generate_tag_ids(
1465 &self.node_data.as_container(),
1466 &self.node_hierarchy,
1467 );
1468 self.tag_ids_to_node_ids = new_tag_ids.into();
1469 }
1470
1471 #[inline]
1473 #[must_use] pub const fn node_count(&self) -> usize {
1474 self.node_data.len()
1475 }
1476
1477 #[inline]
1479 #[must_use] pub fn get_css_property_cache(&self) -> &CssPropertyCache {
1480 &self.css_property_cache.ptr
1481 }
1482
1483 #[inline]
1485 pub fn get_css_property_cache_mut(&mut self) -> &mut CssPropertyCache {
1486 &mut self.css_property_cache.ptr
1487 }
1488
1489 #[inline]
1491 #[must_use] pub fn get_styled_node_state(&self, node_id: &NodeId) -> StyledNodeState {
1492 self.styled_nodes.as_container()[*node_id]
1493 .styled_node_state
1494 }
1495
1496 #[must_use]
1498 pub fn restyle_nodes_hover(
1499 &mut self,
1500 nodes: &[NodeId],
1501 new_hover_state: bool,
1502 ) -> RestyleNodes {
1503 self.restyle_nodes_state(
1504 nodes,
1505 new_hover_state,
1506 |state, val| state.hover = val,
1507 azul_css::dynamic_selector::PseudoStateType::Hover,
1508 )
1509 }
1510
1511 #[must_use]
1513 pub fn restyle_nodes_active(
1514 &mut self,
1515 nodes: &[NodeId],
1516 new_active_state: bool,
1517 ) -> RestyleNodes {
1518 self.restyle_nodes_state(
1519 nodes,
1520 new_active_state,
1521 |state, val| state.active = val,
1522 azul_css::dynamic_selector::PseudoStateType::Active,
1523 )
1524 }
1525
1526 #[must_use]
1528 pub fn restyle_nodes_focus(
1529 &mut self,
1530 nodes: &[NodeId],
1531 new_focus_state: bool,
1532 ) -> RestyleNodes {
1533 self.restyle_nodes_state(
1534 nodes,
1535 new_focus_state,
1536 |state, val| state.focused = val,
1537 azul_css::dynamic_selector::PseudoStateType::Focus,
1538 )
1539 }
1540
1541 fn restyle_nodes_state(
1543 &mut self,
1544 nodes: &[NodeId],
1545 new_state_value: bool,
1546 set_state: impl Fn(&mut StyledNodeState, bool),
1547 pseudo_state_type: azul_css::dynamic_selector::PseudoStateType,
1548 ) -> RestyleNodes {
1549 let node_count = self.node_count();
1554 let nodes: Vec<NodeId> = nodes
1555 .iter()
1556 .copied()
1557 .filter(|nid| nid.index() < node_count)
1558 .collect();
1559
1560 let old_node_states = nodes
1562 .iter()
1563 .map(|nid| {
1564 self.styled_nodes.as_container()[*nid]
1565 .styled_node_state
1566 })
1567 .collect::<Vec<_>>();
1568
1569 for nid in &nodes {
1570 set_state(
1571 &mut self.styled_nodes.as_container_mut()[*nid].styled_node_state,
1572 new_state_value,
1573 );
1574 }
1575
1576 let css_property_cache = self.get_css_property_cache();
1577 let styled_nodes = self.styled_nodes.as_container();
1578 let node_data = self.node_data.as_container();
1579
1580 let v = nodes
1582 .iter()
1583 .zip(old_node_states.iter())
1584 .filter_map(|(node_id, old_node_state)| {
1585 let mut keys_normal: Vec<_> = CssPropertyCache::prop_types_for_state(
1586 css_property_cache.css_props.get_slice(node_id.index()),
1587 pseudo_state_type,
1588 ).collect();
1589 let mut keys_inherited: Vec<_> = CssPropertyCache::prop_types_for_state(
1590 css_property_cache.cascaded_props.get_slice(node_id.index()),
1591 pseudo_state_type,
1592 ).collect();
1593 let keys_inline: Vec<CssPropertyType> = {
1594 use azul_css::dynamic_selector::DynamicSelector;
1595 node_data[*node_id]
1596 .style
1597 .iter_inline_properties()
1598 .filter_map(|(prop, conds)| {
1599 let matches = conds.as_slice().iter().any(|c| {
1600 matches!(c, DynamicSelector::PseudoState(pst) if *pst == pseudo_state_type)
1601 });
1602 if matches {
1603 Some(prop.get_type())
1604 } else {
1605 None
1606 }
1607 })
1608 .collect()
1609 };
1610 let mut keys_inline_ref: Vec<_> = keys_inline.iter().collect();
1611
1612 keys_normal.append(&mut keys_inherited);
1613 keys_normal.append(&mut keys_inline_ref);
1614
1615 let node_properties_that_could_have_changed = keys_normal;
1616
1617 if node_properties_that_could_have_changed.is_empty() {
1618 return None;
1619 }
1620
1621 let new_node_state = &styled_nodes[*node_id].styled_node_state;
1622 let node_data = &node_data[*node_id];
1623
1624 let changes = node_properties_that_could_have_changed
1625 .into_iter()
1626 .filter_map(|prop| {
1627 let old = css_property_cache.get_property_slow(
1629 node_data,
1630 node_id,
1631 old_node_state,
1632 prop,
1633 );
1634 let new = css_property_cache.get_property_slow(
1635 node_data,
1636 node_id,
1637 new_node_state,
1638 prop,
1639 );
1640 if old == new {
1641 None
1642 } else {
1643 Some(ChangedCssProperty {
1644 previous_state: *old_node_state,
1645 previous_prop: old.map_or_else(|| CssProperty::auto(*prop), Clone::clone),
1646 current_state: *new_node_state,
1647 current_prop: new.map_or_else(|| CssProperty::auto(*prop), Clone::clone),
1648 })
1649 }
1650 })
1651 .collect::<Vec<_>>();
1652
1653 if changes.is_empty() {
1654 None
1655 } else {
1656 Some((*node_id, changes))
1657 }
1658 })
1659 .collect::<Vec<_>>();
1660
1661 v.into_iter().collect()
1662 }
1663
1664 #[must_use]
1678 pub fn restyle_on_state_change(
1679 &mut self,
1680 focus_changes: Option<FocusChange>,
1681 hover_changes: Option<HoverChange>,
1682 active_changes: Option<ActiveChange>,
1683 ) -> RestyleResult {
1684
1685 let mut result = RestyleResult {
1687 gpu_only_changes: true,
1688 ..RestyleResult::default()
1689 };
1690
1691 let mut process_changes = |changes: RestyleNodes| {
1693 for (node_id, props) in changes {
1694 for change in &props {
1695 let prop_type = change.current_prop.get_type();
1696
1697 let scope = prop_type.relayout_scope(true);
1704
1705 if scope > result.max_relayout_scope {
1707 result.max_relayout_scope = scope;
1708 }
1709
1710 if scope != RelayoutScope::None {
1712 result.needs_layout = true;
1713 result.gpu_only_changes = false;
1714 }
1715
1716 if !prop_type.is_gpu_only_property() {
1718 result.gpu_only_changes = false;
1719 }
1720
1721 result.needs_display_list = true;
1723 }
1724
1725 result.changed_nodes.entry(node_id).or_default().extend(props);
1726 }
1727 };
1728
1729 if let Some(focus) = focus_changes {
1731 if let Some(old) = focus.lost_focus {
1732 let changes = self.restyle_nodes_focus(&[old], false);
1733 process_changes(changes);
1734 }
1735 if let Some(new) = focus.gained_focus {
1736 let changes = self.restyle_nodes_focus(&[new], true);
1737 process_changes(changes);
1738 }
1739 }
1740
1741 if let Some(hover) = hover_changes {
1743 if !hover.left_nodes.is_empty() {
1744 let changes = self.restyle_nodes_hover(&hover.left_nodes, false);
1745 process_changes(changes);
1746 }
1747 if !hover.entered_nodes.is_empty() {
1748 let changes = self.restyle_nodes_hover(&hover.entered_nodes, true);
1749 process_changes(changes);
1750 }
1751 }
1752
1753 if let Some(active) = active_changes {
1755 if !active.deactivated.is_empty() {
1756 let changes = self.restyle_nodes_active(&active.deactivated, false);
1757 process_changes(changes);
1758 }
1759 if !active.activated.is_empty() {
1760 let changes = self.restyle_nodes_active(&active.activated, true);
1761 process_changes(changes);
1762 }
1763 }
1764
1765 if result.changed_nodes.is_empty() {
1767 result.needs_display_list = false;
1768 result.gpu_only_changes = false;
1769 }
1770
1771 if result.needs_layout {
1773 result.needs_display_list = true;
1774 result.gpu_only_changes = false;
1775 }
1776
1777 result
1778 }
1779
1780 #[must_use]
1791 pub fn restyle_user_property(
1792 &mut self,
1793 node_id: &NodeId,
1794 new_properties: &[CssProperty],
1795 ) -> RestyleNodes {
1796 let mut map = BTreeMap::default();
1797
1798 if new_properties.is_empty() {
1799 return map;
1800 }
1801
1802 let node_count = self.node_data.as_ref().len();
1803 if node_id.index() >= node_count {
1804 return map;
1805 }
1806
1807 let node_data = self.node_data.as_container();
1808 let node_data = &node_data[*node_id];
1809
1810 let node_states = &self.styled_nodes.as_container();
1811 let old_node_state = &node_states[*node_id].styled_node_state;
1812
1813 let changes: Vec<ChangedCssProperty> = {
1814 let css_property_cache = self.get_css_property_cache();
1815
1816 new_properties
1817 .iter()
1818 .filter_map(|new_prop| {
1819 let old_prop = css_property_cache.get_property_slow(
1820 node_data,
1821 node_id,
1822 old_node_state,
1823 &new_prop.get_type(),
1824 );
1825
1826 let old_prop = old_prop.map_or_else(|| CssProperty::auto(new_prop.get_type()), Clone::clone);
1827
1828 if old_prop == *new_prop {
1829 None
1830 } else {
1831 Some(ChangedCssProperty {
1832 previous_state: *old_node_state,
1833 previous_prop: old_prop,
1834 current_state: *old_node_state,
1836 current_prop: new_prop.clone(),
1837 })
1838 }
1839 })
1840 .collect()
1841 };
1842
1843 let css_property_cache_mut = self.get_css_property_cache_mut();
1844
1845 if css_property_cache_mut.user_overridden_properties.len() < node_count {
1850 css_property_cache_mut
1851 .user_overridden_properties
1852 .resize(node_count, Vec::new());
1853 }
1854
1855 for new_prop in new_properties {
1856 let prop_type = new_prop.get_type();
1857 let vec = &mut css_property_cache_mut
1858 .user_overridden_properties[node_id.index()];
1859 if new_prop.is_initial() {
1860 if let Ok(idx) = vec.binary_search_by_key(&prop_type, |(k, _)| *k) {
1862 vec.remove(idx);
1863 }
1864 } else {
1865 match vec.binary_search_by_key(&prop_type, |(k, _)| *k) {
1866 Ok(idx) => vec[idx].1 = new_prop.clone(),
1867 Err(idx) => vec.insert(idx, (prop_type, new_prop.clone())),
1868 }
1869 }
1870 }
1871
1872 if !changes.is_empty() {
1873 map.insert(*node_id, changes);
1874 }
1875
1876 map
1877 }
1878
1879 #[must_use] pub fn get_html_string(&self, custom_head: &str, custom_body: &str, test_mode: bool) -> String {
1889 let css_property_cache = self.get_css_property_cache();
1890
1891 let mut output = String::new();
1892
1893 let mut should_print_close_tag_after_node: BTreeMap<NodeId, Vec<(NodeId, usize)>> = BTreeMap::new();
1895
1896 let should_print_close_tag_debug = self
1897 .non_leaf_nodes
1898 .iter()
1899 .filter_map(|p| {
1900 let parent_node_id = p.node_id.into_crate_internal()?;
1901 let mut total_last_child = None;
1902 recursive_get_last_child(
1903 parent_node_id,
1904 self.node_hierarchy.as_ref(),
1905 &mut total_last_child,
1906 );
1907 let total_last_child = total_last_child?;
1908 Some((parent_node_id, (total_last_child, p.depth)))
1909 })
1910 .collect::<BTreeMap<_, _>>();
1911
1912 for (parent_id, (last_child, parent_depth)) in should_print_close_tag_debug {
1913 should_print_close_tag_after_node
1914 .entry(last_child)
1915 .or_default()
1916 .push((parent_id, parent_depth));
1917 }
1918
1919 let mut all_node_depths = self
1920 .non_leaf_nodes
1921 .iter()
1922 .filter_map(|p| {
1923 let parent_node_id = p.node_id.into_crate_internal()?;
1924 Some((parent_node_id, p.depth))
1925 })
1926 .collect::<BTreeMap<_, _>>();
1927
1928 for (parent_node_id, parent_depth) in self
1929 .non_leaf_nodes
1930 .iter()
1931 .filter_map(|p| Some((p.node_id.into_crate_internal()?, p.depth)))
1932 {
1933 for child_id in parent_node_id.az_children(&self.node_hierarchy.as_container()) {
1934 all_node_depths.insert(child_id, parent_depth + 1);
1935 }
1936 }
1937
1938 for node_id in self.node_hierarchy.as_container().linear_iter() {
1939 let depth = all_node_depths.get(&node_id).copied().unwrap_or(0);
1943
1944 let node_data = &self.node_data.as_container()[node_id];
1945 let node_state = &self.styled_nodes.as_container()[node_id].styled_node_state;
1946 let tabs = String::from(" ").repeat(depth);
1947
1948 output.push_str("\r\n");
1949 output.push_str(&tabs);
1950 output.push_str(&node_data.debug_print_start(css_property_cache, &node_id, node_state));
1951
1952 if let Some(content) = node_data.get_node_type().format().as_ref() {
1953 output.push_str(content);
1954 }
1955
1956 let node_has_children = self.node_hierarchy.as_container()[node_id]
1957 .first_child_id(node_id)
1958 .is_some();
1959 if !node_has_children {
1960 let node_data = &self.node_data.as_container()[node_id];
1961 output.push_str(&node_data.debug_print_end());
1962 }
1963
1964 if let Some(close_tag_vec) = should_print_close_tag_after_node.get(&node_id) {
1965 let mut close_tag_vec = close_tag_vec.clone();
1966 close_tag_vec.sort_by(|a, b| b.1.cmp(&a.1)); for (close_tag_parent_id, close_tag_depth) in close_tag_vec {
1968 let node_data = &self.node_data.as_container()[close_tag_parent_id];
1969 let tabs = String::from(" ").repeat(close_tag_depth);
1970 output.push_str("\r\n");
1971 output.push_str(&tabs);
1972 output.push_str(&node_data.debug_print_end());
1973 }
1974 }
1975 }
1976
1977 if test_mode {
1978 output
1979 } else {
1980 format!(
1981 "
1982 <html>
1983 <head>
1984 <style>* {{ margin:0px; padding:0px; }}</style>
1985 {custom_head}
1986 </head>
1987 {output}
1988 {custom_body}
1989 </html>
1990 "
1991 )
1992 }
1993 }
1994
1995 #[must_use] pub fn get_rects_in_rendering_order(&self) -> ContentGroup {
1997 Self::determine_rendering_order(
1998 self.non_leaf_nodes.as_ref(),
1999 &self.node_hierarchy.as_container(),
2000 &self.styled_nodes.as_container(),
2001 &self.node_data.as_container(),
2002 self.get_css_property_cache(),
2003 )
2004 }
2005
2006 fn determine_rendering_order(
2009 non_leaf_nodes: &[ParentWithNodeDepth],
2010 node_hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2011 styled_nodes: &NodeDataContainerRef<'_, StyledNode>,
2012 node_data_container: &NodeDataContainerRef<'_, NodeData>,
2013 css_property_cache: &CssPropertyCache,
2014 ) -> ContentGroup {
2015 let children_sorted = non_leaf_nodes
2016 .iter()
2017 .filter_map(|parent| {
2018 Some((
2019 parent.node_id,
2020 sort_children_by_position(
2021 parent.node_id.into_crate_internal()?,
2022 node_hierarchy,
2023 styled_nodes,
2024 node_data_container,
2025 css_property_cache,
2026 ),
2027 ))
2028 })
2029 .collect::<Vec<_>>();
2030
2031 let children_sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> =
2032 children_sorted.into_iter().collect();
2033
2034 let mut root_content_group = ContentGroup {
2035 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
2036 children: Vec::new().into(),
2037 };
2038
2039 fill_content_group_children(&mut root_content_group, &children_sorted);
2040
2041 root_content_group
2042 }
2043
2044 #[must_use] pub fn swap_with_default(&mut self) -> Self {
2046 let mut new = Self::default();
2047 core::mem::swap(self, &mut new);
2048 new
2049 }
2050
2051}
2052
2053#[derive(Debug, PartialEq, PartialOrd, Eq)]
2055pub struct CompactDom {
2056 pub node_hierarchy: NodeHierarchy,
2058 pub node_data: NodeDataContainer<NodeData>,
2060 pub root: NodeId,
2062}
2063
2064impl CompactDom {
2065 #[inline]
2067 #[must_use] pub fn len(&self) -> usize {
2068 self.node_hierarchy.as_ref().len()
2069 }
2070
2071 #[inline]
2073 #[must_use] pub fn is_empty(&self) -> bool {
2074 self.node_hierarchy.as_ref().is_empty()
2075 }
2076}
2077
2078impl From<Dom> for CompactDom {
2079 fn from(dom: Dom) -> Self {
2080 convert_dom_into_compact_dom(dom)
2081 }
2082}
2083
2084#[must_use] pub fn convert_dom_into_compact_dom(mut dom: Dom) -> CompactDom {
2086 fn convert_dom_into_compact_dom_internal(
2088 dom: &mut Dom,
2089 node_hierarchy: &mut [Node],
2090 node_data: &mut Vec<NodeData>,
2091 parent_node_id: NodeId,
2092 node: Node,
2093 cur_node_id: &mut usize,
2094 ) {
2095 node_hierarchy[parent_node_id.index()] = node;
2106
2107 let copy = dom.root.copy_special_moving_complex();
2119
2120 node_data[parent_node_id.index()] = copy;
2121
2122 *cur_node_id += 1;
2123
2124 let mut previous_sibling_id = None;
2125 let children_len = dom.children.len();
2126 for (child_index, child_dom) in dom.children.as_mut().iter_mut().enumerate() {
2127 let child_node_id = NodeId::new(*cur_node_id);
2128 let is_last_child = (child_index + 1) == children_len;
2129 let child_dom_is_empty = child_dom.children.is_empty();
2130 let child_node = Node {
2131 parent: Some(parent_node_id),
2132 previous_sibling: previous_sibling_id,
2133 next_sibling: if is_last_child {
2134 None
2135 } else {
2136 Some(child_node_id + child_dom.estimated_total_children + 1)
2137 },
2138 last_child: if child_dom_is_empty {
2139 None
2140 } else {
2141 Some(child_node_id + child_dom.estimated_total_children)
2142 },
2143 };
2144 previous_sibling_id = Some(child_node_id);
2145 convert_dom_into_compact_dom_internal(
2147 child_dom,
2148 node_hierarchy,
2149 node_data,
2150 child_node_id,
2151 child_node,
2152 cur_node_id,
2153 );
2154 }
2155 }
2156
2157 let sum_nodes = dom.fixup_children_estimated();
2159
2160 let mut node_hierarchy = vec![Node::ROOT; sum_nodes + 1];
2161 let mut node_data = vec![NodeData::create_div(); sum_nodes + 1];
2162 let mut cur_node_id = 0;
2163
2164 let root_node_id = NodeId::ZERO;
2165 let root_node = Node {
2166 parent: None,
2167 previous_sibling: None,
2168 next_sibling: None,
2169 last_child: if dom.children.is_empty() {
2170 None
2171 } else {
2172 Some(root_node_id + dom.estimated_total_children)
2173 },
2174 };
2175
2176 convert_dom_into_compact_dom_internal(
2177 &mut dom,
2178 &mut node_hierarchy,
2179 &mut node_data,
2180 root_node_id,
2181 root_node,
2182 &mut cur_node_id,
2183 );
2184
2185 CompactDom {
2186 node_hierarchy: NodeHierarchy {
2187 internal: node_hierarchy,
2188 },
2189 node_data: NodeDataContainer {
2190 internal: node_data,
2191 },
2192 root: root_node_id,
2193 }
2194}
2195
2196fn scope_inline_css(dom: &mut Dom, next_id: &mut usize) {
2204 let start = *next_id;
2205 let end = start + dom.estimated_total_children;
2206 for css in dom.css.as_mut().iter_mut() {
2207 for rule in css.rules.as_mut().iter_mut() {
2208 rule.path.push_front_scope(start, end);
2212 }
2213 }
2214 *next_id += 1;
2215 for child in dom.children.as_mut().iter_mut() {
2216 scope_inline_css(child, next_id);
2217 }
2218}
2219
2220fn collect_css_from_dom(dom: &Dom, out: &mut Vec<Css>) {
2224 for child in &dom.children {
2226 collect_css_from_dom(child, out);
2227 }
2228 for css in &dom.css {
2230 out.push(css.clone());
2231 }
2232}
2233
2234fn strip_css_from_dom(dom: &mut Dom) {
2237 dom.css = Vec::new().into();
2238 for child in dom.children.as_mut().iter_mut() {
2239 strip_css_from_dom(child);
2240 }
2241}
2242
2243fn fill_content_group_children(
2244 group: &mut ContentGroup,
2245 children_sorted: &BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>>,
2246) {
2247 if let Some(c) = children_sorted.get(&group.root) {
2248 group.children = c
2250 .iter()
2251 .map(|child| ContentGroup {
2252 root: *child,
2253 children: Vec::new().into(),
2254 })
2255 .collect::<Vec<ContentGroup>>()
2256 .into();
2257
2258 for c in group.children.as_mut() {
2259 fill_content_group_children(c, children_sorted);
2260 }
2261 }
2262}
2263
2264fn sort_children_by_position(
2265 parent: NodeId,
2266 node_hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2267 rectangles: &NodeDataContainerRef<'_, StyledNode>,
2268 node_data_container: &NodeDataContainerRef<'_, NodeData>,
2269 css_property_cache: &CssPropertyCache,
2270) -> Vec<NodeHierarchyItemId> {
2271 use azul_css::props::layout::LayoutPosition::Absolute;
2272
2273 let children_positions = parent
2274 .az_children(node_hierarchy)
2275 .map(|nid| {
2276 let position = css_property_cache
2277 .get_position(
2278 &node_data_container[nid],
2279 &nid,
2280 &rectangles[nid].styled_node_state,
2281 )
2282 .and_then(|p| (*p).get_property_or_default())
2283 .unwrap_or_default();
2284 let id = NodeHierarchyItemId::from_crate_internal(Some(nid));
2285 (id, position)
2286 })
2287 .collect::<Vec<_>>();
2288
2289 let mut not_absolute_children = children_positions
2290 .iter()
2291 .filter_map(|(node_id, position)| {
2292 if *position == Absolute {
2293 None
2294 } else {
2295 Some(*node_id)
2296 }
2297 })
2298 .collect::<Vec<_>>();
2299
2300 let mut absolute_children = children_positions
2301 .iter()
2302 .filter_map(|(node_id, position)| {
2303 if *position == Absolute {
2304 Some(*node_id)
2305 } else {
2306 None
2307 }
2308 })
2309 .collect::<Vec<_>>();
2310
2311 not_absolute_children.append(&mut absolute_children);
2313 not_absolute_children
2314}
2315
2316fn recursive_get_last_child(
2319 node_id: NodeId,
2320 node_hierarchy: &[NodeHierarchyItem],
2321 target: &mut Option<NodeId>,
2322) {
2323 match node_hierarchy[node_id.index()].last_child_id() {
2324 None => (),
2325 Some(s) => {
2326 *target = Some(s);
2327 recursive_get_last_child(s, node_hierarchy, target);
2328 }
2329 }
2330}
2331
2332#[must_use] pub fn is_before_in_document_order(
2346 hierarchy: &NodeHierarchyItemVec,
2347 node_a: NodeId,
2348 node_b: NodeId,
2349) -> bool {
2350 if node_a == node_b {
2351 return false;
2352 }
2353
2354 let hierarchy = hierarchy.as_container();
2355
2356 let path_a = get_path_to_root(&hierarchy, node_a);
2358 let path_b = get_path_to_root(&hierarchy, node_b);
2359
2360 let min_len = path_a.len().min(path_b.len());
2362
2363 for i in 0..min_len {
2364 if path_a[i] != path_b[i] {
2365 let child_towards_a = path_a[i];
2367 let child_towards_b = path_b[i];
2368
2369 return child_towards_a.index() < child_towards_b.index();
2372 }
2373 }
2374
2375 path_a.len() < path_b.len()
2377}
2378
2379fn get_path_to_root(
2381 hierarchy: &NodeDataContainerRef<'_, NodeHierarchyItem>,
2382 node: NodeId,
2383) -> Vec<NodeId> {
2384 let mut path = Vec::new();
2385 let mut current = Some(node);
2386
2387 while let Some(node_id) = current {
2388 path.push(node_id);
2389 current = hierarchy.get(node_id).and_then(NodeHierarchyItem::parent_id);
2390 }
2391
2392 path.reverse();
2394 path
2395}
2396
2397#[must_use] pub fn collect_nodes_in_document_order(
2410 hierarchy: &NodeHierarchyItemVec,
2411 start_node: NodeId,
2412 end_node: NodeId,
2413) -> Vec<NodeId> {
2414 if start_node == end_node {
2415 return vec![start_node];
2416 }
2417
2418 let hierarchy_container = hierarchy.as_container();
2419 let hierarchy_slice = hierarchy.as_ref();
2420
2421 let mut result = Vec::new();
2422 let mut in_range = false;
2423
2424 let mut stack: Vec<NodeId> = vec![NodeId::ZERO]; while let Some(current) = stack.pop() {
2429 if current == start_node {
2431 in_range = true;
2432 }
2433
2434 if in_range {
2436 result.push(current);
2437 }
2438
2439 if current == end_node {
2441 break;
2442 }
2443
2444 if let Some(item) = hierarchy_container.get(current) {
2447 if let Some(first_child) = item.first_child_id(current) {
2449 let mut children = Vec::new();
2451 let mut child = Some(first_child);
2452 while let Some(child_id) = child {
2453 children.push(child_id);
2454 child = hierarchy_container.get(child_id).and_then(NodeHierarchyItem::next_sibling_id);
2455 }
2456 for child_id in children.into_iter().rev() {
2458 stack.push(child_id);
2459 }
2460 }
2461 }
2462 }
2463
2464 result
2465}
2466
2467#[must_use] pub fn is_layout_equivalent(old: &StyledDom, new: &StyledDom) -> bool {
2481 use crate::dom::NodeType;
2482 use crate::resources::DecodedImage;
2483
2484 let old_nodes = old.node_data.as_ref();
2486 let new_nodes = new.node_data.as_ref();
2487 if old_nodes.len() != new_nodes.len() {
2488 return false;
2489 }
2490
2491 let old_hier = old.node_hierarchy.as_ref();
2493 let new_hier = new.node_hierarchy.as_ref();
2494 if old_hier.len() != new_hier.len() {
2495 return false;
2496 }
2497 if old_hier != new_hier {
2498 return false;
2499 }
2500
2501 for (old_node, new_node) in old_nodes.iter().zip(new_nodes.iter()) {
2503
2504 if core::mem::discriminant(&old_node.node_type)
2506 != core::mem::discriminant(&new_node.node_type)
2507 {
2508 return false;
2509 }
2510
2511 match (&old_node.node_type, &new_node.node_type) {
2513 (NodeType::Image(old_img), NodeType::Image(new_img)) => {
2514 match (old_img.get_data(), new_img.get_data()) {
2515 (DecodedImage::Callback(old_cb), DecodedImage::Callback(new_cb)) => {
2516 if old_cb.callback.cb != new_cb.callback.cb {
2518 return false;
2519 }
2520 if old_cb.refany.get_type_id() != new_cb.refany.get_type_id() {
2522 return false;
2523 }
2524 }
2525 _ => {
2526 if old_img != new_img {
2528 return false;
2529 }
2530 }
2531 }
2532 }
2533 _ => {
2534 if old_node.node_type != new_node.node_type {
2535 return false;
2536 }
2537 }
2538 }
2539
2540 {
2542 use crate::dom::AttributeType;
2543 let old_ids_classes: Vec<_> = old_node.attributes().as_ref().iter()
2544 .filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
2545 .collect();
2546 let new_ids_classes: Vec<_> = new_node.attributes().as_ref().iter()
2547 .filter(|a| matches!(a, AttributeType::Id(_) | AttributeType::Class(_)))
2548 .collect();
2549 if old_ids_classes != new_ids_classes {
2550 return false;
2551 }
2552 }
2553
2554 if old_node.style != new_node.style {
2556 return false;
2557 }
2558
2559 let old_cbs = old_node.callbacks.as_ref();
2562 let new_cbs = new_node.callbacks.as_ref();
2563 if old_cbs.len() != new_cbs.len() {
2564 return false;
2565 }
2566 for (old_cb, new_cb) in old_cbs.iter().zip(new_cbs.iter()) {
2567 if old_cb.event != new_cb.event {
2568 return false;
2569 }
2570 }
2571
2572 if old_node.attributes().as_ref() != new_node.attributes().as_ref() {
2574 return false;
2575 }
2576 }
2577
2578 let old_styled = old.styled_nodes.as_ref();
2580 let new_styled = new.styled_nodes.as_ref();
2581 if old_styled.len() != new_styled.len() {
2582 return false;
2583 }
2584 if old_styled != new_styled {
2585 return false;
2586 }
2587
2588 true
2589}
2590
2591#[cfg(test)]
2592mod audit_tests {
2593 use super::*;
2594 use azul_css::props::basic::StyleFontFamily;
2595
2596 fn fam(name: &str) -> StyleFontFamily {
2597 StyleFontFamily::System(name.to_string().into())
2598 }
2599
2600 #[test]
2601 fn style_font_families_hash_is_length_sensitive() {
2602 let a = StyleFontFamiliesHash::new(&[fam("Arial")]);
2605 let a2 = StyleFontFamiliesHash::new(&[fam("Arial")]);
2606 assert_eq!(a, a2, "hash must be deterministic");
2607
2608 let two = StyleFontFamiliesHash::new(&[fam("Arial"), fam("Helvetica")]);
2609 assert_ne!(a, two, "different-length family lists must not collide");
2610
2611 let empty = StyleFontFamiliesHash::new(&[]);
2612 assert_ne!(empty, a);
2613 assert_ne!(empty, two);
2614
2615 let rev = StyleFontFamiliesHash::new(&[fam("Helvetica"), fam("Arial")]);
2617 assert_ne!(two, rev);
2618 }
2619}
2620
2621#[cfg(test)]
2622#[allow(clippy::too_many_lines)]
2623mod autotest_generated {
2624 use azul_css::{
2625 dynamic_selector::PseudoStateFlags,
2626 props::basic::StyleFontFamily,
2627 };
2628
2629 use super::*;
2630
2631 const fn raw_item(parent: usize, prev: usize, next: usize, last: usize) -> NodeHierarchyItem {
2638 NodeHierarchyItem {
2639 parent,
2640 previous_sibling: prev,
2641 next_sibling: next,
2642 last_child: last,
2643 }
2644 }
2645
2646 fn flat_body(n: usize) -> StyledDom {
2649 let children: Vec<Dom> = (0..n).map(|_| Dom::create_div()).collect();
2650 let mut dom = Dom::create_body().with_children(children.into());
2651 StyledDom::create(&mut dom, Css::empty())
2652 }
2653
2654 fn nested_body() -> StyledDom {
2656 let mut dom = Dom::create_body().with_children(
2657 vec![Dom::create_div().with_children(vec![Dom::create_div()].into())].into(),
2658 );
2659 StyledDom::create(&mut dom, Css::empty())
2660 }
2661
2662 fn parse_css(s: &str) -> Css {
2663 azul_css::parser2::new_from_str(s).0
2664 }
2665
2666 fn family(name: &str) -> StyleFontFamily {
2667 StyleFontFamily::System(name.to_string().into())
2668 }
2669
2670 const fn pseudo_flags(all: bool) -> PseudoStateFlags {
2671 PseudoStateFlags {
2672 hover: all,
2673 active: all,
2674 focused: all,
2675 disabled: all,
2676 checked: all,
2677 focus_within: all,
2678 visited: all,
2679 backdrop: all,
2680 dragging: all,
2681 drag_over: all,
2682 }
2683 }
2684
2685 fn empty_menu() -> Menu {
2686 let items: Vec<crate::menu::MenuItem> = Vec::new();
2687 Menu::create(items.into())
2688 }
2689
2690 #[test]
2695 fn restyle_result_default_reports_no_changes() {
2696 let r = RestyleResult::default();
2697 assert!(!r.has_changes());
2698 assert!(!r.needs_layout);
2699 assert!(!r.needs_display_list);
2700 assert!(!r.gpu_only_changes);
2701 assert_eq!(r.max_relayout_scope, RelayoutScope::None);
2702 }
2703
2704 #[test]
2705 fn restyle_result_has_changes_keys_off_node_map_not_property_count() {
2706 let mut r = RestyleResult::default();
2709 r.changed_nodes.insert(NodeId::ZERO, Vec::new());
2710 assert!(r.has_changes());
2711
2712 r.changed_nodes.clear();
2713 assert!(!r.has_changes());
2714 }
2715
2716 #[test]
2717 fn restyle_result_merge_ors_layout_flags_and_ands_gpu_only() {
2718 let mut a = RestyleResult {
2719 needs_layout: false,
2720 needs_display_list: false,
2721 gpu_only_changes: true,
2722 ..RestyleResult::default()
2723 };
2724 let b = RestyleResult {
2725 needs_layout: true,
2726 needs_display_list: true,
2727 gpu_only_changes: true,
2728 ..RestyleResult::default()
2729 };
2730 a.merge(b);
2731 assert!(a.needs_layout, "needs_layout is OR-ed");
2732 assert!(a.needs_display_list, "needs_display_list is OR-ed");
2733 assert!(a.gpu_only_changes, "true && true stays true");
2734
2735 let mut c = RestyleResult {
2737 gpu_only_changes: true,
2738 ..RestyleResult::default()
2739 };
2740 c.merge(RestyleResult {
2741 gpu_only_changes: false,
2742 ..RestyleResult::default()
2743 });
2744 assert!(!c.gpu_only_changes, "gpu_only_changes is AND-ed");
2745 }
2746
2747 #[test]
2748 fn restyle_result_merge_keeps_the_most_expensive_scope() {
2749 let mut low = RestyleResult {
2750 max_relayout_scope: RelayoutScope::None,
2751 ..RestyleResult::default()
2752 };
2753 low.merge(RestyleResult {
2754 max_relayout_scope: RelayoutScope::Full,
2755 ..RestyleResult::default()
2756 });
2757 assert_eq!(low.max_relayout_scope, RelayoutScope::Full);
2758
2759 let mut high = RestyleResult {
2761 max_relayout_scope: RelayoutScope::Full,
2762 ..RestyleResult::default()
2763 };
2764 high.merge(RestyleResult {
2765 max_relayout_scope: RelayoutScope::IfcOnly,
2766 ..RestyleResult::default()
2767 });
2768 assert_eq!(high.max_relayout_scope, RelayoutScope::Full);
2769 }
2770
2771 #[test]
2772 fn restyle_result_merge_of_default_is_not_the_identity_for_gpu_only() {
2773 let mut a = RestyleResult {
2777 gpu_only_changes: true,
2778 ..RestyleResult::default()
2779 };
2780 a.merge(RestyleResult::default());
2781 assert!(!a.gpu_only_changes);
2782 assert!(!a.has_changes());
2783 }
2784
2785 #[test]
2786 fn restyle_result_merge_concatenates_changes_for_the_same_node() {
2787 let prop = |t| ChangedCssProperty {
2788 previous_state: StyledNodeState::new(),
2789 previous_prop: CssProperty::auto(t),
2790 current_state: StyledNodeState::new(),
2791 current_prop: CssProperty::initial(t),
2792 };
2793
2794 let mut a = RestyleResult::default();
2795 a.changed_nodes
2796 .insert(NodeId::ZERO, vec![prop(CssPropertyType::Width)]);
2797
2798 let mut b = RestyleResult::default();
2799 b.changed_nodes
2800 .insert(NodeId::ZERO, vec![prop(CssPropertyType::Height)]);
2801 b.changed_nodes
2802 .insert(NodeId::new(1), vec![prop(CssPropertyType::Opacity)]);
2803
2804 a.merge(b);
2805
2806 assert_eq!(a.changed_nodes.len(), 2);
2807 assert_eq!(
2808 a.changed_nodes[&NodeId::ZERO].len(),
2809 2,
2810 "changes for the same node are appended, not replaced"
2811 );
2812 assert_eq!(a.changed_nodes[&NodeId::new(1)].len(), 1);
2813 assert!(a.has_changes());
2814 }
2815
2816 #[test]
2821 fn styled_node_state_new_is_all_false_and_normal() {
2822 let s = StyledNodeState::new();
2823 assert!(s.is_normal());
2824 assert!(!s.hover);
2825 assert!(!s.active);
2826 assert!(!s.focused);
2827 assert!(!s.disabled);
2828 assert!(!s.checked);
2829 assert!(!s.focus_within);
2830 assert!(!s.visited);
2831 assert!(!s.backdrop);
2832 assert!(!s.dragging);
2833 assert!(!s.drag_over);
2834 assert_eq!(s, StyledNodeState::default());
2835 }
2836
2837 #[test]
2838 fn styled_node_state_has_state_zero_is_always_true() {
2839 assert!(StyledNodeState::new().has_state(0));
2841 assert!(StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true)).has_state(0));
2842 }
2843
2844 #[test]
2845 fn styled_node_state_has_state_maps_every_index_exactly_once() {
2846 let setters: [(u8, fn(&mut StyledNodeState)); 10] = [
2848 (1, |s| s.hover = true),
2849 (2, |s| s.active = true),
2850 (3, |s| s.focused = true),
2851 (4, |s| s.disabled = true),
2852 (5, |s| s.checked = true),
2853 (6, |s| s.focus_within = true),
2854 (7, |s| s.visited = true),
2855 (8, |s| s.backdrop = true),
2856 (9, |s| s.dragging = true),
2857 (10, |s| s.drag_over = true),
2858 ];
2859
2860 for (expected_idx, set) in setters {
2861 let mut s = StyledNodeState::new();
2862 set(&mut s);
2863 assert!(!s.is_normal(), "state {expected_idx} must not be 'normal'");
2864 for idx in 1..=10u8 {
2865 assert_eq!(
2866 s.has_state(idx),
2867 idx == expected_idx,
2868 "state index {idx} misreported for setter {expected_idx}"
2869 );
2870 }
2871 }
2872 }
2873
2874 #[test]
2875 fn styled_node_state_has_state_is_false_for_every_out_of_range_u8() {
2876 let all_on = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true));
2877 for idx in 11..=u8::MAX {
2878 assert!(!StyledNodeState::new().has_state(idx));
2879 assert!(
2880 !all_on.has_state(idx),
2881 "unknown state index {idx} must be inactive even when every flag is set"
2882 );
2883 }
2884 }
2885
2886 #[test]
2887 fn styled_node_state_from_pseudo_state_flags_roundtrips_every_field() {
2888 let all_on = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(true));
2889 assert!(!all_on.is_normal());
2890 for idx in 0..=10u8 {
2891 assert!(all_on.has_state(idx), "state {idx} should be active");
2892 }
2893
2894 let all_off = StyledNodeState::from_pseudo_state_flags(&pseudo_flags(false));
2895 assert!(all_off.is_normal());
2896 assert_eq!(all_off, StyledNodeState::new());
2897 }
2898
2899 #[test]
2900 fn styled_node_state_debug_lists_active_states_and_normal_when_empty() {
2901 assert_eq!(format!("{:?}", StyledNodeState::new()), "[\"normal\"]");
2902
2903 let mut s = StyledNodeState::new();
2904 s.hover = true;
2905 s.drag_over = true;
2906 let dbg = format!("{s:?}");
2907 assert!(dbg.contains("hover"), "{dbg}");
2908 assert!(dbg.contains("drag_over"), "{dbg}");
2909 assert!(!dbg.contains("normal"), "{dbg}");
2910 }
2911
2912 #[test]
2917 fn styled_node_vec_empty_container_is_empty_and_get_returns_none() {
2918 let v: StyledNodeVec = Vec::new().into();
2919 let c = v.as_container();
2920 assert_eq!(c.len(), 0);
2921 assert!(c.is_empty());
2922 assert!(c.get(NodeId::ZERO).is_none());
2923 assert!(c.get(NodeId::new(usize::MAX)).is_none());
2924 }
2925
2926 #[test]
2927 fn styled_node_vec_container_mut_writes_are_visible_through_container() {
2928 let mut v: StyledNodeVec = vec![StyledNode::default(), StyledNode::default()].into();
2929 {
2930 let mut c = v.as_container_mut();
2931 c[NodeId::new(1)].styled_node_state.hover = true;
2932 }
2933 let c = v.as_container();
2934 assert_eq!(c.len(), 2);
2935 assert!(!c[NodeId::ZERO].styled_node_state.hover);
2936 assert!(c[NodeId::new(1)].styled_node_state.hover);
2937 assert!(c.get(NodeId::new(2)).is_none());
2938 }
2939
2940 #[test]
2945 fn style_font_family_hash_is_deterministic_and_input_sensitive() {
2946 assert_eq!(
2947 StyleFontFamilyHash::new(&family("Arial")),
2948 StyleFontFamilyHash::new(&family("Arial"))
2949 );
2950 assert_ne!(
2951 StyleFontFamilyHash::new(&family("Arial")),
2952 StyleFontFamilyHash::new(&family("Ariaĺ"))
2953 );
2954 assert_ne!(
2956 StyleFontFamilyHash::new(&StyleFontFamily::System("x".to_string().into())),
2957 StyleFontFamilyHash::new(&StyleFontFamily::File("x".to_string().into()))
2958 );
2959 }
2960
2961 #[test]
2962 fn style_font_family_hash_handles_empty_unicode_and_huge_names() {
2963 let empty = family("");
2964 let unicode = family("🦀 ノート ﷽ عربى");
2965 let huge = family(&"A".repeat(100_000));
2966
2967 assert_eq!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&empty));
2969 assert_eq!(
2970 StyleFontFamilyHash::new(&unicode),
2971 StyleFontFamilyHash::new(&unicode)
2972 );
2973 assert_eq!(StyleFontFamilyHash::new(&huge), StyleFontFamilyHash::new(&huge));
2974 assert_ne!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&unicode));
2975 assert_ne!(StyleFontFamilyHash::new(&empty), StyleFontFamilyHash::new(&huge));
2976 }
2977
2978 #[test]
2979 fn style_font_families_hash_empty_slice_is_stable_and_distinct() {
2980 let empty = StyleFontFamiliesHash::new(&[]);
2981 assert_eq!(empty, StyleFontFamiliesHash::new(&[]));
2982 assert_ne!(empty, StyleFontFamiliesHash::new(&[family("")]));
2983 }
2984
2985 #[test]
2986 fn style_font_families_hash_scales_to_large_lists_and_is_length_sensitive() {
2987 let big: Vec<StyleFontFamily> = (0..1000).map(|i| family(&format!("font-{i}"))).collect();
2988 let one_shorter = &big[..999];
2989
2990 assert_eq!(
2991 StyleFontFamiliesHash::new(&big),
2992 StyleFontFamiliesHash::new(&big),
2993 "hashing 1000 families must be deterministic"
2994 );
2995 assert_ne!(
2996 StyleFontFamiliesHash::new(&big),
2997 StyleFontFamiliesHash::new(one_shorter),
2998 "the length prefix must separate [0..1000) from [0..999)"
2999 );
3000 }
3001
3002 #[test]
3007 fn node_hierarchy_item_id_none_is_zero() {
3008 assert_eq!(NodeHierarchyItemId::NONE.into_raw(), 0);
3009 assert_eq!(NodeHierarchyItemId::NONE.into_crate_internal(), None);
3010 assert_eq!(NodeHierarchyItemId::from_crate_internal(None).into_raw(), 0);
3011 assert_eq!(NodeHierarchyItemId::from_raw(0).into_crate_internal(), None);
3012 assert_eq!(NodeHierarchyItemId::from_crate_internal(None), NodeHierarchyItemId::NONE);
3013 }
3014
3015 #[test]
3016 fn node_hierarchy_item_id_encode_decode_roundtrip_at_boundaries() {
3017 for idx in [0usize, 1, 2, 1023, usize::MAX / 2, usize::MAX - 1] {
3019 let id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(idx)));
3020 assert_eq!(id.into_raw(), idx + 1, "1-based encoding for {idx}");
3021 assert_eq!(
3022 id.into_crate_internal(),
3023 Some(NodeId::new(idx)),
3024 "decode(encode(x)) == x for {idx}"
3025 );
3026 }
3027 }
3028
3029 #[test]
3030 fn node_hierarchy_item_id_raw_roundtrip_is_identity_even_at_usize_max() {
3031 for raw in [0usize, 1, 2, 7, u32::MAX as usize, usize::MAX] {
3032 let decoded = NodeHierarchyItemId::from_raw(raw).into_crate_internal();
3033 let reencoded = NodeHierarchyItemId::from_crate_internal(decoded).into_raw();
3034 assert_eq!(reencoded, raw, "encode(decode(raw)) must be identity for {raw}");
3035 }
3036 }
3037
3038 #[test]
3039 fn node_hierarchy_item_id_from_raw_decodes_one_based() {
3040 assert_eq!(
3041 NodeHierarchyItemId::from_raw(1).into_crate_internal(),
3042 Some(NodeId::ZERO),
3043 "raw 1 is NodeId(0), NOT NodeId(1)"
3044 );
3045 assert_eq!(
3046 NodeHierarchyItemId::from_raw(usize::MAX).into_crate_internal(),
3047 Some(NodeId::new(usize::MAX - 1))
3048 );
3049 }
3050
3051 #[test]
3052 fn node_hierarchy_item_id_debug_and_display_agree() {
3053 let none = NodeHierarchyItemId::NONE;
3054 assert_eq!(format!("{none:?}"), "None");
3055 assert_eq!(format!("{none}"), format!("{none:?}"));
3056
3057 let some = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(5)));
3058 assert_eq!(format!("{some:?}"), "Some(NodeId(5))");
3059 assert_eq!(format!("{some}"), format!("{some:?}"));
3060
3061 let max = NodeHierarchyItemId::from_raw(usize::MAX);
3063 assert!(!format!("{max:?}").is_empty());
3064 }
3065
3066 #[test]
3067 fn node_hierarchy_item_id_ordering_follows_raw_value() {
3068 let a = NodeHierarchyItemId::from_raw(0);
3069 let b = NodeHierarchyItemId::from_raw(1);
3070 let c = NodeHierarchyItemId::from_raw(usize::MAX);
3071 assert!(a < b);
3072 assert!(b < c);
3073 assert_eq!(a, NodeHierarchyItemId::NONE);
3074 }
3075
3076 #[test]
3077 fn node_hierarchy_item_id_from_impls_match_the_explicit_ones() {
3078 let opt = Some(NodeId::new(41));
3079 let via_from: NodeHierarchyItemId = opt.into();
3080 assert_eq!(via_from, NodeHierarchyItemId::from_crate_internal(opt));
3081
3082 let back: Option<NodeId> = via_from.into();
3083 assert_eq!(back, opt);
3084
3085 let none: NodeHierarchyItemId = None.into();
3086 assert_eq!(none.into_raw(), 0);
3087 }
3088
3089 #[test]
3094 fn node_hierarchy_item_zeroed_has_no_links() {
3095 let z = NodeHierarchyItem::zeroed();
3096 assert_eq!(z.parent_id(), None);
3097 assert_eq!(z.previous_sibling_id(), None);
3098 assert_eq!(z.next_sibling_id(), None);
3099 assert_eq!(z.last_child_id(), None);
3100 assert_eq!(z.first_child_id(NodeId::ZERO), None);
3101 assert_eq!(z.first_child_id(NodeId::new(usize::MAX)), None);
3102 assert_eq!(z, NodeHierarchyItem::from(Node::ROOT));
3103 }
3104
3105 #[test]
3106 fn node_hierarchy_item_getters_decode_the_one_based_fields() {
3107 let item = raw_item(1, 2, 3, 4);
3108 assert_eq!(item.parent_id(), Some(NodeId::new(0)));
3109 assert_eq!(item.previous_sibling_id(), Some(NodeId::new(1)));
3110 assert_eq!(item.next_sibling_id(), Some(NodeId::new(2)));
3111 assert_eq!(item.last_child_id(), Some(NodeId::new(3)));
3112
3113 assert_eq!(item.first_child_id(NodeId::new(7)), Some(NodeId::new(8)));
3115 }
3116
3117 #[test]
3118 fn node_hierarchy_item_getters_at_usize_max_do_not_overflow() {
3119 let item = raw_item(usize::MAX, usize::MAX, usize::MAX, usize::MAX);
3120 assert_eq!(item.parent_id(), Some(NodeId::new(usize::MAX - 1)));
3121 assert_eq!(item.previous_sibling_id(), Some(NodeId::new(usize::MAX - 1)));
3122 assert_eq!(item.next_sibling_id(), Some(NodeId::new(usize::MAX - 1)));
3123 assert_eq!(item.last_child_id(), Some(NodeId::new(usize::MAX - 1)));
3124
3125 assert_eq!(
3128 item.first_child_id(NodeId::new(usize::MAX)),
3129 Some(NodeId::new(usize::MAX)),
3130 "first_child_id must saturate, never wrap to NodeId(0)"
3131 );
3132 }
3133
3134 #[test]
3135 fn node_hierarchy_item_from_node_preserves_every_link() {
3136 let node = Node {
3137 parent: Some(NodeId::new(3)),
3138 previous_sibling: None,
3139 next_sibling: Some(NodeId::new(9)),
3140 last_child: Some(NodeId::new(12)),
3141 };
3142 let item: NodeHierarchyItem = node.into();
3143 assert_eq!(item.parent_id(), node.parent);
3144 assert_eq!(item.previous_sibling_id(), node.previous_sibling);
3145 assert_eq!(item.next_sibling_id(), node.next_sibling);
3146 assert_eq!(item.last_child_id(), node.last_child);
3147 }
3148
3149 #[test]
3154 fn node_hierarchy_item_vec_containers_read_and_write() {
3155 let mut v: NodeHierarchyItemVec = vec![NodeHierarchyItem::zeroed(); 2].into();
3156 {
3157 let mut c = v.as_container_mut();
3158 c[NodeId::new(1)].parent = 1; }
3160 let c = v.as_container();
3161 assert_eq!(c.len(), 2);
3162 assert_eq!(c[NodeId::new(1)].parent_id(), Some(NodeId::ZERO));
3163 assert!(c.get(NodeId::new(2)).is_none());
3164
3165 let empty: NodeHierarchyItemVec = Vec::new().into();
3166 assert!(empty.as_container().is_empty());
3167 }
3168
3169 #[test]
3170 fn subtree_len_counts_descendants_of_a_real_tree() {
3171 let sd = nested_body();
3173 let h = sd.node_hierarchy.as_container();
3174 assert_eq!(h.len(), 3);
3175 assert_eq!(h.subtree_len(NodeId::ZERO), 2, "root has 2 descendants");
3176 assert_eq!(h.subtree_len(NodeId::new(1)), 1);
3177 assert_eq!(h.subtree_len(NodeId::new(2)), 0, "a leaf has no descendants");
3178 }
3179
3180 #[test]
3181 fn subtree_len_saturates_on_a_malformed_backwards_next_sibling() {
3182 let v: NodeHierarchyItemVec = vec![
3185 raw_item(0, 0, 0, 0),
3186 raw_item(0, 0, 0, 0),
3187 raw_item(0, 0, 1, 0),
3188 ]
3189 .into();
3190 let c = v.as_container();
3191 assert_eq!(c.subtree_len(NodeId::new(2)), 0);
3192
3193 let v2: NodeHierarchyItemVec = vec![raw_item(0, 0, 0, 0), raw_item(0, 0, 2, 0)].into();
3195 assert_eq!(v2.as_container().subtree_len(NodeId::new(1)), 0);
3196 }
3197
3198 #[test]
3203 fn memory_report_default_total_is_zero() {
3204 assert_eq!(StyledDomMemoryReport::default().total_bytes(), 0);
3205 }
3206
3207 #[test]
3208 fn memory_report_total_bytes_sums_every_field() {
3209 let r = StyledDomMemoryReport {
3210 node_count: 3,
3211 node_hierarchy_bytes: 1,
3212 node_data_bytes: 2,
3213 styled_nodes_bytes: 4,
3214 cascade_info_bytes: 8,
3215 tag_ids_bytes: 16,
3216 non_leaf_nodes_bytes: 32,
3217 callback_vecs_bytes: 64,
3218 ..StyledDomMemoryReport::default()
3219 };
3220 assert_eq!(r.total_bytes(), 127, "node_count must NOT be part of the sum");
3221
3222 let extreme = StyledDomMemoryReport {
3224 node_data_bytes: usize::MAX,
3225 ..StyledDomMemoryReport::default()
3226 };
3227 assert_eq!(extreme.total_bytes(), usize::MAX);
3228 }
3229
3230 #[test]
3231 fn memory_report_tracks_node_count_and_is_monotonic_in_dom_size() {
3232 let small = flat_body(1).memory_report();
3233 let large = flat_body(50).memory_report();
3234 assert_eq!(small.node_count, 2);
3235 assert_eq!(large.node_count, 51);
3236 assert!(large.total_bytes() > small.total_bytes());
3237 assert!(small.total_bytes() >= small.node_hierarchy_bytes + small.node_data_bytes);
3238
3239 let d = StyledDom::default().memory_report();
3241 assert_eq!(d.node_count, 1);
3242 assert!(d.total_bytes() > 0);
3243 }
3244
3245 #[test]
3250 fn default_styled_dom_is_a_single_rooted_body() {
3251 let sd = StyledDom::default();
3252 assert_eq!(sd.node_count(), 1);
3253 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3254 assert_eq!(sd.node_hierarchy.as_ref().len(), 1);
3255 assert_eq!(sd.styled_nodes.as_ref().len(), 1);
3256 assert_eq!(sd.cascade_info.as_ref().len(), 1);
3257 assert_eq!(sd.non_leaf_nodes.as_ref().len(), 1);
3258 assert_eq!(sd.non_leaf_nodes.as_ref()[0].depth, 0);
3259 assert!(sd.tag_ids_to_node_ids.as_ref().is_empty());
3260 assert!(sd.get_styled_node_state(&NodeId::ZERO).is_normal());
3261 }
3262
3263 #[test]
3264 fn create_empties_the_source_dom() {
3265 let mut dom = Dom::create_body().with_children(vec![Dom::create_div(); 3].into());
3267 let sd = StyledDom::create(&mut dom, Css::empty());
3268 assert_eq!(sd.node_count(), 4);
3269 assert!(
3270 dom.children.as_ref().is_empty(),
3271 "the source Dom must be left empty (it is swapped out, not cloned)"
3272 );
3273 }
3274
3275 #[test]
3276 fn create_keeps_every_parallel_array_the_same_length() {
3277 for n in [0usize, 1, 3, 64] {
3278 let sd = flat_body(n);
3279 let count = sd.node_count();
3280 assert_eq!(count, n + 1);
3281 assert_eq!(sd.node_hierarchy.as_ref().len(), count);
3282 assert_eq!(sd.styled_nodes.as_ref().len(), count);
3283 assert_eq!(sd.cascade_info.as_ref().len(), count);
3284 }
3285 }
3286
3287 #[test]
3288 fn create_survives_malformed_truncated_and_unicode_css() {
3289 let cases: Vec<String> = vec![
3290 String::new(),
3291 "}}}{{{".to_string(),
3292 "div {".to_string(),
3293 "div { color: }".to_string(),
3294 "div { : red; }".to_string(),
3295 "@media".to_string(),
3296 "/* unterminated comment".to_string(),
3297 "div { width: 99999999999999999999999px; }".to_string(),
3298 "div { width: -0px; opacity: 1e400; }".to_string(),
3299 "div { width: NaNpx; height: infpx; }".to_string(),
3300 "* { color: #ZZZZZZ; }".to_string(),
3301 "日本語 { content: \"🦀\"; }".to_string(),
3302 ".\u{202e}rtl { color: red; }".to_string(),
3303 "a".repeat(10_000),
3304 "div { color: red; }".repeat(500),
3305 ];
3306
3307 for case in &cases {
3308 let css = parse_css(case);
3309 let mut dom = Dom::create_body().with_children(vec![Dom::create_div()].into());
3310 let sd = StyledDom::create(&mut dom, css);
3311 assert_eq!(
3312 sd.node_count(),
3313 2,
3314 "CSS must never change the node count; failing input: {case:?}"
3315 );
3316 }
3317 }
3318
3319 #[test]
3320 fn create_handles_deep_and_wide_doms() {
3321 let mut deep = Dom::create_div();
3323 for _ in 0..63 {
3324 deep = Dom::create_div().with_children(vec![deep].into());
3325 }
3326 let mut deep_body = Dom::create_body().with_children(vec![deep].into());
3327 let sd = StyledDom::create(&mut deep_body, Css::empty());
3328 assert_eq!(sd.node_count(), 65);
3329 assert_eq!(
3330 sd.non_leaf_nodes.as_ref().len(),
3331 64,
3332 "every node except the innermost leaf is a parent"
3333 );
3334
3335 let wide = flat_body(1000);
3337 assert_eq!(wide.node_count(), 1001);
3338 assert_eq!(wide.node_hierarchy.as_container().subtree_len(NodeId::ZERO), 1000);
3339 assert_eq!(wide.non_leaf_nodes.as_ref().len(), 1);
3340 }
3341
3342 #[test]
3343 fn create_from_dom_collects_scoped_css_without_changing_the_tree() {
3344 let dom = Dom::create_body().with_children(
3345 vec![
3346 Dom::create_div().with_css("color: red"),
3347 Dom::create_div().with_children(vec![Dom::create_div().with_css("width: 5px")].into()),
3348 ]
3349 .into(),
3350 );
3351 let sd = StyledDom::create_from_dom(dom);
3352 assert_eq!(sd.node_count(), 4);
3353 assert_eq!(sd.node_hierarchy.as_ref().len(), 4);
3354 assert!(sd.get_css_property_cache().compact_cache.is_some());
3355 }
3356
3357 #[test]
3358 fn create_from_dom_on_a_bare_leaf_produces_one_node() {
3359 let sd = StyledDom::create_from_dom(Dom::create_div());
3360 assert_eq!(sd.node_count(), 1);
3361 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3362 }
3363
3364 #[test]
3369 fn append_child_grows_the_node_count_by_the_child_dom_size() {
3370 let mut base = flat_body(2);
3371 base.append_child(flat_body(3));
3372 assert_eq!(base.node_count(), 3 + 4);
3373 assert_eq!(base.node_hierarchy.as_ref().len(), 7);
3374 assert_eq!(base.styled_nodes.as_ref().len(), 7);
3375 assert_eq!(base.cascade_info.as_ref().len(), 7);
3376 }
3377
3378 #[test]
3379 fn append_child_links_the_new_root_as_the_last_sibling() {
3380 let mut base = flat_body(2);
3382 base.append_child(StyledDom::default());
3383
3384 let h = base.node_hierarchy.as_container();
3385 let children: Vec<NodeId> = NodeId::ZERO.az_children(&h).collect();
3386 assert_eq!(
3387 children,
3388 vec![NodeId::new(1), NodeId::new(2), NodeId::new(3)],
3389 "the appended root must become the last direct child"
3390 );
3391 assert_eq!(h[NodeId::new(3)].parent_id(), Some(NodeId::ZERO));
3392 assert_eq!(h[NodeId::new(3)].previous_sibling_id(), Some(NodeId::new(2)));
3393 assert_eq!(h[NodeId::new(3)].next_sibling_id(), None);
3394 }
3395
3396 #[test]
3401 fn append_child_keeps_the_root_children_reachable_for_a_nested_dom() {
3402 let mut base = nested_body(); base.append_child(StyledDom::default());
3404 assert_eq!(base.node_count(), 4);
3405
3406 let h = base.node_hierarchy.as_container();
3407 let children: Vec<NodeId> = NodeId::ZERO.az_children(&h).collect();
3408 assert_eq!(
3409 children,
3410 vec![NodeId::new(1), NodeId::new(3)],
3411 "after append_child the root must have exactly its old child plus the appended root"
3412 );
3413 }
3414
3415 #[test]
3416 fn append_child_with_index_saturates_the_u32_cascade_index() {
3417 for (child_index, expected) in [
3418 (0usize, 0u32),
3419 (7, 7),
3420 (u32::MAX as usize, u32::MAX),
3421 (u32::MAX as usize + 1, u32::MAX),
3422 (usize::MAX, u32::MAX),
3423 ] {
3424 let mut base = flat_body(0); base.append_child_with_index(StyledDom::default(), child_index);
3426
3427 assert_eq!(
3429 base.cascade_info.as_ref()[1].index_in_parent,
3430 expected,
3431 "child_index {child_index} must saturate to {expected}, never wrap"
3432 );
3433 assert!(base.cascade_info.as_ref()[1].is_last_child);
3434 assert_eq!(base.node_count(), 2);
3435 }
3436 }
3437
3438 #[test]
3439 fn finalize_non_leaf_nodes_sorts_by_depth_and_is_idempotent() {
3440 let mut base = flat_body(1);
3441 base.append_child_with_index(flat_body(2), 1);
3442 base.append_child_with_index(flat_body(2), 2);
3443 base.finalize_non_leaf_nodes();
3444
3445 let depths: Vec<usize> = base.non_leaf_nodes.as_ref().iter().map(|p| p.depth).collect();
3446 let mut sorted = depths.clone();
3447 sorted.sort_unstable();
3448 assert_eq!(depths, sorted, "non_leaf_nodes must be depth-ordered");
3449
3450 base.finalize_non_leaf_nodes();
3451 let again: Vec<usize> = base.non_leaf_nodes.as_ref().iter().map(|p| p.depth).collect();
3452 assert_eq!(depths, again, "finalize must be idempotent");
3453 }
3454
3455 #[test]
3456 fn with_child_matches_append_child() {
3457 let mut appended = flat_body(2);
3458 appended.append_child(flat_body(1));
3459
3460 let built = flat_body(2).with_child(flat_body(1));
3461
3462 assert_eq!(built.node_count(), appended.node_count());
3463 assert_eq!(
3464 built.node_hierarchy.as_ref(),
3465 appended.node_hierarchy.as_ref()
3466 );
3467 }
3468
3469 #[test]
3470 fn swap_with_default_returns_the_old_dom_and_resets_self() {
3471 let mut sd = flat_body(3);
3472 let old = sd.swap_with_default();
3473 assert_eq!(old.node_count(), 4);
3474 assert_eq!(sd.node_count(), 1, "self must be left as the default StyledDom");
3475 assert_eq!(sd.root.into_crate_internal(), Some(NodeId::ZERO));
3476 }
3477
3478 #[test]
3483 fn context_menu_and_menu_bar_are_stored_on_the_root_node() {
3484 let mut sd = flat_body(1);
3485 assert!(sd.node_data.as_container()[NodeId::ZERO].get_context_menu().is_none());
3486
3487 sd.set_context_menu(empty_menu());
3488 sd.set_menu_bar(empty_menu());
3489
3490 let data = sd.node_data.as_container();
3491 assert!(data[NodeId::ZERO].get_context_menu().is_some());
3492 assert!(data[NodeId::ZERO].get_menu_bar().is_some());
3493
3494 assert!(data[NodeId::new(1)].get_context_menu().is_none());
3496 assert!(data[NodeId::new(1)].get_menu_bar().is_none());
3497 }
3498
3499 #[test]
3500 fn menu_builders_are_equivalent_to_the_setters_and_dont_touch_the_tree() {
3501 let sd = StyledDom::default()
3502 .with_context_menu(empty_menu())
3503 .with_menu_bar(empty_menu());
3504 assert_eq!(sd.node_count(), 1);
3505 let data = sd.node_data.as_container();
3506 assert!(data[NodeId::ZERO].get_context_menu().is_some());
3507 assert!(data[NodeId::ZERO].get_menu_bar().is_some());
3508 }
3509
3510 #[test]
3515 fn restyle_nodes_hover_sets_and_clears_the_state_flag() {
3516 let mut sd = flat_body(2);
3517 let _ = sd.restyle_nodes_hover(&[NodeId::new(1)], true);
3518 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3519 assert!(!sd.get_styled_node_state(&NodeId::new(2)).hover);
3520
3521 let _ = sd.restyle_nodes_hover(&[NodeId::new(1)], false);
3522 assert!(!sd.get_styled_node_state(&NodeId::new(1)).hover);
3523 assert!(sd.get_styled_node_state(&NodeId::new(1)).is_normal());
3524 }
3525
3526 #[test]
3527 fn restyle_nodes_active_and_focus_set_independent_flags() {
3528 let mut sd = flat_body(1);
3529 let _ = sd.restyle_nodes_active(&[NodeId::ZERO], true);
3530 let _ = sd.restyle_nodes_focus(&[NodeId::ZERO], true);
3531
3532 let state = sd.get_styled_node_state(&NodeId::ZERO);
3533 assert!(state.active);
3534 assert!(state.focused);
3535 assert!(!state.hover, "hover must be untouched");
3536 assert!(!state.is_normal());
3537 }
3538
3539 #[test]
3540 fn restyle_nodes_ignores_out_of_range_node_ids_instead_of_panicking() {
3541 let mut sd = flat_body(1); let changed = sd.restyle_nodes_hover(&[NodeId::new(2), NodeId::new(usize::MAX)], true);
3543 assert!(changed.is_empty());
3544 assert!(!sd.get_styled_node_state(&NodeId::ZERO).hover);
3545 assert!(!sd.get_styled_node_state(&NodeId::new(1)).hover);
3546
3547 let _ = sd.restyle_nodes_hover(&[NodeId::new(1), NodeId::new(999)], true);
3549 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3550 }
3551
3552 #[test]
3553 fn restyle_nodes_handles_empty_and_duplicated_input() {
3554 let mut sd = flat_body(1);
3555 assert!(sd.restyle_nodes_focus(&[], true).is_empty());
3556
3557 let _ = sd.restyle_nodes_focus(&[NodeId::ZERO, NodeId::ZERO, NodeId::ZERO], true);
3559 assert!(sd.get_styled_node_state(&NodeId::ZERO).focused);
3560 }
3561
3562 #[test]
3563 #[should_panic(expected = "index out of bounds")]
3564 fn get_styled_node_state_panics_on_an_out_of_range_node_id() {
3565 let sd = flat_body(1);
3568 let _ = sd.get_styled_node_state(&NodeId::new(99));
3569 }
3570
3571 #[test]
3572 fn restyle_on_state_change_with_no_changes_reports_nothing_to_do() {
3573 let mut sd = flat_body(2);
3574 let r = sd.restyle_on_state_change(None, None, None);
3575 assert!(!r.has_changes());
3576 assert!(!r.needs_layout);
3577 assert!(!r.needs_display_list);
3578 assert!(!r.gpu_only_changes);
3579 assert_eq!(r.max_relayout_scope, RelayoutScope::None);
3580 }
3581
3582 #[test]
3583 fn restyle_on_state_change_tolerates_stale_node_ids() {
3584 let mut sd = flat_body(1);
3585 let r = sd.restyle_on_state_change(
3586 Some(FocusChange {
3587 lost_focus: Some(NodeId::new(500)),
3588 gained_focus: Some(NodeId::new(usize::MAX)),
3589 }),
3590 Some(HoverChange {
3591 left_nodes: vec![NodeId::new(700)],
3592 entered_nodes: vec![NodeId::new(800)],
3593 }),
3594 Some(ActiveChange {
3595 deactivated: vec![NodeId::new(900)],
3596 activated: vec![NodeId::new(1000)],
3597 }),
3598 );
3599 assert!(!r.has_changes(), "stale ids must be filtered, not applied");
3600 assert_eq!(sd.node_count(), 2);
3601 }
3602
3603 #[test]
3604 fn restyle_on_state_change_applies_state_to_valid_nodes() {
3605 let mut sd = flat_body(1);
3606 let r = sd.restyle_on_state_change(
3607 None,
3608 Some(HoverChange {
3609 left_nodes: Vec::new(),
3610 entered_nodes: vec![NodeId::new(1)],
3611 }),
3612 None,
3613 );
3614 assert!(sd.get_styled_node_state(&NodeId::new(1)).hover);
3615 assert!(
3616 r.changed_nodes.keys().all(|n| *n == NodeId::new(1)),
3617 "only the node whose state actually changed may be reported"
3618 );
3619 }
3620
3621 #[test]
3622 fn restyle_user_property_rejects_empty_lists_and_stale_nodes() {
3623 let mut sd = flat_body(1);
3624 assert!(sd.restyle_user_property(&NodeId::ZERO, &[]).is_empty());
3625 assert!(
3626 sd.restyle_user_property(
3627 &NodeId::new(50),
3628 &[CssProperty::auto(CssPropertyType::Width)]
3629 )
3630 .is_empty(),
3631 "an out-of-range node id must be a no-op, not a panic"
3632 );
3633 assert!(
3634 sd.get_css_property_cache()
3635 .user_overridden_properties
3636 .iter()
3637 .all(Vec::is_empty),
3638 "a rejected call must not record an override"
3639 );
3640 }
3641
3642 #[test]
3643 fn restyle_user_property_stores_the_override_and_initial_removes_it() {
3644 let mut sd = flat_body(1);
3645 let node = NodeId::ZERO;
3646
3647 let _ = sd.restyle_user_property(&node, &[CssProperty::auto(CssPropertyType::Width)]);
3648 {
3649 let overrides = &sd.get_css_property_cache().user_overridden_properties;
3650 assert_eq!(overrides.len(), sd.node_count(), "table grows to cover the DOM");
3651 assert_eq!(overrides[0].len(), 1);
3652 assert_eq!(overrides[0][0].0, CssPropertyType::Width);
3653 }
3654
3655 let _ = sd.restyle_user_property(&node, &[CssProperty::none(CssPropertyType::Width)]);
3657 assert_eq!(sd.get_css_property_cache().user_overridden_properties[0].len(), 1);
3658
3659 let _ = sd.restyle_user_property(&node, &[CssProperty::initial(CssPropertyType::Width)]);
3661 assert!(sd.get_css_property_cache().user_overridden_properties[0].is_empty());
3662
3663 let _ = sd.restyle_user_property(&node, &[CssProperty::initial(CssPropertyType::Height)]);
3665 assert!(sd.get_css_property_cache().user_overridden_properties[0].is_empty());
3666 }
3667
3668 #[test]
3669 fn restyle_and_recompute_preserve_the_tree_and_rebuild_the_compact_cache() {
3670 let mut sd = flat_body(3);
3671 let before = sd.node_count();
3672
3673 sd.restyle(parse_css("div { color: red; } body > div:hover { color: blue; }"));
3674 assert_eq!(sd.node_count(), before);
3675 assert!(sd.get_css_property_cache().compact_cache.is_some());
3676
3677 sd.restyle(parse_css("}}} div { : ; }"));
3679 assert_eq!(sd.node_count(), before);
3680
3681 sd.recompute_inheritance_and_compact_cache();
3682 assert_eq!(sd.node_count(), before);
3683 assert!(sd.get_css_property_cache().compact_cache.is_some());
3684 }
3685
3686 #[test]
3687 fn get_css_property_cache_mut_sees_the_same_cache_as_the_shared_getter() {
3688 let mut sd = flat_body(1);
3689 let node_count = sd.node_count();
3690 sd.get_css_property_cache_mut()
3691 .user_overridden_properties
3692 .resize(node_count, Vec::new());
3693 assert_eq!(
3694 sd.get_css_property_cache().user_overridden_properties.len(),
3695 node_count
3696 );
3697 }
3698
3699 #[test]
3704 fn get_html_string_test_mode_omits_the_html_wrapper() {
3705 let sd = flat_body(2);
3706 let out = sd.get_html_string("HEAD_MARK", "BODY_MARK", true);
3707 assert!(!out.is_empty());
3708 assert!(!out.contains("HEAD_MARK"), "test_mode must not emit the custom head");
3709 assert!(!out.contains("BODY_MARK"), "test_mode must not emit the custom body");
3710 assert!(!out.contains("<html>"));
3711 }
3712
3713 #[test]
3714 fn get_html_string_embeds_custom_head_and_body_verbatim() {
3715 let sd = flat_body(1);
3716 let head = "🦀 <meta charset=\"utf-8\"> & ünïcödé";
3717 let body = "x".repeat(10_000);
3718 let out = sd.get_html_string(head, &body, false);
3719 assert!(out.contains("<html>"));
3720 assert!(out.contains(head));
3721 assert!(out.contains(&body));
3722 }
3723
3724 #[test]
3725 fn get_html_string_does_not_panic_on_extreme_doms() {
3726 assert!(!StyledDom::default().get_html_string("", "", true).is_empty());
3729 assert!(!flat_body(0).get_html_string("", "", true).is_empty());
3730 assert!(!nested_body().get_html_string("", "", true).is_empty());
3731 assert!(!flat_body(200).get_html_string("", "", true).is_empty());
3732 }
3733
3734 #[test]
3739 fn get_rects_in_rendering_order_is_a_permutation_of_the_children() {
3740 let sd = flat_body(3);
3741 let group = sd.get_rects_in_rendering_order();
3742 assert_eq!(group.root.into_crate_internal(), Some(NodeId::ZERO));
3743
3744 let mut ids: Vec<usize> = group
3745 .children
3746 .as_ref()
3747 .iter()
3748 .filter_map(|c| c.root.into_crate_internal())
3749 .map(|n| n.index())
3750 .collect();
3751 ids.sort_unstable();
3752 assert_eq!(ids, vec![1, 2, 3], "every child appears exactly once");
3753 }
3754
3755 #[test]
3756 fn get_rects_in_rendering_order_nests_grandchildren() {
3757 let sd = nested_body(); let group = sd.get_rects_in_rendering_order();
3759 assert_eq!(group.children.as_ref().len(), 1);
3760
3761 let child = &group.children.as_ref()[0];
3762 assert_eq!(child.root.into_crate_internal(), Some(NodeId::new(1)));
3763 assert_eq!(child.children.as_ref().len(), 1);
3764 assert_eq!(
3765 child.children.as_ref()[0].root.into_crate_internal(),
3766 Some(NodeId::new(2))
3767 );
3768 }
3769
3770 #[test]
3771 fn determine_rendering_order_with_no_parents_yields_a_childless_root() {
3772 let sd = StyledDom::default();
3773 let hierarchy = sd.node_hierarchy.as_container();
3774 let styled = sd.styled_nodes.as_container();
3775 let data = sd.node_data.as_container();
3776
3777 let group = StyledDom::determine_rendering_order(
3778 &[],
3779 &hierarchy,
3780 &styled,
3781 &data,
3782 sd.get_css_property_cache(),
3783 );
3784 assert_eq!(group.root.into_crate_internal(), Some(NodeId::ZERO));
3785 assert!(group.children.as_ref().is_empty());
3786 }
3787
3788 #[test]
3789 fn sort_children_by_position_returns_every_child_of_a_leaf_free_parent() {
3790 let sd = flat_body(3);
3791 let hierarchy = sd.node_hierarchy.as_container();
3792 let styled = sd.styled_nodes.as_container();
3793 let data = sd.node_data.as_container();
3794
3795 let sorted = sort_children_by_position(
3796 NodeId::ZERO,
3797 &hierarchy,
3798 &styled,
3799 &data,
3800 sd.get_css_property_cache(),
3801 );
3802 assert_eq!(sorted.len(), 3);
3803
3804 let leaf = sort_children_by_position(
3806 NodeId::new(3),
3807 &hierarchy,
3808 &styled,
3809 &data,
3810 sd.get_css_property_cache(),
3811 );
3812 assert!(leaf.is_empty());
3813 }
3814
3815 #[test]
3816 fn fill_content_group_children_builds_the_nested_group_tree() {
3817 let id = |i: usize| NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(i)));
3818
3819 let mut sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> = BTreeMap::new();
3820 sorted.insert(id(0), vec![id(1), id(2)]);
3821 sorted.insert(id(1), vec![id(3)]);
3822
3823 let mut group = ContentGroup {
3824 root: id(0),
3825 children: Vec::new().into(),
3826 };
3827 fill_content_group_children(&mut group, &sorted);
3828
3829 assert_eq!(group.children.as_ref().len(), 2);
3830 assert_eq!(group.children.as_ref()[0].root, id(1));
3831 assert_eq!(group.children.as_ref()[0].children.as_ref().len(), 1);
3832 assert_eq!(group.children.as_ref()[0].children.as_ref()[0].root, id(3));
3833 assert!(
3834 group.children.as_ref()[1].children.as_ref().is_empty(),
3835 "a node with no entry in the map is a leaf"
3836 );
3837 }
3838
3839 #[test]
3840 fn fill_content_group_children_leaves_an_unknown_root_untouched() {
3841 let sorted: BTreeMap<NodeHierarchyItemId, Vec<NodeHierarchyItemId>> = BTreeMap::new();
3842 let mut group = ContentGroup {
3843 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(9))),
3844 children: Vec::new().into(),
3845 };
3846 fill_content_group_children(&mut group, &sorted);
3847 assert!(group.children.as_ref().is_empty());
3848 }
3849
3850 #[test]
3855 fn recursive_get_last_child_descends_to_the_deepest_last_child() {
3856 let items = vec![
3858 raw_item(0, 0, 0, 2), raw_item(1, 0, 0, 3), raw_item(2, 0, 0, 0), ];
3862
3863 let mut target = None;
3864 recursive_get_last_child(NodeId::ZERO, &items, &mut target);
3865 assert_eq!(target, Some(NodeId::new(2)));
3866 }
3867
3868 #[test]
3869 fn recursive_get_last_child_leaves_the_target_untouched_for_a_leaf() {
3870 let items = vec![raw_item(0, 0, 0, 0)];
3871 let mut target = None;
3872 recursive_get_last_child(NodeId::ZERO, &items, &mut target);
3873 assert_eq!(target, None);
3874
3875 let mut preset = Some(NodeId::new(7));
3877 recursive_get_last_child(NodeId::ZERO, &items, &mut preset);
3878 assert_eq!(preset, Some(NodeId::new(7)));
3879 }
3880
3881 #[test]
3882 fn get_path_to_root_is_root_first_and_tolerates_unknown_nodes() {
3883 let sd = nested_body(); let h = sd.node_hierarchy.as_container();
3885
3886 assert_eq!(get_path_to_root(&h, NodeId::ZERO), vec![NodeId::ZERO]);
3887 assert_eq!(
3888 get_path_to_root(&h, NodeId::new(2)),
3889 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2)]
3890 );
3891
3892 assert_eq!(
3894 get_path_to_root(&h, NodeId::new(9999)),
3895 vec![NodeId::new(9999)]
3896 );
3897 }
3898
3899 #[test]
3904 fn is_before_in_document_order_is_false_for_identical_nodes() {
3905 let sd = flat_body(2);
3906 assert!(!is_before_in_document_order(
3907 &sd.node_hierarchy,
3908 NodeId::new(1),
3909 NodeId::new(1)
3910 ));
3911 }
3912
3913 #[test]
3914 fn is_before_in_document_order_orders_ancestors_and_siblings() {
3915 let sd = flat_body(3); let h = &sd.node_hierarchy;
3917
3918 assert!(is_before_in_document_order(h, NodeId::ZERO, NodeId::new(1)));
3919 assert!(!is_before_in_document_order(h, NodeId::new(1), NodeId::ZERO));
3920 assert!(is_before_in_document_order(h, NodeId::new(1), NodeId::new(3)));
3921 assert!(!is_before_in_document_order(h, NodeId::new(3), NodeId::new(1)));
3922 }
3923
3924 #[test]
3925 fn is_before_in_document_order_is_antisymmetric_across_a_nested_tree() {
3926 let sd = nested_body();
3927 let h = &sd.node_hierarchy;
3928 for a in 0..3 {
3929 for b in 0..3 {
3930 let ab = is_before_in_document_order(h, NodeId::new(a), NodeId::new(b));
3931 let ba = is_before_in_document_order(h, NodeId::new(b), NodeId::new(a));
3932 if a == b {
3933 assert!(!ab && !ba, "a node is never before itself");
3934 } else {
3935 assert_ne!(ab, ba, "exactly one of ({a},{b}) / ({b},{a}) must hold");
3936 }
3937 }
3938 }
3939 }
3940
3941 #[test]
3942 fn is_before_in_document_order_is_deterministic_for_unknown_nodes() {
3943 let sd = flat_body(1);
3944 let h = &sd.node_hierarchy;
3945 assert!(is_before_in_document_order(h, NodeId::ZERO, NodeId::new(usize::MAX)));
3948 assert!(!is_before_in_document_order(h, NodeId::new(usize::MAX), NodeId::ZERO));
3949 }
3950
3951 #[test]
3952 fn collect_nodes_in_document_order_start_equals_end() {
3953 let sd = flat_body(2);
3954 assert_eq!(
3955 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(2), NodeId::new(2)),
3956 vec![NodeId::new(2)]
3957 );
3958 assert_eq!(
3960 collect_nodes_in_document_order(
3961 &sd.node_hierarchy,
3962 NodeId::new(usize::MAX),
3963 NodeId::new(usize::MAX)
3964 ),
3965 vec![NodeId::new(usize::MAX)]
3966 );
3967 }
3968
3969 #[test]
3970 fn collect_nodes_in_document_order_walks_the_tree_in_pre_order() {
3971 let sd = flat_body(3); assert_eq!(
3973 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::ZERO, NodeId::new(3)),
3974 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2), NodeId::new(3)]
3975 );
3976 assert_eq!(
3977 collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(1), NodeId::new(2)),
3978 vec![NodeId::new(1), NodeId::new(2)]
3979 );
3980
3981 let nested = nested_body();
3983 assert_eq!(
3984 collect_nodes_in_document_order(&nested.node_hierarchy, NodeId::ZERO, NodeId::new(2)),
3985 vec![NodeId::ZERO, NodeId::new(1), NodeId::new(2)]
3986 );
3987 }
3988
3989 #[test]
3990 fn collect_nodes_in_document_order_terminates_when_end_precedes_start() {
3991 let sd = flat_body(3);
3994 let out = collect_nodes_in_document_order(&sd.node_hierarchy, NodeId::new(2), NodeId::new(1));
3995 assert!(out.is_empty());
3996 }
3997
3998 #[test]
3999 fn collect_nodes_in_document_order_with_an_unreachable_end_stops_at_the_tree_end() {
4000 let sd = flat_body(3);
4001 let out = collect_nodes_in_document_order(
4002 &sd.node_hierarchy,
4003 NodeId::new(1),
4004 NodeId::new(usize::MAX),
4005 );
4006 assert_eq!(
4007 out,
4008 vec![NodeId::new(1), NodeId::new(2), NodeId::new(3)],
4009 "an end node that is never reached must terminate at the end of the traversal"
4010 );
4011 }
4012
4013 #[test]
4018 fn is_layout_equivalent_holds_for_independently_built_identical_doms() {
4019 assert!(is_layout_equivalent(&flat_body(3), &flat_body(3)));
4020 assert!(is_layout_equivalent(
4021 &StyledDom::default(),
4022 &StyledDom::default()
4023 ));
4024 assert!(is_layout_equivalent(&nested_body(), &nested_body()));
4025 }
4026
4027 #[test]
4028 fn is_layout_equivalent_rejects_a_different_node_count() {
4029 assert!(!is_layout_equivalent(&flat_body(3), &flat_body(4)));
4030 assert!(!is_layout_equivalent(&flat_body(0), &flat_body(1)));
4031 }
4032
4033 #[test]
4034 fn is_layout_equivalent_rejects_a_different_structure() {
4035 assert!(!is_layout_equivalent(&nested_body(), &flat_body(2)));
4037 }
4038
4039 #[test]
4040 fn is_layout_equivalent_rejects_a_changed_class() {
4041 let build = |class: &str| {
4042 let mut dom = Dom::create_body().with_children(
4043 vec![Dom::create_div().with_class(class.to_string().into())].into(),
4044 );
4045 StyledDom::create(&mut dom, Css::empty())
4046 };
4047 assert!(is_layout_equivalent(&build("a"), &build("a")));
4048 assert!(!is_layout_equivalent(&build("a"), &build("b")));
4049 }
4050
4051 #[test]
4052 fn is_layout_equivalent_rejects_a_changed_pseudo_state() {
4053 let base = flat_body(2);
4054 let mut hovered = flat_body(2);
4055 let _ = hovered.restyle_nodes_hover(&[NodeId::new(1)], true);
4056 assert!(
4057 !is_layout_equivalent(&base, &hovered),
4058 ":hover changes CSS resolution, so the DOMs are not layout-equivalent"
4059 );
4060 }
4061
4062 #[test]
4067 fn compact_dom_len_and_is_empty() {
4068 let single = convert_dom_into_compact_dom(Dom::create_div());
4069 assert_eq!(single.len(), 1);
4070 assert!(!single.is_empty());
4071
4072 let tree = convert_dom_into_compact_dom(
4073 Dom::create_body().with_children(vec![Dom::create_div(); 4].into()),
4074 );
4075 assert_eq!(tree.len(), 5);
4076 assert!(!tree.is_empty());
4077
4078 let empty = CompactDom {
4080 node_hierarchy: NodeHierarchy {
4081 internal: Vec::new(),
4082 },
4083 node_data: NodeDataContainer {
4084 internal: Vec::new(),
4085 },
4086 root: NodeId::ZERO,
4087 };
4088 assert_eq!(empty.len(), 0);
4089 assert!(empty.is_empty());
4090 }
4091
4092 #[test]
4093 fn convert_dom_into_compact_dom_links_flat_siblings() {
4094 let compact = convert_dom_into_compact_dom(
4095 Dom::create_body().with_children(vec![Dom::create_div(); 3].into()),
4096 );
4097 assert_eq!(compact.len(), 4);
4098 assert_eq!(compact.root, NodeId::ZERO);
4099
4100 let h = compact.node_hierarchy.as_ref();
4101 assert_eq!(h[NodeId::ZERO].parent, None);
4102 assert_eq!(h[NodeId::ZERO].last_child, Some(NodeId::new(3)));
4103
4104 for i in 1..=3usize {
4105 assert_eq!(h[NodeId::new(i)].parent, Some(NodeId::ZERO));
4106 let expected_next = if i == 3 { None } else { Some(NodeId::new(i + 1)) };
4107 assert_eq!(h[NodeId::new(i)].next_sibling, expected_next);
4108 let expected_prev = if i == 1 { None } else { Some(NodeId::new(i - 1)) };
4109 assert_eq!(h[NodeId::new(i)].previous_sibling, expected_prev);
4110 assert_eq!(h[NodeId::new(i)].last_child, None, "the children are leaves");
4111 }
4112 }
4113
4114 #[test]
4121 fn convert_dom_into_compact_dom_last_child_is_the_last_direct_child() {
4122 let sd = nested_body();
4124 let h = sd.node_hierarchy.as_container();
4125
4126 let last_direct_child = NodeId::ZERO.az_children(&h).last();
4127 assert_eq!(last_direct_child, Some(NodeId::new(1)));
4128 assert_eq!(
4129 h[NodeId::ZERO].last_child_id(),
4130 last_direct_child,
4131 "last_child_id() must agree with the forward child iteration"
4132 );
4133 }
4134
4135 #[test]
4136 fn convert_dom_into_compact_dom_handles_an_empty_and_a_deep_tree() {
4137 assert_eq!(convert_dom_into_compact_dom(Dom::create_body()).len(), 1);
4138
4139 let mut deep = Dom::create_div();
4140 for _ in 0..64 {
4141 deep = Dom::create_div().with_children(vec![deep].into());
4142 }
4143 let compact = convert_dom_into_compact_dom(deep);
4144 assert_eq!(compact.len(), 65);
4145 let h = compact.node_hierarchy.as_ref();
4147 for i in 1..65usize {
4148 assert_eq!(h[NodeId::new(i)].parent, Some(NodeId::new(i - 1)));
4149 }
4150 }
4151
4152 #[test]
4157 fn scope_inline_css_advances_next_id_once_per_node() {
4158 let mut dom = Dom::create_body().with_children(
4159 vec![
4160 Dom::create_div().with_children(vec![Dom::create_div()].into()),
4161 Dom::create_div(),
4162 ]
4163 .into(),
4164 );
4165 let _ = dom.fixup_children_estimated();
4166
4167 let mut next = 0usize;
4168 scope_inline_css(&mut dom, &mut next);
4169 assert_eq!(next, 4, "4 nodes → the counter must land on 4 (pre-order ids 0..3)");
4170 }
4171
4172 #[test]
4173 fn scope_inline_css_from_zero_and_from_a_large_offset() {
4174 let mut leaf = Dom::create_div();
4175 let _ = leaf.fixup_children_estimated();
4176 let mut next = 0usize;
4177 scope_inline_css(&mut leaf, &mut next);
4178 assert_eq!(next, 1, "a single leaf consumes exactly one id");
4179
4180 let mut dom = Dom::create_body().with_children(vec![Dom::create_div(); 2].into());
4182 let _ = dom.fixup_children_estimated();
4183 let mut big = 1_000_000usize;
4184 scope_inline_css(&mut dom, &mut big);
4185 assert_eq!(big, 1_000_003);
4186 }
4187
4188 #[test]
4189 fn scope_inline_css_preserves_the_rule_count_of_every_node() {
4190 let mut dom = Dom::create_body()
4191 .with_css("color: red")
4192 .with_children(vec![Dom::create_div().with_css("width: 5px")].into());
4193 let _ = dom.fixup_children_estimated();
4194
4195 let rules_before: usize = dom
4196 .css
4197 .as_ref()
4198 .iter()
4199 .map(|c| c.rules.as_ref().len())
4200 .sum::<usize>()
4201 + dom.children.as_ref()[0]
4202 .css
4203 .as_ref()
4204 .iter()
4205 .map(|c| c.rules.as_ref().len())
4206 .sum::<usize>();
4207 assert!(rules_before > 0, "with_css must produce at least one rule");
4208
4209 let mut next = 0usize;
4210 scope_inline_css(&mut dom, &mut next);
4211
4212 let rules_after: usize = dom
4213 .css
4214 .as_ref()
4215 .iter()
4216 .map(|c| c.rules.as_ref().len())
4217 .sum::<usize>()
4218 + dom.children.as_ref()[0]
4219 .css
4220 .as_ref()
4221 .iter()
4222 .map(|c| c.rules.as_ref().len())
4223 .sum::<usize>();
4224 assert_eq!(
4225 rules_before, rules_after,
4226 "scoping rewrites paths in place; it must not add or drop rules"
4227 );
4228 assert_eq!(next, 2);
4229 }
4230
4231 #[test]
4232 fn collect_css_from_dom_yields_inner_css_before_outer_css() {
4233 let outer = parse_css("div { color: red; } span { color: blue; }");
4234 let inner = parse_css("p { color: green; }");
4235 let outer_rules = outer.rules.as_ref().len();
4236 let inner_rules = inner.rules.as_ref().len();
4237 assert_ne!(
4238 outer_rules, inner_rules,
4239 "the two stylesheets must be distinguishable by rule count"
4240 );
4241
4242 let mut child = Dom::create_div();
4243 child.add_component_css(inner);
4244 let mut dom = Dom::create_body().with_children(vec![child].into());
4245 dom.add_component_css(outer);
4246
4247 let mut out = Vec::new();
4248 collect_css_from_dom(&dom, &mut out);
4249
4250 assert_eq!(out.len(), 2);
4251 assert_eq!(
4252 out[0].rules.as_ref().len(),
4253 inner_rules,
4254 "deeper CSS is collected first (lower cascade priority)"
4255 );
4256 assert_eq!(out[1].rules.as_ref().len(), outer_rules);
4257 }
4258
4259 #[test]
4260 fn collect_css_from_dom_on_a_css_free_tree_appends_nothing() {
4261 let dom = Dom::create_body().with_children(vec![Dom::create_div(); 3].into());
4262 let mut out = Vec::new();
4263 collect_css_from_dom(&dom, &mut out);
4264 assert!(out.is_empty());
4265
4266 let mut prefilled = vec![Css::empty()];
4268 collect_css_from_dom(&dom, &mut prefilled);
4269 assert_eq!(prefilled.len(), 1);
4270 }
4271
4272 #[test]
4273 fn strip_css_from_dom_clears_every_node_recursively() {
4274 let mut dom = Dom::create_body()
4275 .with_css("color: red")
4276 .with_children(
4277 vec![Dom::create_div()
4278 .with_css("width: 5px")
4279 .with_children(vec![Dom::create_div().with_css("height: 5px")].into())]
4280 .into(),
4281 );
4282 assert!(!dom.css.as_ref().is_empty());
4283
4284 strip_css_from_dom(&mut dom);
4285
4286 assert!(dom.css.as_ref().is_empty());
4287 let child = &dom.children.as_ref()[0];
4288 assert!(child.css.as_ref().is_empty());
4289 assert!(child.children.as_ref()[0].css.as_ref().is_empty());
4290
4291 strip_css_from_dom(&mut dom);
4293 assert!(dom.css.as_ref().is_empty());
4294 }
4295}