1use super::*;
4
5pub fn selftest() -> (usize, usize) {
8 let cases: &[(&str, &str, &str, bool)] = &[
9 (
11 "<input id='t' type='text'>",
12 "input[type=text]{zz:1}",
13 "t",
14 true,
15 ),
16 (
17 "<input id='t' type='text'>",
18 "input[type=checkbox]{zz:1}",
19 "t",
20 false,
21 ),
22 (
26 "<input id='t' type='CHECKBOX'>",
27 "input[type=\"checkbox\" i]{zz:1}",
28 "t",
29 true,
30 ),
31 (
33 "<input id='t' type='CHECKBOX'>",
34 "input[type=checkbox i]{zz:1}",
35 "t",
36 true,
37 ),
38 (
40 "<input id='t' type='CHECKBOX'>",
41 "input[type=checkbox]{zz:1}",
42 "t",
43 false,
44 ),
45 (
47 "<input id='t' type='CHECKBOX'>",
48 "input[type=\"checkbox\" s]{zz:1}",
49 "t",
50 false,
51 ),
52 (
53 "<div id='t' data-role='nav main'></div>",
54 "[data-role~=main]{zz:1}",
55 "t",
56 true,
57 ),
58 (
59 "<a id='t' href='https://x'></a>",
60 "[href^='https']{zz:1}",
61 "t",
62 true,
63 ),
64 (
65 "<a id='t' href='/page.html'></a>",
66 "[href$='.html']{zz:1}",
67 "t",
68 true,
69 ),
70 (
71 "<a id='t' href='/img/a.png'></a>",
72 "[href*='img']{zz:1}",
73 "t",
74 true,
75 ),
76 ("<div id='t' class='box'></div>", "[class]{zz:1}", "t", true),
77 ("<div id='t'></div>", "[class]{zz:1}", "t", false),
78 (
80 "<input id='t' type='checkbox' checked>",
81 "input:checked{zz:1}",
82 "t",
83 true,
84 ),
85 (
86 "<input id='t' type='checkbox'>",
87 "input:checked{zz:1}",
88 "t",
89 false,
90 ),
91 (
92 "<button id='t' disabled>x</button>",
93 "button:disabled{zz:1}",
94 "t",
95 true,
96 ),
97 (
98 "<button id='t'>x</button>",
99 "button:disabled{zz:1}",
100 "t",
101 false,
102 ),
103 (
105 "<button id='t' disabled>x</button>",
106 "button:enabled{zz:1}",
107 "t",
108 false,
109 ),
110 (
111 "<button id='t'>x</button>",
112 "button:enabled{zz:1}",
113 "t",
114 true,
115 ),
116 (
121 "<input id='t' type='checkbox' _indeterminate>",
122 "input:indeterminate{zz:1}",
123 "t",
124 true,
125 ),
126 (
132 "<input id='t' type='text' required _user_interacted>",
133 "input:user-invalid{zz:1}",
134 "t",
135 true,
136 ),
137 (
138 "<input id='t' type='text' required value='x' _user_interacted>",
139 "input:user-valid{zz:1}",
140 "t",
141 true,
142 ),
143 (
144 "<input id='t' type='text' required>",
145 "input:user-invalid{zz:1}",
146 "t",
147 false,
148 ),
149 (
153 "<div id='t'></div>",
154 "div:defined{zz:1}",
155 "t",
156 true,
157 ),
158 (
159 "<span id='t'></span>",
160 ":defined{zz:1}",
161 "t",
162 true,
163 ),
164 (
167 "<div id='t' _fullscreen></div>",
168 "div:fullscreen{zz:1}",
169 "t",
170 true,
171 ),
172 (
173 "<div id='t'></div>",
174 "div:fullscreen{zz:1}",
175 "t",
176 false,
177 ),
178 (
181 "<dialog id='t' open _modal></dialog>",
182 "dialog:modal{zz:1}",
183 "t",
184 true,
185 ),
186 (
187 "<dialog id='t' open></dialog>",
188 "dialog:modal{zz:1}",
189 "t",
190 false,
191 ),
192 (
193 "<div id='t' _fullscreen></div>",
194 "div:modal{zz:1}",
195 "t",
196 true,
197 ),
198 (
201 "<div id='t' popover _popover_open></div>",
202 "div:popover-open{zz:1}",
203 "t",
204 true,
205 ),
206 (
207 "<div id='t' popover></div>",
208 "div:popover-open{zz:1}",
209 "t",
210 false,
211 ),
212 (
213 "<input id='t' type='checkbox'>",
214 "input:indeterminate{zz:1}",
215 "t",
216 false,
217 ),
218 (
220 "<ul><li></li><li id='t'></li><li></li></ul>",
221 "li:nth-child(2){zz:1}",
222 "t",
223 true,
224 ),
225 (
226 "<ul><li id='t'></li><li></li></ul>",
227 "li:nth-child(odd){zz:1}",
228 "t",
229 true,
230 ),
231 (
232 "<ul><li></li><li id='t'></li></ul>",
233 "li:nth-child(odd){zz:1}",
234 "t",
235 false,
236 ),
237 (
238 "<ul><li></li><li id='t'></li><li></li><li></li></ul>",
239 "li:nth-child(even){zz:1}",
240 "t",
241 true,
242 ),
243 (
244 "<ul><li></li><li></li><li id='t'></li></ul>",
245 "li:nth-child(3){zz:1}",
246 "t",
247 true,
248 ),
249 (
250 "<ul><li></li><li></li><li id='t'></li></ul>",
251 "li:nth-last-child(1){zz:1}",
252 "t",
253 true,
254 ),
255 (
260 "<ul><li></li><li></li><li id='t'></li><li></li></ul>",
261 "li:nth-child(-n+3){zz:1}",
262 "t",
263 true,
264 ),
265 (
266 "<ul><li></li><li></li><li></li><li id='t'></li></ul>",
267 "li:nth-child(-n+3){zz:1}",
268 "t",
269 false,
270 ),
271 (
272 "<ul><li id='t'></li><li></li><li></li></ul>",
273 "li:nth-child(-n+3){zz:1}",
274 "t",
275 true,
276 ),
277 (
283 "<ul><li id='t'></li><li></li></ul>",
284 "li:nth-child(2n+1 of .foo){zz:1}",
285 "t",
286 false,
287 ),
288 (
290 "<ul><li class='foo'></li><li></li><li class='foo' id='t'></li><li class='foo'></li></ul>",
291 "li:nth-child(2n+1 of .foo){zz:1}",
292 "t",
293 false,
294 ),
295 (
296 "<ul><li class='foo'></li><li></li><li class='foo' id='t'></li><li class='foo'></li></ul>",
297 "li:nth-child(2n of .foo){zz:1}",
298 "t",
299 true,
300 ),
301 (
303 "<ul><li class='foo'></li><li></li><li class='foo' id='t'></li><li class='foo'></li></ul>",
304 "li:nth-last-child(1 of .foo){zz:1}",
305 "t",
306 false,
307 ),
308 (
309 "<ul><li class='foo'></li><li></li><li class='foo'></li><li class='foo' id='t'></li></ul>",
310 "li:nth-last-child(1 of .foo){zz:1}",
311 "t",
312 true,
313 ),
314 (
316 "<div><li></li><p></p><li></li><li id='t'></li></div>",
317 "li:nth-last-of-type(1){zz:1}",
318 "t",
319 true,
320 ),
321 (
322 "<div><li id='t'></li><p></p><li></li><li></li></div>",
323 "li:nth-last-of-type(1){zz:1}",
324 "t",
325 false,
326 ),
327 (
328 "<ul><li id='t'></li><li></li><li></li></ul>",
329 "li:nth-child(2n+1){zz:1}",
330 "t",
331 true,
332 ),
333 ("<p id='t' class='a'></p>", "p:not(.b){zz:1}", "t", true),
335 ("<p id='t' class='b'></p>", "p:not(.b){zz:1}", "t", false),
336 ("<div id='t'></div>", "*{zz:1}", "t", true),
338 (
340 "<ul><li class='x'></li><li id='t' class='x'></li></ul>",
341 ".x:nth-child(2){zz:1}",
342 "t",
343 true,
344 ),
345 (
347 "<div><h1></h1><p id='t'></p></div>",
348 "h1 + p{zz:1}",
349 "t",
350 true,
351 ),
352 (
353 "<div><h1></h1><span></span><p id='t'></p></div>",
354 "h1 + p{zz:1}",
355 "t",
356 false,
357 ),
358 (
359 "<div><h1></h1><span></span><p id='t'></p></div>",
360 "h1 ~ p{zz:1}",
361 "t",
362 true,
363 ),
364 (
365 "<div><p id='t'></p><h1></h1></div>",
366 "h1 ~ p{zz:1}",
367 "t",
368 false,
369 ),
370 (
371 "<ul><li class='a'></li><li id='t' class='b'></li></ul>",
372 ".a + .b{zz:1}",
373 "t",
374 true,
375 ),
376 (
378 "<input id='t' type='checkbox' checked>",
379 "input:checked{zz:1}",
380 "t",
381 true,
382 ),
383 (
384 "<input id='t' type='checkbox'>",
385 "input:checked{zz:1}",
386 "t",
387 false,
388 ),
389 (
390 "<input id='t' disabled>",
391 "input:disabled{zz:1}",
392 "t",
393 true,
394 ),
395 ("<input id='t'>", "input:disabled{zz:1}", "t", false),
396 (
397 "<input id='t' required>",
398 "input:required{zz:1}",
399 "t",
400 true,
401 ),
402 ("<input id='t'>", "input:optional{zz:1}", "t", true),
404 (
405 "<input id='t' required>",
406 "input:optional{zz:1}",
407 "t",
408 false,
409 ),
410 (
412 "<input id='t' required>",
413 "input:invalid{zz:1}",
414 "t",
415 true,
416 ),
417 (
418 "<input id='t' required value='x'>",
419 "input:invalid{zz:1}",
420 "t",
421 false,
422 ),
423 (
424 "<input id='t' required value='x'>",
425 "input:valid{zz:1}",
426 "t",
427 true,
428 ),
429 (
430 "<input id='t' min='1' max='10' value='20'>",
431 "input:invalid{zz:1}",
432 "t",
433 true,
434 ),
435 (
436 "<input id='t' min='1' max='10' value='5'>",
437 "input:valid{zz:1}",
438 "t",
439 true,
440 ),
441 (
446 "<input id='t' type='checkbox' required>",
447 "input:invalid{zz:1}",
448 "t",
449 true,
450 ),
451 (
452 "<input id='t' type='checkbox' required checked>",
453 "input:valid{zz:1}",
454 "t",
455 true,
456 ),
457 (
460 "<div><input id='t' type='radio' name='g' required><input type='radio' name='g' checked></div>",
461 "input:valid{zz:1}",
462 "t",
463 true,
464 ),
465 (
466 "<div><input id='t' type='radio' name='g' required><input type='radio' name='g'></div>",
467 "input:invalid{zz:1}",
468 "t",
469 true,
470 ),
471 (
472 "<input id='t' readonly>",
473 "input:readonly{zz:1}",
474 "t",
475 true,
476 ),
477 (
479 "<input id='t' readonly>",
480 "input:read-write{zz:1}",
481 "t",
482 false,
483 ),
484 (
485 "<input id='t'>",
486 "input:read-write{zz:1}",
487 "t",
488 true,
489 ),
490 (
492 "<input id='t' placeholder='name'>",
493 "input:placeholder-shown{zz:1}",
494 "t",
495 true,
496 ),
497 (
498 "<input id='t' placeholder='name' value='x'>",
499 "input:placeholder-shown{zz:1}",
500 "t",
501 false,
502 ),
503 ("<input id='t'>", "input:placeholder-shown{zz:1}", "t", false),
504 (
506 "<input id='t' type='number' min='1' max='10' value='5'>",
507 "input:in-range{zz:1}",
508 "t",
509 true,
510 ),
511 (
512 "<input id='t' type='number' min='1' max='10' value='50'>",
513 "input:in-range{zz:1}",
514 "t",
515 false,
516 ),
517 (
518 "<input id='t' type='number' min='1' max='10' value='50'>",
519 "input:out-of-range{zz:1}",
520 "t",
521 true,
522 ),
523 (
524 "<input id='t' type='number' min='1' max='10' value='5'>",
525 "input:out-of-range{zz:1}",
526 "t",
527 false,
528 ),
529 (
531 "<input id='t' type='number' value='5'>",
532 "input:in-range{zz:1}",
533 "t",
534 false,
535 ),
536 (
538 "<input id='t' type='checkbox' checked>",
539 "input:default{zz:1}",
540 "t",
541 true,
542 ),
543 (
544 "<input id='t' type='checkbox'>",
545 "input:default{zz:1}",
546 "t",
547 false,
548 ),
549 (
550 "<input id='t' type='text' checked>",
551 "input:default{zz:1}",
552 "t",
553 false,
554 ),
555 (
556 "<option id='t' selected>x</option>",
557 "option:default{zz:1}",
558 "t",
559 true,
560 ),
561 (
562 "<option id='t'>x</option>",
563 "option:default{zz:1}",
564 "t",
565 false,
566 ),
567 ("<a id='t' href='https://example.com'>x</a>", "a:link{zz:1}", "t", true),
570 ("<a id='t'>x</a>", "a:link{zz:1}", "t", false),
571 (
572 "<a id='t' href='https://example.com'>x</a>",
573 "a:visited{zz:1}",
574 "t",
575 false,
576 ),
577 (
580 "<div><p></p><span id='t'></span><span></span></div>",
581 "span:first-of-type{zz:1}",
582 "t",
583 true,
584 ),
585 (
586 "<div><span></span><p></p><span id='t'></span></div>",
587 "span:first-of-type{zz:1}",
588 "t",
589 false,
590 ),
591 (
592 "<div><span></span><span id='t'></span><p></p></div>",
593 "span:last-of-type{zz:1}",
594 "t",
595 true,
596 ),
597 (
598 "<div><p></p><span></span><span id='t'></span><span></span></div>",
599 "span:nth-of-type(2){zz:1}",
600 "t",
601 true,
602 ),
603 (
604 "<div><p id='t'></p><span></span></div>",
605 "p:only-of-type{zz:1}",
606 "t",
607 true,
608 ),
609 (
610 "<div><p id='t'></p><p></p></div>",
611 "p:only-of-type{zz:1}",
612 "t",
613 false,
614 ),
615 ("<div><p id='t'></p></div>", "p:only-child{zz:1}", "t", true),
616 (
617 "<div><p id='t'></p><span></span></div>",
618 "p:only-child{zz:1}",
619 "t",
620 false,
621 ),
622 ("<div id='t'></div>", "div:empty{zz:1}", "t", true),
623 (
624 "<div id='t'><span></span></div>",
625 "div:empty{zz:1}",
626 "t",
627 false,
628 ),
629 (
630 "<div id='t'>text</div>",
631 "div:empty{zz:1}",
632 "t",
633 false,
634 ),
635 (
637 "<p id='t' class='a'></p>",
638 ":is(.a, .b){zz:1}",
639 "t",
640 true,
641 ),
642 (
643 "<p id='t' class='c'></p>",
644 ":is(.a, .b){zz:1}",
645 "t",
646 false,
647 ),
648 (
649 "<div><section><p id='t'></p></section></div>",
650 ":is(section, article) p{zz:1}",
651 "t",
652 true,
653 ),
654 (
655 "<span id='t' class='x'></span>",
656 ":where(.x, .y){zz:1}",
657 "t",
658 true,
659 ),
660 (
662 "<div id='t'><span><img></span></div>",
663 "div:has(img){zz:1}",
664 "t",
665 true,
666 ),
667 (
668 "<div id='t'><span></span></div>",
669 "div:has(img){zz:1}",
670 "t",
671 false,
672 ),
673 (
674 "<div id='t'><p class='note'>x</p></div>",
675 "div:has(.note, .warn){zz:1}",
676 "t",
677 true,
678 ),
679 (
682 "<div id='t'><img></div>",
683 "div:has(> img){zz:1}",
684 "t",
685 true,
686 ),
687 (
688 "<div id='t'><span><img></span></div>",
689 "div:has(> img){zz:1}",
690 "t",
691 false,
692 ),
693 (
695 "<div><p id='t'></p><img></div>",
696 "p:has(+ img){zz:1}",
697 "t",
698 true,
699 ),
700 (
701 "<div><p id='t'></p><span></span><img></div>",
702 "p:has(+ img){zz:1}",
703 "t",
704 false,
705 ),
706 (
708 "<div><p id='t'></p><span></span><img></div>",
709 "p:has(~ img){zz:1}",
710 "t",
711 true,
712 ),
713 (
714 "<div><p id='t'></p><span></span></div>",
715 "p:has(~ img){zz:1}",
716 "t",
717 false,
718 ),
719 (
721 "<div class='card'><span id='t'></span></div>",
722 ".card { & span { zz:1; } }",
723 "t",
724 true,
725 ),
726 (
728 "<div class='card'><span id='t'></span></div>",
729 ".card { span { zz:1; } }",
730 "t",
731 true,
732 ),
733 (
735 "<div id='t' class='card'></div>",
736 ".card { &.active { zz:1; } }",
737 "t",
738 false,
739 ),
740 (
741 "<div id='t' class='card active'></div>",
742 ".card { &.active { zz:1; } }",
743 "t",
744 true,
745 ),
746 (
750 "<div id='t' class='card'></div>",
751 ".card { &:hover{color:blue;} zz:1; }",
752 "t",
753 true,
754 ),
755 (
758 "<div id='t'></div>",
759 "@supports (display: grid) { #t{zz:1} }",
760 "t",
761 true,
762 ),
763 (
764 "<div id='t'></div>",
765 "@supports not (display: grid) { #t{zz:1} }",
766 "t",
767 false,
768 ),
769 (
770 "<div id='t'></div>",
771 "@supports (display: grid) and (gap: 1rem) { #t{zz:1} }",
772 "t",
773 true,
774 ),
775 (
776 "<div id='t'></div>",
777 "@supports not (display: grid) or (gap: 1rem) { #t{zz:1} }",
778 "t",
779 true,
780 ),
781 (
783 "<div id='t'></div>",
784 "@media (prefers-color-scheme: light) { #t{zz:1} }",
785 "t",
786 true,
787 ),
788 (
789 "<div id='t'></div>",
790 "@media (prefers-color-scheme: dark) { #t{zz:1} }",
791 "t",
792 false,
793 ),
794 (
796 "<div id='t'></div>",
797 "@media (prefers-reduced-motion: no-preference) { #t{zz:1} }",
798 "t",
799 true,
800 ),
801 (
802 "<div id='t'></div>",
803 "@media (prefers-reduced-motion: reduce) { #t{zz:1} }",
804 "t",
805 false,
806 ),
807 (
810 "<div id='t'></div>",
811 "@media (prefers-contrast: no-preference) { #t{zz:1} }",
812 "t",
813 true,
814 ),
815 (
816 "<div id='t'></div>",
817 "@media (prefers-contrast: more) { #t{zz:1} }",
818 "t",
819 false,
820 ),
821 (
822 "<div id='t'></div>",
823 "@media (prefers-contrast: less) { #t{zz:1} }",
824 "t",
825 false,
826 ),
827 (
831 "<div id='t'></div>",
832 "@media (update: fast) { #t{zz:1} }",
833 "t",
834 true,
835 ),
836 (
837 "<div id='t'></div>",
838 "@media (update: none) { #t{zz:1} }",
839 "t",
840 false,
841 ),
842 (
843 "<div id='t'></div>",
844 "@media (grid: 0) { #t{zz:1} }",
845 "t",
846 true,
847 ),
848 (
849 "<div id='t'></div>",
850 "@media (grid: 1) { #t{zz:1} }",
851 "t",
852 false,
853 ),
854 (
855 "<div id='t'></div>",
856 "@media (prefers-reduced-transparency: no-preference) { #t{zz:1} }",
857 "t",
858 true,
859 ),
860 (
861 "<div id='t'></div>",
862 "@media (prefers-reduced-transparency: reduce) { #t{zz:1} }",
863 "t",
864 false,
865 ),
866 (
867 "<div id='t'></div>",
868 "@media (prefers-reduced-data: no-preference) { #t{zz:1} }",
869 "t",
870 true,
871 ),
872 (
873 "<div id='t'></div>",
874 "@media (prefers-reduced-data: reduce) { #t{zz:1} }",
875 "t",
876 false,
877 ),
878 (
883 "<div id='t'></div>",
884 "@media (pointer: fine) { #t{zz:1} }",
885 "t",
886 true,
887 ),
888 (
889 "<div id='t'></div>",
890 "@media (pointer: coarse) { #t{zz:1} }",
891 "t",
892 false,
893 ),
894 (
895 "<div id='t'></div>",
896 "@media (hover: hover) { #t{zz:1} }",
897 "t",
898 true,
899 ),
900 (
901 "<div id='t'></div>",
902 "@media (hover: none) { #t{zz:1} }",
903 "t",
904 false,
905 ),
906 (
910 "<div id='t'></div>",
911 "@media (scripting: enabled) { #t{zz:1} }",
912 "t",
913 true,
914 ),
915 (
916 "<div id='t'></div>",
917 "@media (scripting: none) { #t{zz:1} }",
918 "t",
919 false,
920 ),
921 (
922 "<div id='t'></div>",
923 "@media (forced-colors: none) { #t{zz:1} }",
924 "t",
925 true,
926 ),
927 (
928 "<div id='t'></div>",
929 "@media (forced-colors: active) { #t{zz:1} }",
930 "t",
931 false,
932 ),
933 (
938 "<div id='t'></div>",
939 "@media (min-resolution: 2dppx) { #t{zz:1} }",
940 "t",
941 false,
942 ),
943 (
944 "<div id='t'></div>",
945 "@media (min-resolution: 1dppx) { #t{zz:1} }",
946 "t",
947 true,
948 ),
949 (
950 "<div id='t'></div>",
951 "@media (max-resolution: 2dppx) { #t{zz:1} }",
952 "t",
953 true,
954 ),
955 (
956 "<div id='t'></div>",
957 "@media (min-resolution: 96dpi) { #t{zz:1} }",
958 "t",
959 true,
960 ),
961 (
963 "<div id='t'></div>",
964 "@media (monochrome) { #t{zz:1} }",
965 "t",
966 false,
967 ),
968 (
969 "<div id='t'></div>",
970 "@media (monochrome: 0) { #t{zz:1} }",
971 "t",
972 true,
973 ),
974 (
975 "<div id='t'></div>",
976 "@media (min-monochrome: 1) { #t{zz:1} }",
977 "t",
978 false,
979 ),
980 (
984 "<div id='t'></div>",
985 "@media screen and (min-width: 700px) { #t{zz:1} }",
986 "t",
987 true,
988 ),
989 (
990 "<div id='t'></div>",
991 "@media screen and (max-width: 700px) { #t{zz:1} }",
992 "t",
993 false,
994 ),
995 (
997 "<div id='t'></div>",
998 "@media (min-width: 700px) and (max-width: 900px) { #t{zz:1} }",
999 "t",
1000 true,
1001 ),
1002 (
1003 "<div id='t'></div>",
1004 "@media (min-width: 700px) and (max-width: 750px) { #t{zz:1} }",
1005 "t",
1006 false,
1007 ),
1008 (
1012 "<p id='t' lang='en-US'>x</p>",
1013 ":lang(en){zz:1}",
1014 "t",
1015 true,
1016 ),
1017 (
1018 "<p id='t' lang='fr'>x</p>",
1019 ":lang(en){zz:1}",
1020 "t",
1021 false,
1022 ),
1023 (
1024 "<p id='t' lang='EN'>x</p>",
1025 ":lang(en){zz:1}",
1026 "t",
1027 true,
1028 ),
1029 (
1032 "<p id='t' dir='rtl'>x</p>",
1033 ":dir(rtl){zz:1}",
1034 "t",
1035 true,
1036 ),
1037 (
1038 "<p id='t' dir='ltr'>x</p>",
1039 ":dir(rtl){zz:1}",
1040 "t",
1041 false,
1042 ),
1043 (
1044 "<p id='t'>x</p>",
1045 ":dir(ltr){zz:1}",
1046 "t",
1047 true,
1048 ),
1049 (
1051 "<input id='t' indeterminate>",
1052 "input:indeterminate{zz:1}",
1053 "t",
1054 true,
1055 ),
1056 (
1057 "<input id='t' checked>",
1058 "input:indeterminate{zz:1}",
1059 "t",
1060 false,
1061 ),
1062 (
1063 "<progress id='t'></progress>",
1064 "progress:indeterminate{zz:1}",
1065 "t",
1066 true,
1067 ),
1068 (
1069 "<progress id='t' value='50'></progress>",
1070 "progress:indeterminate{zz:1}",
1071 "t",
1072 false,
1073 ),
1074 ];
1075
1076 fn find_marked(node: &StyledNode, id: &str) -> Option<bool> {
1077 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
1078 if nid.as_deref() == Some(id) {
1079 return Some(node.specified_values.contains_key("zz"));
1080 }
1081 }
1082 for c in &node.children {
1083 if let Some(r) = find_marked(c, id) {
1084 return Some(r);
1085 }
1086 }
1087 None
1088 }
1089
1090 let value_cases: &[(&str, &str, &str, &str, &str)] = &[
1092 (
1093 "<p id='t'></p>",
1094 "p{color:red} p{color:blue}",
1095 "t",
1096 "color",
1097 "blue",
1098 ), (
1102 "<p id='t'></p>",
1103 "@layer a { #t{color:red} } @layer b { p{color:blue} }",
1104 "t",
1105 "color",
1106 "blue",
1107 ),
1108 (
1111 "<p id='t'></p>",
1112 "@layer a { #t{color:red} } p{color:blue}",
1113 "t",
1114 "color",
1115 "blue",
1116 ),
1117 (
1119 "<p id='t'></p>",
1120 "@layer a { p{color:red} } @layer a { p{color:green} }",
1121 "t",
1122 "color",
1123 "green",
1124 ),
1125 (
1130 "<p id='t'></p>",
1131 "@layer a { p{color:red} } @layer b { #t{color:blue} } @layer a { p{color:green} }",
1132 "t",
1133 "color",
1134 "blue",
1135 ),
1136 (
1140 "<p id='t' style='color:blue'></p>",
1141 "p{color:red !important}",
1142 "t",
1143 "color",
1144 "red",
1145 ),
1146 (
1148 "<p id='t' style='color:blue !important'></p>",
1149 "p{color:red !important}",
1150 "t",
1151 "color",
1152 "blue",
1153 ),
1154 (
1156 "<p id='t' style='color:blue'></p>",
1157 "p{color:red}",
1158 "t",
1159 "color",
1160 "blue",
1161 ),
1162 (
1166 "<div id='t' class='a'></div>",
1167 ".a{color:green; &:hover{color:blue;}}",
1168 "t",
1169 "color",
1170 "green",
1171 ),
1172 (
1176 "<div style='visibility:hidden'><span id='t'></span></div>",
1177 "",
1178 "t",
1179 "visibility",
1180 "hidden",
1181 ),
1182 (
1184 "<div style='visibility:hidden'><span id='t' style='visibility:visible'></span></div>",
1185 "",
1186 "t",
1187 "visibility",
1188 "visible",
1189 ),
1190 (
1194 "<ul><li id='t'></li></ul>",
1195 "ul{list-style-image:url(bullet.png)}",
1196 "t",
1197 "list-style-image",
1198 "url(bullet.png)",
1199 ),
1200 (
1204 "<ul><li id='t'></li></ul>",
1205 "ul{list-style-position:inside}",
1206 "t",
1207 "list-style-position",
1208 "inside",
1209 ),
1210 (
1213 "<p><span id='t'></span></p>",
1214 "p{text-indent:2em}",
1215 "t",
1216 "text-indent",
1217 "2em",
1218 ),
1219 (
1220 "<p id='t' class='x'></p>",
1221 "p{color:red} .x{color:green}",
1222 "t",
1223 "color",
1224 "green",
1225 ), (
1231 "<div id='t' class='b c'></div>",
1232 ":is(#nomatch, .b){color:red} .c{color:blue}",
1233 "t",
1234 "color",
1235 "red",
1236 ),
1237 (
1240 "<div id='t' class='a'></div>",
1241 ".a{color:blue} :where(.a){color:red}",
1242 "t",
1243 "color",
1244 "blue",
1245 ),
1246 (
1247 "<p id='t' class='x'></p>",
1248 "p{color:red !important} .x{color:green}",
1249 "t",
1250 "color",
1251 "red",
1252 ), (
1254 "<p id='t'></p>",
1255 "p{color:red !important} p{color:blue !important}",
1256 "t",
1257 "color",
1258 "blue",
1259 ), (
1261 "<p id='t' class='x'></p>",
1262 "#nope{color:black} .x{color:purple}",
1263 "t",
1264 "color",
1265 "purple",
1266 ),
1267 (
1269 "<div><p id='t'>x</p></div>",
1270 "div{text-transform:uppercase}",
1271 "t",
1272 "text-transform",
1273 "uppercase",
1274 ),
1275 (
1277 "<div><p id='t'>x</p></div>",
1278 "div{-webkit-text-fill-color:red}",
1279 "t",
1280 "-webkit-text-fill-color",
1281 "red",
1282 ),
1283 (
1285 "<ul><li id='t'>x</li></ul>",
1286 "ul{list-style-type:none}",
1287 "t",
1288 "list-style-type",
1289 "none",
1290 ),
1291 (
1293 "<div id='t' class='f'></div>",
1294 ".f{display:flex; gap:10px}",
1295 "t",
1296 "gap",
1297 "10px",
1298 ),
1299 (
1301 "<div id='t' class='o'></div>",
1302 ".o{overflow:hidden}",
1303 "t",
1304 "overflow",
1305 "hidden",
1306 ),
1307 (
1308 "<div id='t' class='lc'></div>",
1309 ".lc{-webkit-line-clamp: 3}",
1310 "t",
1311 "-webkit-line-clamp",
1312 "3",
1313 ),
1314 (
1315 "<div id='t' class='p'></div>",
1316 ".p{position:absolute; top:5px}",
1317 "t",
1318 "position",
1319 "absolute",
1320 ),
1321 (
1323 "<div id='t' class='q'></div>",
1324 ".q::before{content:\"★\"}",
1325 "t",
1326 "x-before-content",
1327 "★",
1328 ),
1329 (
1330 "<div id='t' class='q'></div>",
1331 ".q::after{content:'!'}",
1332 "t",
1333 "x-after-content",
1334 "!",
1335 ),
1336 (
1338 "<div id='t' class='q'></div>",
1339 ".q::before{content:\"x\";color:#ff0000}",
1340 "t",
1341 "color",
1342 "",
1343 ),
1344 (
1346 "<div id='t' class='q'></div>",
1347 ".q::before{content:none}",
1348 "t",
1349 "x-before-content",
1350 "",
1351 ),
1352 (
1354 "<a id='t' href='/foo'></a>",
1355 "a::after{content:attr(href)}",
1356 "t",
1357 "x-after-content",
1358 "/foo",
1359 ),
1360 (
1365 "<div id='t' class='q'></div>",
1366 ".q::before{content:\"\\f2c9\"}",
1367 "t",
1368 "x-before-content",
1369 "\u{f2c9}",
1370 ),
1371 (
1373 "<div id='t' class='q'></div>",
1374 ".q::before{content:\"\\f2c9 icon\"}",
1375 "t",
1376 "x-before-content",
1377 "\u{f2c9}icon",
1378 ),
1379 (
1383 "<div id='t' class='q'></div>",
1384 ".q::before{content:\"x\";display:none}",
1385 "t",
1386 "x-before-display",
1387 "none",
1388 ),
1389 (
1390 "<div id='t' data-tip='hi'></div>",
1391 "div::after{content:attr(data-tip)}",
1392 "t",
1393 "x-after-content",
1394 "hi",
1395 ),
1396 (
1398 "<a id='t' href='/x'></a>",
1399 "a::after{content:\"(\" attr(href) \")\"}",
1400 "t",
1401 "x-after-content",
1402 "(/x)",
1403 ),
1404 (
1406 "<div id='t'></div>",
1407 "div::after{content:attr(data-tip)}",
1408 "t",
1409 "x-after-content",
1410 "",
1411 ),
1412 (
1416 "<div id='t'></div>",
1417 "div::after{content:attr(data-tip, \"none\")}",
1418 "t",
1419 "x-after-content",
1420 "none",
1421 ),
1422 (
1424 "<div id='t' data-tip='hi'></div>",
1425 "div::after{content:attr(data-tip, \"none\")}",
1426 "t",
1427 "x-after-content",
1428 "hi",
1429 ),
1430 (
1432 "<div id='t' class='q'></div>",
1433 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c)}",
1434 "t",
1435 "x-after-content",
1436 "5",
1437 ),
1438 (
1441 "<div id='t' class='q'></div>",
1442 "@counter-style stars{system:cyclic;symbols:\"★\" \"☆\";suffix:\" \"} \
1443 .q{counter-reset:c 1;counter-increment:c}.q::after{content:counter(c, stars)}",
1444 "t",
1445 "x-after-content",
1446 "☆",
1447 ),
1448 (
1451 "<div id='t' class='q'></div>",
1452 "@counter-style bin{system:numeric;symbols:\"0\" \"1\"} \
1453 .q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, bin)}",
1454 "t",
1455 "x-after-content",
1456 "101",
1457 ),
1458 (
1461 "<div id='t' class='q'></div>",
1462 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, upper-roman)}",
1463 "t",
1464 "x-after-content",
1465 "V",
1466 ),
1467 (
1468 "<div id='t' class='q'></div>",
1469 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, lower-roman)}",
1470 "t",
1471 "x-after-content",
1472 "v",
1473 ),
1474 (
1475 "<div id='t' class='q'></div>",
1476 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, upper-alpha)}",
1477 "t",
1478 "x-after-content",
1479 "E",
1480 ),
1481 (
1482 "<div id='t' class='q'></div>",
1483 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, lower-alpha)}",
1484 "t",
1485 "x-after-content",
1486 "e",
1487 ),
1488 (
1490 "<ol><li class='c' id='a'>x</li><li class='c' id='t'>y</li></ol>",
1491 ".c{counter-increment:item}.c::after{content:counter(item)}",
1492 "t",
1493 "x-after-content",
1494 "2",
1495 ),
1496 (
1498 "<div id='t' class='q'></div>",
1499 ".q::after{content:counter(missing)}",
1500 "t",
1501 "x-after-content",
1502 "0",
1503 ),
1504 (
1511 "<ol><li class='c'></li><li class='c'><ol><li class='c'></li><li class='c'></li></ol></li><li class='c' id='t'></li></ol>",
1512 "ol{counter-reset:item}.c{counter-increment:item}.c::after{content:counter(item)}",
1513 "t",
1514 "x-after-content",
1515 "3",
1516 ),
1517 (
1521 "<ol><li class='q'></li><li class='q' id='t'></li></ol>",
1522 ".q::after{content:counter(list-item)}",
1523 "t",
1524 "x-after-content",
1525 "2",
1526 ),
1527 (
1530 "<ol start='5'><li class='q' id='t'></li><li class='q'></li></ol>",
1531 ".q::after{content:counter(list-item)}",
1532 "t",
1533 "x-after-content",
1534 "5",
1535 ),
1536 (
1539 "<ol reversed><li class='q' id='t'></li><li class='q'></li><li class='q'></li></ol>",
1540 ".q::after{content:counter(list-item)}",
1541 "t",
1542 "x-after-content",
1543 "3",
1544 ),
1545 (
1547 "<ol reversed start='10'><li class='q' id='t'></li><li class='q'></li></ol>",
1548 ".q::after{content:counter(list-item)}",
1549 "t",
1550 "x-after-content",
1551 "10",
1552 ),
1553 (
1557 "<ol><li class='q' id='t'></li></ol>",
1558 "ol{counter-reset:list-item 10}.q::after{content:counter(list-item)}",
1559 "t",
1560 "x-after-content",
1561 "11",
1562 ),
1563 (
1567 "<div id='t' class='q'></div>",
1568 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counters(c, \".\")}",
1569 "t",
1570 "x-after-content",
1571 "5",
1572 ),
1573 (
1575 "<div id='t' class='q'></div>",
1576 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counters(c, \".\", upper-roman)}",
1577 "t",
1578 "x-after-content",
1579 "V",
1580 ),
1581 (
1583 "<q id='t' class='q'></q>",
1584 ".q::before{content:open-quote}",
1585 "t",
1586 "x-before-content",
1587 "\u{201C}",
1588 ),
1589 (
1590 "<q id='t' class='q'></q>",
1591 ".q::after{content:close-quote}",
1592 "t",
1593 "x-after-content",
1594 "\u{201D}",
1595 ),
1596 (
1598 "<q id='t' class='q'></q>",
1599 ".q::before{content:no-open-quote}",
1600 "t",
1601 "x-before-content",
1602 "",
1603 ),
1604 (
1606 "<q id='t' class='q'></q>",
1607 ".q::before{content:open-quote \"cited\" close-quote}",
1608 "t",
1609 "x-before-content",
1610 "\u{201C}cited\u{201D}",
1611 ),
1612 (
1614 "<q id='t' class='q'></q>",
1615 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\"}.q::before{content:open-quote}",
1616 "t",
1617 "x-before-content",
1618 "\u{00AB}",
1619 ),
1620 (
1621 "<q id='t' class='q'></q>",
1622 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\"}.q::after{content:close-quote}",
1623 "t",
1624 "x-after-content",
1625 "\u{00BB}",
1626 ),
1627 (
1629 "<q id='t' class='q'></q>",
1630 ".q{quotes:none}.q::before{content:open-quote}",
1631 "t",
1632 "x-before-content",
1633 "",
1634 ),
1635 (
1639 "<q class='q'><q id='t' class='q'></q></q>",
1640 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\" \"\u{2018}\" \"\u{2019}\"}.q::before{content:open-quote}",
1641 "t",
1642 "x-before-content",
1643 "\u{2018}",
1644 ),
1645 (
1647 "<q class='q'><q class='q'><q id='t' class='q'></q></q></q>",
1648 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\" \"\u{2018}\" \"\u{2019}\"}.q::before{content:open-quote}",
1649 "t",
1650 "x-before-content",
1651 "\u{2018}",
1652 ),
1653 (
1655 "<li id='t' class='q'></li>",
1656 ".q::marker{color:#ff0000}",
1657 "t",
1658 "x-marker-color",
1659 "#ff0000",
1660 ),
1661 (
1663 "<li id='t' class='q'></li>",
1664 ".q::marker{font-size:99px}",
1665 "t",
1666 "x-marker-font-size",
1667 "99px",
1668 ),
1669 (
1673 "<li id='t' class='q'></li>",
1674 ".q::marker{content:\"\u{2192} \"}",
1675 "t",
1676 "x-marker-content",
1677 "\u{2192} ",
1678 ),
1679 (
1681 "<li id='t' class='q'></li>",
1682 ".q::marker{font-weight:bold}",
1683 "t",
1684 "x-marker-color",
1685 "",
1686 ),
1687 (
1689 "<input id='t' class='q'></input>",
1690 ".q::placeholder{color:#00aaff}",
1691 "t",
1692 "x-placeholder-color",
1693 "#00aaff",
1694 ),
1695 (
1697 "<input id='t' class='q'></input>",
1698 ".q::placeholder{font-size:99px}",
1699 "t",
1700 "x-placeholder-color",
1701 "",
1702 ),
1703 (
1705 "<p id='t' class='q'></p>",
1706 ".q::first-line{color:#00ff00}",
1707 "t",
1708 "x-first-line-color",
1709 "#00ff00",
1710 ),
1711 (
1712 "<p id='t' class='q'></p>",
1713 ".q::first-line{font-size:24px}",
1714 "t",
1715 "x-first-line-font-size",
1716 "24px",
1717 ),
1718 (
1720 "<p id='t' class='q'></p>",
1721 ".q::first-line{background-color:#000000}",
1722 "t",
1723 "x-first-line-background-color",
1724 "",
1725 ),
1726 (
1728 "<p id='t' class='q'></p>",
1729 ".q::first-letter{color:#0000ff}",
1730 "t",
1731 "x-first-letter-color",
1732 "#0000ff",
1733 ),
1734 (
1735 "<p id='t' class='q'></p>",
1736 ".q::first-letter{font-size:32px}",
1737 "t",
1738 "x-first-letter-font-size",
1739 "32px",
1740 ),
1741 (
1743 "<div id='t'></div>",
1744 "#t{margin-block-start:10px;margin-block-end:20px}",
1745 "t",
1746 "margin-top",
1747 "10px",
1748 ),
1749 (
1751 "<div id='t'></div>",
1752 "#t{padding-inline-start:5px}",
1753 "t",
1754 "padding-left",
1755 "5px",
1756 ),
1757 (
1761 "<div id='t'></div>",
1762 "#t{margin-block:15px}",
1763 "t",
1764 "margin-top",
1765 "15px",
1766 ),
1767 (
1768 "<div id='t'></div>",
1769 "#t{margin-block:15px}",
1770 "t",
1771 "margin-bottom",
1772 "15px",
1773 ),
1774 (
1776 "<div id='t'></div>",
1777 "#t{padding-inline:5px 9px}",
1778 "t",
1779 "padding-left",
1780 "5px",
1781 ),
1782 (
1783 "<div id='t'></div>",
1784 "#t{padding-inline:5px 9px}",
1785 "t",
1786 "padding-right",
1787 "9px",
1788 ),
1789 (
1791 "<div id='t'></div>",
1792 "#t{margin-inline:1px;margin-inline-start:2px}",
1793 "t",
1794 "margin-left",
1795 "2px",
1796 ),
1797 (
1799 "<div id='t'></div>",
1800 "#t{direction:rtl;margin-inline-start:7px}",
1801 "t",
1802 "margin-right",
1803 "7px",
1804 ),
1805 (
1807 "<div style='direction:rtl'><div id='t'></div></div>",
1808 "#t{padding-inline-end:9px}",
1809 "t",
1810 "padding-left",
1811 "9px",
1812 ),
1813 (
1815 "<div id='t'></div>",
1816 "#t{margin-left:1px;margin-inline-start:2px}",
1817 "t",
1818 "margin-left",
1819 "1px",
1820 ),
1821 (
1824 "<div id='t'></div>",
1825 "#t{border-inline-start-width:3px}",
1826 "t",
1827 "border-left-width",
1828 "3px",
1829 ),
1830 (
1831 "<div id='t'></div>",
1832 "#t{border-inline-start-style:dashed}",
1833 "t",
1834 "border-left-style",
1835 "dashed",
1836 ),
1837 (
1838 "<div id='t'></div>",
1839 "#t{direction:rtl;border-inline-end-color:#ff0000}",
1840 "t",
1841 "border-left-color",
1842 "#ff0000",
1843 ),
1844 (
1845 "<div id='t'></div>",
1846 "#t{border-block-start-width:4px}",
1847 "t",
1848 "border-top-width",
1849 "4px",
1850 ),
1851 (
1853 "<div id='t'></div>",
1854 "#t{grid-template:1fr 2fr / 100px 200px}",
1855 "t",
1856 "grid-template-rows",
1857 "1fr 2fr",
1858 ),
1859 (
1860 "<div id='t'></div>",
1861 "#t{grid-template:1fr 2fr / 100px 200px}",
1862 "t",
1863 "grid-template-columns",
1864 "100px 200px",
1865 ),
1866 (
1868 "<div id='t'></div>",
1869 "#t{text-emphasis:dot red}",
1870 "t",
1871 "text-emphasis-style",
1872 "dot",
1873 ),
1874 (
1875 "<div id='t'></div>",
1876 "#t{text-emphasis:dot red}",
1877 "t",
1878 "text-emphasis-color",
1879 "red",
1880 ),
1881 (
1883 "<div id='t'></div>",
1884 "#t{text-emphasis:filled circle #00ff00}",
1885 "t",
1886 "text-emphasis-style",
1887 "circle",
1888 ),
1889 (
1892 "<div id='t'></div>",
1893 "#t{grid-template:\"a\" 1fr / 1fr}",
1894 "t",
1895 "grid-template-areas",
1896 "\"a\" 1fr",
1897 ),
1898 (
1899 "<div id='t'></div>",
1900 "#t{grid-template:\"a\" 1fr / 1fr}",
1901 "t",
1902 "grid-template-rows",
1903 "1fr",
1904 ),
1905 (
1906 "<div id='t'></div>",
1907 "#t{grid-template:\"h h\" 1fr \"m m\" 2fr / 1fr 1fr}",
1908 "t",
1909 "grid-template-areas",
1910 "\"h h\" 1fr \"m m\" 2fr",
1911 ),
1912 (
1913 "<div id='t'></div>",
1914 "#t{grid-template:\"h h\" 1fr \"m m\" 2fr / 1fr 1fr}",
1915 "t",
1916 "grid-template-rows",
1917 "1fr 2fr",
1918 ),
1919 (
1920 "<div id='t'></div>",
1921 "#t{grid-template:\"h h\" 1fr \"m m\" 2fr / 1fr 1fr}",
1922 "t",
1923 "grid-template-columns",
1924 "1fr 1fr",
1925 ),
1926 (
1928 "<div id='t'></div>",
1929 "#t{grid-template-rows:5fr;grid-template:1fr / 1fr}",
1930 "t",
1931 "grid-template-rows",
1932 "5fr",
1933 ),
1934 (
1937 "<div style='color:red'><p id='t' style='color:inherit'></p></div>",
1938 "",
1939 "t",
1940 "color",
1941 "red",
1942 ),
1943 (
1946 "<div id='t'></div>",
1947 "#t{--b:red;--a:var(--b);color:var(--a)}",
1948 "t",
1949 "color",
1950 "red",
1951 ),
1952 (
1954 "<div id='t'></div>",
1955 "#t{--c:blue;--b:var(--c);--a:var(--b);color:var(--a)}",
1956 "t",
1957 "color",
1958 "blue",
1959 ),
1960 (
1962 "<div id='t'></div>",
1963 "#t{--a:var(--undefined, var(--also-undefined, green));color:var(--a)}",
1964 "t",
1965 "color",
1966 "green",
1967 ),
1968 (
1973 "<div id='t'></div>",
1974 "#t{margin-top:env(safe-area-inset-top)}",
1975 "t",
1976 "margin-top",
1977 "0px",
1978 ),
1979 (
1981 "<div id='t'></div>",
1982 "#t{margin-top:env(safe-area-inset-top, 20px)}",
1983 "t",
1984 "margin-top",
1985 "20px",
1986 ),
1987 (
1989 "<div id='t'></div>",
1990 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
1991 "t",
1992 "font-style",
1993 "italic",
1994 ),
1995 (
1996 "<div id='t'></div>",
1997 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
1998 "t",
1999 "font-weight",
2000 "bold",
2001 ),
2002 (
2003 "<div id='t'></div>",
2004 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
2005 "t",
2006 "font-size",
2007 "16px",
2008 ),
2009 (
2010 "<div id='t'></div>",
2011 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
2012 "t",
2013 "line-height",
2014 "1.5",
2015 ),
2016 (
2017 "<div id='t'></div>",
2018 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
2019 "t",
2020 "font-family",
2021 "Arial, sans-serif",
2022 ),
2023 (
2025 "<div id='t'></div>",
2026 "#t{font:700 14px sans-serif}",
2027 "t",
2028 "font-weight",
2029 "700",
2030 ),
2031 (
2032 "<div id='t'></div>",
2033 "#t{font:700 14px sans-serif}",
2034 "t",
2035 "font-size",
2036 "14px",
2037 ),
2038 (
2042 "<div id='t'></div>",
2043 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2044 "t",
2045 "font-size",
2046 "12px",
2047 ),
2048 (
2049 "<div id='t'></div>",
2050 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2051 "t",
2052 "line-height",
2053 "1.5",
2054 ),
2055 (
2056 "<div id='t'></div>",
2057 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2058 "t",
2059 "font-family",
2060 "sans-serif",
2061 ),
2062 (
2063 "<div id='t'></div>",
2064 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2065 "t",
2066 "font-style",
2067 "oblique",
2068 ),
2069 (
2071 "<div id='t'></div>",
2072 "#t{font-size:9px;font:16px Arial}",
2073 "t",
2074 "font-size",
2075 "9px",
2076 ),
2077 (
2080 "<div id='t'></div>",
2081 "#t{border:2px groove #333333}",
2082 "t",
2083 "border-top-style",
2084 "solid",
2085 ),
2086 (
2087 "<div id='t'></div>",
2088 "#t{border:2px ridge #333333}",
2089 "t",
2090 "border-top-width",
2091 "2px",
2092 ),
2093 (
2094 "<div id='t'></div>",
2095 "#t{border:2px hidden #333333}",
2096 "t",
2097 "border-top-style",
2098 "none",
2099 ),
2100 (
2101 "<div id='t'></div>",
2102 "#t{border:2px outset #333333}",
2103 "t",
2104 "border-top-color",
2105 "#333333",
2106 ),
2107 (
2109 "<div id='t' style='color:#ff0000;border-color:currentColor'></div>",
2110 "",
2111 "t",
2112 "border-color",
2113 "#ff0000",
2114 ),
2115 (
2117 "<div style='color:blue'><p id='t' style='outline-color:currentColor'></p></div>",
2118 "",
2119 "t",
2120 "outline-color",
2121 "blue",
2122 ),
2123 (
2125 "<div id='t' style='color:green;background-color:CURRENTCOLOR'></div>",
2126 "",
2127 "t",
2128 "background-color",
2129 "green",
2130 ),
2131 (
2133 "<div id='t' style='color:currentColor'></div>",
2134 "",
2135 "t",
2136 "color",
2137 "currentColor",
2138 ),
2139 (
2141 "<div id='t' style='width:initial'></div>",
2142 "",
2143 "t",
2144 "width",
2145 "",
2146 ),
2147 (
2149 "<div id='t' style='border-color:unset'></div>",
2150 "",
2151 "t",
2152 "border-color",
2153 "",
2154 ),
2155 (
2157 "<div id='t' style='color:red;all:unset'></div>",
2158 "",
2159 "t",
2160 "color",
2161 "",
2162 ),
2163 (
2166 "<div id='t' style='color:red;all:revert-layer'></div>",
2167 "",
2168 "t",
2169 "color",
2170 "red",
2171 ),
2172 (
2174 "<div id='t' style='place-items:center'></div>",
2175 "",
2176 "t",
2177 "align-items",
2178 "center",
2179 ),
2180 (
2181 "<div id='t' style='place-items:center'></div>",
2182 "",
2183 "t",
2184 "justify-items",
2185 "center",
2186 ),
2187 (
2189 "<div id='t' style='place-content:start end'></div>",
2190 "",
2191 "t",
2192 "align-content",
2193 "start",
2194 ),
2195 (
2196 "<div id='t' style='place-content:start end'></div>",
2197 "",
2198 "t",
2199 "justify-content",
2200 "end",
2201 ),
2202 (
2204 "<div id='t' style='align-self:end;place-self:start'></div>",
2205 "",
2206 "t",
2207 "align-self",
2208 "end",
2209 ),
2210 ];
2211
2212 let tt_cases: &[(&str, &str, &str)] = &[
2214 ("hello world", "uppercase", "HELLO WORLD"),
2215 ("Hello WORLD", "lowercase", "hello world"),
2216 ("hello world", "capitalize", "Hello World"),
2217 ("hello world", "none", "hello world"),
2218 ];
2219
2220 fn find_prop(node: &StyledNode, id: &str, prop: &str) -> Option<String> {
2221 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2222 if nid.as_deref() == Some(id) {
2223 return Some(node.specified_values.get(prop).cloned().unwrap_or_default());
2224 }
2225 }
2226 for c in &node.children {
2227 if let Some(r) = find_prop(c, id, prop) {
2228 return Some(r);
2229 }
2230 }
2231 None
2232 }
2233
2234 let alpha_roman_cases: &[(&str, u32, bool, &str)] = &[
2236 ("alpha", 1, true, "A"),
2237 ("alpha", 26, true, "Z"),
2238 ("alpha", 27, true, "AA"),
2239 ("alpha", 1, false, "a"),
2240 ("roman", 1994, true, "MCMXCIV"),
2241 ("roman", 4, false, "iv"),
2242 ("roman", 2026, true, "MMXXVI"),
2243 ];
2244
2245 let filter_cases: &[(&str, (u8, u8, u8), (u8, u8, u8))] = &[
2247 ("grayscale(1)", (255, 0, 0), (76, 76, 76)),
2248 ("brightness(0.5)", (200, 200, 200), (100, 100, 100)),
2249 ("invert(1)", (0, 0, 0), (255, 255, 255)),
2250 ("", (10, 20, 30), (10, 20, 30)),
2251 ("contrast(2)", (200, 50, 128), (255, 0, 128)),
2253 ("saturate(0)", (255, 0, 0), (76, 76, 76)),
2255 ];
2256
2257 let blur_radius_cases: &[(&str, i32)] = &[
2259 ("blur(5px)", 5),
2260 ("blur(20px)", 8), ("grayscale(1)", 0),
2262 ("", 0),
2263 ];
2264
2265 let blend_cases: &[(&str, (u8, u8, u8), (u8, u8, u8), (u8, u8, u8))] = &[
2267 ("multiply", (200, 200, 200), (100, 100, 100), (78, 78, 78)),
2269 ("screen", (200, 200, 200), (100, 100, 100), (222, 222, 222)),
2271 ("darken", (200, 50, 128), (100, 150, 128), (100, 50, 128)),
2272 ("lighten", (200, 50, 128), (100, 150, 128), (200, 150, 128)),
2273 ("difference", (200, 50, 128), (100, 150, 128), (100, 100, 0)),
2274 ("normal", (10, 20, 30), (200, 200, 200), (10, 20, 30)),
2276 ("overlay", (200, 200, 200), (100, 100, 100), (156, 156, 156)),
2278 ("hard-light", (200, 200, 200), (100, 100, 100), (189, 189, 189)),
2280 ("exclusion", (200, 200, 200), (100, 100, 100), (144, 144, 144)),
2282 ("color-dodge", (200, 200, 200), (100, 100, 100), (255, 255, 255)),
2284 ("color-burn", (200, 200, 200), (100, 100, 100), (58, 58, 58)),
2286 ("luminosity", (255, 255, 255), (0, 0, 0), (255, 255, 255)),
2289 ("color", (255, 255, 255), (0, 0, 0), (0, 0, 0)),
2291 ("hue", (255, 255, 255), (0, 0, 0), (0, 0, 0)),
2293 ("saturation", (255, 255, 255), (0, 0, 0), (0, 0, 0)),
2295 ("soft-light", (200, 200, 200), (100, 100, 100), (133, 133, 133)),
2297 ];
2298
2299 let bg_repeat_cases: &[(&str, super::super::web_engine::BgRepeatMode, super::super::web_engine::BgRepeatMode)] = &[
2301 ("no-repeat", super::super::web_engine::BgRepeatMode::NoRepeat, super::super::web_engine::BgRepeatMode::NoRepeat),
2302 ("repeat-x", super::super::web_engine::BgRepeatMode::Repeat, super::super::web_engine::BgRepeatMode::NoRepeat),
2303 ("repeat-y", super::super::web_engine::BgRepeatMode::NoRepeat, super::super::web_engine::BgRepeatMode::Repeat),
2304 ("space", super::super::web_engine::BgRepeatMode::Space, super::super::web_engine::BgRepeatMode::Space),
2305 ("round", super::super::web_engine::BgRepeatMode::Round, super::super::web_engine::BgRepeatMode::Round),
2306 ("repeat space", super::super::web_engine::BgRepeatMode::Repeat, super::super::web_engine::BgRepeatMode::Space),
2307 ("round no-repeat", super::super::web_engine::BgRepeatMode::Round, super::super::web_engine::BgRepeatMode::NoRepeat),
2308 ];
2309
2310 let rgb_pct_cases: &[(&str, u32)] = &[
2312 ("rgb(100%, 50%, 0%)", 0xFFFF7F00),
2313 ("rgb(0%, 0%, 0%)", 0xFF000000),
2314 ("rgba(100%, 0%, 0%, 0.5)", 0x7FFF0000),
2315 ("rgb(255, 50%, 0)", 0xFFFF7F00),
2317 ("#f00f", 0xFFFF0000),
2321 ("#f008", 0x88FF0000),
2322 ];
2323
2324 let hsl_hue_wrap_cases: &[(&str, u32)] = &[
2326 ("hsl(390, 100%, 50%)", 0xFFFF7F00),
2328 ("hsl(30, 100%, 50%)", 0xFFFF7F00),
2329 ("hsl(-30, 100%, 50%)", 0xFFFF007F),
2331 ("hsl(330, 100%, 50%)", 0xFFFF007F),
2332 ];
2333
2334 let rgb_space_cases: &[(&str, u32)] = &[
2339 ("rgb(255 0 0)", 0xFFFF0000),
2340 ("rgb(255 0 0 / 50%)", 0x7FFF0000),
2341 ("rgba(0 255 0 / 0.5)", 0x7F00FF00),
2342 ];
2343
2344 let oklab_oklch_cases: &[(&str, u32)] = &[
2347 ("oklch(1 0 0)", 0xFFFFFFFF),
2348 ("oklch(0 0 0)", 0xFF000000),
2349 ("oklab(1 0 0)", 0xFFFFFFFF),
2350 ("oklab(0 0 0)", 0xFF000000),
2351 ];
2352
2353 let color_fn_cases: &[(&str, u32)] = &[
2358 ("color(srgb 1 0 0)", 0xFFFF0000),
2359 ("color(srgb 0 1 0)", 0xFF00FF00),
2360 ("color(srgb 1 1 1 / 0.5)", 0x7FFFFFFF),
2361 ("color(srgb-linear 1 1 1)", 0xFFFFFFFF),
2362 ("color(srgb-linear 0 0 0)", 0xFF000000),
2363 ];
2364
2365 let hwb_cases: &[(&str, u32)] = &[
2367 ("hwb(0, 0%, 0%)", 0xFFFF0000), ("hwb(0, 100%, 0%)", 0xFFFFFFFF), ("hwb(0, 0%, 100%)", 0xFF000000), ("hwb(120, 0%, 0%)", 0xFF00FF00), ("hwb(240, 0%, 0%)", 0xFF0000FF), ("hwb(0 0% 0% / 50%)", 0x7FFF0000), ];
2374
2375 let color_mix_cases: &[(&str, u32)] = &[
2378 ("color-mix(in srgb, red, blue)", 0xFF7F007F), ("color-mix(in srgb, red 100%, blue 0%)", 0xFFFF0000), ("color-mix(in srgb, red 0%, blue 100%)", 0xFF0000FF),
2381 ("color-mix(in srgb, red 75%, blue 25%)", 0xFFBF003F), ("color-mix(in srgb, red 40%, blue)", 0xFF660099), ("color-mix(in srgb, white, black)", 0xFF7F7F7F), ];
2385
2386 let text_decoration_cases: &[(&str, bool, bool, bool, bool, Option<&str>, Option<u32>)] = &[
2389 ("underline", true, false, false, false, None, None),
2390 ("none", false, false, false, true, None, None),
2391 ("underline wavy red", true, false, false, false, Some("wavy"), Some(0xFFFF0000)),
2392 (
2393 "line-through dotted #00ff00",
2394 false,
2395 true,
2396 false,
2397 false,
2398 Some("dotted"),
2399 Some(0xFF00FF00),
2400 ),
2401 ("overline", false, false, true, false, None, None),
2402 ];
2403
2404 let mut passed = 0;
2405 let mut total = cases.len()
2424 + value_cases.len()
2425 + tt_cases.len()
2426 + alpha_roman_cases.len()
2427 + filter_cases.len()
2428 + blur_radius_cases.len()
2429 + blend_cases.len()
2430 + text_decoration_cases.len()
2431 + bg_repeat_cases.len()
2432 + rgb_pct_cases.len()
2433 + rgb_space_cases.len()
2434 + hsl_hue_wrap_cases.len()
2435 + hwb_cases.len()
2436 + oklab_oklch_cases.len()
2437 + color_fn_cases.len()
2438 + color_mix_cases.len()
2439 + 3
2440 + 2
2441 + 3
2442 + 1
2443 + 1
2444 + 2
2445 + 1
2446 + 1
2447 + 3
2448 + 10
2449 + 10
2450 + 7
2451 + 5
2452 + 5
2453 + 5
2454 + 4;
2455 for (input, want_radius) in blur_radius_cases {
2456 let got = super::super::web_engine::draw_helpers::parse_blur_radius(input);
2457 if got == *want_radius {
2458 passed += 1;
2459 } else {
2460 crate::println!(
2461 "CSS_SELFTEST FAIL: parse_blur_radius(`{}`) => {} (want {})",
2462 input, got, want_radius
2463 );
2464 }
2465 }
2466 {
2469 let mut rgba = alloc::vec![0u8; 3 * 3 * 4];
2470 let center_idx = 4 * 4;
2471 rgba[center_idx] = 255;
2472 rgba[center_idx + 3] = 255;
2473 let img = super::super::web_engine::DecodedImage { width: 3, height: 3, rgba };
2474 let got = super::super::web_engine::draw_helpers::sample_box_blur(&img, 1, 1, 1);
2475 if got == (28, 0, 0) {
2476 passed += 1;
2477 } else {
2478 crate::println!("CSS_SELFTEST FAIL: sample_box_blur => {:?} (want (28,0,0))", got);
2479 }
2480 }
2481 {
2487 let calc_cases: &[(&str, i32, i32)] = &[
2488 ("calc(100px - 20px)", 0, 80),
2490 ("calc(50% + 10px)", 200, 110),
2491 ("calc(10px * 2)", 0, 20),
2492 ("calc(100px / 4)", 0, 25),
2494 ("calc(300% / 3)", 100, 100),
2495 ("calc(100% - 20px - 10px)", 200, 170),
2497 ("calc(10px + 20px + 30px)", 0, 60),
2498 ("calc((100px - 20px) / 2)", 0, 40),
2500 ("calc(50% - (10px + 5px))", 200, 85),
2501 ("calc(-10px + 50px)", 0, 40),
2503 ];
2504 for (expr, cw, want) in calc_cases {
2505 let got = super::values::eval_calc(expr, *cw);
2506 if got == Some(*want) {
2507 passed += 1;
2508 } else {
2509 crate::println!(
2510 "CSS_SELFTEST FAIL: eval_calc(`{}`, {}) => {:?} (want Some({}))",
2511 expr, cw, got, want
2512 );
2513 }
2514 }
2515 }
2516 {
2523 let orig_w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
2528 let orig_h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
2529 crate::os_lib::layout::VIEWPORT_HINT_W.store(1000, core::sync::atomic::Ordering::Relaxed);
2530 crate::os_lib::layout::VIEWPORT_HINT_H.store(500, core::sync::atomic::Ordering::Relaxed);
2531 let px_cases: &[(&str, i32)] = &[
2532 ("50vmin", 250),
2534 ("50vmax", 500),
2535 ("1in", 96),
2537 ("2.54cm", 96),
2538 ("25.4mm", 96),
2539 ("72pt", 96),
2540 ("6pc", 96),
2541 ("thin", 1),
2545 ("medium", 3),
2546 ("thick", 5),
2547 ];
2548 for (input, want) in px_cases {
2549 let got = crate::os_lib::layout::parse_px_like(input);
2550 if got == Some(*want) {
2551 passed += 1;
2552 } else {
2553 crate::println!(
2554 "CSS_SELFTEST FAIL: parse_px_like(`{}`) => {:?} (want Some({}))",
2555 input, got, want
2556 );
2557 }
2558 }
2559 let calc_unit_cases: &[(&str, i32, i32)] = &[
2567 ("calc(50vw + 10px)", 0, 510),
2568 ("calc(50vh - 10px)", 0, 240),
2569 ("calc(50vmin + 50px)", 0, 300),
2570 ("calc(1in + 4px)", 0, 100),
2571 ("min(50vw, 300px)", 0, 300),
2572 ("max(50vw, 300px)", 0, 500),
2573 ("clamp(10px, 50vw, 100px)", 0, 100),
2574 ];
2575 for (expr, cw, want) in calc_unit_cases {
2576 let got = super::values::eval_css_math(expr, *cw);
2577 if got == Some(*want) {
2578 passed += 1;
2579 } else {
2580 crate::println!(
2581 "CSS_SELFTEST FAIL: eval_css_math(`{}`, {}) => {:?} (want Some({}))",
2582 expr, cw, got, want
2583 );
2584 }
2585 }
2586 crate::os_lib::layout::VIEWPORT_HINT_W.store(orig_w, core::sync::atomic::Ordering::Relaxed);
2587 crate::os_lib::layout::VIEWPORT_HINT_H.store(orig_h, core::sync::atomic::Ordering::Relaxed);
2588 }
2589 {
2594 let line_height_cases: &[(&str, u32, i32)] = &[
2595 ("16px", 16, 16),
2596 ("1.5", 16, 24),
2597 ("150%", 16, 24),
2598 ("1.5em", 16, 24),
2599 ("2em", 20, 40),
2600 ];
2601 for (input, font_size, want) in line_height_cases {
2602 let got = super::super::web_engine::parse_line_height(input, *font_size);
2603 if got == *want {
2604 passed += 1;
2605 } else {
2606 crate::println!(
2607 "CSS_SELFTEST FAIL: parse_line_height(`{}`, {}) => {} (want {})",
2608 input, font_size, got, want
2609 );
2610 }
2611 }
2612 }
2613 {
2617 let text_indent_cases: &[(&str, i32, u32, i32)] = &[
2619 ("20px", 400, 16, 20),
2620 ("10", 400, 16, 10),
2621 ("10%", 400, 16, 40),
2622 ("2em", 400, 16, 32),
2623 ("2em", 400, 20, 40),
2624 ("1.5rem", 400, 20, 24),
2625 ];
2626 for (input, cw, font_size, want) in text_indent_cases {
2627 let got = super::super::web_engine::resolve_text_indent(input, *cw, *font_size);
2628 if got == *want {
2629 passed += 1;
2630 } else {
2631 crate::println!(
2632 "CSS_SELFTEST FAIL: resolve_text_indent(`{}`, {}, {}) => {} (want {})",
2633 input, cw, font_size, got, want
2634 );
2635 }
2636 }
2637 }
2638 {
2643 let shadow_len_cases: &[(&str, Option<i32>)] = &[
2644 ("10px", Some(10)),
2645 ("0.5rem", Some(8)),
2646 ("2rem", Some(32)),
2647 ("red", None),
2648 ("inset", None),
2649 ];
2650 for (input, want) in shadow_len_cases {
2651 let got = super::super::web_engine::parse_shadow_len_tok(input);
2652 if got == *want {
2653 passed += 1;
2654 } else {
2655 crate::println!(
2656 "CSS_SELFTEST FAIL: parse_shadow_len_tok(`{}`) => {:?} (want {:?})",
2657 input, got, want
2658 );
2659 }
2660 }
2661 }
2662 {
2666 let border_radius_cases: &[(&str, i32, i32)] = &[
2668 ("10px", 200, 10),
2669 ("50%", 200, 100),
2670 ("1rem", 200, 16),
2671 ("0.5rem", 200, 8),
2672 ];
2673 for (input, cw, want) in border_radius_cases {
2674 let got = super::super::web_engine::resolve_border_radius(input, *cw);
2675 if got == *want {
2676 passed += 1;
2677 } else {
2678 crate::println!(
2679 "CSS_SELFTEST FAIL: resolve_border_radius(`{}`, {}) => {} (want {})",
2680 input, cw, got, want
2681 );
2682 }
2683 }
2684 }
2685 for (input, want_u, want_lt, want_ov, want_none, want_style, want_color) in text_decoration_cases {
2686 let (u, lt, ov, is_none, style, color) =
2687 super::super::web_engine::helpers::parse_text_decoration_shorthand(input);
2688 let style_ok = style.as_deref() == *want_style;
2689 let color_ok = color == *want_color;
2690 if u == *want_u
2691 && lt == *want_lt
2692 && ov == *want_ov
2693 && is_none == *want_none
2694 && style_ok
2695 && color_ok
2696 {
2697 passed += 1;
2698 } else {
2699 crate::println!(
2700 "CSS_SELFTEST FAIL: parse_text_decoration_shorthand(`{}`) => ({},{},{},{},{:?},{:?}) (want ({},{},{},{},{:?},{:?}))",
2701 input, u, lt, ov, is_none, style, color, want_u, want_lt, want_ov, want_none, want_style, want_color
2702 );
2703 }
2704 }
2705 for (input, want_x, want_y) in bg_repeat_cases {
2706 let (x, y) = super::super::web_engine::helpers::parse_bg_repeat(Some(input));
2707 if x == *want_x && y == *want_y {
2708 passed += 1;
2709 } else {
2710 crate::println!(
2711 "CSS_SELFTEST FAIL: parse_bg_repeat(`{}`) => ({:?},{:?}) (want ({:?},{:?}))",
2712 input, x, y, want_x, want_y
2713 );
2714 }
2715 }
2716 for (input, want) in rgb_pct_cases {
2717 let got = parse_color(input);
2718 if got == Some(*want) {
2719 passed += 1;
2720 } else {
2721 crate::println!(
2722 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2723 input, got, want
2724 );
2725 }
2726 }
2727 for (input, want) in rgb_space_cases {
2728 let got = parse_color(input);
2729 if got == Some(*want) {
2730 passed += 1;
2731 } else {
2732 crate::println!(
2733 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2734 input, got, want
2735 );
2736 }
2737 }
2738 for (input, want) in hsl_hue_wrap_cases {
2739 let got = parse_color(input);
2740 if got == Some(*want) {
2741 passed += 1;
2742 } else {
2743 crate::println!(
2744 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2745 input, got, want
2746 );
2747 }
2748 }
2749 for (input, want) in hwb_cases {
2750 let got = parse_color(input);
2751 if got == Some(*want) {
2752 passed += 1;
2753 } else {
2754 crate::println!(
2755 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2756 input, got, want
2757 );
2758 }
2759 }
2760 for (input, want) in oklab_oklch_cases {
2761 let got = parse_color(input);
2762 if got == Some(*want) {
2763 passed += 1;
2764 } else {
2765 crate::println!(
2766 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2767 input, got, want
2768 );
2769 }
2770 }
2771 for (input, want) in color_fn_cases {
2772 let got = parse_color(input);
2773 if got == Some(*want) {
2774 passed += 1;
2775 } else {
2776 crate::println!(
2777 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2778 input, got, want
2779 );
2780 }
2781 }
2782 for (input, want) in color_mix_cases {
2783 let got = parse_color(input);
2784 if got == Some(*want) {
2785 passed += 1;
2786 } else {
2787 crate::println!(
2788 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2789 input, got, want
2790 );
2791 }
2792 }
2793 {
2796 let saved = PREFERS_DARK.load(AtomicOrd::Relaxed);
2797 PREFERS_DARK.store(false, AtomicOrd::Relaxed);
2798 let got_light = parse_color("light-dark(#ff0000, #0000ff)");
2799 if got_light == Some(0xFFFF0000) {
2800 passed += 1;
2801 } else {
2802 crate::println!(
2803 "CSS_SELFTEST FAIL: light-dark (light mode) => {:?} (want 0xFFFF0000)",
2804 got_light
2805 );
2806 }
2807 PREFERS_DARK.store(true, AtomicOrd::Relaxed);
2808 let got_dark = parse_color("light-dark(#ff0000, #0000ff)");
2809 if got_dark == Some(0xFF0000FF) {
2810 passed += 1;
2811 } else {
2812 crate::println!(
2813 "CSS_SELFTEST FAIL: light-dark (dark mode) => {:?} (want 0xFF0000FF)",
2814 got_dark
2815 );
2816 }
2817 let got_nested = parse_color("light-dark(#ff0000, light-dark(#00ff00, #0000ff))");
2819 if got_nested == Some(0xFF0000FF) {
2820 passed += 1;
2821 } else {
2822 crate::println!(
2823 "CSS_SELFTEST FAIL: light-dark nested => {:?} (want 0xFF0000FF)",
2824 got_nested
2825 );
2826 }
2827 PREFERS_DARK.store(saved, AtomicOrd::Relaxed);
2828 }
2829 {
2831 *super::super::layout::CURRENT_TARGET_ID.lock() = String::from("sec2");
2832 let dom = super::super::dom::parse_html(
2833 "<div id='sec1'></div><div id='sec2'></div><div id='t'></div>",
2834 );
2835 let sheet = parse_css("#sec2:target{zz:1}");
2836 let styled = style_tree(&dom, &sheet);
2837 fn find_marked_target(node: &StyledNode, id: &str) -> Option<bool> {
2838 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2839 if nid.as_deref() == Some(id) {
2840 return Some(node.specified_values.contains_key("zz"));
2841 }
2842 }
2843 for c in &node.children {
2844 if let Some(r) = find_marked_target(c, id) {
2845 return Some(r);
2846 }
2847 }
2848 None
2849 }
2850 let matched = find_marked_target(&styled, "sec2").unwrap_or(false);
2851 let not_matched = find_marked_target(&styled, "sec1").unwrap_or(true);
2852 if matched && !not_matched {
2853 passed += 1;
2854 } else {
2855 crate::println!(
2856 "CSS_SELFTEST FAIL: :target => sec2={} sec1={} (want true/false)",
2857 matched, not_matched
2858 );
2859 }
2860 *super::super::layout::CURRENT_TARGET_ID.lock() = String::new();
2861 }
2862 {
2866 fn collect_path_to(
2867 node: &super::super::dom::Node,
2868 target_id: &str,
2869 path: &mut alloc::vec::Vec<usize>,
2870 ) -> bool {
2871 path.push(node as *const _ as usize);
2872 if let NodeType::Element { id, .. } = &node.node_type {
2873 if id.as_deref() == Some(target_id) {
2874 return true;
2875 }
2876 }
2877 for c in &node.children {
2878 if collect_path_to(c, target_id, path) {
2879 return true;
2880 }
2881 }
2882 path.pop();
2883 false
2884 }
2885 let dom = super::super::dom::parse_html(
2886 "<div id='outer'><span><input id='inp'></span></div><div id='other'></div>",
2887 );
2888 let mut path = alloc::vec::Vec::new();
2889 collect_path_to(&dom, "inp", &mut path);
2890 {
2891 let mut set = super::super::layout::FOCUS_WITHIN_PTRS.lock();
2892 set.clear();
2893 set.extend(path);
2894 }
2895 let sheet = parse_css("#outer:focus-within{zz:1}");
2896 let styled = style_tree(&dom, &sheet);
2897 fn find_marked_fw(node: &StyledNode, id: &str) -> Option<bool> {
2898 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2899 if nid.as_deref() == Some(id) {
2900 return Some(node.specified_values.contains_key("zz"));
2901 }
2902 }
2903 for c in &node.children {
2904 if let Some(r) = find_marked_fw(c, id) {
2905 return Some(r);
2906 }
2907 }
2908 None
2909 }
2910 let matched = find_marked_fw(&styled, "outer").unwrap_or(false);
2911 let not_matched = find_marked_fw(&styled, "other").unwrap_or(true);
2912 if matched && !not_matched {
2913 passed += 1;
2914 } else {
2915 crate::println!(
2916 "CSS_SELFTEST FAIL: :focus-within => outer={} other={} (want true/false)",
2917 matched, not_matched
2918 );
2919 }
2920 super::super::layout::FOCUS_WITHIN_PTRS.lock().clear();
2921 }
2922 {
2924 let html = r#"<div id="root"><fieldset disabled="true"><input id="i1" /></fieldset><fieldset><input id="i2" /></fieldset></div>"#;
2925 let dom = super::super::dom::parse_html(html);
2926 let sheet = parse_css("input:disabled{zz:1} input:enabled{yy:1}");
2927 let styled = style_tree(&dom, &sheet);
2928 fn find_marked_styles(node: &StyledNode, id: &str) -> Option<(bool, bool)> {
2929 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2930 if nid.as_deref() == Some(id) {
2931 return Some((
2932 node.specified_values.contains_key("zz"),
2933 node.specified_values.contains_key("yy")
2934 ));
2935 }
2936 }
2937 for c in &node.children {
2938 if let Some(r) = find_marked_styles(c, id) {
2939 return Some(r);
2940 }
2941 }
2942 None
2943 }
2944 let i1_styles = find_marked_styles(&styled, "i1").unwrap_or((false, false));
2945 let i2_styles = find_marked_styles(&styled, "i2").unwrap_or((false, false));
2946
2947 let ok = i1_styles.0 && !i1_styles.1 && !i2_styles.0 && i2_styles.1;
2950 total += 1;
2951 if ok {
2952 passed += 1;
2953 } else {
2954 crate::println!(
2955 "CSS_SELFTEST FAIL: fieldset[disabled] CSS state propagation => i1={:?} i2={:?} (want (true,false) and (false,true))",
2956 i1_styles, i2_styles
2957 );
2958 }
2959 }
2960 {
2962 let got = super::super::web_engine::helpers::extract_all_url_paths("url(a.png), url('b.png')");
2963 if got == alloc::vec!["a.png", "b.png"] {
2964 passed += 1;
2965 } else {
2966 crate::println!("CSS_SELFTEST FAIL: extract_all_url_paths multi => {:?}", got);
2967 }
2968 let got_single = super::super::web_engine::helpers::extract_all_url_paths("url(only.png)");
2969 if got_single == alloc::vec!["only.png"] {
2970 passed += 1;
2971 } else {
2972 crate::println!(
2973 "CSS_SELFTEST FAIL: extract_all_url_paths single => {:?}",
2974 got_single
2975 );
2976 }
2977 let got_none = super::super::web_engine::helpers::extract_all_url_paths("none");
2978 if got_none.is_empty() {
2979 passed += 1;
2980 } else {
2981 crate::println!("CSS_SELFTEST FAIL: extract_all_url_paths none => {:?}", got_none);
2982 }
2983 }
2984 {
2991 let radius = super::super::web_engine::draw_helpers::parse_blur_radius("blur(8px) brightness(1.2)");
2992 total += 1;
2993 if radius == 8 {
2994 passed += 1;
2995 } else {
2996 crate::println!("CSS_SELFTEST FAIL: parse_blur_radius => {}", radius);
2997 }
2998 }
2999 {
3001 let stops = alloc::vec![
3002 super::super::web_engine::GradientStop { position: 0.0, color: 0x00FF0000 },
3003 super::super::web_engine::GradientStop { position: 1.0, color: 0xFF00FF00 },
3004 ];
3005 let mid_color = super::super::web_engine::draw_helpers::sample_gradient_stops(&stops, 0.5);
3006 let mid_alpha = (mid_color >> 24) & 0xFF;
3007 total += 1;
3008 if (120..=135).contains(&mid_alpha) {
3009 passed += 1;
3010 } else {
3011 crate::println!("CSS_SELFTEST FAIL: sample_gradient_stops alpha => 0x{:08X}", mid_color);
3012 }
3013 }
3014 {
3016 total += 1;
3017 if let Some(shape) = super::super::web_engine::draw_helpers::parse_clip_shape("inset(10px round 20px)", 100, 100) {
3018 let inside_center = super::super::web_engine::draw_helpers::clip_inside(&shape, 50, 50);
3019 let inside_corner = super::super::web_engine::draw_helpers::clip_inside(&shape, 12, 12);
3020 if inside_center && !inside_corner {
3021 passed += 1;
3022 } else {
3023 crate::println!("CSS_SELFTEST FAIL: clip-path inset round => center={}, corner={}", inside_center, inside_corner);
3024 }
3025 } else {
3026 crate::println!("CSS_SELFTEST FAIL: clip-path inset round parse failed");
3027 }
3028 }
3029 {
3031 total += 1;
3032 if let Some(shape) = super::super::web_engine::draw_helpers::parse_clip_shape(r#"path("M 0 0 L 100 0 L 100 100 Z")"#, 100, 100) {
3033 let inside = super::super::web_engine::draw_helpers::clip_inside(&shape, 50, 20);
3034 let outside = super::super::web_engine::draw_helpers::clip_inside(&shape, 20, 50);
3035 if inside && !outside {
3036 passed += 1;
3037 } else {
3038 crate::println!("CSS_SELFTEST FAIL: clip-path path() => in={}, out={}", inside, outside);
3039 }
3040 } else {
3041 crate::println!("CSS_SELFTEST FAIL: clip-path path() parse failed");
3042 }
3043 }
3044
3045 {
3047 let sheet = parse_css("h1 { -webkit-text-stroke: 2px #ff0000; }");
3048 total += 1;
3049 if !sheet.rules.is_empty() {
3050 passed += 1;
3051 } else {
3052 crate::println!("CSS_SELFTEST FAIL: text-stroke parse failed");
3053 }
3054 }
3055 {
3063 let dom = super::super::dom::parse_html("<div id='d'></div>");
3064 let sheet = parse_css(
3065 "#d{background:url('x.jpg') center/cover no-repeat}",
3066 );
3067 let styled = style_tree(&dom, &sheet);
3068 fn find_div<'a>(node: &'a StyledNode<'a>) -> Option<&'a StyledNode<'a>> {
3069 if let NodeType::Element { id, .. } = &node.node.node_type {
3070 if id.as_deref() == Some("d") {
3071 return Some(node);
3072 }
3073 }
3074 for c in &node.children {
3075 if let Some(r) = find_div(c) {
3076 return Some(r);
3077 }
3078 }
3079 None
3080 }
3081 total += 1;
3082 if let Some(d) = find_div(&styled) {
3083 let size = d.specified_values.get("background-size").map(|s| s.as_str());
3084 let position = d.specified_values.get("background-position").map(|s| s.as_str());
3085 let repeat = d.specified_values.get("background-repeat").map(|s| s.as_str());
3086 if size == Some("cover") && position == Some("center") && repeat == Some("no-repeat") {
3087 passed += 1;
3088 } else {
3089 crate::println!(
3090 "CSS_SELFTEST FAIL: background shorthand slash expansion => size={:?} position={:?} repeat={:?}",
3091 size, position, repeat
3092 );
3093 }
3094 } else {
3095 crate::println!("CSS_SELFTEST FAIL: background shorthand slash expansion => #d not found");
3096 }
3097 }
3098 {
3107 let sheet = parse_css(
3108 "@font-face{font-family:'Noto Sans JP';src:url(a.woff2) format('woff2'),url(b.ttf) format('truetype')}",
3109 );
3110 total += 1;
3111 if sheet.font_faces.len() == 1
3112 && sheet.font_faces[0].family == "Noto Sans JP"
3113 && sheet.font_faces[0].src_url.as_deref() == Some("a.woff2")
3114 {
3115 passed += 1;
3116 } else {
3117 crate::println!(
3118 "CSS_SELFTEST FAIL: @font-face parse => {:?}",
3119 sheet.font_faces
3120 );
3121 }
3122 }
3123 {
3126 let sheet = parse_css("@font-face{font-family:'X';src:url(only.ttf)}");
3127 total += 1;
3128 if sheet.font_faces.len() == 1 && sheet.font_faces[0].src_url.as_deref() == Some("only.ttf")
3129 {
3130 passed += 1;
3131 } else {
3132 crate::println!(
3133 "CSS_SELFTEST FAIL: @font-face parse (no format hint) => {:?}",
3134 sheet.font_faces
3135 );
3136 }
3137 }
3138 {
3144 let font_bytes = alloc::vec::Vec::from(
3145 &include_bytes!("../../../fonts/ipa/ipag.ttf")[..],
3146 );
3147 total += 1;
3148 let ok = crate::kernel::vector_font::register_custom_font("Test Custom Font", font_bytes);
3149 if ok && crate::kernel::vector_font::has_custom_font("test custom font") {
3150 passed += 1;
3151 } else {
3152 crate::println!("CSS_SELFTEST FAIL: register_custom_font round-trip => registered={}", ok);
3153 }
3154 }
3155 {
3166 total += 1;
3167 let font_a = include_bytes!("../../../fonts/MPLUS2/MPLUS2-Regular.ttf");
3168 let font_b = include_bytes!("../../../fonts/ipa/ipag.ttf");
3169 match crate::kernel::vector_font::VectorFont::new(font_a) {
3170 Ok(mut vf) => {
3171 let had_glyph_before = vf.get_glyph('a', 16).is_some();
3172 let switched = vf.set_primary(font_b).is_ok();
3173 let has_glyph_after = vf.get_glyph('a', 16).is_some();
3174 if had_glyph_before && switched && has_glyph_after {
3175 passed += 1;
3176 } else {
3177 crate::println!(
3178 "CSS_SELFTEST FAIL: VectorFont::set_primary => before={} switched={} after={}",
3179 had_glyph_before, switched, has_glyph_after
3180 );
3181 }
3182 }
3183 Err(_) => {
3184 crate::println!("CSS_SELFTEST FAIL: VectorFont::set_primary => failed to build base font");
3185 }
3186 }
3187 }
3188 {
3198 total += 1;
3199 if let Some(dec) = crate::kernel::vector_font::decompress_brotli(&[6], 0) {
3200 if dec.is_empty() {
3201 passed += 1;
3202 } else {
3203 crate::println!("CSS_SELFTEST FAIL: decompress_brotli empty stream should be empty");
3204 }
3205 } else {
3206 crate::println!("CSS_SELFTEST FAIL: decompress_brotli empty stream failed");
3207 }
3208 }
3209 {
3211 let fake_woff2 = alloc::vec![b'w', b'O', b'F', b'2', 0, 0, 0, 0];
3212 total += 1;
3213 if !crate::kernel::vector_font::register_custom_font("Fake Woff2", fake_woff2) {
3214 passed += 1;
3215 } else {
3216 crate::println!("CSS_SELFTEST FAIL: register_custom_font should reject invalid WOFF2 header");
3217 }
3218 }
3219 {
3221 total += 1;
3222 let html = "<html><body><div id='parent' style='word-break:break-all;overflow-wrap:break-word;'><span id='child'>text</span></div></body></html>";
3223 let doc = super::super::dom::parse_html(html);
3224 let stylesheet = parse_css("");
3225 let styled = style_tree(&doc, &stylesheet);
3226 fn find_by_id<'a>(node: &'a StyledNode<'a>, id: &str) -> Option<&'a StyledNode<'a>> {
3227 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
3228 if nid.as_deref() == Some(id) {
3229 return Some(node);
3230 }
3231 }
3232 for c in &node.children {
3233 if let Some(r) = find_by_id(c, id) {
3234 return Some(r);
3235 }
3236 }
3237 None
3238 }
3239 if let Some(child) = find_by_id(&styled, "child") {
3240 let wb = child.value("word-break").map(|v| v.trim() == "break-all").unwrap_or(false);
3241 let ow = child.value("overflow-wrap").map(|v| v.trim() == "break-word").unwrap_or(false);
3242 if wb && ow {
3243 passed += 1;
3244 } else {
3245 crate::println!("CSS_SELFTEST FAIL: word-break/overflow-wrap inheritance => wb={} ow={}", wb, ow);
3246 }
3247 } else {
3248 crate::println!("CSS_SELFTEST FAIL: child node not found in style_tree");
3249 }
3250 }
3251 for (mode, src, dst, expect) in blend_cases {
3252 let got = super::super::web_engine::draw_helpers::blend_pixel(mode, *src, *dst);
3253 if got == *expect {
3254 passed += 1;
3255 } else {
3256 crate::println!(
3257 "CSS_SELFTEST FAIL: blend_pixel(`{}`, {:?}, {:?}) => {:?} (want {:?})",
3258 mode,
3259 src,
3260 dst,
3261 got,
3262 expect
3263 );
3264 }
3265 }
3266 for (filter, input, expect) in filter_cases {
3267 let got = super::super::web_engine::draw_helpers::apply_css_filters(filter, input.0, input.1, input.2);
3268 if got == *expect {
3269 passed += 1;
3270 } else {
3271 crate::println!(
3272 "CSS_SELFTEST FAIL: apply_css_filters(`{}`, {:?}) => {:?} (want {:?})",
3273 filter,
3274 input,
3275 got,
3276 expect
3277 );
3278 }
3279 }
3280 for (kind, n, upper, expect) in alpha_roman_cases {
3281 let got = if *kind == "alpha" {
3282 super::super::web_engine::number_to_alpha(*n, *upper)
3283 } else {
3284 super::super::web_engine::number_to_roman(*n, *upper)
3285 };
3286 if got == *expect {
3287 passed += 1;
3288 } else {
3289 crate::println!(
3290 "CSS_SELFTEST FAIL: number_to_{}({}, {}) => `{}` (want `{}`)",
3291 kind,
3292 n,
3293 upper,
3294 got,
3295 expect
3296 );
3297 }
3298 }
3299 for (input, mode, expect) in tt_cases {
3300 if apply_text_transform(input, mode) == *expect {
3301 passed += 1;
3302 } else {
3303 crate::println!(
3304 "CSS_SELFTEST FAIL: text-transform({}, {}) => `{}` (want `{}`)",
3305 input,
3306 mode,
3307 apply_text_transform(input, mode),
3308 expect
3309 );
3310 }
3311 }
3312 for (html, css, id, expect) in cases {
3313 let dom = super::super::dom::parse_html(html);
3314 let sheet = parse_css(css);
3315 let styled = style_tree(&dom, &sheet);
3316 let got = find_marked(&styled, id).unwrap_or(false);
3317 if got == *expect {
3318 passed += 1;
3319 } else {
3320 crate::println!(
3321 "CSS_SELFTEST FAIL: `{}` + `{}` on #{} => {} (want {})",
3322 html,
3323 css,
3324 id,
3325 got,
3326 expect
3327 );
3328 }
3329 }
3330 for (html, css, id, prop, expect) in value_cases {
3331 let dom = super::super::dom::parse_html(html);
3332 let sheet = parse_css(css);
3333 let styled = style_tree(&dom, &sheet);
3334 let got = find_prop(&styled, id, prop).unwrap_or_default();
3335 if got == *expect {
3336 passed += 1;
3337 } else {
3338 crate::println!(
3339 "CSS_SELFTEST FAIL: `{}` + `{}` on #{}.{} => `{}` (want `{}`)",
3340 html,
3341 css,
3342 id,
3343 prop,
3344 got,
3345 expect
3346 );
3347 }
3348 }
3349 {
3353 fn find_node<'a>(node: &'a StyledNode, id: &str) -> Option<&'a StyledNode<'a>> {
3354 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
3355 if nid.as_deref() == Some(id) {
3356 return Some(node);
3357 }
3358 }
3359 for c in &node.children {
3360 if let Some(r) = find_node(c, id) {
3361 return Some(r);
3362 }
3363 }
3364 None
3365 }
3366 let dom = super::super::dom::parse_html("<input id='t' type='text'>");
3367 let sheet = parse_css("input:focus{border-color:#ff0000}");
3368 let styled = style_tree(&dom, &sheet);
3369 let focus_border = find_node(&styled, "t")
3370 .and_then(|n| n.focus_values.as_ref())
3371 .and_then(|h| h.get("border-color").cloned())
3372 .unwrap_or_default();
3373 if focus_border == "#ff0000" {
3374 passed += 1;
3375 } else {
3376 crate::println!(
3377 "CSS_SELFTEST FAIL: :focus border-color => `{}` (want `#ff0000`)",
3378 focus_border
3379 );
3380 }
3381 let sheet_fv = parse_css("input:focus-visible{border-color:#00ff00}");
3383 let styled_fv = style_tree(&dom, &sheet_fv);
3384 let focus_visible_border = find_node(&styled_fv, "t")
3385 .and_then(|n| n.focus_values.as_ref())
3386 .and_then(|h| h.get("border-color").cloned())
3387 .unwrap_or_default();
3388 if focus_visible_border == "#00ff00" {
3389 passed += 1;
3390 } else {
3391 crate::println!(
3392 "CSS_SELFTEST FAIL: :focus-visible border-color => `{}` (want `#00ff00`)",
3393 focus_visible_border
3394 );
3395 }
3396 let sheet_active = parse_css("input:active{border-color:#0000ff}");
3399 let styled_active = style_tree(&dom, &sheet_active);
3400 let active_border = find_node(&styled_active, "t")
3401 .and_then(|n| n.active_values.as_ref())
3402 .and_then(|h| h.get("border-color").cloned())
3403 .unwrap_or_default();
3404 if active_border == "#0000ff" {
3405 passed += 1;
3406 } else {
3407 crate::println!(
3408 "CSS_SELFTEST FAIL: :active border-color => `{}` (want `#0000ff`)",
3409 active_border
3410 );
3411 }
3412 }
3413
3414 {
3420 let s1 = parse_selector("div::before").map(|s| selector_specificity(&s));
3421 let s2 = parse_selector("div").map(|s| selector_specificity(&s));
3422 if s1 == Some((0, 0, 2)) && s2 == Some((0, 0, 1)) {
3423 passed += 1;
3424 } else {
3425 crate::println!(
3426 "CSS_SELFTEST FAIL: pseudo-element specificity => div::before={:?} (want Some((0,0,2))), div={:?} (want Some((0,0,1)))",
3427 s1, s2
3428 );
3429 }
3430 }
3431
3432 {
3436 let dom_root = super::super::dom::parse_html("<div id='child'></div>");
3437 let sheet_root = parse_css(":root{zz:1}");
3438 let styled_root = style_tree(&dom_root, &sheet_root);
3439 let root_marker = styled_root
3440 .specified_values
3441 .get("zz")
3442 .cloned()
3443 .unwrap_or_default();
3444 if root_marker == "1" {
3445 passed += 1;
3446 } else {
3447 crate::println!(
3448 "CSS_SELFTEST FAIL: :root zz => `{}` (want `1`)",
3449 root_marker
3450 );
3451 }
3452 fn find_node2<'a>(node: &'a StyledNode, id: &str) -> Option<&'a StyledNode<'a>> {
3454 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
3455 if nid.as_deref() == Some(id) {
3456 return Some(node);
3457 }
3458 }
3459 for c in &node.children {
3460 if let Some(r) = find_node2(c, id) {
3461 return Some(r);
3462 }
3463 }
3464 None
3465 }
3466 let child_matched = find_node2(&styled_root, "child")
3467 .and_then(|n| n.specified_values.get("zz").cloned())
3468 .is_some();
3469 if !child_matched {
3470 passed += 1;
3471 } else {
3472 crate::println!("CSS_SELFTEST FAIL: :root should not match non-root child element");
3473 }
3474 }
3475
3476 {
3481 fn find_tag_text<'a>(node: &'a super::super::dom::Node, tag: &str) -> Option<&'a str> {
3482 if let NodeType::Element { tag_name, .. } = &node.node_type {
3483 if tag_name == tag {
3484 for c in &node.children {
3485 if let NodeType::Text(t) = &c.node_type {
3486 return Some(t.as_str());
3487 }
3488 }
3489 return None;
3490 }
3491 }
3492 for c in &node.children {
3493 if let Some(r) = find_tag_text(c, tag) {
3494 return Some(r);
3495 }
3496 }
3497 None
3498 }
3499 total += 1;
3500 let dom = super::super::dom::parse_html(
3501 "<html><style>a{color:red}</style><script>var x=1;</script></html>",
3502 );
3503 let style_text = find_tag_text(&dom, "style").unwrap_or("");
3504 if style_text.contains("color:red") {
3505 passed += 1;
3506 } else {
3507 crate::println!(
3508 "CSS_SELFTEST FAIL: <style> text child => `{}` (want to contain `color:red`)",
3509 style_text
3510 );
3511 }
3512 let script_text = find_tag_text(&dom, "script").unwrap_or("");
3513 if script_text.contains("var x=1") {
3514 passed += 1;
3515 } else {
3516 crate::println!(
3517 "CSS_SELFTEST FAIL: <script> text child => `{}` (want to contain `var x=1`)",
3518 script_text
3519 );
3520 }
3521 }
3522
3523 let mut anim_total = 0usize;
3526 let mut anim_check = |desc: &str, got: String, want: &str| {
3527 anim_total += 1;
3528 if got == want {
3529 passed += 1;
3530 } else {
3531 crate::println!(
3532 "CSS_SELFTEST FAIL: anim {} => `{}` (want `{}`)",
3533 desc,
3534 got,
3535 want
3536 );
3537 }
3538 };
3539
3540 anim_check(
3542 "linear@0.5",
3543 fmt_num(TimingFunction::Linear.ease(0.5)),
3544 "0.5",
3545 );
3546 anim_check("linear@0", fmt_num(TimingFunction::Linear.ease(0.0)), "0");
3547 anim_check("linear@1", fmt_num(TimingFunction::Linear.ease(1.0)), "1");
3548 {
3550 let e = TimingFunction::parse("ease").ease(0.5);
3551 anim_check("ease@0.5>0.5", (e > 0.55).to_string(), "true");
3552 }
3553 {
3555 let e = TimingFunction::parse("ease-in").ease(0.5);
3556 anim_check("ease-in@0.5<0.5", (e < 0.45).to_string(), "true");
3557 }
3558 anim_check(
3560 "ease@0",
3561 fmt_num(TimingFunction::parse("ease").ease(0.0)),
3562 "0",
3563 );
3564 anim_check(
3565 "ease@1",
3566 fmt_num(TimingFunction::parse("ease").ease(1.0)),
3567 "1",
3568 );
3569 anim_check(
3571 "steps(4,end)@0.5",
3572 fmt_num(TimingFunction::parse("steps(4, end)").ease(0.5)),
3573 "0.5",
3574 );
3575 anim_check(
3577 "steps(4,end)@0.7",
3578 fmt_num(TimingFunction::parse("steps(4, end)").ease(0.7)),
3579 "0.5",
3580 );
3581 anim_check(
3583 "steps(4,start)@0.5",
3584 fmt_num(TimingFunction::parse("steps(4, start)").ease(0.5)),
3585 "0.75",
3586 );
3587 anim_check(
3589 "cubic-linear@0.3",
3590 fmt_num(TimingFunction::parse("cubic-bezier(0,0,1,1)").ease(0.3)),
3591 "0.3",
3592 );
3593
3594 anim_check("px@0.5", interpolate_value("0px", "100px", 0.5), "50px");
3596 anim_check("px@0", interpolate_value("10px", "100px", 0.0), "10px");
3597 anim_check("px@1", interpolate_value("10px", "100px", 1.0), "100px");
3598 anim_check("num@0.25", interpolate_value("0", "1", 0.25), "0.25");
3599 anim_check("pct@0.5", interpolate_value("0%", "50%", 0.5), "25%");
3600 anim_check("em@0.5", interpolate_value("1em", "3em", 0.5), "2em");
3601 anim_check("zero-to-pct@0.5", interpolate_value("0", "100%", 0.5), "50%");
3606 anim_check("zero-to-pct@0", interpolate_value("0", "100%", 0.0), "0%");
3607 anim_check("zero-to-pct@1", interpolate_value("0", "100%", 1.0), "100%");
3608 anim_check("pct-to-zero@0.5", interpolate_value("100%", "0", 0.5), "50%");
3609 anim_check("zero-to-px@0.5", interpolate_value("0", "40px", 0.5), "20px");
3610 anim_check(
3618 "translateY-fadein@0.5",
3619 interpolate_value("translateY(30px)", "translateY(0)", 0.5),
3620 "translateY(15px)",
3621 );
3622 anim_check(
3624 "color@0.5",
3625 interpolate_value("#000000", "#ffffff", 0.5),
3626 "#7f7f7f",
3627 );
3628 anim_check(
3629 "color-name@0",
3630 interpolate_value("red", "blue", 0.0),
3631 "#ff0000",
3632 );
3633 anim_check(
3635 "multi@0.5",
3636 interpolate_value("0px 0px", "10px 20px", 0.5),
3637 "5px 10px",
3638 );
3639 anim_check(
3641 "translateX@0.5",
3642 interpolate_value("translateX(0px)", "translateX(40px)", 0.5),
3643 "translateX(20px)",
3644 );
3645 anim_check(
3648 "translate-xy@0.5",
3649 interpolate_value("translate(10px, 20px)", "translate(30px, 40px)", 0.5),
3650 "translate(20px, 30px)",
3651 );
3652 anim_check(
3654 "matrix@0.5",
3655 interpolate_value("matrix(0, 0, 0, 0, 0, 0)", "matrix(2, 2, 2, 2, 2, 2)", 0.5),
3656 "matrix(1, 1, 1, 1, 1, 1)",
3657 );
3658 anim_check(
3661 "translate-rotate-chain@0.5",
3662 interpolate_value(
3663 "translateX(0px) rotate(0deg)",
3664 "translateX(40px) rotate(90deg)",
3665 0.5,
3666 ),
3667 "translateX(20px) rotate(45deg)",
3668 );
3669 anim_check(
3673 "multi-layer-shadow@0.5",
3674 interpolate_value("0px 0px, 1px 1px", "10px 10px, 21px 21px", 0.5),
3675 "5px 5px, 11px 11px",
3676 );
3677 anim_check(
3679 "discrete@0.3",
3680 interpolate_value("auto", "100px", 0.3),
3681 "auto",
3682 );
3683 anim_check(
3684 "discrete@0.7",
3685 interpolate_value("auto", "100px", 0.7),
3686 "100px",
3687 );
3688
3689 anim_check("time-1s", fmt_num(parse_time_ms("1s")), "1000");
3691 anim_check("time-250ms", fmt_num(parse_time_ms("250ms")), "250");
3692 anim_check("time-0.5s", fmt_num(parse_time_ms("0.5s")), "500");
3693
3694 {
3696 let specs = parse_transition("width 1s ease, background-color 0.5s linear 0.2s");
3697 anim_check("trans-count", specs.len().to_string(), "2");
3698 if specs.len() == 2 {
3699 anim_check("trans0-prop", specs[0].property.clone(), "width");
3700 anim_check("trans0-dur", fmt_num(specs[0].duration_ms), "1000");
3701 anim_check("trans1-prop", specs[1].property.clone(), "background-color");
3702 anim_check("trans1-delay", fmt_num(specs[1].delay_ms), "200");
3703 anim_check(
3704 "trans1-timing",
3705 (specs[1].timing == TimingFunction::Linear).to_string(),
3706 "true",
3707 );
3708 }
3709 }
3710
3711 {
3716 let specs = parse_transition("flex-basis 0.3s ease");
3717 anim_check("trans-s-count-flexbasis", specs.len().to_string(), "1");
3718 if specs.len() == 1 {
3719 anim_check("trans-s-prop-flexbasis", specs[0].property.clone(), "flex-basis");
3720 anim_check("trans-s-dur-flexbasis", fmt_num(specs[0].duration_ms), "300");
3721 }
3722 }
3723 {
3724 let specs = parse_transition("border-radius 0.5s linear");
3725 if specs.len() == 1 {
3726 anim_check("trans-s-prop-borderradius", specs[0].property.clone(), "border-radius");
3727 anim_check("trans-s-dur-borderradius", fmt_num(specs[0].duration_ms), "500");
3728 }
3729 }
3730
3731 {
3733 let specs = parse_animations("appears 1s ease");
3734 anim_check("anim-s-count", specs.len().to_string(), "1");
3735 if specs.len() == 1 {
3736 anim_check("anim-s-name", specs[0].name.clone(), "appears");
3737 anim_check("anim-s-dur", fmt_num(specs[0].duration_ms), "1000");
3738 }
3739 }
3740
3741 {
3746 let specs = parse_animations("2.5s steps(16,end) typing");
3747 anim_check("anim-steps-count", specs.len().to_string(), "1");
3748 if specs.len() == 1 {
3749 anim_check("anim-steps-name", specs[0].name.clone(), "typing");
3750 anim_check("anim-steps-dur", fmt_num(specs[0].duration_ms), "2500");
3751 anim_check("anim-steps-timing", fmt_num(specs[0].timing.ease(0.5)), "0.5");
3753 }
3754 }
3755
3756 {
3758 let spec = TransitionSpec {
3759 property: "width".into(),
3760 duration_ms: 1000.0,
3761 timing: TimingFunction::Linear,
3762 delay_ms: 0.0,
3763 };
3764 let (v, active) = sample_transition(&spec, "0px", "100px", 500.0);
3765 anim_check("samp-trans-mid", v, "50px");
3766 anim_check("samp-trans-active", active.to_string(), "true");
3767 let (v2, active2) = sample_transition(&spec, "0px", "100px", 1200.0);
3768 anim_check("samp-trans-end", v2, "100px");
3769 anim_check("samp-trans-done", active2.to_string(), "false");
3770 let spec_d = TransitionSpec {
3772 property: "width".into(),
3773 duration_ms: 1000.0,
3774 timing: TimingFunction::Linear,
3775 delay_ms: 300.0,
3776 };
3777 let (v3, _) = sample_transition(&spec_d, "0px", "100px", 100.0);
3778 anim_check("samp-trans-delay", v3, "0px");
3779 }
3780
3781 {
3783 let sheet = parse_css(
3784 "@keyframes slide { from { left: 0px; } to { left: 100px; } } \
3785 @keyframes fade { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } }",
3786 );
3787 anim_check("kf-count", sheet.keyframes.len().to_string(), "2");
3788 let slide = sheet.keyframes.iter().find(|k| k.name == "slide");
3789 anim_check("kf-slide-found", slide.is_some().to_string(), "true");
3790 if let Some(slide) = slide {
3791 anim_check("kf-slide-frames", slide.frames.len().to_string(), "2");
3792 let spec = AnimationSpec {
3793 name: "slide".into(),
3794 duration_ms: 1000.0,
3795 timing: TimingFunction::Linear,
3796 delay_ms: 0.0,
3797 iterations: 1.0,
3798 direction: "normal".into(),
3799 fill: "none".into(),
3800 play_state: "running".into(),
3801 };
3802 let m = sample_animation(&spec, slide, 500.0);
3803 anim_check(
3804 "anim-slide-mid",
3805 m.get("left").cloned().unwrap_or_default(),
3806 "50px",
3807 );
3808 }
3809 let fade = sheet.keyframes.iter().find(|k| k.name == "fade");
3810 if let Some(fade) = fade {
3811 anim_check("kf-fade-frames", fade.frames.len().to_string(), "3");
3812 let spec = AnimationSpec {
3813 name: "fade".into(),
3814 duration_ms: 1000.0,
3815 timing: TimingFunction::Linear,
3816 delay_ms: 0.0,
3817 iterations: 1.0,
3818 direction: "normal".into(),
3819 fill: "none".into(),
3820 play_state: "running".into(),
3821 };
3822 let m = sample_animation(&spec, fade, 250.0);
3824 anim_check(
3825 "anim-fade-q1",
3826 m.get("opacity").cloned().unwrap_or_default(),
3827 "0.5",
3828 );
3829 let m2 = sample_animation(&spec, fade, 500.0);
3831 anim_check(
3832 "anim-fade-mid",
3833 m2.get("opacity").cloned().unwrap_or_default(),
3834 "1",
3835 );
3836 }
3837 }
3838
3839 {
3841 let spec = parse_animation("slide 2s ease-in-out 0.5s infinite alternate both");
3842 anim_check("anim-parse-some", spec.is_some().to_string(), "true");
3843 if let Some(s) = spec {
3844 anim_check("anim-name", s.name.clone(), "slide");
3845 anim_check("anim-dur", fmt_num(s.duration_ms), "2000");
3846 anim_check("anim-delay", fmt_num(s.delay_ms), "500");
3847 anim_check(
3848 "anim-iter-inf",
3849 s.iterations.is_infinite().to_string(),
3850 "true",
3851 );
3852 anim_check("anim-dir", s.direction.clone(), "alternate");
3853 anim_check("anim-fill", s.fill.clone(), "both");
3854 }
3855 }
3856
3857 {
3860 let specs = parse_animations("fade 1s, bounce 2s infinite");
3861 anim_check("anim-multi-count", specs.len().to_string(), "2");
3862 if specs.len() == 2 {
3863 anim_check("anim-multi-name-0", specs[0].name.clone(), "fade");
3864 anim_check("anim-multi-dur-0", fmt_num(specs[0].duration_ms), "1000");
3865 anim_check("anim-multi-name-1", specs[1].name.clone(), "bounce");
3866 anim_check("anim-multi-dur-1", fmt_num(specs[1].duration_ms), "2000");
3867 anim_check(
3868 "anim-multi-iter-1-inf",
3869 specs[1].iterations.is_infinite().to_string(),
3870 "true",
3871 );
3872 }
3873 }
3874
3875 {
3877 let sheet = parse_css("@keyframes mv { from { left: 0px; } to { left: 100px; } }");
3878 let kf = &sheet.keyframes[0];
3879 let spec = AnimationSpec {
3880 name: "mv".into(),
3881 duration_ms: 1000.0,
3882 timing: TimingFunction::Linear,
3883 delay_ms: 0.0,
3884 iterations: 4.0,
3885 direction: "alternate".into(),
3886 fill: "none".into(),
3887 play_state: "running".into(),
3888 };
3889 let m0 = sample_animation(&spec, kf, 500.0);
3891 anim_check(
3892 "anim-alt-iter0",
3893 m0.get("left").cloned().unwrap_or_default(),
3894 "50px",
3895 );
3896 let m1 = sample_animation(&spec, kf, 1100.0);
3899 anim_check(
3900 "anim-alt-iter1",
3901 m1.get("left").cloned().unwrap_or_default(),
3902 "90px",
3903 );
3904 }
3905
3906 {
3912 let sheet = parse_css("@keyframes mv3b { from { left: 0px; } to { left: 100px; } }");
3913 let kf = &sheet.keyframes[0];
3914 let spec = AnimationSpec {
3915 name: "mv3b".into(),
3916 duration_ms: 1000.0,
3917 timing: TimingFunction::Linear,
3918 delay_ms: 0.0,
3919 iterations: 2.25,
3920 direction: "alternate".into(),
3921 fill: "forwards".into(),
3922 play_state: "running".into(),
3923 };
3924 let m = sample_animation(&spec, kf, 2250.0);
3928 anim_check(
3929 "anim-frac-iter-alternate-forwards",
3930 m.get("left").cloned().unwrap_or_default(),
3931 "25px",
3932 );
3933 }
3934
3935 {
3937 let sheet = parse_css("@keyframes mv2 { from { left: 0px; } to { left: 100px; } }");
3938 let kf = &sheet.keyframes[0];
3939 let spec = AnimationSpec {
3940 name: "mv2".into(),
3941 duration_ms: 1000.0,
3942 timing: TimingFunction::Linear,
3943 delay_ms: 0.0,
3944 iterations: 1.0,
3945 direction: "normal".into(),
3946 fill: "forwards".into(),
3947 play_state: "running".into(),
3948 };
3949 let m = sample_animation(&spec, kf, 2000.0);
3951 anim_check(
3952 "anim-fill-fwd",
3953 m.get("left").cloned().unwrap_or_default(),
3954 "100px",
3955 );
3956 }
3957
3958 {
3960 let mut eng = AnimationEngine::new();
3961 let spec = TransitionSpec {
3962 property: "left".into(),
3963 duration_ms: 1000.0,
3964 timing: TimingFunction::Linear,
3965 delay_ms: 0.0,
3966 };
3967 eng.start_transition("box", "left", "0px", "100px", spec, 0.0);
3969 anim_check(
3970 "eng-tr-active",
3971 if eng.is_active() {
3972 "y".into()
3973 } else {
3974 "n".into()
3975 },
3976 "y",
3977 );
3978 let active = eng.tick(500.0);
3980 let v = eng
3981 .computed
3982 .iter()
3983 .find(|(e, p, _)| e == "box" && p == "left")
3984 .map(|(_, _, v)| v.clone())
3985 .unwrap_or_default();
3986 anim_check("eng-tr-mid", v, "50px");
3987 anim_check(
3988 "eng-tr-mid-active",
3989 if active { "y".into() } else { "n".into() },
3990 "y",
3991 );
3992 eng.tick(1000.0);
3994 let v_end = eng
3995 .computed
3996 .iter()
3997 .find(|(e, p, _)| e == "box" && p == "left")
3998 .map(|(_, _, v)| v.clone())
3999 .unwrap_or_default();
4000 anim_check("eng-tr-end", v_end, "100px");
4001 anim_check(
4002 "eng-tr-done",
4003 if eng.is_active() {
4004 "y".into()
4005 } else {
4006 "n".into()
4007 },
4008 "n",
4009 );
4010 }
4011
4012 {
4017 let mut eng = AnimationEngine::new();
4018 let spec = TransitionSpec {
4019 property: "left".into(),
4020 duration_ms: 1000.0,
4021 timing: TimingFunction::Linear,
4022 delay_ms: 0.0,
4023 };
4024 eng.start_transition("box3", "left", "0px", "100px", spec, 0.0);
4025 eng.tick(500.0);
4028 anim_check(
4029 "eng-transitionend-mid-empty",
4030 if eng.lifecycle_events.is_empty() { "y".into() } else { "n".into() },
4031 "y",
4032 );
4033 eng.tick(1000.0);
4035 let got = eng
4036 .lifecycle_events
4037 .iter()
4038 .find(|(e, ty, _)| e == "box3" && *ty == "transitionend")
4039 .map(|(_, _, name)| name.clone())
4040 .unwrap_or_default();
4041 anim_check("eng-transitionend-name", got, "left");
4042 eng.tick(1500.0);
4045 anim_check(
4046 "eng-transitionend-no-duplicate",
4047 if eng.lifecycle_events.is_empty() { "y".into() } else { "n".into() },
4048 "y",
4049 );
4050 }
4051 {
4052 let sheet = parse_css("@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }");
4053 let kf = sheet.keyframes[0].clone();
4054 let spec = AnimationSpec {
4055 name: "fadein".into(),
4056 duration_ms: 1000.0,
4057 timing: TimingFunction::Linear,
4058 delay_ms: 100.0,
4059 iterations: 1.0,
4060 direction: "normal".into(),
4061 fill: "none".into(),
4062 play_state: "running".into(),
4063 };
4064 let mut eng = AnimationEngine::new();
4065 eng.start_animation("box4", spec, kf, 0.0);
4066 eng.tick(50.0);
4068 anim_check(
4069 "eng-animationstart-before-delay-empty",
4070 if eng.lifecycle_events.is_empty() { "y".into() } else { "n".into() },
4071 "y",
4072 );
4073 eng.tick(500.0);
4076 let got_start = eng
4077 .lifecycle_events
4078 .iter()
4079 .find(|(e, ty, _)| e == "box4" && *ty == "animationstart")
4080 .map(|(_, _, name)| name.clone())
4081 .unwrap_or_default();
4082 anim_check("eng-animationstart-name", got_start, "fadein");
4083 anim_check(
4084 "eng-animationend-mid-empty",
4085 if eng.lifecycle_events.iter().any(|(_, ty, _)| *ty == "animationend") {
4086 "n".into()
4087 } else {
4088 "y".into()
4089 },
4090 "y",
4091 );
4092 eng.tick(1100.0);
4095 let got = eng
4096 .lifecycle_events
4097 .iter()
4098 .find(|(e, ty, _)| e == "box4" && *ty == "animationend")
4099 .map(|(_, _, name)| name.clone())
4100 .unwrap_or_default();
4101 anim_check("eng-animationend-name", got, "fadein");
4102 }
4103
4104 {
4108 let mut eng = AnimationEngine::new();
4109 let spec = TransitionSpec {
4110 property: "opacity".into(),
4111 duration_ms: 1000.0,
4112 timing: TimingFunction::Linear,
4113 delay_ms: 0.0,
4114 };
4115 eng.start_transition("box2", "opacity", "1", "0", spec.clone(), 0.0);
4117 eng.start_transition("box2", "opacity", "0", "1", spec, 500.0);
4121 eng.tick(500.0);
4122 let v_retarget_start = eng
4123 .computed
4124 .iter()
4125 .find(|(e, p, _)| e == "box2" && p == "opacity")
4126 .map(|(_, _, v)| v.clone())
4127 .unwrap_or_default();
4128 anim_check("eng-tr-retarget-from-current", v_retarget_start, "0.5");
4129 eng.tick(1000.0);
4131 let v_retarget_mid = eng
4132 .computed
4133 .iter()
4134 .find(|(e, p, _)| e == "box2" && p == "opacity")
4135 .map(|(_, _, v)| v.clone())
4136 .unwrap_or_default();
4137 anim_check("eng-tr-retarget-progress", v_retarget_mid, "0.75");
4138 }
4139
4140 {
4142 let sheet = parse_css("@keyframes slide { from { left: 0px; } to { left: 200px; } }");
4143 let kf = sheet.keyframes[0].clone();
4144 let spec = AnimationSpec {
4145 name: "slide".into(),
4146 duration_ms: 1000.0,
4147 timing: TimingFunction::Linear,
4148 delay_ms: 0.0,
4149 iterations: 1.0,
4150 direction: "normal".into(),
4151 fill: "forwards".into(),
4152 play_state: "running".into(),
4153 };
4154 let mut eng = AnimationEngine::new();
4155 eng.start_animation("a", spec, kf, 0.0);
4156 eng.tick(250.0);
4158 let v = eng
4159 .computed
4160 .iter()
4161 .find(|(e, p, _)| e == "a" && p == "left")
4162 .map(|(_, _, v)| v.clone())
4163 .unwrap_or_default();
4164 anim_check("eng-anim-q1", v, "50px");
4165 eng.tick(2000.0);
4167 let v_end = eng
4168 .computed
4169 .iter()
4170 .find(|(e, p, _)| e == "a" && p == "left")
4171 .map(|(_, _, v)| v.clone())
4172 .unwrap_or_default();
4173 anim_check("eng-anim-fwd-hold", v_end, "200px");
4174 anim_check(
4175 "eng-anim-done",
4176 if eng.is_active() {
4177 "y".into()
4178 } else {
4179 "n".into()
4180 },
4181 "n",
4182 );
4183 }
4184
4185 {
4189 let sheet = parse_css("@keyframes slide2 { from { left: 0px; } to { left: 200px; } }");
4190 let kf = sheet.keyframes[0].clone();
4191 let mut spec = AnimationSpec {
4192 name: "slide2".into(),
4193 duration_ms: 1000.0,
4194 timing: TimingFunction::Linear,
4195 delay_ms: 0.0,
4196 iterations: 1.0,
4197 direction: "normal".into(),
4198 fill: "forwards".into(),
4199 play_state: "running".into(),
4200 };
4201 let mut eng = AnimationEngine::new();
4202 eng.start_animation("b", spec.clone(), kf.clone(), 0.0);
4203 eng.tick(250.0);
4205 spec.play_state = "paused".into();
4207 eng.start_animation("b", spec.clone(), kf.clone(), 250.0);
4208 eng.tick(250.0);
4209 let v_paused = eng
4210 .computed
4211 .iter()
4212 .find(|(e, p, _)| e == "b" && p == "left")
4213 .map(|(_, _, v)| v.clone())
4214 .unwrap_or_default();
4215 anim_check("eng-anim-paused-frozen1", v_paused.clone(), "50px");
4216 eng.tick(1000.0);
4218 let v_still_paused = eng
4219 .computed
4220 .iter()
4221 .find(|(e, p, _)| e == "b" && p == "left")
4222 .map(|(_, _, v)| v.clone())
4223 .unwrap_or_default();
4224 anim_check("eng-anim-paused-frozen2", v_still_paused, "50px");
4225 spec.play_state = "running".into();
4231 eng.start_animation("b", spec, kf, 1000.0);
4232 eng.tick(1000.0);
4233 let v_resume_instant = eng
4234 .computed
4235 .iter()
4236 .find(|(e, p, _)| e == "b" && p == "left")
4237 .map(|(_, _, v)| v.clone())
4238 .unwrap_or_default();
4239 anim_check("eng-anim-resume-instant", v_resume_instant, "50px");
4240 eng.tick(1250.0);
4241 let v_resumed = eng
4242 .computed
4243 .iter()
4244 .find(|(e, p, _)| e == "b" && p == "left")
4245 .map(|(_, _, v)| v.clone())
4246 .unwrap_or_default();
4247 anim_check("eng-anim-resumed", v_resumed, "100px");
4248 }
4249
4250 {
4255 let sheet = parse_css(
4256 "@keyframes fadeA { from { opacity: 0; } to { opacity: 1; } } \
4257 @keyframes fadeB { from { opacity: 1; } to { opacity: 0; } }",
4258 );
4259 let empty_kf = |name: &str| Keyframes { name: name.into(), frames: Vec::new() };
4260 let kf_a = sheet
4261 .keyframes
4262 .iter()
4263 .find(|k| k.name == "fadeA")
4264 .cloned()
4265 .unwrap_or_else(|| empty_kf("fadeA"));
4266 let kf_b = sheet
4267 .keyframes
4268 .iter()
4269 .find(|k| k.name == "fadeB")
4270 .cloned()
4271 .unwrap_or_else(|| empty_kf("fadeB"));
4272 let mut eng = AnimationEngine::new();
4273 let spec_a = AnimationSpec {
4274 name: "fadeA".into(),
4275 duration_ms: 1000.0,
4276 timing: TimingFunction::Linear,
4277 delay_ms: 0.0,
4278 iterations: f32::INFINITY,
4279 direction: "normal".into(),
4280 fill: "none".into(),
4281 play_state: "running".into(),
4282 };
4283 eng.start_animation("c", spec_a, kf_a, 0.0);
4284 eng.retain_animation_names("c", &[alloc::string::String::from("fadeB")]);
4287 let spec_b = AnimationSpec {
4288 name: "fadeB".into(),
4289 duration_ms: 1000.0,
4290 timing: TimingFunction::Linear,
4291 delay_ms: 0.0,
4292 iterations: 1.0,
4293 direction: "normal".into(),
4294 fill: "forwards".into(),
4295 play_state: "running".into(),
4296 };
4297 eng.start_animation("c", spec_b, kf_b, 500.0);
4298 eng.tick(500.0);
4299 let props: alloc::vec::Vec<&alloc::string::String> = eng
4300 .computed
4301 .iter()
4302 .filter(|(e, _, _)| e == "c")
4303 .map(|(_, p, _)| p)
4304 .collect();
4305 anim_check("eng-anim-name-switch-count", props.len().to_string(), "1");
4308 }
4309
4310 {
4315 let sheet_v1 = parse_css("@keyframes mv3 { from { left: 0px; } to { left: 100px; } }");
4316 let kf_v1 = sheet_v1.keyframes[0].clone();
4317 let spec = AnimationSpec {
4318 name: "mv3".into(),
4319 duration_ms: 1000.0,
4320 timing: TimingFunction::Linear,
4321 delay_ms: 0.0,
4322 iterations: f32::INFINITY,
4323 direction: "normal".into(),
4324 fill: "none".into(),
4325 play_state: "running".into(),
4326 };
4327 let mut eng = AnimationEngine::new();
4328 eng.start_animation("d", spec.clone(), kf_v1, 0.0);
4329 eng.tick(500.0);
4330 let v_before = eng
4331 .computed
4332 .iter()
4333 .find(|(e, p, _)| e == "d" && p == "left")
4334 .map(|(_, _, v)| v.clone())
4335 .unwrap_or_default();
4336 anim_check("eng-anim-kf-before-update", v_before, "50px");
4337 let sheet_v2 = parse_css("@keyframes mv3 { from { left: 0px; } to { left: 400px; } }");
4340 let kf_v2 = sheet_v2.keyframes[0].clone();
4341 eng.start_animation("d", spec, kf_v2, 500.0);
4342 eng.tick(500.0);
4343 let v_after = eng
4344 .computed
4345 .iter()
4346 .find(|(e, p, _)| e == "d" && p == "left")
4347 .map(|(_, _, v)| v.clone())
4348 .unwrap_or_default();
4349 anim_check("eng-anim-kf-after-update", v_after, "200px");
4350 }
4351
4352 {
4354 let mut eng = AnimationEngine::new();
4355 let spec = TransitionSpec {
4356 property: "left".into(),
4357 duration_ms: 1000.0,
4358 timing: TimingFunction::Linear,
4359 delay_ms: 0.0,
4360 };
4361 eng.start_transition("b", "left", "0px", "100px", spec.clone(), 0.0);
4362 eng.tick(500.0); eng.start_transition("b", "left", "50px", "0px", spec, 500.0);
4365 eng.tick(1000.0); let v = eng
4368 .computed
4369 .iter()
4370 .find(|(e, p, _)| e == "b" && p == "left")
4371 .map(|(_, _, v)| v.clone())
4372 .unwrap_or_default();
4373 anim_check("eng-retarget", v, "25px");
4374 }
4375
4376 let total = total + anim_total;
4377 (passed, total)
4378}