1use azul_core::dom::{Dom, DomVec, IdOrClass, IdOrClass::Class, IdOrClassVec};
8#[allow(clippy::wildcard_imports)] use azul_css::{
10 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
11 props::{
12 basic::*,
13 layout::*,
14 property::{CssProperty, *},
15 style::*,
16 },
17 *,
18};
19
20const BORDER_COLOR: ColorU = ColorU {
21 r: 221,
22 g: 221,
23 b: 221,
24 a: 255,
25};
26
27const STRING_16146701490593874959: AzString = AzString::from_const_str("system:ui");
28const STYLE_FONT_FAMILY_8122988506401935406_ITEMS: &[StyleFontFamily] =
29 &[StyleFontFamily::System(STRING_16146701490593874959)];
30
31const CSS_MATCH_15775557796860201720_PROPERTIES: &[CssPropertyWithConditions] = &[
32 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
34 LayoutHeight::Px(PixelValue::const_px(8)),
35 ))),
36 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
37 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
38 inner: PixelValue::const_px(1),
39 }),
40 )),
41 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
42 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
43 inner: BorderStyle::Solid,
44 }),
45 )),
46 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
47 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
48 inner: BORDER_COLOR,
49 }),
50 )),
51 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
52 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
53 inner: PixelValue::const_px(1),
54 }),
55 )),
56 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
57 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
58 inner: BorderStyle::Solid,
59 }),
60 )),
61 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
62 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
63 inner: BORDER_COLOR,
64 }),
65 )),
66 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
68 LayoutWidth::Px(PixelValue::const_px(5)),
69 ))),
70 CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
71 LayoutMarginTop {
72 inner: PixelValue::const_px(6),
73 },
74 ))),
75 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
76 LayoutFlexGrow {
77 inner: FloatValue::const_new(1),
78 },
79 ))),
80 CssPropertyWithConditions::simple(CssProperty::FlexDirection(LayoutFlexDirectionValue::Exact(
81 LayoutFlexDirection::Column,
82 ))),
83];
84const CSS_MATCH_15775557796860201720: CssPropertyWithConditionsVec =
85 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_15775557796860201720_PROPERTIES);
86
87const CSS_MATCH_16739370686243728873_PROPERTIES: &[CssPropertyWithConditions] = &[
88 CssPropertyWithConditions::simple(CssProperty::FontSize(StyleFontSizeValue::Exact(
90 StyleFontSize {
91 inner: PixelValue::const_px(11),
92 },
93 ))),
94 CssPropertyWithConditions::simple(CssProperty::FontFamily(StyleFontFamilyVecValue::Exact(
95 StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_8122988506401935406_ITEMS),
96 ))),
97 CssPropertyWithConditions::simple(CssProperty::FlexDirection(LayoutFlexDirectionValue::Exact(
98 LayoutFlexDirection::Row,
99 ))),
100 CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
101 LayoutAlignItems::End,
102 ))),
103];
104const CSS_MATCH_16739370686243728873: CssPropertyWithConditionsVec =
105 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_16739370686243728873_PROPERTIES);
106
107const CSS_MATCH_4236783900531286611_PROPERTIES: &[CssPropertyWithConditions] = &[
108 CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
110 StyleTextAlign::Center,
111 ))),
112 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
113 LayoutPaddingLeft {
114 inner: PixelValue::const_px(3),
115 },
116 ))),
117 CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
118 LayoutPaddingRight {
119 inner: PixelValue::const_px(1),
120 },
121 ))),
122 CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
123 LayoutPaddingBottom {
124 inner: PixelValue::const_px(0),
125 },
126 ))),
127 CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
128 LayoutPaddingTop {
129 inner: PixelValue::const_px(0),
130 },
131 ))),
132];
133const CSS_MATCH_4236783900531286611: CssPropertyWithConditionsVec =
134 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_4236783900531286611_PROPERTIES);
135
136const CSS_MATCH_8602559445190067154_PROPERTIES: &[CssPropertyWithConditions] = &[
137 CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
139 LayoutPaddingRight {
140 inner: PixelValue::const_px(5),
141 },
142 ))),
143 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
144 LayoutPaddingLeft {
145 inner: PixelValue::const_px(5),
146 },
147 ))),
148 CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
149 LayoutPaddingBottom {
150 inner: PixelValue::const_px(3),
151 },
152 ))),
153 CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
154 LayoutPaddingTop {
155 inner: PixelValue::const_px(3),
156 },
157 ))),
158];
159const CSS_MATCH_8602559445190067154: CssPropertyWithConditionsVec =
160 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_8602559445190067154_PROPERTIES);
161
162const CSS_MATCH_9156589477016488419_PROPERTIES: &[CssPropertyWithConditions] = &[
163 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
165 LayoutHeight::Px(PixelValue::const_px(8)),
166 ))),
167 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
168 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
169 inner: PixelValue::const_px(1),
170 }),
171 )),
172 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
173 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
174 inner: BorderStyle::Solid,
175 }),
176 )),
177 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
178 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
179 inner: BORDER_COLOR,
180 }),
181 )),
182 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
183 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
184 inner: PixelValue::const_px(1),
185 }),
186 )),
187 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
188 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
189 inner: BorderStyle::Solid,
190 }),
191 )),
192 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
193 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
194 inner: BORDER_COLOR,
195 }),
196 )),
197 CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
199 LayoutMarginTop {
200 inner: PixelValue::const_px(6),
201 },
202 ))),
203 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
204 LayoutFlexGrow {
205 inner: FloatValue::const_new(1),
206 },
207 ))),
208 CssPropertyWithConditions::simple(CssProperty::FlexDirection(LayoutFlexDirectionValue::Exact(
209 LayoutFlexDirection::Column,
210 ))),
211];
212const CSS_MATCH_9156589477016488419: CssPropertyWithConditionsVec =
213 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_9156589477016488419_PROPERTIES);
214
215const CSS_MATCH_CONTENT_AREA_PROPERTIES: &[CssPropertyWithConditions] = &[
216 CssPropertyWithConditions::simple(CssProperty::PaddingRight(
218 LayoutPaddingRightValue::Exact(LayoutPaddingRight {
219 inner: PixelValue::const_px(5),
220 }),
221 )),
222 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(
223 LayoutPaddingLeftValue::Exact(LayoutPaddingLeft {
224 inner: PixelValue::const_px(5),
225 }),
226 )),
227 CssPropertyWithConditions::simple(CssProperty::PaddingBottom(
228 LayoutPaddingBottomValue::Exact(LayoutPaddingBottom {
229 inner: PixelValue::const_px(5),
230 }),
231 )),
232 CssPropertyWithConditions::simple(CssProperty::PaddingTop(
233 LayoutPaddingTopValue::Exact(LayoutPaddingTop {
234 inner: PixelValue::const_px(5),
235 }),
236 )),
237 CssPropertyWithConditions::simple(CssProperty::FontSize(
238 StyleFontSizeValue::Exact(StyleFontSize {
239 inner: PixelValue::const_px(11),
240 }),
241 )),
242 CssPropertyWithConditions::simple(CssProperty::FontFamily(
243 StyleFontFamilyVecValue::Exact(StyleFontFamilyVec::from_const_slice(
244 STYLE_FONT_FAMILY_8122988506401935406_ITEMS,
245 )),
246 )),
247 CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
248 LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
249 inner: PixelValue::const_px(1),
250 }),
251 )),
252 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
253 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
254 inner: PixelValue::const_px(1),
255 }),
256 )),
257 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
258 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
259 inner: PixelValue::const_px(1),
260 }),
261 )),
262 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
263 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
264 inner: PixelValue::const_px(1),
265 }),
266 )),
267 CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
268 StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
269 inner: BorderStyle::Solid,
270 }),
271 )),
272 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
273 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
274 inner: BorderStyle::Solid,
275 }),
276 )),
277 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
278 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
279 inner: BorderStyle::Solid,
280 }),
281 )),
282 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
283 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
284 inner: BorderStyle::Solid,
285 }),
286 )),
287 CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
288 StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
289 inner: BORDER_COLOR,
290 }),
291 )),
292 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
293 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
294 inner: BORDER_COLOR,
295 }),
296 )),
297 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
298 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
299 inner: BORDER_COLOR,
300 }),
301 )),
302 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
303 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
304 inner: BORDER_COLOR,
305 }),
306 )),
307];
308
309#[derive(Debug, Clone)]
313#[repr(C)]
314pub struct Frame {
315 pub title: AzString,
316 pub flex_grow: f32,
317 pub content: Dom,
318}
319
320impl Frame {
321 #[must_use] pub const fn create(title: AzString, content: Dom) -> Self {
323 Self {
324 title,
325 content,
326 flex_grow: 0.0,
327 }
328 }
329
330 #[must_use]
332 pub const fn swap_with_default(&mut self) -> Self {
333 let mut s = Self::create(AzString::from_const_str(""), Dom::create_div());
334 core::mem::swap(&mut s, self);
335 s
336 }
337
338 pub const fn set_flex_grow(&mut self, flex_grow: f32) {
340 self.flex_grow = flex_grow;
341 }
342
343 #[must_use] pub const fn with_flex_grow(mut self, flex_grow: f32) -> Self {
345 self.set_flex_grow(flex_grow);
346 self
347 }
348
349 #[must_use] pub fn dom(self) -> Dom {
350 Dom::create_div()
351 .with_css_props(CSS_MATCH_8602559445190067154)
352 .with_ids_and_classes({
353 const IDS_AND_CLASSES_14615537625743340639: &[IdOrClass] =
354 &[Class(AzString::from_const_str("__azul-native-frame"))];
355 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_14615537625743340639)
356 })
357 .with_children(DomVec::from_vec(vec![
358 Dom::create_div()
359 .with_css_props(CSS_MATCH_16739370686243728873)
360 .with_ids_and_classes({
361 const IDS_AND_CLASSES_17776797146874875377: &[IdOrClass] = &[Class(
362 AzString::from_const_str("__azul-native-frame-header"),
363 )];
364 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_17776797146874875377)
365 })
366 .with_children(DomVec::from_vec(vec![
367 Dom::create_div()
368 .with_css_props(CSS_MATCH_15775557796860201720)
369 .with_ids_and_classes({
370 const IDS_AND_CLASSES_15264202958442287530: &[IdOrClass] =
371 &[Class(AzString::from_const_str(
372 "__azul-native-frame-header-before",
373 ))];
374 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_15264202958442287530)
375 })
376 .with_children(DomVec::from_vec(vec![Dom::create_div()])),
377 Dom::create_text(self.title).with_css_props(CSS_MATCH_4236783900531286611),
378 Dom::create_div()
379 .with_css_props(CSS_MATCH_9156589477016488419)
380 .with_ids_and_classes({
381 const IDS_AND_CLASSES_5689091102265932280: &[IdOrClass] = &[Class(
382 AzString::from_const_str("__azul-native-frame-header-after"),
383 )];
384 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_5689091102265932280)
385 })
386 .with_children(DomVec::from_vec(vec![Dom::create_div()])),
387 ])),
388 Dom::create_div()
389 .with_css_props({
390 let mut props = vec![
391 CssPropertyWithConditions::simple(CssProperty::FlexGrow(
392 LayoutFlexGrowValue::Exact(LayoutFlexGrow {
393 inner: FloatValue::new(self.flex_grow),
394 }),
395 )),
396 ];
397 props.extend_from_slice(CSS_MATCH_CONTENT_AREA_PROPERTIES);
398 CssPropertyWithConditionsVec::from_vec(props)
399 })
400 .with_ids_and_classes({
401 const IDS_AND_CLASSES_9898887665724137124: &[IdOrClass] = &[Class(
402 AzString::from_const_str("__azul-native-frame-content"),
403 )];
404 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_9898887665724137124)
405 })
406 .with_children(vec![self.content].into()),
407 ]))
408 }
409}
410
411#[cfg(test)]
412#[allow(
413 clippy::float_cmp,
414 clippy::cast_precision_loss,
415 clippy::unreadable_literal,
416 clippy::too_many_lines
417)]
418mod autotest_generated {
419 use std::collections::HashSet;
420
421 use azul_core::dom::{NodeData, NodeType};
422
423 use super::*;
424
425 const ADVERSARIAL_TEXT: [&str; 6] = [
434 "",
435 "a\0b",
436 "e\u{0301}\u{0301}\u{0301}",
437 "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}",
438 "\u{202E}gnirts desrever\u{202C}",
439 "\u{FFFD}\u{FEFF}\t\n",
440 ];
441
442 const ADVERSARIAL_FLOATS: [f32; 14] = [
447 0.0,
448 -0.0,
449 1.0,
450 -1.0,
451 0.001,
452 -0.001,
453 f32::EPSILON,
454 f32::MIN_POSITIVE,
455 -f32::MIN_POSITIVE,
456 f32::MAX,
457 f32::MIN,
458 f32::INFINITY,
459 f32::NEG_INFINITY,
460 f32::NAN,
461 ];
462
463 fn frame(title: &str, content: Dom) -> Frame {
465 Frame::create(AzString::from(title), content)
466 }
467
468 fn default_frame() -> Frame {
470 Frame::create(AzString::from_const_str(""), Dom::create_div())
471 }
472
473 fn kids(dom: &Dom) -> &[Dom] {
474 dom.children.as_ref()
475 }
476
477 fn header(dom: &Dom) -> &Dom {
479 &kids(dom)[0]
480 }
481
482 fn content_area(dom: &Dom) -> &Dom {
484 &kids(dom)[1]
485 }
486
487 fn rule_before(dom: &Dom) -> &Dom {
489 &kids(header(dom))[0]
490 }
491
492 fn title_node(dom: &Dom) -> &Dom {
494 &kids(header(dom))[1]
495 }
496
497 fn rule_after(dom: &Dom) -> &Dom {
499 &kids(header(dom))[2]
500 }
501
502 fn props_of(node: &NodeData) -> Vec<CssProperty> {
504 node.style
505 .iter_inline_properties()
506 .map(|(p, _)| p.clone())
507 .collect()
508 }
509
510 fn inline_props(dom: &Dom) -> Vec<CssProperty> {
511 props_of(&dom.root)
512 }
513
514 fn declared(v: &[CssPropertyWithConditions]) -> Vec<CssProperty> {
516 v.iter().map(|p| p.property.clone()).collect()
517 }
518
519 fn classes(dom: &Dom) -> Vec<String> {
521 dom.root
522 .get_ids_and_classes()
523 .as_ref()
524 .iter()
525 .filter_map(|c| match c {
526 IdOrClass::Class(s) => Some(s.as_str().to_string()),
527 IdOrClass::Id(_) => None,
528 })
529 .collect()
530 }
531
532 fn text_of(dom: &Dom) -> Option<&str> {
533 match dom.root.get_node_type() {
534 NodeType::Text(s) => Some(s.as_ref().as_str()),
535 _ => None,
536 }
537 }
538
539 fn flex_grow_of(dom: &Dom) -> Option<f32> {
542 dom.root
543 .style
544 .iter_inline_properties()
545 .find_map(|(p, _)| match p {
546 CssProperty::FlexGrow(v) => v.get_property().map(|f| f.inner.get()),
547 _ => None,
548 })
549 }
550
551 fn content_flex_grow(dom: &Dom) -> Option<f32> {
554 flex_grow_of(content_area(dom))
555 }
556
557 fn count_flex_grow(dom: &Dom) -> usize {
558 inline_props(dom)
559 .iter()
560 .filter(|p| matches!(p, CssProperty::FlexGrow(_)))
561 .count()
562 }
563
564 fn px(pv: &PixelValue) -> f32 {
568 assert_eq!(
569 pv.metric,
570 SizeMetric::Px,
571 "frame geometry must be absolute px, got {:?}",
572 pv.metric
573 );
574 pv.number.get()
575 }
576
577 fn length_of(p: &CssProperty) -> Option<PixelValue> {
579 match p {
580 CssProperty::Height(v) => match v.get_property() {
581 Some(LayoutHeight::Px(pv)) => Some(*pv),
582 _ => None,
583 },
584 CssProperty::Width(v) => match v.get_property() {
585 Some(LayoutWidth::Px(pv)) => Some(*pv),
586 _ => None,
587 },
588 CssProperty::FontSize(v) => v.get_property().map(|x| x.inner),
589 CssProperty::MarginTop(v) => v.get_property().map(|x| x.inner),
590 CssProperty::PaddingTop(v) => v.get_property().map(|x| x.inner),
591 CssProperty::PaddingBottom(v) => v.get_property().map(|x| x.inner),
592 CssProperty::PaddingLeft(v) => v.get_property().map(|x| x.inner),
593 CssProperty::PaddingRight(v) => v.get_property().map(|x| x.inner),
594 CssProperty::BorderTopWidth(v) => v.get_property().map(|x| x.inner),
595 CssProperty::BorderBottomWidth(v) => v.get_property().map(|x| x.inner),
596 CssProperty::BorderLeftWidth(v) => v.get_property().map(|x| x.inner),
597 CssProperty::BorderRightWidth(v) => v.get_property().map(|x| x.inner),
598 _ => None,
599 }
600 }
601
602 fn border_color_of(p: &CssProperty) -> Option<ColorU> {
604 match p {
605 CssProperty::BorderTopColor(v) => v.get_property().map(|x| x.inner),
606 CssProperty::BorderBottomColor(v) => v.get_property().map(|x| x.inner),
607 CssProperty::BorderLeftColor(v) => v.get_property().map(|x| x.inner),
608 CssProperty::BorderRightColor(v) => v.get_property().map(|x| x.inner),
609 _ => None,
610 }
611 }
612
613 fn walk<'a>(dom: &'a Dom, out: &mut Vec<&'a Dom>) {
614 out.push(dom);
615 for c in dom.children.as_ref() {
616 walk(c, out);
617 }
618 }
619
620 fn all_nodes(dom: &Dom) -> Vec<&Dom> {
622 let mut v = Vec::new();
623 walk(dom, &mut v);
624 v
625 }
626
627 fn count_descendants(dom: &Dom) -> usize {
632 dom.children
633 .as_ref()
634 .iter()
635 .map(|c| 1 + count_descendants(c))
636 .sum()
637 }
638
639 fn nested_divs(depth: usize) -> Dom {
642 let mut d = Dom::create_div();
643 for _ in 0..depth {
644 d = Dom::create_div().with_child(d);
645 }
646 d
647 }
648
649 #[test]
654 fn create_zeroes_flex_grow_and_stores_both_arguments_verbatim() {
655 let f = frame("Settings", Dom::create_text("body"));
656
657 assert_eq!(f.flex_grow.to_bits(), 0_u32, "flex_grow must start at +0.0");
660 assert_eq!(f.title.as_str(), "Settings", "the title was not stored verbatim");
661 assert_eq!(
662 text_of(&f.content),
663 Some("body"),
664 "the content was not stored verbatim",
665 );
666 }
667
668 #[test]
669 fn create_stores_pathological_titles_byte_for_byte() {
670 for t in ADVERSARIAL_TEXT {
671 let f = frame(t, Dom::create_div());
672 assert_eq!(f.title.as_str(), t, "the frame mangled or normalised its title");
673 assert_eq!(
674 f.title.as_str().len(),
675 t.len(),
676 "the byte length of the title changed (NUL truncation or re-encoding?)",
677 );
678
679 let dom = f.dom();
681 assert_eq!(
682 text_of(title_node(&dom)),
683 Some(t),
684 "the title was corrupted on the way into the DOM",
685 );
686 }
687 }
688
689 #[test]
690 fn create_accepts_a_very_long_title_without_truncating_it() {
691 let huge: String = "\u{1F600}".repeat(100_000);
693 let f = frame(&huge, Dom::create_div());
694 assert_eq!(f.title.as_str().len(), 400_000, "the long title was truncated on store");
695
696 let dom = f.dom();
697 assert_eq!(
698 text_of(title_node(&dom)).map(str::len),
699 Some(400_000),
700 "the long title was truncated on the way into the DOM",
701 );
702 }
703
704 #[test]
705 fn create_accepts_deeply_nested_and_very_wide_content() {
706 let deep = frame("deep", nested_divs(64)).dom();
707 assert_eq!(
708 deep.estimated_total_children,
709 count_descendants(&deep),
710 "the cached child count desynced for deeply nested content",
711 );
712 assert_eq!(deep.estimated_total_children, 8 + 64, "frame chrome is 8 nodes + the content");
713
714 let wide = frame(
715 "wide",
716 Dom::create_div().with_children(
717 (0..2000).map(|_| Dom::create_div()).collect::<Vec<_>>().into(),
718 ),
719 )
720 .dom();
721 assert_eq!(
722 wide.estimated_total_children,
723 count_descendants(&wide),
724 "the cached child count desynced for very wide content",
725 );
726 assert_eq!(wide.estimated_total_children, 8 + 2000);
727 }
728
729 #[test]
730 fn frames_nest_without_desyncing_the_child_count_cache() {
731 let inner = frame("inner", Dom::create_div()).dom();
732 assert_eq!(inner.estimated_total_children, 8, "a bare frame is 8 nodes below the root");
733
734 let outer = frame("outer", inner).dom();
735 assert_eq!(
736 outer.estimated_total_children,
737 count_descendants(&outer),
738 "nesting a frame inside a frame desynced the cached child count",
739 );
740 assert_eq!(outer.estimated_total_children, 16);
741 }
742
743 #[test]
748 fn swap_with_default_moves_every_field_out_and_leaves_a_default() {
749 let mut f = frame("payload", Dom::create_text("body")).with_flex_grow(2.5);
750
751 let taken = f.swap_with_default();
752
753 assert_eq!(taken.title.as_str(), "payload", "the title did not travel out");
754 assert_eq!(text_of(&taken.content), Some("body"), "the content did not travel out");
755 assert_eq!(taken.flex_grow, 2.5, "flex_grow did not travel out");
756
757 assert_eq!(f.title.as_str(), "", "the swapped-in default kept a title");
758 assert_eq!(f.content, Dom::create_div(), "the swapped-in default is not an empty div");
759 assert_eq!(
760 f.flex_grow.to_bits(),
761 0_u32,
762 "the swapped-in default has a non-zero flex_grow",
763 );
764 }
765
766 #[test]
767 fn repeated_swap_with_default_never_accumulates_state() {
768 let mut f = frame("x", Dom::create_text("y")).with_flex_grow(1.0);
769 let first = f.swap_with_default();
770 assert_eq!(first.title.as_str(), "x");
771
772 for i in 0..8 {
773 let taken = f.swap_with_default();
774 assert_eq!(taken.title.as_str(), "", "swap #{i} handed back a non-default title");
775 assert_eq!(taken.content, Dom::create_div(), "swap #{i} handed back non-default content");
776 assert_eq!(taken.flex_grow.to_bits(), 0_u32, "swap #{i} handed back a non-zero factor");
777 assert_eq!(f.title.as_str(), "", "swap #{i} left a non-default title behind");
778 }
779
780 let dom = f.dom();
782 assert_eq!(kids(&dom).len(), 2);
783 assert_eq!(text_of(title_node(&dom)), Some(""));
784 assert_eq!(content_flex_grow(&dom), Some(0.0));
785 }
786
787 #[test]
788 fn swap_with_default_preserves_a_nan_factor_in_the_value_it_hands_back() {
789 let mut f = default_frame().with_flex_grow(f32::NAN);
792 let taken = f.swap_with_default();
793 assert!(taken.flex_grow.is_nan(), "the NaN factor was rewritten by the swap");
794 assert_eq!(f.flex_grow.to_bits(), 0_u32, "the NaN leaked into the swapped-in default");
795 }
796
797 #[test]
798 fn swap_with_default_leaves_a_frame_that_still_renders() {
799 let mut f = frame("original", nested_divs(8));
802 let taken = f.swap_with_default();
803
804 let left_behind = f.dom();
805 let moved_out = taken.dom();
806
807 assert_eq!(text_of(title_node(&left_behind)), Some(""));
808 assert_eq!(text_of(title_node(&moved_out)), Some("original"));
809 assert_eq!(left_behind.estimated_total_children, 8);
810 assert_eq!(moved_out.estimated_total_children, 8 + 8);
811 }
812
813 #[test]
818 fn set_flex_grow_stores_the_bit_pattern_verbatim_without_sanitising() {
819 for v in ADVERSARIAL_FLOATS {
822 let mut f = default_frame();
823 f.set_flex_grow(v);
824 if v.is_nan() {
825 assert!(f.flex_grow.is_nan(), "a NaN flex_grow was silently rewritten");
826 } else {
827 assert_eq!(
828 f.flex_grow.to_bits(),
829 v.to_bits(),
830 "flex_grow {v} was not stored verbatim",
831 );
832 }
833 }
834 }
835
836 #[test]
837 fn set_flex_grow_is_last_write_wins() {
838 let mut f = default_frame();
839 for v in ADVERSARIAL_FLOATS {
840 f.set_flex_grow(v);
841 }
842 assert!(f.flex_grow.is_nan(), "the final write (NaN) did not win");
843
844 f.set_flex_grow(1.5);
845 assert_eq!(f.flex_grow, 1.5, "a later write did not overwrite the NaN");
846 assert_eq!(count_flex_grow(content_area(&f.dom())), 1, "flex-grow was declared twice");
847 }
848
849 #[test]
850 fn with_flex_grow_is_exactly_set_flex_grow() {
851 for v in ADVERSARIAL_FLOATS {
852 let mut by_setter = default_frame();
853 by_setter.set_flex_grow(v);
854 let by_builder = default_frame().with_flex_grow(v);
855
856 assert_eq!(
857 by_setter.flex_grow.to_bits(),
858 by_builder.flex_grow.to_bits(),
859 "the builder and the setter disagree for {v}",
860 );
861 }
862 }
863
864 #[test]
865 fn with_flex_grow_touches_only_the_numeric_field() {
866 let f = frame("title", Dom::create_text("body")).with_flex_grow(f32::NAN);
867
868 assert_eq!(f.title.as_str(), "title", "with_flex_grow clobbered the title");
869 assert_eq!(text_of(&f.content), Some("body"), "with_flex_grow clobbered the content");
870 }
871
872 #[test]
873 fn flex_grow_zero_encodes_to_positive_zero_even_from_negative_zero() {
874 for v in [0.0_f32, -0.0_f32] {
875 let dom = default_frame().with_flex_grow(v).dom();
876 let got = content_flex_grow(&dom).expect("flex-grow must always be declared");
877 assert_eq!(got.to_bits(), 0_u32, "flex_grow {v} did not encode to +0.0 (got {got})");
878 }
879 }
880
881 #[test]
882 fn flex_grow_round_trips_through_the_dom_at_milli_precision() {
883 for v in [0.0_f32, 0.001, 0.5, 1.0, 2.5, 3.0, -1.5, 1000.0, 65536.0] {
886 let got = content_flex_grow(&default_frame().with_flex_grow(v).dom())
887 .expect("flex-grow must always be declared");
888 assert!(
889 (got - v).abs() <= 0.001,
890 "flex_grow {v} did not survive the FloatValue encoding (got {got})",
891 );
892 }
893 }
894
895 #[test]
896 fn flex_grow_below_the_encoding_precision_truncates_to_zero() {
897 for v in [
900 f32::EPSILON,
901 f32::MIN_POSITIVE,
902 -f32::MIN_POSITIVE,
903 1e-4,
904 -1e-4,
905 0.0005,
906 -0.0009,
907 ] {
908 let got = content_flex_grow(&default_frame().with_flex_grow(v).dom())
909 .expect("flex-grow must always be declared");
910 assert_eq!(
911 got.to_bits(),
912 0_u32,
913 "sub-milli flex_grow {v} did not truncate to +0.0 (got {got})",
914 );
915 }
916
917 let smallest = content_flex_grow(&default_frame().with_flex_grow(0.001).dom()).unwrap();
919 assert!(smallest > 0.0, "0.001 is supposed to be the smallest representable factor");
920 }
921
922 #[test]
923 fn a_nan_flex_grow_does_not_panic_and_lands_on_zero() {
924 let dom = default_frame().with_flex_grow(f32::NAN).dom();
925 let got = content_flex_grow(&dom).expect("flex-grow must always be declared");
926 assert!(!got.is_nan(), "a NaN flex-grow reached the style tree");
927 assert_eq!(got.to_bits(), 0_u32, "NaN must encode to +0.0 (saturating cast), got {got}");
928 }
929
930 #[test]
931 fn infinite_and_maximal_flex_grow_saturates_instead_of_overflowing() {
932 let max_encoded = (isize::MAX as f32) / 1000.0;
936 let min_encoded = (isize::MIN as f32) / 1000.0;
937
938 for v in [f32::INFINITY, f32::MAX] {
939 let got = content_flex_grow(&default_frame().with_flex_grow(v).dom()).unwrap();
940 assert!(got.is_finite(), "an infinite flex-grow reached the style tree for {v}");
941 assert_eq!(got, max_encoded, "{v} did not saturate at the isize upper bound");
942 assert!(got > 0.0, "{v} wrapped around into a non-positive factor");
943 }
944
945 for v in [f32::NEG_INFINITY, f32::MIN] {
946 let got = content_flex_grow(&default_frame().with_flex_grow(v).dom()).unwrap();
947 assert!(got.is_finite(), "an infinite flex-grow reached the style tree for {v}");
948 assert_eq!(got, min_encoded, "{v} did not saturate at the isize lower bound");
949 assert!(got < 0.0, "{v} wrapped around into a non-negative factor");
950 }
951 }
952
953 #[test]
954 fn no_flex_grow_input_can_put_a_nan_or_an_infinity_into_the_style_tree() {
955 for v in ADVERSARIAL_FLOATS {
956 let dom = default_frame().with_flex_grow(v).dom();
957 let got = content_flex_grow(&dom)
958 .unwrap_or_else(|| panic!("flex-grow disappeared for input {v}"));
959 assert!(
960 got.is_finite(),
961 "input {v} produced a non-finite flex-grow ({got}) — the layout solver would \
962 NaN out",
963 );
964 }
965 }
966
967 #[test]
968 fn flex_grow_encoding_is_monotonic() {
969 let ascending = [-1000.0_f32, -1.5, -0.001, 0.0, 0.001, 1.5, 1000.0];
971 let mut prev = f32::NEG_INFINITY;
972 for v in ascending {
973 let got = content_flex_grow(&default_frame().with_flex_grow(v).dom()).unwrap();
974 assert!(
975 got >= prev,
976 "encoding is not monotonic: {v} encoded to {got}, below the previous {prev}",
977 );
978 prev = got;
979 }
980 }
981
982 #[test]
983 fn the_callers_factor_only_ever_reaches_the_content_area() {
984 for v in ADVERSARIAL_FLOATS {
988 let dom = default_frame().with_flex_grow(v).dom();
989
990 assert_eq!(count_flex_grow(&dom), 0, "the frame root declared a flex-grow");
991 assert_eq!(count_flex_grow(header(&dom)), 0, "the header declared a flex-grow");
992 assert_eq!(count_flex_grow(title_node(&dom)), 0, "the title declared a flex-grow");
993 assert_eq!(flex_grow_of(rule_before(&dom)), Some(1.0), "left rule factor changed");
994 assert_eq!(flex_grow_of(rule_after(&dom)), Some(1.0), "right rule factor changed");
995 assert_eq!(
996 count_flex_grow(content_area(&dom)),
997 1,
998 "the content area must declare flex-grow exactly once",
999 );
1000 }
1001 }
1002
1003 #[test]
1008 fn dom_has_the_documented_shape() {
1009 let dom = frame("Title", Dom::create_text("content")).dom();
1010
1011 assert_eq!(kids(&dom).len(), 2, "a frame is a header plus a content area");
1012 assert_eq!(kids(header(&dom)).len(), 3, "the header is rule / title / rule");
1013 assert_eq!(kids(rule_before(&dom)).len(), 1, "the left rule wraps exactly one div");
1014 assert_eq!(kids(rule_after(&dom)).len(), 1, "the right rule wraps exactly one div");
1015 assert!(kids(title_node(&dom)).is_empty(), "the title node must be a leaf");
1016 assert_eq!(kids(content_area(&dom)).len(), 1, "the content area holds exactly one child");
1017
1018 assert_eq!(text_of(title_node(&dom)), Some("Title"));
1019 assert_eq!(text_of(&kids(content_area(&dom))[0]), Some("content"));
1020 assert_eq!(dom.estimated_total_children, count_descendants(&dom));
1021 }
1022
1023 #[test]
1024 fn an_empty_title_still_produces_a_text_node_between_the_two_rules() {
1025 let dom = default_frame().dom();
1028 assert_eq!(kids(header(&dom)).len(), 3, "an empty title collapsed the header");
1029 assert_eq!(text_of(title_node(&dom)), Some(""), "the empty title node vanished");
1030 }
1031
1032 #[test]
1033 fn dom_carries_every_class_the_stylesheet_selects_on() {
1034 let dom = frame("t", Dom::create_div()).dom();
1035
1036 assert_eq!(classes(&dom), vec!["__azul-native-frame".to_string()]);
1037 assert_eq!(classes(header(&dom)), vec!["__azul-native-frame-header".to_string()]);
1038 assert_eq!(
1039 classes(rule_before(&dom)),
1040 vec!["__azul-native-frame-header-before".to_string()],
1041 );
1042 assert_eq!(
1043 classes(rule_after(&dom)),
1044 vec!["__azul-native-frame-header-after".to_string()],
1045 );
1046 assert_eq!(classes(content_area(&dom)), vec!["__azul-native-frame-content".to_string()]);
1047
1048 assert!(classes(title_node(&dom)).is_empty(), "the title node grew a class");
1051 assert!(classes(&kids(rule_before(&dom))[0]).is_empty());
1052 assert!(classes(&kids(rule_after(&dom))[0]).is_empty());
1053 }
1054
1055 #[test]
1056 fn dom_declares_exactly_the_build_time_constants_on_the_chrome_nodes() {
1057 let dom = frame("t", Dom::create_div()).dom();
1058
1059 assert_eq!(inline_props(&dom), declared(CSS_MATCH_8602559445190067154.as_ref()));
1060 assert_eq!(inline_props(header(&dom)), declared(CSS_MATCH_16739370686243728873.as_ref()));
1061 assert_eq!(
1062 inline_props(rule_before(&dom)),
1063 declared(CSS_MATCH_15775557796860201720.as_ref()),
1064 );
1065 assert_eq!(
1066 inline_props(rule_after(&dom)),
1067 declared(CSS_MATCH_9156589477016488419.as_ref()),
1068 );
1069 assert_eq!(inline_props(title_node(&dom)), declared(CSS_MATCH_4236783900531286611.as_ref()));
1070 }
1071
1072 #[test]
1073 fn the_content_area_declares_flex_grow_first_then_the_static_block_verbatim() {
1074 let dom = default_frame().with_flex_grow(2.5).dom();
1075
1076 let mut expected = vec![CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
1077 LayoutFlexGrow {
1078 inner: FloatValue::new(2.5),
1079 },
1080 ))];
1081 expected.extend(declared(CSS_MATCH_CONTENT_AREA_PROPERTIES));
1082
1083 assert_eq!(
1084 inline_props(content_area(&dom)),
1085 expected,
1086 "the content-area style block drifted from FlexGrow + the static constant",
1087 );
1088 }
1089
1090 #[test]
1091 fn no_node_declares_the_same_property_twice() {
1092 let dom = frame("t", Dom::create_div()).with_flex_grow(1.0).dom();
1095 for (i, node) in all_nodes(&dom).into_iter().enumerate() {
1096 let props = inline_props(node);
1097 let mut seen = HashSet::new();
1098 for p in &props {
1099 assert!(
1100 seen.insert(p.get_type()),
1101 "node {i} declares {:?} twice",
1102 p.get_type(),
1103 );
1104 }
1105 }
1106 }
1107
1108 #[test]
1109 fn every_declaration_is_unconditional() {
1110 let dom = frame("t", Dom::create_div()).dom();
1113 for (i, node) in all_nodes(&dom).into_iter().enumerate() {
1114 for (p, conditions) in node.root.style.iter_inline_properties() {
1115 assert!(
1116 conditions.as_ref().is_empty(),
1117 "node {i} gates {:?} behind a dynamic selector",
1118 p.get_type(),
1119 );
1120 }
1121 }
1122 }
1123
1124 #[test]
1125 fn all_frame_lengths_are_absolute_px() {
1126 let dom = frame("t", Dom::create_div()).dom();
1127 for node in all_nodes(&dom) {
1128 for p in inline_props(node) {
1129 if let Some(pv) = length_of(&p) {
1130 let v = px(&pv); assert!(
1132 v.is_finite() && (0.0..=64.0).contains(&v),
1133 "{:?} = {v}px is outside the plausible frame-chrome range",
1134 p.get_type(),
1135 );
1136 }
1137 }
1138 }
1139 }
1140
1141 #[test]
1142 fn every_border_in_the_frame_uses_the_one_border_colour() {
1143 let dom = frame("t", Dom::create_div()).dom();
1144 let mut seen = 0_usize;
1145 for node in all_nodes(&dom) {
1146 for p in inline_props(node) {
1147 if let Some(c) = border_color_of(&p) {
1148 assert_eq!(c, BORDER_COLOR, "{:?} uses an off-palette colour", p.get_type());
1149 seen += 1;
1150 }
1151 }
1152 }
1153 assert_eq!(seen, 8, "the number of coloured borders in the frame changed");
1156 }
1157
1158 #[test]
1159 fn the_content_area_is_fully_boxed_in_on_all_four_sides() {
1160 let props = inline_props(content_area(&frame("t", Dom::create_div()).dom()));
1162 let widths = [
1163 CssPropertyType::BorderTopWidth,
1164 CssPropertyType::BorderBottomWidth,
1165 CssPropertyType::BorderLeftWidth,
1166 CssPropertyType::BorderRightWidth,
1167 ];
1168 for want in widths {
1169 let found = props
1170 .iter()
1171 .find(|p| p.get_type() == want)
1172 .unwrap_or_else(|| panic!("the content area is missing {want:?}"));
1173 assert_eq!(
1174 length_of(found).map(|pv| px(&pv)),
1175 Some(1.0),
1176 "{want:?} is not a 1px hairline",
1177 );
1178 }
1179 }
1180
1181 #[test]
1182 fn the_two_header_rules_are_symmetric_apart_from_the_left_rule_carrying_a_width() {
1183 let dom = frame("t", Dom::create_div()).dom();
1189 let before: Vec<CssPropertyType> =
1190 inline_props(rule_before(&dom)).iter().map(CssProperty::get_type).collect();
1191 let after: Vec<CssPropertyType> =
1192 inline_props(rule_after(&dom)).iter().map(CssProperty::get_type).collect();
1193
1194 assert!(before.contains(&CssPropertyType::Width), "the left rule lost its width cap");
1195 assert!(
1196 !after.contains(&CssPropertyType::Width),
1197 "the right rule gained a width — the known before/after asymmetry was fixed, \
1198 update this test",
1199 );
1200
1201 assert!(before.contains(&CssPropertyType::BorderLeftWidth));
1203 assert!(after.contains(&CssPropertyType::BorderRightWidth));
1204
1205 for shared in [
1207 CssPropertyType::Height,
1208 CssPropertyType::BorderTopWidth,
1209 CssPropertyType::BorderTopStyle,
1210 CssPropertyType::BorderTopColor,
1211 CssPropertyType::MarginTop,
1212 CssPropertyType::FlexGrow,
1213 CssPropertyType::FlexDirection,
1214 ] {
1215 assert!(before.contains(&shared), "the left rule is missing {shared:?}");
1216 assert!(after.contains(&shared), "the right rule is missing {shared:?}");
1217 }
1218 }
1219
1220 #[test]
1221 fn the_inner_rule_divs_carry_no_style_of_their_own() {
1222 let dom = frame("t", Dom::create_div()).dom();
1228 for inner in [&kids(rule_before(&dom))[0], &kids(rule_after(&dom))[0]] {
1229 assert!(
1230 inline_props(inner).is_empty(),
1231 "the inner rule div grew inline properties",
1232 );
1233 assert!(kids(inner).is_empty(), "the inner rule div grew children");
1234 assert_eq!(inner.root.get_node_type(), &NodeType::Div);
1235 }
1236
1237 for outer in [rule_before(&dom), rule_after(&dom)] {
1239 let types: Vec<CssPropertyType> =
1240 inline_props(outer).iter().map(CssProperty::get_type).collect();
1241 assert!(types.contains(&CssPropertyType::Height));
1242 assert!(types.contains(&CssPropertyType::BorderTopWidth));
1243 }
1244 }
1245
1246 #[test]
1247 fn the_frame_is_inert_and_attaches_no_callbacks_or_stylesheets() {
1248 let dom = frame("t", Dom::create_div()).with_flex_grow(1.0).dom();
1249 for (i, node) in all_nodes(&dom).into_iter().enumerate() {
1250 assert!(
1251 node.root.callbacks.as_ref().is_empty(),
1252 "node {i} of a stateless frame carries a callback",
1253 );
1254 assert!(
1255 node.css.as_ref().is_empty(),
1256 "node {i} attached a whole stylesheet instead of inline props",
1257 );
1258 }
1259 }
1260
1261 #[test]
1262 fn dom_moves_the_content_in_verbatim_including_its_own_classes_and_children() {
1263 let content = Dom::create_div()
1264 .with_ids_and_classes(IdOrClassVec::from_vec(vec![Class(AzString::from(
1265 "user-content",
1266 ))]))
1267 .with_children(DomVec::from_vec(vec![
1268 Dom::create_text("a"),
1269 Dom::create_text("b"),
1270 ]));
1271 let expected = content.clone();
1272
1273 let dom = frame("t", content).dom();
1274 let placed = &kids(content_area(&dom))[0];
1275
1276 assert_eq!(*placed, expected, "the content DOM was rewritten on the way in");
1277 assert_eq!(classes(placed), vec!["user-content".to_string()]);
1278 assert_eq!(kids(placed).len(), 2);
1279 }
1280
1281 #[test]
1282 fn dom_is_deterministic_for_equal_inputs() {
1283 for t in ADVERSARIAL_TEXT {
1284 for v in ADVERSARIAL_FLOATS {
1285 let a = frame(t, Dom::create_text(t)).with_flex_grow(v).dom();
1286 let b = frame(t, Dom::create_text(t)).with_flex_grow(v).dom();
1287 assert_eq!(a, b, "two identical frames rendered differently for {t:?} / {v}");
1288 }
1289 }
1290 }
1291
1292 #[test]
1293 fn distinct_factors_that_encode_differently_produce_distinct_doms() {
1294 let a = default_frame().with_flex_grow(1.0).dom();
1297 let b = default_frame().with_flex_grow(2.0).dom();
1298 assert_ne!(a, b, "the flex factor never reached the rendered DOM");
1299
1300 let nan = default_frame().with_flex_grow(f32::NAN).dom();
1302 let zero = default_frame().with_flex_grow(0.0).dom();
1303 assert_eq!(nan, zero, "NaN and 0.0 stopped encoding to the same style tree");
1304 }
1305
1306 #[test]
1307 fn the_title_is_styled_the_same_no_matter_what_it_contains() {
1308 let baseline = declared(CSS_MATCH_4236783900531286611.as_ref());
1309 for t in ADVERSARIAL_TEXT {
1310 let dom = frame(t, Dom::create_div()).dom();
1311 assert_eq!(
1312 inline_props(title_node(&dom)),
1313 baseline,
1314 "the title style changed for input {t:?}",
1315 );
1316 assert_eq!(kids(header(&dom)).len(), 3, "the header shape changed for input {t:?}");
1317 assert_eq!(dom.estimated_total_children, 8, "the node count changed for {t:?}");
1318 }
1319 }
1320}