1use azul_css::{
34 css::CssPropertyValue,
35 dynamic_selector::{
36 CssPropertyWithConditions,
37 DynamicSelector, DynamicSelectorContext, OsCondition, ThemeCondition,
38 },
39 props::{
40 basic::{
41 font::StyleFontWeight, pixel::PixelValue, ColorU,
42 StyleFontSize,
43 },
44 layout::{
45 dimensions::{LayoutHeight, LayoutWidth},
46 display::LayoutDisplay,
47 fragmentation::{BreakInside, PageBreak},
48 spacing::{
49 LayoutMarginBottom, LayoutMarginLeft, LayoutMarginRight, LayoutMarginTop,
50 LayoutPaddingBottom, LayoutPaddingInlineEnd, LayoutPaddingInlineStart,
51 LayoutPaddingLeft, LayoutPaddingRight, LayoutPaddingTop,
52 },
53 },
54 property::{CssProperty, CssPropertyType},
55 style::{
56 border::{
57 BorderStyle,
58 LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, LayoutBorderTopWidth,
59 StyleBorderBottomColor, StyleBorderBottomStyle,
60 StyleBorderLeftColor, StyleBorderLeftStyle,
61 StyleBorderRightColor, StyleBorderRightStyle,
62 StyleBorderTopColor, StyleBorderTopStyle,
63 },
64 content::CounterReset,
65 effects::StyleCursor,
66 lists::StyleListStyleType,
67 scrollbar::{
68 LayoutScrollbarWidth, ScrollbarColorCustom, ScrollbarFadeDelay,
69 ScrollbarFadeDuration, ScrollbarVisibilityMode, StyleScrollbarColor,
70 },
71 text::StyleTextDecoration,
72 StyleTextAlign, StyleVerticalAlign,
73 },
74 },
75};
76
77use crate::dom::NodeType;
78
79static WIDTH_100_PERCENT: CssProperty = CssProperty::Width(CssPropertyValue::Exact(
81 LayoutWidth::Px(PixelValue::const_percent(100)),
82));
83
84static HEIGHT_100_PERCENT: CssProperty = CssProperty::Height(CssPropertyValue::Exact(
86 LayoutHeight::Px(PixelValue::const_percent(100)),
87));
88
89static DISPLAY_BLOCK: CssProperty =
91 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::Block));
92
93static DISPLAY_INLINE: CssProperty =
95 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::Inline));
96
97static DISPLAY_INLINE_BLOCK: CssProperty =
99 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::InlineBlock));
100
101static DISPLAY_NONE: CssProperty =
103 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::None));
104
105static DISPLAY_TABLE: CssProperty =
107 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::Table));
108
109static DISPLAY_TABLE_ROW: CssProperty =
111 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableRow));
112
113static DISPLAY_TABLE_CELL: CssProperty =
115 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableCell));
116
117static DISPLAY_TABLE_HEADER_GROUP: CssProperty =
119 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableHeaderGroup));
120
121static DISPLAY_TABLE_ROW_GROUP: CssProperty =
123 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableRowGroup));
124
125static DISPLAY_TABLE_FOOTER_GROUP: CssProperty =
127 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableFooterGroup));
128
129static DISPLAY_TABLE_CAPTION: CssProperty =
131 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableCaption));
132
133static DISPLAY_TABLE_COLUMN_GROUP: CssProperty =
135 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableColumnGroup));
136
137static DISPLAY_TABLE_COLUMN: CssProperty =
139 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::TableColumn));
140
141static DISPLAY_LIST_ITEM: CssProperty =
143 CssProperty::Display(CssPropertyValue::Exact(LayoutDisplay::ListItem));
144
145static CURSOR_POINTER: CssProperty =
147 CssProperty::Cursor(CssPropertyValue::Exact(StyleCursor::Pointer));
148
149static CURSOR_TEXT: CssProperty =
151 CssProperty::Cursor(CssPropertyValue::Exact(StyleCursor::Text));
152
153static MARGIN_TOP_ZERO: CssProperty =
155 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
156 inner: PixelValue::const_px(0),
157 }));
158
159static MARGIN_BOTTOM_ZERO: CssProperty =
161 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
162 inner: PixelValue::const_px(0),
163 }));
164
165static MARGIN_LEFT_ZERO: CssProperty =
167 CssProperty::MarginLeft(CssPropertyValue::Exact(LayoutMarginLeft {
168 inner: PixelValue::const_px(0),
169 }));
170
171static MARGIN_RIGHT_ZERO: CssProperty =
173 CssProperty::MarginRight(CssPropertyValue::Exact(LayoutMarginRight {
174 inner: PixelValue::const_px(0),
175 }));
176
177static MARGIN_TOP_8PX: CssProperty =
180 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
181 inner: PixelValue::const_px(8),
182 }));
183
184static MARGIN_BOTTOM_8PX: CssProperty =
186 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
187 inner: PixelValue::const_px(8),
188 }));
189
190static MARGIN_LEFT_8PX: CssProperty =
192 CssProperty::MarginLeft(CssPropertyValue::Exact(LayoutMarginLeft {
193 inner: PixelValue::const_px(8),
194 }));
195
196static MARGIN_RIGHT_8PX: CssProperty =
198 CssProperty::MarginRight(CssPropertyValue::Exact(LayoutMarginRight {
199 inner: PixelValue::const_px(8),
200 }));
201
202static FONT_SIZE_2EM: CssProperty = CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
204 inner: PixelValue::const_em(2),
205}));
206
207static FONT_SIZE_1_5EM: CssProperty =
209 CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
210 inner: PixelValue::const_em_fractional(1, 5),
211 }));
212
213static FONT_SIZE_1_17EM: CssProperty =
215 CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
216 inner: PixelValue::const_em_fractional(1, 17),
217 }));
218
219static FONT_SIZE_1EM: CssProperty = CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
221 inner: PixelValue::const_em(1),
222}));
223
224static FONT_SIZE_0_83EM: CssProperty =
226 CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
227 inner: PixelValue::const_em_fractional(0, 83),
228 }));
229
230static FONT_SIZE_0_67EM: CssProperty =
232 CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
233 inner: PixelValue::const_em_fractional(0, 67),
234 }));
235
236static MARGIN_TOP_1EM: CssProperty =
238 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
239 inner: PixelValue::const_em(1),
240 }));
241
242static MARGIN_BOTTOM_1EM: CssProperty =
244 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
245 inner: PixelValue::const_em(1),
246 }));
247
248static MARGIN_TOP_0_67EM: CssProperty =
250 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
251 inner: PixelValue::const_em_fractional(0, 67),
252 }));
253
254static MARGIN_BOTTOM_0_67EM: CssProperty =
256 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
257 inner: PixelValue::const_em_fractional(0, 67),
258 }));
259
260static MARGIN_TOP_0_83EM: CssProperty =
262 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
263 inner: PixelValue::const_em_fractional(0, 83),
264 }));
265
266static MARGIN_BOTTOM_0_83EM: CssProperty =
268 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
269 inner: PixelValue::const_em_fractional(0, 83),
270 }));
271
272static MARGIN_TOP_1_33EM: CssProperty =
274 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
275 inner: PixelValue::const_em_fractional(1, 33),
276 }));
277
278static MARGIN_BOTTOM_1_33EM: CssProperty =
280 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
281 inner: PixelValue::const_em_fractional(1, 33),
282 }));
283
284static MARGIN_TOP_1_67EM: CssProperty =
286 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
287 inner: PixelValue::const_em_fractional(1, 67),
288 }));
289
290static MARGIN_BOTTOM_1_67EM: CssProperty =
292 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
293 inner: PixelValue::const_em_fractional(1, 67),
294 }));
295
296static MARGIN_TOP_2_33EM: CssProperty =
298 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
299 inner: PixelValue::const_em_fractional(2, 33),
300 }));
301
302static MARGIN_BOTTOM_2_33EM: CssProperty =
304 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
305 inner: PixelValue::const_em_fractional(2, 33),
306 }));
307
308static FONT_WEIGHT_BOLD: CssProperty =
310 CssProperty::FontWeight(CssPropertyValue::Exact(StyleFontWeight::Bold));
311
312static FONT_WEIGHT_BOLDER: CssProperty =
314 CssProperty::FontWeight(CssPropertyValue::Exact(StyleFontWeight::Bolder));
315
316static PADDING_TOP_1PX: CssProperty =
318 CssProperty::PaddingTop(CssPropertyValue::Exact(LayoutPaddingTop {
319 inner: PixelValue::const_px(1),
320 }));
321
322static PADDING_BOTTOM_1PX: CssProperty =
323 CssProperty::PaddingBottom(CssPropertyValue::Exact(LayoutPaddingBottom {
324 inner: PixelValue::const_px(1),
325 }));
326
327static PADDING_LEFT_1PX: CssProperty =
328 CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
329 inner: PixelValue::const_px(1),
330 }));
331
332static PADDING_RIGHT_1PX: CssProperty =
333 CssProperty::PaddingRight(CssPropertyValue::Exact(LayoutPaddingRight {
334 inner: PixelValue::const_px(1),
335 }));
336
337static TEXT_ALIGN_CENTER: CssProperty =
339 CssProperty::TextAlign(CssPropertyValue::Exact(StyleTextAlign::Center));
340
341static VERTICAL_ALIGN_MIDDLE: CssProperty =
343 CssProperty::VerticalAlign(CssPropertyValue::Exact(StyleVerticalAlign::Middle));
344
345static LIST_STYLE_TYPE_DISC: CssProperty =
347 CssProperty::ListStyleType(CssPropertyValue::Exact(StyleListStyleType::Disc));
348
349static LIST_STYLE_TYPE_DECIMAL: CssProperty =
351 CssProperty::ListStyleType(CssPropertyValue::Exact(StyleListStyleType::Decimal));
352
353static MARGIN_TOP_0_5EM: CssProperty =
358 CssProperty::MarginTop(CssPropertyValue::Exact(LayoutMarginTop {
359 inner: PixelValue::const_em_fractional(0, 5),
360 }));
361
362static MARGIN_BOTTOM_0_5EM: CssProperty =
364 CssProperty::MarginBottom(CssPropertyValue::Exact(LayoutMarginBottom {
365 inner: PixelValue::const_em_fractional(0, 5),
366 }));
367
368static BORDER_TOP_STYLE_INSET: CssProperty =
370 CssProperty::BorderTopStyle(CssPropertyValue::Exact(StyleBorderTopStyle {
371 inner: BorderStyle::Inset,
372 }));
373
374static BORDER_TOP_WIDTH_1PX: CssProperty =
376 CssProperty::BorderTopWidth(CssPropertyValue::Exact(LayoutBorderTopWidth {
377 inner: PixelValue::const_px(1),
378 }));
379
380static BORDER_TOP_COLOR_GRAY: CssProperty =
382 CssProperty::BorderTopColor(CssPropertyValue::Exact(StyleBorderTopColor {
383 inner: ColorU {
384 r: 128,
385 g: 128,
386 b: 128,
387 a: 255,
388 },
389 }));
390
391static HEIGHT_ZERO: CssProperty = CssProperty::Height(CssPropertyValue::Exact(LayoutHeight::Px(
393 PixelValue::const_px(0),
394)));
395
396static COUNTER_RESET_LIST_ITEM: CssProperty =
399 CssProperty::CounterReset(CssPropertyValue::Exact(CounterReset::list_item()));
400
401static BREAK_INSIDE_AVOID: CssProperty = CssProperty::break_inside(BreakInside::Avoid);
410
411static BREAK_AFTER_AVOID: CssProperty = CssProperty::break_after(PageBreak::Avoid);
414
415static PADDING_INLINE_START_40PX: CssProperty =
428 CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
429 inner: PixelValue::const_px(40),
430 }));
431
432static TEXT_DECORATION_UNDERLINE: CssProperty = CssProperty::TextDecoration(
434 CssPropertyValue::Exact(StyleTextDecoration::Underline),
435);
436
437static FONT_SIZE_13PX: CssProperty = CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
443 inner: PixelValue::const_px(13),
444}));
445
446static PADDING_TOP_5PX: CssProperty =
448 CssProperty::PaddingTop(CssPropertyValue::Exact(LayoutPaddingTop {
449 inner: PixelValue::const_px(5),
450 }));
451
452static PADDING_BOTTOM_5PX: CssProperty =
454 CssProperty::PaddingBottom(CssPropertyValue::Exact(LayoutPaddingBottom {
455 inner: PixelValue::const_px(5),
456 }));
457
458static PADDING_LEFT_10PX: CssProperty =
460 CssProperty::PaddingLeft(CssPropertyValue::Exact(LayoutPaddingLeft {
461 inner: PixelValue::const_px(10),
462 }));
463
464static PADDING_RIGHT_10PX: CssProperty =
466 CssProperty::PaddingRight(CssPropertyValue::Exact(LayoutPaddingRight {
467 inner: PixelValue::const_px(10),
468 }));
469
470static BUTTON_BORDER_COLOR: ColorU = ColorU { r: 200, g: 200, b: 200, a: 255 };
472
473static BUTTON_BORDER_TOP_COLOR: CssProperty =
474 CssProperty::BorderTopColor(CssPropertyValue::Exact(StyleBorderTopColor {
475 inner: BUTTON_BORDER_COLOR,
476 }));
477static BUTTON_BORDER_BOTTOM_COLOR: CssProperty =
478 CssProperty::BorderBottomColor(CssPropertyValue::Exact(StyleBorderBottomColor {
479 inner: BUTTON_BORDER_COLOR,
480 }));
481static BUTTON_BORDER_LEFT_COLOR: CssProperty =
482 CssProperty::BorderLeftColor(CssPropertyValue::Exact(StyleBorderLeftColor {
483 inner: BUTTON_BORDER_COLOR,
484 }));
485static BUTTON_BORDER_RIGHT_COLOR: CssProperty =
486 CssProperty::BorderRightColor(CssPropertyValue::Exact(StyleBorderRightColor {
487 inner: BUTTON_BORDER_COLOR,
488 }));
489
490static BUTTON_BORDER_TOP_STYLE: CssProperty =
491 CssProperty::BorderTopStyle(CssPropertyValue::Exact(StyleBorderTopStyle {
492 inner: BorderStyle::Solid,
493 }));
494static BUTTON_BORDER_BOTTOM_STYLE: CssProperty =
495 CssProperty::BorderBottomStyle(CssPropertyValue::Exact(StyleBorderBottomStyle {
496 inner: BorderStyle::Solid,
497 }));
498static BUTTON_BORDER_LEFT_STYLE: CssProperty =
499 CssProperty::BorderLeftStyle(CssPropertyValue::Exact(StyleBorderLeftStyle {
500 inner: BorderStyle::Solid,
501 }));
502static BUTTON_BORDER_RIGHT_STYLE: CssProperty =
503 CssProperty::BorderRightStyle(CssPropertyValue::Exact(StyleBorderRightStyle {
504 inner: BorderStyle::Solid,
505 }));
506
507static BUTTON_BORDER_TOP_WIDTH: CssProperty =
508 CssProperty::BorderTopWidth(CssPropertyValue::Exact(LayoutBorderTopWidth {
509 inner: PixelValue::const_px(1),
510 }));
511static BUTTON_BORDER_BOTTOM_WIDTH: CssProperty =
512 CssProperty::BorderBottomWidth(CssPropertyValue::Exact(LayoutBorderBottomWidth {
513 inner: PixelValue::const_px(1),
514 }));
515static BUTTON_BORDER_LEFT_WIDTH: CssProperty =
516 CssProperty::BorderLeftWidth(CssPropertyValue::Exact(LayoutBorderLeftWidth {
517 inner: PixelValue::const_px(1),
518 }));
519static BUTTON_BORDER_RIGHT_WIDTH: CssProperty =
520 CssProperty::BorderRightWidth(CssPropertyValue::Exact(LayoutBorderRightWidth {
521 inner: PixelValue::const_px(1),
522 }));
523
524#[allow(clippy::match_same_arms)]
542#[allow(clippy::too_many_lines)] #[must_use] pub fn get_ua_property(
544 node_type: &NodeType,
545 property_type: CssPropertyType,
546) -> Option<&'static CssProperty> {
547 use CssPropertyType as PT;
548 use NodeType as NT;
549
550
551
552 match (node_type, property_type) {
553 (NT::Body, PT::Display) => Some(&DISPLAY_BLOCK),
555 (NT::Body, PT::MarginTop) => Some(&MARGIN_TOP_8PX),
558 (NT::Body, PT::MarginBottom) => Some(&MARGIN_BOTTOM_8PX),
559 (NT::Body, PT::MarginLeft) => Some(&MARGIN_LEFT_8PX),
560 (NT::Body, PT::MarginRight) => Some(&MARGIN_RIGHT_8PX),
561
562 (NT::Div, PT::Display) => Some(&DISPLAY_BLOCK),
568 (NT::P, PT::Display) => Some(&DISPLAY_BLOCK),
569 (NT::P, PT::MarginTop) => Some(&MARGIN_TOP_1EM),
574 (NT::P, PT::MarginBottom) => Some(&MARGIN_BOTTOM_1EM),
575 (NT::Main, PT::Display) => Some(&DISPLAY_BLOCK),
576 (NT::Header, PT::Display) => Some(&DISPLAY_BLOCK),
577 (NT::Footer, PT::Display) => Some(&DISPLAY_BLOCK),
578 (NT::Section, PT::Display) => Some(&DISPLAY_BLOCK),
579 (NT::Article, PT::Display) => Some(&DISPLAY_BLOCK),
580 (NT::Aside, PT::Display) => Some(&DISPLAY_BLOCK),
581 (NT::Nav, PT::Display) => Some(&DISPLAY_BLOCK),
582
583 (NT::H1, PT::Display) => Some(&DISPLAY_BLOCK),
587 (NT::H1, PT::FontSize) => Some(&FONT_SIZE_2EM),
588 (NT::H1, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
589 (NT::H1, PT::MarginTop) => Some(&MARGIN_TOP_0_67EM),
590 (NT::H1, PT::MarginBottom) => Some(&MARGIN_BOTTOM_0_67EM),
591 (NT::H1, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
592 (NT::H1, PT::BreakAfter) => Some(&BREAK_AFTER_AVOID),
593
594 (NT::H2, PT::Display) => Some(&DISPLAY_BLOCK),
595 (NT::H2, PT::FontSize) => Some(&FONT_SIZE_1_5EM),
596 (NT::H2, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
597 (NT::H2, PT::MarginTop) => Some(&MARGIN_TOP_0_83EM),
598 (NT::H2, PT::MarginBottom) => Some(&MARGIN_BOTTOM_0_83EM),
599 (NT::H2, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
600 (NT::H2, PT::BreakAfter) => Some(&BREAK_AFTER_AVOID),
601
602 (NT::H3, PT::Display) => Some(&DISPLAY_BLOCK),
603 (NT::H3, PT::FontSize) => Some(&FONT_SIZE_1_17EM),
604 (NT::H3, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
605 (NT::H3, PT::MarginTop) => Some(&MARGIN_TOP_1EM),
606 (NT::H3, PT::MarginBottom) => Some(&MARGIN_BOTTOM_1EM),
607 (NT::H3, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
608 (NT::H3, PT::BreakAfter) => Some(&BREAK_AFTER_AVOID),
609
610 (NT::H4, PT::Display) => Some(&DISPLAY_BLOCK),
611 (NT::H4, PT::FontSize) => Some(&FONT_SIZE_1EM),
612 (NT::H4, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
613 (NT::H4, PT::MarginTop) => Some(&MARGIN_TOP_1_33EM),
614 (NT::H4, PT::MarginBottom) => Some(&MARGIN_BOTTOM_1_33EM),
615 (NT::H4, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
616 (NT::H4, PT::BreakAfter) => Some(&BREAK_AFTER_AVOID),
617
618 (NT::H5, PT::Display) => Some(&DISPLAY_BLOCK),
619 (NT::H5, PT::FontSize) => Some(&FONT_SIZE_0_83EM),
620 (NT::H5, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
621 (NT::H5, PT::MarginTop) => Some(&MARGIN_TOP_1_67EM),
622 (NT::H5, PT::MarginBottom) => Some(&MARGIN_BOTTOM_1_67EM),
623 (NT::H5, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
624 (NT::H5, PT::BreakAfter) => Some(&BREAK_AFTER_AVOID),
625
626 (NT::H6, PT::Display) => Some(&DISPLAY_BLOCK),
627 (NT::H6, PT::FontSize) => Some(&FONT_SIZE_0_67EM),
628 (NT::H6, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
629 (NT::H6, PT::MarginTop) => Some(&MARGIN_TOP_2_33EM),
630 (NT::H6, PT::MarginBottom) => Some(&MARGIN_BOTTOM_2_33EM),
631 (NT::H6, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
632 (NT::H6, PT::BreakAfter) => Some(&BREAK_AFTER_AVOID),
633
634 (NT::Ul, PT::Display) => Some(&DISPLAY_BLOCK),
636 (NT::Ul, PT::ListStyleType) => Some(&LIST_STYLE_TYPE_DISC),
637 (NT::Ul, PT::CounterReset) => Some(&COUNTER_RESET_LIST_ITEM),
638 (NT::Ul, PT::PaddingLeft) => Some(&PADDING_INLINE_START_40PX),
639 (NT::Ul, PT::MarginTop) => Some(&MARGIN_TOP_1EM),
640 (NT::Ul, PT::MarginBottom) => Some(&MARGIN_BOTTOM_1EM),
641 (NT::Ol, PT::Display) => Some(&DISPLAY_BLOCK),
642 (NT::Ol, PT::ListStyleType) => Some(&LIST_STYLE_TYPE_DECIMAL),
643 (NT::Ol, PT::CounterReset) => Some(&COUNTER_RESET_LIST_ITEM),
644 (NT::Ol, PT::PaddingLeft) => Some(&PADDING_INLINE_START_40PX),
645 (NT::Ol, PT::MarginTop) => Some(&MARGIN_TOP_1EM),
646 (NT::Ol, PT::MarginBottom) => Some(&MARGIN_BOTTOM_1EM),
647 (NT::Li, PT::Display) => Some(&DISPLAY_LIST_ITEM),
648 (NT::Dl, PT::Display) => Some(&DISPLAY_BLOCK),
649 (NT::Dt, PT::Display) => Some(&DISPLAY_BLOCK),
650 (NT::Dd, PT::Display) => Some(&DISPLAY_BLOCK),
651
652 (NT::Span, PT::Display) => Some(&DISPLAY_INLINE),
654 (NT::A, PT::Display) => Some(&DISPLAY_INLINE),
655 (NT::A, PT::TextDecoration) => Some(&TEXT_DECORATION_UNDERLINE),
656 (NT::Strong, PT::Display) => Some(&DISPLAY_INLINE),
657 (NT::Strong, PT::FontWeight) => Some(&FONT_WEIGHT_BOLDER),
658 (NT::Em, PT::Display) => Some(&DISPLAY_INLINE),
659 (NT::B, PT::Display) => Some(&DISPLAY_INLINE),
660 (NT::B, PT::FontWeight) => Some(&FONT_WEIGHT_BOLDER),
661 (NT::I, PT::Display) => Some(&DISPLAY_INLINE),
662 (NT::U, PT::Display) => Some(&DISPLAY_INLINE),
663 (NT::U, PT::TextDecoration) => Some(&TEXT_DECORATION_UNDERLINE),
664 (NT::Small, PT::Display) => Some(&DISPLAY_INLINE),
665 (NT::Code, PT::Display) => Some(&DISPLAY_INLINE),
666 (NT::Kbd, PT::Display) => Some(&DISPLAY_INLINE),
667 (NT::Samp, PT::Display) => Some(&DISPLAY_INLINE),
668 (NT::Sub, PT::Display) => Some(&DISPLAY_INLINE),
669 (NT::Sup, PT::Display) => Some(&DISPLAY_INLINE),
670
671 (NT::Pre, PT::Display) => Some(&DISPLAY_BLOCK),
673 (NT::BlockQuote, PT::Display) => Some(&DISPLAY_BLOCK),
674 (NT::Hr, PT::Display) => Some(&DISPLAY_BLOCK),
675 (NT::Hr, PT::Width) => Some(&WIDTH_100_PERCENT),
676 (NT::Hr, PT::Height) => Some(&HEIGHT_ZERO),
677 (NT::Hr, PT::MarginTop) => Some(&MARGIN_TOP_0_5EM),
678 (NT::Hr, PT::MarginBottom) => Some(&MARGIN_BOTTOM_0_5EM),
679 (NT::Hr, PT::BorderTopStyle) => Some(&BORDER_TOP_STYLE_INSET),
680 (NT::Hr, PT::BorderTopWidth) => Some(&BORDER_TOP_WIDTH_1PX),
681 (NT::Hr, PT::BorderTopColor) => Some(&BORDER_TOP_COLOR_GRAY),
682
683 (NT::Table, PT::Display) => Some(&DISPLAY_TABLE),
687 (NT::THead, PT::Display) => Some(&DISPLAY_TABLE_HEADER_GROUP),
689 (NT::THead, PT::VerticalAlign) => Some(&VERTICAL_ALIGN_MIDDLE),
690 (NT::THead, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
691 (NT::TBody, PT::Display) => Some(&DISPLAY_TABLE_ROW_GROUP),
692 (NT::TBody, PT::VerticalAlign) => Some(&VERTICAL_ALIGN_MIDDLE),
693 (NT::TFoot, PT::Display) => Some(&DISPLAY_TABLE_FOOTER_GROUP),
695 (NT::TFoot, PT::VerticalAlign) => Some(&VERTICAL_ALIGN_MIDDLE),
696 (NT::TFoot, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
697 (NT::Tr, PT::Display) => Some(&DISPLAY_TABLE_ROW),
698 (NT::Tr, PT::VerticalAlign) => Some(&VERTICAL_ALIGN_MIDDLE),
699 (NT::Tr, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
700 (NT::Th, PT::Display) => Some(&DISPLAY_TABLE_CELL),
701 (NT::Th, PT::TextAlign) => Some(&TEXT_ALIGN_CENTER),
702 (NT::Th, PT::FontWeight) => Some(&FONT_WEIGHT_BOLD),
703 (NT::Th, PT::VerticalAlign) => Some(&VERTICAL_ALIGN_MIDDLE),
704 (NT::Th, PT::PaddingTop) => Some(&PADDING_TOP_1PX),
705 (NT::Th, PT::PaddingBottom) => Some(&PADDING_BOTTOM_1PX),
706 (NT::Th, PT::PaddingLeft) => Some(&PADDING_LEFT_1PX),
707 (NT::Th, PT::PaddingRight) => Some(&PADDING_RIGHT_1PX),
708 (NT::Td, PT::Display) => Some(&DISPLAY_TABLE_CELL),
709 (NT::Td, PT::VerticalAlign) => Some(&VERTICAL_ALIGN_MIDDLE),
710 (NT::Td, PT::PaddingTop) => Some(&PADDING_TOP_1PX),
711 (NT::Td, PT::PaddingBottom) => Some(&PADDING_BOTTOM_1PX),
712 (NT::Td, PT::PaddingLeft) => Some(&PADDING_LEFT_1PX),
713 (NT::Td, PT::PaddingRight) => Some(&PADDING_RIGHT_1PX),
714
715 (NT::Form, PT::Display) => Some(&DISPLAY_BLOCK),
717 (NT::Input, PT::Display) => Some(&DISPLAY_INLINE_BLOCK),
718 (NT::Button, PT::Display) => Some(&DISPLAY_INLINE_BLOCK),
719 (NT::Button, PT::Cursor) => Some(&CURSOR_POINTER),
720 (NT::Button, PT::FontSize) => Some(&FONT_SIZE_13PX),
721 (NT::Button, PT::PaddingTop) => Some(&PADDING_TOP_5PX),
722 (NT::Button, PT::PaddingBottom) => Some(&PADDING_BOTTOM_5PX),
723 (NT::Button, PT::PaddingLeft) => Some(&PADDING_LEFT_10PX),
724 (NT::Button, PT::PaddingRight) => Some(&PADDING_RIGHT_10PX),
725 (NT::Button, PT::BorderTopWidth) => Some(&BUTTON_BORDER_TOP_WIDTH),
726 (NT::Button, PT::BorderBottomWidth) => Some(&BUTTON_BORDER_BOTTOM_WIDTH),
727 (NT::Button, PT::BorderLeftWidth) => Some(&BUTTON_BORDER_LEFT_WIDTH),
728 (NT::Button, PT::BorderRightWidth) => Some(&BUTTON_BORDER_RIGHT_WIDTH),
729 (NT::Button, PT::BorderTopStyle) => Some(&BUTTON_BORDER_TOP_STYLE),
730 (NT::Button, PT::BorderBottomStyle) => Some(&BUTTON_BORDER_BOTTOM_STYLE),
731 (NT::Button, PT::BorderLeftStyle) => Some(&BUTTON_BORDER_LEFT_STYLE),
732 (NT::Button, PT::BorderRightStyle) => Some(&BUTTON_BORDER_RIGHT_STYLE),
733 (NT::Button, PT::BorderTopColor) => Some(&BUTTON_BORDER_TOP_COLOR),
734 (NT::Button, PT::BorderBottomColor) => Some(&BUTTON_BORDER_BOTTOM_COLOR),
735 (NT::Button, PT::BorderLeftColor) => Some(&BUTTON_BORDER_LEFT_COLOR),
736 (NT::Button, PT::BorderRightColor) => Some(&BUTTON_BORDER_RIGHT_COLOR),
737 (NT::Text(_), PT::Cursor) => Some(&CURSOR_TEXT),
741 (NT::Select, PT::Display) => Some(&DISPLAY_INLINE_BLOCK),
742 (NT::TextArea, PT::Display) => Some(&DISPLAY_INLINE_BLOCK),
743 (NT::TextArea, PT::Cursor) => Some(&CURSOR_TEXT),
745 (NT::Label, PT::Display) => Some(&DISPLAY_INLINE),
746 (NT::Head, PT::Display) => Some(&DISPLAY_NONE),
748 (NT::Title, PT::Display) => Some(&DISPLAY_NONE),
749 (NT::Script, PT::Display) => Some(&DISPLAY_NONE),
750 (NT::Style, PT::Display) => Some(&DISPLAY_NONE),
751 (NT::Link, PT::Display) => Some(&DISPLAY_NONE),
752
753 (NT::Br, PT::Display) => Some(&DISPLAY_INLINE),
761 (NT::Image(_), PT::Display) => Some(&DISPLAY_INLINE_BLOCK),
763
764 (NT::Video, PT::Display) => Some(&DISPLAY_INLINE),
766 (NT::Audio, PT::Display) => Some(&DISPLAY_INLINE),
767 (NT::Canvas, PT::Display) => Some(&DISPLAY_INLINE),
768 (NT::Svg, PT::Display) => Some(&DISPLAY_INLINE),
769 (NT::VirtualView, PT::Display) => Some(&DISPLAY_BLOCK),
772
773 (NT::Icon(_), PT::Display) => Some(&DISPLAY_INLINE_BLOCK),
775
776 (NT::SelectOption, PT::Display) => Some(&DISPLAY_NONE),
777 (NT::OptGroup, PT::Display) => Some(&DISPLAY_NONE),
778
779 (NT::Abbr, PT::Display) => Some(&DISPLAY_INLINE),
781 (NT::Cite, PT::Display) => Some(&DISPLAY_INLINE),
782 (NT::Del, PT::Display) => Some(&DISPLAY_INLINE),
783 (NT::Ins, PT::Display) => Some(&DISPLAY_INLINE),
784 (NT::Mark, PT::Display) => Some(&DISPLAY_INLINE),
785 (NT::Q, PT::Display) => Some(&DISPLAY_INLINE),
786 (NT::Dfn, PT::Display) => Some(&DISPLAY_INLINE),
787 (NT::Var, PT::Display) => Some(&DISPLAY_INLINE),
788 (NT::Time, PT::Display) => Some(&DISPLAY_INLINE),
789 (NT::Data, PT::Display) => Some(&DISPLAY_INLINE),
790 (NT::Wbr, PT::Display) => Some(&DISPLAY_INLINE),
791 (NT::Bdi, PT::Display) => Some(&DISPLAY_INLINE),
792 (NT::Bdo, PT::Display) => Some(&DISPLAY_INLINE),
793 (NT::Rp, PT::Display) => Some(&DISPLAY_INLINE),
794 (NT::Rt, PT::Display) => Some(&DISPLAY_INLINE),
795 (NT::Rtc, PT::Display) => Some(&DISPLAY_INLINE),
796 (NT::Ruby, PT::Display) => Some(&DISPLAY_INLINE),
797
798 (NT::FieldSet, PT::Display) => Some(&DISPLAY_BLOCK),
801 (NT::Figure, PT::Display) => Some(&DISPLAY_BLOCK),
802 (NT::Figure, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
803 (NT::FigCaption, PT::Display) => Some(&DISPLAY_BLOCK),
804 (NT::FigCaption, PT::BreakInside) => Some(&BREAK_INSIDE_AVOID),
805 (NT::Details, PT::Display) => Some(&DISPLAY_BLOCK),
806 (NT::Summary, PT::Display) => Some(&DISPLAY_BLOCK),
807 (NT::Dialog, PT::Display) => Some(&DISPLAY_BLOCK),
808
809 (NT::Caption, PT::Display) => Some(&DISPLAY_TABLE_CAPTION),
811 (NT::ColGroup, PT::Display) => Some(&DISPLAY_TABLE_COLUMN_GROUP),
812 (NT::Col, PT::Display) => Some(&DISPLAY_TABLE_COLUMN),
813
814 (NT::Menu, PT::Display) => Some(&DISPLAY_BLOCK),
816 (NT::Dir, PT::Display) => Some(&DISPLAY_BLOCK),
817
818 (NT::Html, PT::Display) => Some(&DISPLAY_BLOCK),
829 (_, PT::Display) => Some(&DISPLAY_INLINE),
840
841 _ => None,
843 }
844}
845
846const fn scrollbar_color(thumb: ColorU, track: ColorU) -> CssProperty {
890 CssProperty::ScrollbarColor(CssPropertyValue::Exact(
891 StyleScrollbarColor::Custom(ScrollbarColorCustom { thumb, track }),
892 ))
893}
894
895const fn scrollbar_width(w: LayoutScrollbarWidth) -> CssProperty {
897 CssProperty::ScrollbarWidth(CssPropertyValue::Exact(w))
898}
899
900const fn scrollbar_visibility(v: ScrollbarVisibilityMode) -> CssProperty {
902 CssProperty::ScrollbarVisibility(CssPropertyValue::Exact(v))
903}
904
905const fn scrollbar_fade_delay(ms: u32) -> CssProperty {
907 CssProperty::ScrollbarFadeDelay(CssPropertyValue::Exact(ScrollbarFadeDelay::new(ms)))
908}
909
910const fn scrollbar_fade_duration(ms: u32) -> CssProperty {
912 CssProperty::ScrollbarFadeDuration(CssPropertyValue::Exact(ScrollbarFadeDuration::new(ms)))
913}
914
915pub(crate) static UA_SCROLLBAR_CSS: &[CssPropertyWithConditions] = &[
922 CssPropertyWithConditions::with_single_condition(
925 scrollbar_width(LayoutScrollbarWidth::Thin),
926 &[DynamicSelector::Os(OsCondition::MacOS)],
927 ),
928 CssPropertyWithConditions::with_single_condition(
930 scrollbar_width(LayoutScrollbarWidth::Thin),
931 &[DynamicSelector::Os(OsCondition::IOS)],
932 ),
933 CssPropertyWithConditions::with_single_condition(
935 scrollbar_width(LayoutScrollbarWidth::Thin),
936 &[DynamicSelector::Os(OsCondition::Android)],
937 ),
938 CssPropertyWithConditions::simple(
940 scrollbar_width(LayoutScrollbarWidth::Auto),
941 ),
942
943 CssPropertyWithConditions::with_single_condition(
946 scrollbar_visibility(ScrollbarVisibilityMode::WhenScrolling),
947 &[DynamicSelector::Os(OsCondition::MacOS)],
948 ),
949 CssPropertyWithConditions::with_single_condition(
951 scrollbar_visibility(ScrollbarVisibilityMode::WhenScrolling),
952 &[DynamicSelector::Os(OsCondition::IOS)],
953 ),
954 CssPropertyWithConditions::with_single_condition(
956 scrollbar_visibility(ScrollbarVisibilityMode::WhenScrolling),
957 &[DynamicSelector::Os(OsCondition::Android)],
958 ),
959 CssPropertyWithConditions::simple(
961 scrollbar_visibility(ScrollbarVisibilityMode::Always),
962 ),
963
964 CssPropertyWithConditions::with_single_condition(
966 scrollbar_fade_delay(500),
967 &[DynamicSelector::Os(OsCondition::MacOS)],
968 ),
969 CssPropertyWithConditions::with_single_condition(
970 scrollbar_fade_delay(500),
971 &[DynamicSelector::Os(OsCondition::IOS)],
972 ),
973 CssPropertyWithConditions::with_single_condition(
974 scrollbar_fade_delay(300),
975 &[DynamicSelector::Os(OsCondition::Android)],
976 ),
977 CssPropertyWithConditions::simple(
979 scrollbar_fade_delay(0),
980 ),
981
982 CssPropertyWithConditions::with_single_condition(
984 scrollbar_fade_duration(200),
985 &[DynamicSelector::Os(OsCondition::MacOS)],
986 ),
987 CssPropertyWithConditions::with_single_condition(
988 scrollbar_fade_duration(200),
989 &[DynamicSelector::Os(OsCondition::IOS)],
990 ),
991 CssPropertyWithConditions::with_single_condition(
992 scrollbar_fade_duration(150),
993 &[DynamicSelector::Os(OsCondition::Android)],
994 ),
995 CssPropertyWithConditions::simple(
997 scrollbar_fade_duration(0),
998 ),
999
1000 CssPropertyWithConditions::with_single_condition(
1003 scrollbar_color(
1004 ColorU { r: 180, g: 180, b: 180, a: 200 },
1005 ColorU { r: 40, g: 40, b: 40, a: 80 },
1006 ),
1007 &[DynamicSelector::Os(OsCondition::MacOS), DynamicSelector::Theme(ThemeCondition::Dark)],
1008 ),
1009 CssPropertyWithConditions::with_single_condition(
1011 scrollbar_color(
1012 ColorU { r: 80, g: 80, b: 80, a: 200 },
1013 ColorU { r: 200, g: 200, b: 200, a: 80 },
1014 ),
1015 &[DynamicSelector::Os(OsCondition::MacOS), DynamicSelector::Theme(ThemeCondition::Light)],
1016 ),
1017 CssPropertyWithConditions::with_single_condition(
1019 scrollbar_color(
1020 ColorU { r: 110, g: 110, b: 110, a: 255 },
1021 ColorU { r: 32, g: 32, b: 32, a: 255 },
1022 ),
1023 &[DynamicSelector::Os(OsCondition::Windows), DynamicSelector::Theme(ThemeCondition::Dark)],
1024 ),
1025 CssPropertyWithConditions::with_single_condition(
1027 scrollbar_color(
1028 ColorU { r: 130, g: 130, b: 130, a: 255 },
1029 ColorU { r: 241, g: 241, b: 241, a: 255 },
1030 ),
1031 &[DynamicSelector::Os(OsCondition::Windows), DynamicSelector::Theme(ThemeCondition::Light)],
1032 ),
1033 CssPropertyWithConditions::with_single_condition(
1035 scrollbar_color(
1036 ColorU { r: 255, g: 255, b: 255, a: 100 },
1037 ColorU::TRANSPARENT,
1038 ),
1039 &[DynamicSelector::Os(OsCondition::IOS), DynamicSelector::Theme(ThemeCondition::Dark)],
1040 ),
1041 CssPropertyWithConditions::with_single_condition(
1043 scrollbar_color(
1044 ColorU { r: 0, g: 0, b: 0, a: 100 },
1045 ColorU::TRANSPARENT,
1046 ),
1047 &[DynamicSelector::Os(OsCondition::IOS), DynamicSelector::Theme(ThemeCondition::Light)],
1048 ),
1049 CssPropertyWithConditions::with_single_condition(
1051 scrollbar_color(
1052 ColorU { r: 255, g: 255, b: 255, a: 77 },
1053 ColorU::TRANSPARENT,
1054 ),
1055 &[DynamicSelector::Os(OsCondition::Android), DynamicSelector::Theme(ThemeCondition::Dark)],
1056 ),
1057 CssPropertyWithConditions::with_single_condition(
1059 scrollbar_color(
1060 ColorU { r: 0, g: 0, b: 0, a: 77 },
1061 ColorU::TRANSPARENT,
1062 ),
1063 &[DynamicSelector::Os(OsCondition::Android), DynamicSelector::Theme(ThemeCondition::Light)],
1064 ),
1065 CssPropertyWithConditions::with_single_condition(
1067 scrollbar_color(
1068 ColorU { r: 100, g: 100, b: 100, a: 255 },
1069 ColorU { r: 45, g: 45, b: 45, a: 255 },
1070 ),
1071 &[DynamicSelector::Theme(ThemeCondition::Dark)],
1072 ),
1073 CssPropertyWithConditions::simple(
1075 scrollbar_color(
1076 ColorU { r: 193, g: 193, b: 193, a: 255 },
1077 ColorU { r: 241, g: 241, b: 241, a: 255 },
1078 ),
1079 ),
1080];
1081
1082#[derive(Debug, Copy, Clone)]
1087pub struct ResolvedUaScrollbar {
1088 pub color: StyleScrollbarColor,
1089 pub width: LayoutScrollbarWidth,
1090 pub visibility: ScrollbarVisibilityMode,
1091 pub fade_delay: ScrollbarFadeDelay,
1092 pub fade_duration: ScrollbarFadeDuration,
1093}
1094
1095#[must_use] pub fn evaluate_ua_scrollbar_css(ctx: &DynamicSelectorContext) -> ResolvedUaScrollbar {
1101 let mut color: Option<StyleScrollbarColor> = None;
1102 let mut width: Option<LayoutScrollbarWidth> = None;
1103 let mut visibility: Option<ScrollbarVisibilityMode> = None;
1104 let mut fade_delay: Option<ScrollbarFadeDelay> = None;
1105 let mut fade_duration: Option<ScrollbarFadeDuration> = None;
1106
1107 for prop in UA_SCROLLBAR_CSS {
1108 if !prop.matches(ctx) {
1109 continue;
1110 }
1111 match &prop.property {
1112 CssProperty::ScrollbarColor(CssPropertyValue::Exact(c)) => {
1113 if color.is_none() {
1114 color = Some(*c);
1115 }
1116 }
1117 CssProperty::ScrollbarWidth(CssPropertyValue::Exact(w)) => {
1118 if width.is_none() {
1119 width = Some(*w);
1120 }
1121 }
1122 CssProperty::ScrollbarVisibility(CssPropertyValue::Exact(v)) => {
1123 if visibility.is_none() {
1124 visibility = Some(*v);
1125 }
1126 }
1127 CssProperty::ScrollbarFadeDelay(CssPropertyValue::Exact(d)) => {
1128 if fade_delay.is_none() {
1129 fade_delay = Some(*d);
1130 }
1131 }
1132 CssProperty::ScrollbarFadeDuration(CssPropertyValue::Exact(d)) => {
1133 if fade_duration.is_none() {
1134 fade_duration = Some(*d);
1135 }
1136 }
1137 _ => {}
1138 }
1139 if color.is_some() && width.is_some() && visibility.is_some()
1140 && fade_delay.is_some() && fade_duration.is_some()
1141 {
1142 break;
1143 }
1144 }
1145
1146 ResolvedUaScrollbar {
1149 color: color.unwrap_or(StyleScrollbarColor::Custom(ScrollbarColorCustom {
1150 thumb: ColorU { r: 193, g: 193, b: 193, a: 255 },
1151 track: ColorU { r: 241, g: 241, b: 241, a: 255 },
1152 })),
1153 width: width.unwrap_or(LayoutScrollbarWidth::Auto),
1154 visibility: visibility.unwrap_or(ScrollbarVisibilityMode::Always),
1155 fade_delay: fade_delay.unwrap_or(ScrollbarFadeDelay { ms: 0 }),
1156 fade_duration: fade_duration.unwrap_or(ScrollbarFadeDuration { ms: 0 }),
1157 }
1158}
1159
1160#[cfg(test)]
1161mod autotest_generated {
1162 use alloc::{string::String, vec, vec::Vec};
1163
1164 use azul_css::{corety::AzString, css::BoxOrStatic, props::basic::length::SizeMetric};
1165
1166 use super::*;
1167 use crate::resources::{ImageRef, RawImageFormat};
1168
1169 fn text_node(s: &str) -> NodeType {
1174 NodeType::Text(BoxOrStatic::heap(AzString::from(s)))
1175 }
1176
1177 fn icon_node(s: &str) -> NodeType {
1178 NodeType::Icon(BoxOrStatic::heap(AzString::from(s)))
1179 }
1180
1181 fn image_node() -> NodeType {
1182 NodeType::Image(BoxOrStatic::heap(ImageRef::null_image(
1183 1,
1184 1,
1185 RawImageFormat::RGBA8,
1186 Vec::new(),
1187 )))
1188 }
1189
1190 fn sample_node_types() -> Vec<NodeType> {
1194 use crate::dom::NodeType as NT;
1195 vec![
1196 NT::Html, NT::Head, NT::Body, NT::Div, NT::P, NT::Main, NT::Header,
1198 NT::Footer, NT::Section, NT::Article, NT::Aside, NT::Nav,
1199 NT::H1, NT::H2, NT::H3, NT::H4, NT::H5, NT::H6,
1200 NT::Ul, NT::Ol, NT::Li, NT::Dl, NT::Dt, NT::Dd,
1201 NT::Span, NT::A, NT::Strong, NT::Em, NT::B, NT::I, NT::U, NT::Small,
1202 NT::Code, NT::Kbd, NT::Samp, NT::Sub, NT::Sup,
1203 NT::Pre, NT::BlockQuote, NT::Hr,
1204 NT::Table, NT::THead, NT::TBody, NT::TFoot, NT::Tr, NT::Th, NT::Td,
1205 NT::Caption, NT::ColGroup, NT::Col,
1206 NT::Form, NT::Input, NT::Button, NT::Select, NT::TextArea, NT::Label,
1207 NT::Title, NT::Script, NT::Style, NT::Link,
1208 NT::Br, NT::Video, NT::Audio, NT::Canvas, NT::Svg, NT::VirtualView,
1209 NT::SelectOption, NT::OptGroup,
1210 NT::Abbr, NT::Cite, NT::Del, NT::Ins, NT::Mark, NT::Q, NT::Dfn,
1211 NT::Var, NT::Time, NT::Data, NT::Wbr, NT::Bdi, NT::Bdo,
1212 NT::Rp, NT::Rt, NT::Rtc, NT::Ruby,
1213 NT::FieldSet, NT::Figure, NT::FigCaption, NT::Details, NT::Summary,
1214 NT::Dialog, NT::Menu, NT::Dir,
1215 NT::Address, NT::Legend, NT::Output, NT::Progress, NT::Meter,
1217 NT::DataList, NT::MenuItem, NT::S, NT::Big, NT::Acronym,
1218 NT::Object, NT::Param, NT::Embed, NT::Source, NT::Track, NT::Map,
1219 NT::Area, NT::Meta, NT::Base, NT::Before, NT::After, NT::Marker,
1220 NT::Placeholder, NT::SvgG, NT::SvgPath, NT::SvgRect,
1221 NT::SvgText(AzString::from("svg-text")),
1222 text_node(""),
1224 text_node("hello"),
1225 icon_node("home"),
1226 image_node(),
1227 ]
1228 }
1229
1230 fn all_os() -> Vec<OsCondition> {
1231 vec![
1232 OsCondition::Any,
1233 OsCondition::Apple,
1234 OsCondition::MacOS,
1235 OsCondition::IOS,
1236 OsCondition::Linux,
1237 OsCondition::Windows,
1238 OsCondition::Android,
1239 OsCondition::Web,
1240 ]
1241 }
1242
1243 fn all_themes() -> Vec<ThemeCondition> {
1244 vec![
1245 ThemeCondition::Light,
1246 ThemeCondition::Dark,
1247 ThemeCondition::Custom(AzString::from("neon")),
1248 ThemeCondition::SystemPreferred,
1249 ]
1250 }
1251
1252 fn ctx(os: OsCondition, theme: ThemeCondition) -> DynamicSelectorContext {
1253 DynamicSelectorContext {
1254 os,
1255 theme,
1256 ..DynamicSelectorContext::default()
1257 }
1258 }
1259
1260 const CLASSIC_LIGHT_THUMB: ColorU = ColorU { r: 193, g: 193, b: 193, a: 255 };
1261 const CLASSIC_LIGHT_TRACK: ColorU = ColorU { r: 241, g: 241, b: 241, a: 255 };
1262
1263 fn custom_color(thumb: ColorU, track: ColorU) -> StyleScrollbarColor {
1264 StyleScrollbarColor::Custom(ScrollbarColorCustom { thumb, track })
1265 }
1266
1267 fn unwrap_custom(c: StyleScrollbarColor) -> (ColorU, ColorU) {
1270 match c {
1271 StyleScrollbarColor::Custom(c) => (c.thumb, c.track),
1272 StyleScrollbarColor::Auto => panic!("expected a Custom scrollbar color, got Auto"),
1273 }
1274 }
1275
1276 fn display_of(nt: &NodeType) -> LayoutDisplay {
1277 match get_ua_property(nt, CssPropertyType::Display) {
1278 Some(CssProperty::Display(CssPropertyValue::Exact(d))) => *d,
1279 other => panic!("{nt:?}: expected an exact display value, got {other:?}"),
1280 }
1281 }
1282
1283 fn font_size_em(nt: &NodeType) -> f32 {
1284 match get_ua_property(nt, CssPropertyType::FontSize) {
1285 Some(CssProperty::FontSize(CssPropertyValue::Exact(fs))) => {
1286 assert_eq!(fs.inner.metric, SizeMetric::Em, "{nt:?}: font-size must be em-relative");
1287 fs.inner.number.get()
1288 }
1289 other => panic!("{nt:?}: expected an exact em font-size, got {other:?}"),
1290 }
1291 }
1292
1293 #[test]
1302 fn returned_property_always_has_the_requested_type() {
1303 for nt in sample_node_types() {
1304 for pt in CssPropertyType::ALL {
1305 if let Some(prop) = get_ua_property(&nt, *pt) {
1306 assert_eq!(
1307 prop.get_type(),
1308 *pt,
1309 "get_ua_property({nt:?}, {pt:?}) returned a {:?} property",
1310 prop.get_type()
1311 );
1312 }
1313 }
1314 }
1315 }
1316
1317 #[test]
1318 fn full_cross_product_never_panics_and_is_deterministic() {
1319 for nt in sample_node_types() {
1320 for pt in CssPropertyType::ALL {
1321 let a = get_ua_property(&nt, *pt);
1322 let b = get_ua_property(&nt, *pt);
1323 match (a, b) {
1324 (Some(a), Some(b)) => assert!(
1325 core::ptr::eq(a, b),
1326 "{nt:?}/{pt:?}: repeated lookups must hand back the same static"
1327 ),
1328 (None, None) => {}
1329 _ => panic!("{nt:?}/{pt:?}: lookup is not deterministic"),
1330 }
1331 }
1332 }
1333 }
1334
1335 #[test]
1338 fn display_resolves_for_every_node_type() {
1339 for nt in sample_node_types() {
1340 assert!(
1341 get_ua_property(&nt, CssPropertyType::Display).is_some(),
1342 "{nt:?} has no default display"
1343 );
1344 }
1345 }
1346
1347 #[test]
1348 fn unknown_elements_default_to_inline_display() {
1349 for nt in [NodeType::Address, NodeType::Legend, NodeType::Meter, NodeType::SvgPath] {
1351 assert_eq!(display_of(&nt), LayoutDisplay::Inline, "{nt:?}");
1352 }
1353 }
1354
1355 #[test]
1358 fn cursor_default_exists_only_for_button_textarea_and_text() {
1359 for nt in sample_node_types() {
1360 let has_cursor = get_ua_property(&nt, CssPropertyType::Cursor).is_some();
1361 let expected = matches!(nt, NodeType::Button | NodeType::TextArea | NodeType::Text(_));
1362 assert_eq!(has_cursor, expected, "{nt:?}: unexpected cursor default");
1363 }
1364 }
1365
1366 #[test]
1371 fn text_node_defaults_are_independent_of_the_payload() {
1372 let huge = "🦀".repeat(100_000);
1373 let payloads: Vec<String> = vec![
1374 String::new(),
1375 "\0".into(),
1376 "\u{202E}\u{200B}\u{FEFF}".into(), "مرحبا بالعالم".into(),
1378 "🇩🇪👨👩👧👦".into(),
1379 "\u{FFFD}".into(),
1380 huge,
1381 ];
1382
1383 for p in payloads {
1384 let nt = text_node(&p);
1385 assert_eq!(
1386 display_of(&nt),
1387 LayoutDisplay::Inline,
1388 "text node display must not depend on its content"
1389 );
1390 assert_eq!(
1391 get_ua_property(&nt, CssPropertyType::Cursor),
1392 Some(&CURSOR_TEXT),
1393 "text node cursor must not depend on its content"
1394 );
1395 assert!(get_ua_property(&nt, CssPropertyType::Width).is_none());
1397 assert!(get_ua_property(&nt, CssPropertyType::Height).is_none());
1398 assert!(get_ua_property(&nt, CssPropertyType::MarginTop).is_none());
1399 }
1400 }
1401
1402 #[test]
1403 fn icon_and_image_nodes_are_inline_block_regardless_of_payload() {
1404 let huge_name = "x".repeat(50_000);
1405 let names: [&str; 4] = ["", "home", "🏠", huge_name.as_str()];
1406 for name in names {
1407 assert_eq!(display_of(&icon_node(name)), LayoutDisplay::InlineBlock, "icon {name:?}");
1408 }
1409 assert_eq!(display_of(&image_node()), LayoutDisplay::InlineBlock);
1410 }
1411
1412 #[test]
1421 fn html_has_no_default_height() {
1422 assert_eq!(get_ua_property(&NodeType::Html, CssPropertyType::Display), Some(&DISPLAY_BLOCK));
1423 assert!(
1424 get_ua_property(&NodeType::Html, CssPropertyType::Height).is_none(),
1425 "the (Html, Height) arm is intentionally disabled — see the DIAG note"
1426 );
1427 }
1428
1429 #[test]
1432 fn body_has_8px_margins_and_no_intrinsic_size() {
1433 assert_eq!(display_of(&NodeType::Body), LayoutDisplay::Block);
1434 assert_eq!(get_ua_property(&NodeType::Body, CssPropertyType::MarginTop), Some(&MARGIN_TOP_8PX));
1435 assert_eq!(get_ua_property(&NodeType::Body, CssPropertyType::MarginBottom), Some(&MARGIN_BOTTOM_8PX));
1436 assert_eq!(get_ua_property(&NodeType::Body, CssPropertyType::MarginLeft), Some(&MARGIN_LEFT_8PX));
1437 assert_eq!(get_ua_property(&NodeType::Body, CssPropertyType::MarginRight), Some(&MARGIN_RIGHT_8PX));
1438 assert!(get_ua_property(&NodeType::Body, CssPropertyType::Width).is_none());
1439 assert!(get_ua_property(&NodeType::Body, CssPropertyType::Height).is_none());
1440 }
1441
1442 #[test]
1445 fn block_elements_have_no_default_width() {
1446 for nt in [NodeType::Div, NodeType::P, NodeType::Section, NodeType::Main, NodeType::VirtualView] {
1447 assert_eq!(display_of(&nt), LayoutDisplay::Block, "{nt:?}");
1448 assert!(
1449 get_ua_property(&nt, CssPropertyType::Width).is_none(),
1450 "{nt:?} must be width:auto so it can flex-grow"
1451 );
1452 }
1453 }
1454
1455 #[test]
1456 fn div_defines_only_a_display_default() {
1457 for pt in CssPropertyType::ALL {
1458 let got = get_ua_property(&NodeType::Div, *pt);
1459 if *pt == CssPropertyType::Display {
1460 assert!(got.is_some());
1461 } else {
1462 assert!(got.is_none(), "Div should not define a UA default for {pt:?}");
1463 }
1464 }
1465 }
1466
1467 #[test]
1468 fn metadata_elements_are_display_none() {
1469 for nt in [NodeType::Head, NodeType::Title, NodeType::Script, NodeType::Style, NodeType::Link] {
1470 assert_eq!(display_of(&nt), LayoutDisplay::None, "{nt:?} must not render");
1471 }
1472 }
1473
1474 #[test]
1475 fn heading_font_sizes_are_strictly_decreasing() {
1476 let sizes: Vec<f32> = [NodeType::H1, NodeType::H2, NodeType::H3, NodeType::H4, NodeType::H5, NodeType::H6]
1477 .iter()
1478 .map(font_size_em)
1479 .collect();
1480
1481 let expected = [2.0_f32, 1.5, 1.17, 1.0, 0.83, 0.67];
1484 for (i, (got, want)) in sizes.iter().zip(expected.iter()).enumerate() {
1485 assert!(
1486 (got - want).abs() < 1e-4,
1487 "H{} font-size: got {got}em, want {want}em",
1488 i + 1
1489 );
1490 }
1491 for w in sizes.windows(2) {
1492 assert!(w[0] > w[1], "heading font sizes must strictly decrease, got {sizes:?}");
1493 }
1494 }
1495
1496 #[test]
1497 fn headings_are_bold_blocks_that_avoid_page_breaks() {
1498 for nt in [NodeType::H1, NodeType::H2, NodeType::H3, NodeType::H4, NodeType::H5, NodeType::H6] {
1499 assert_eq!(display_of(&nt), LayoutDisplay::Block, "{nt:?}");
1500 assert_eq!(
1501 get_ua_property(&nt, CssPropertyType::FontWeight),
1502 Some(&FONT_WEIGHT_BOLD),
1503 "{nt:?}"
1504 );
1505 assert_eq!(
1506 get_ua_property(&nt, CssPropertyType::BreakInside),
1507 Some(&BREAK_INSIDE_AVOID),
1508 "{nt:?}"
1509 );
1510 assert_eq!(
1511 get_ua_property(&nt, CssPropertyType::BreakAfter),
1512 Some(&BREAK_AFTER_AVOID),
1513 "{nt:?}"
1514 );
1515 for pt in [CssPropertyType::MarginTop, CssPropertyType::MarginBottom] {
1517 assert!(get_ua_property(&nt, pt).is_some(), "{nt:?} is missing {pt:?}");
1518 }
1519 }
1520 }
1521
1522 #[test]
1525 fn tables_may_break_across_pages_but_rows_may_not() {
1526 assert!(get_ua_property(&NodeType::Table, CssPropertyType::BreakInside).is_none());
1527 assert!(get_ua_property(&NodeType::TBody, CssPropertyType::BreakInside).is_none());
1528 for nt in [NodeType::THead, NodeType::TFoot, NodeType::Tr] {
1529 assert_eq!(
1530 get_ua_property(&nt, CssPropertyType::BreakInside),
1531 Some(&BREAK_INSIDE_AVOID),
1532 "{nt:?}"
1533 );
1534 }
1535 }
1536
1537 #[test]
1538 fn table_display_types_are_not_crossed() {
1539 assert_eq!(display_of(&NodeType::Table), LayoutDisplay::Table);
1540 assert_eq!(display_of(&NodeType::THead), LayoutDisplay::TableHeaderGroup);
1541 assert_eq!(display_of(&NodeType::TBody), LayoutDisplay::TableRowGroup);
1542 assert_eq!(display_of(&NodeType::TFoot), LayoutDisplay::TableFooterGroup);
1543 assert_eq!(display_of(&NodeType::Tr), LayoutDisplay::TableRow);
1544 assert_eq!(display_of(&NodeType::Th), LayoutDisplay::TableCell);
1545 assert_eq!(display_of(&NodeType::Td), LayoutDisplay::TableCell);
1546 assert_eq!(display_of(&NodeType::Caption), LayoutDisplay::TableCaption);
1547 assert_eq!(display_of(&NodeType::ColGroup), LayoutDisplay::TableColumnGroup);
1548 assert_eq!(display_of(&NodeType::Col), LayoutDisplay::TableColumn);
1549 }
1550
1551 #[test]
1552 fn table_cells_have_1px_padding_on_all_four_sides() {
1553 for nt in [NodeType::Th, NodeType::Td] {
1554 assert_eq!(get_ua_property(&nt, CssPropertyType::PaddingTop), Some(&PADDING_TOP_1PX), "{nt:?}");
1555 assert_eq!(get_ua_property(&nt, CssPropertyType::PaddingBottom), Some(&PADDING_BOTTOM_1PX), "{nt:?}");
1556 assert_eq!(get_ua_property(&nt, CssPropertyType::PaddingLeft), Some(&PADDING_LEFT_1PX), "{nt:?}");
1557 assert_eq!(get_ua_property(&nt, CssPropertyType::PaddingRight), Some(&PADDING_RIGHT_1PX), "{nt:?}");
1558 assert_eq!(get_ua_property(&nt, CssPropertyType::VerticalAlign), Some(&VERTICAL_ALIGN_MIDDLE), "{nt:?}");
1559 }
1560 assert_eq!(get_ua_property(&NodeType::Th, CssPropertyType::TextAlign), Some(&TEXT_ALIGN_CENTER));
1562 assert_eq!(get_ua_property(&NodeType::Th, CssPropertyType::FontWeight), Some(&FONT_WEIGHT_BOLD));
1563 assert!(get_ua_property(&NodeType::Td, CssPropertyType::TextAlign).is_none());
1564 assert!(get_ua_property(&NodeType::Td, CssPropertyType::FontWeight).is_none());
1565 }
1566
1567 #[test]
1571 fn button_border_is_symmetric_on_all_four_sides() {
1572 let widths = [
1573 (CssPropertyType::BorderTopWidth, &BUTTON_BORDER_TOP_WIDTH),
1574 (CssPropertyType::BorderBottomWidth, &BUTTON_BORDER_BOTTOM_WIDTH),
1575 (CssPropertyType::BorderLeftWidth, &BUTTON_BORDER_LEFT_WIDTH),
1576 (CssPropertyType::BorderRightWidth, &BUTTON_BORDER_RIGHT_WIDTH),
1577 ];
1578 for (pt, want) in widths {
1579 assert_eq!(get_ua_property(&NodeType::Button, pt), Some(want), "{pt:?}");
1580 }
1581
1582 let styles = [
1583 (CssPropertyType::BorderTopStyle, &BUTTON_BORDER_TOP_STYLE),
1584 (CssPropertyType::BorderBottomStyle, &BUTTON_BORDER_BOTTOM_STYLE),
1585 (CssPropertyType::BorderLeftStyle, &BUTTON_BORDER_LEFT_STYLE),
1586 (CssPropertyType::BorderRightStyle, &BUTTON_BORDER_RIGHT_STYLE),
1587 ];
1588 for (pt, want) in styles {
1589 assert_eq!(get_ua_property(&NodeType::Button, pt), Some(want), "{pt:?}");
1590 }
1591
1592 let colors = [
1593 (CssPropertyType::BorderTopColor, &BUTTON_BORDER_TOP_COLOR),
1594 (CssPropertyType::BorderBottomColor, &BUTTON_BORDER_BOTTOM_COLOR),
1595 (CssPropertyType::BorderLeftColor, &BUTTON_BORDER_LEFT_COLOR),
1596 (CssPropertyType::BorderRightColor, &BUTTON_BORDER_RIGHT_COLOR),
1597 ];
1598 for (pt, want) in colors {
1599 assert_eq!(get_ua_property(&NodeType::Button, pt), Some(want), "{pt:?}");
1600 }
1601
1602 assert_eq!(display_of(&NodeType::Button), LayoutDisplay::InlineBlock);
1603 assert_eq!(get_ua_property(&NodeType::Button, CssPropertyType::Cursor), Some(&CURSOR_POINTER));
1604 }
1605
1606 #[test]
1609 fn hr_line_comes_from_the_border_not_from_height() {
1610 match get_ua_property(&NodeType::Hr, CssPropertyType::Height) {
1611 Some(CssProperty::Height(CssPropertyValue::Exact(LayoutHeight::Px(pv)))) => {
1612 assert_eq!(pv.metric, SizeMetric::Px);
1613 assert!((pv.number.get() - 0.0).abs() < 1e-6, "hr height must be 0px");
1614 }
1615 other => panic!("hr height: {other:?}"),
1616 }
1617 match get_ua_property(&NodeType::Hr, CssPropertyType::Width) {
1618 Some(CssProperty::Width(CssPropertyValue::Exact(LayoutWidth::Px(pv)))) => {
1619 assert_eq!(pv.metric, SizeMetric::Percent);
1620 assert!((pv.number.get() - 100.0).abs() < 1e-4, "hr width must be 100%");
1621 }
1622 other => panic!("hr width: {other:?}"),
1623 }
1624 assert_eq!(get_ua_property(&NodeType::Hr, CssPropertyType::BorderTopStyle), Some(&BORDER_TOP_STYLE_INSET));
1625 assert_eq!(get_ua_property(&NodeType::Hr, CssPropertyType::BorderTopWidth), Some(&BORDER_TOP_WIDTH_1PX));
1626 assert_eq!(get_ua_property(&NodeType::Hr, CssPropertyType::BorderTopColor), Some(&BORDER_TOP_COLOR_GRAY));
1627 }
1628
1629 #[test]
1630 fn list_containers_reset_the_counter_and_reserve_marker_space() {
1631 for (nt, marker) in [
1632 (NodeType::Ul, &LIST_STYLE_TYPE_DISC),
1633 (NodeType::Ol, &LIST_STYLE_TYPE_DECIMAL),
1634 ] {
1635 assert_eq!(display_of(&nt), LayoutDisplay::Block, "{nt:?}");
1636 assert_eq!(get_ua_property(&nt, CssPropertyType::ListStyleType), Some(marker), "{nt:?}");
1637 assert_eq!(
1638 get_ua_property(&nt, CssPropertyType::CounterReset),
1639 Some(&COUNTER_RESET_LIST_ITEM),
1640 "{nt:?} must reset the list-item counter"
1641 );
1642 assert_eq!(
1643 get_ua_property(&nt, CssPropertyType::PaddingLeft),
1644 Some(&PADDING_INLINE_START_40PX),
1645 "{nt:?}"
1646 );
1647 }
1648 assert_eq!(display_of(&NodeType::Li), LayoutDisplay::ListItem);
1649 }
1650
1651 #[test]
1652 fn inline_emphasis_and_link_defaults() {
1653 assert_eq!(get_ua_property(&NodeType::A, CssPropertyType::TextDecoration), Some(&TEXT_DECORATION_UNDERLINE));
1654 assert_eq!(get_ua_property(&NodeType::U, CssPropertyType::TextDecoration), Some(&TEXT_DECORATION_UNDERLINE));
1655 assert_eq!(get_ua_property(&NodeType::Strong, CssPropertyType::FontWeight), Some(&FONT_WEIGHT_BOLDER));
1656 assert_eq!(get_ua_property(&NodeType::B, CssPropertyType::FontWeight), Some(&FONT_WEIGHT_BOLDER));
1657 assert!(get_ua_property(&NodeType::Em, CssPropertyType::FontWeight).is_none());
1659 assert!(get_ua_property(&NodeType::I, CssPropertyType::FontWeight).is_none());
1660 }
1661
1662 #[test]
1667 fn scrollbar_fade_delay_round_trips_every_boundary() {
1668 for ms in [0_u32, 1, 2, 299, 300, 500, u32::from(u16::MAX), i32::MAX as u32, u32::MAX - 1, u32::MAX] {
1669 match scrollbar_fade_delay(ms) {
1670 CssProperty::ScrollbarFadeDelay(CssPropertyValue::Exact(d)) => {
1671 assert_eq!(d.ms, ms, "fade-delay must round-trip losslessly");
1672 }
1673 other => panic!("scrollbar_fade_delay({ms}) built a {other:?}"),
1674 }
1675 }
1676 }
1677
1678 #[test]
1679 fn scrollbar_fade_duration_round_trips_every_boundary() {
1680 for ms in [0_u32, 1, 150, 200, u32::from(u16::MAX), i32::MAX as u32, u32::MAX - 1, u32::MAX] {
1681 match scrollbar_fade_duration(ms) {
1682 CssProperty::ScrollbarFadeDuration(CssPropertyValue::Exact(d)) => {
1683 assert_eq!(d.ms, ms, "fade-duration must round-trip losslessly");
1684 }
1685 other => panic!("scrollbar_fade_duration({ms}) built a {other:?}"),
1686 }
1687 }
1688 }
1689
1690 #[test]
1694 fn scrollbar_fade_helpers_are_const_evaluable_at_u32_max() {
1695 const MAX_DELAY: CssProperty = scrollbar_fade_delay(u32::MAX);
1696 const MAX_DURATION: CssProperty = scrollbar_fade_duration(u32::MAX);
1697 const ZERO_DELAY: CssProperty = scrollbar_fade_delay(0);
1698
1699 assert_eq!(MAX_DELAY, scrollbar_fade_delay(u32::MAX));
1700 assert_eq!(MAX_DURATION, scrollbar_fade_duration(u32::MAX));
1701 assert_eq!(ZERO_DELAY, scrollbar_fade_delay(0));
1702 }
1703
1704 #[test]
1707 fn fade_delay_and_fade_duration_produce_distinct_property_types() {
1708 assert_eq!(scrollbar_fade_delay(42).get_type(), CssPropertyType::ScrollbarFadeDelay);
1709 assert_eq!(scrollbar_fade_duration(42).get_type(), CssPropertyType::ScrollbarFadeDuration);
1710 assert_ne!(scrollbar_fade_delay(42), scrollbar_fade_duration(42));
1711 }
1712
1713 #[test]
1716 fn zero_fade_delay_and_duration_are_literal_zero() {
1717 assert_eq!(
1718 scrollbar_fade_delay(0),
1719 CssProperty::ScrollbarFadeDelay(CssPropertyValue::Exact(ScrollbarFadeDelay::ZERO))
1720 );
1721 assert_eq!(
1722 scrollbar_fade_duration(0),
1723 CssProperty::ScrollbarFadeDuration(CssPropertyValue::Exact(ScrollbarFadeDuration::ZERO))
1724 );
1725 }
1726
1727 #[test]
1728 fn scrollbar_color_never_swaps_thumb_and_track() {
1729 let cases = [
1730 (ColorU { r: 1, g: 2, b: 3, a: 4 }, ColorU { r: 5, g: 6, b: 7, a: 8 }),
1731 (ColorU { r: 0, g: 0, b: 0, a: 0 }, ColorU { r: 255, g: 255, b: 255, a: 255 }),
1732 (ColorU { r: 255, g: 255, b: 255, a: 255 }, ColorU::TRANSPARENT),
1733 (ColorU::TRANSPARENT, ColorU::TRANSPARENT),
1734 ];
1735 for (thumb, track) in cases {
1736 match scrollbar_color(thumb, track) {
1737 CssProperty::ScrollbarColor(CssPropertyValue::Exact(StyleScrollbarColor::Custom(c))) => {
1738 assert_eq!(c.thumb, thumb, "thumb was not preserved");
1739 assert_eq!(c.track, track, "track was not preserved (arguments swapped?)");
1740 }
1741 other => panic!("scrollbar_color built a {other:?}"),
1742 }
1743 }
1744 }
1745
1746 #[test]
1747 fn scrollbar_width_and_visibility_round_trip_every_variant() {
1748 for w in [LayoutScrollbarWidth::Auto, LayoutScrollbarWidth::Thin, LayoutScrollbarWidth::None] {
1749 match scrollbar_width(w) {
1750 CssProperty::ScrollbarWidth(CssPropertyValue::Exact(got)) => assert_eq!(got, w),
1751 other => panic!("scrollbar_width({w:?}) built a {other:?}"),
1752 }
1753 }
1754 for v in [
1755 ScrollbarVisibilityMode::Always,
1756 ScrollbarVisibilityMode::WhenScrolling,
1757 ScrollbarVisibilityMode::Auto,
1758 ] {
1759 match scrollbar_visibility(v) {
1760 CssProperty::ScrollbarVisibility(CssPropertyValue::Exact(got)) => assert_eq!(got, v),
1761 other => panic!("scrollbar_visibility({v:?}) built a {other:?}"),
1762 }
1763 }
1764 }
1765
1766 #[test]
1774 fn table_contains_only_property_kinds_the_evaluator_understands() {
1775 let understood = [
1776 CssPropertyType::ScrollbarColor,
1777 CssPropertyType::ScrollbarWidth,
1778 CssPropertyType::ScrollbarVisibility,
1779 CssPropertyType::ScrollbarFadeDelay,
1780 CssPropertyType::ScrollbarFadeDuration,
1781 ];
1782 for (i, entry) in UA_SCROLLBAR_CSS.iter().enumerate() {
1783 let ty = entry.property.get_type();
1784 assert!(
1785 understood.contains(&ty),
1786 "UA_SCROLLBAR_CSS[{i}] is a {ty:?}, which evaluate_ua_scrollbar_css ignores"
1787 );
1788 }
1789 }
1790
1791 #[test]
1794 fn every_table_entry_carries_an_exact_value() {
1795 for (i, entry) in UA_SCROLLBAR_CSS.iter().enumerate() {
1796 let is_exact = matches!(
1797 &entry.property,
1798 CssProperty::ScrollbarColor(CssPropertyValue::Exact(_))
1799 | CssProperty::ScrollbarWidth(CssPropertyValue::Exact(_))
1800 | CssProperty::ScrollbarVisibility(CssPropertyValue::Exact(_))
1801 | CssProperty::ScrollbarFadeDelay(CssPropertyValue::Exact(_))
1802 | CssProperty::ScrollbarFadeDuration(CssPropertyValue::Exact(_))
1803 );
1804 assert!(is_exact, "UA_SCROLLBAR_CSS[{i}] is not an Exact value: {:?}", entry.property);
1805 }
1806 }
1807
1808 #[test]
1813 fn each_property_type_has_exactly_one_unconditional_entry_and_it_is_last() {
1814 for ty in [
1815 CssPropertyType::ScrollbarColor,
1816 CssPropertyType::ScrollbarWidth,
1817 CssPropertyType::ScrollbarVisibility,
1818 CssPropertyType::ScrollbarFadeDelay,
1819 CssPropertyType::ScrollbarFadeDuration,
1820 ] {
1821 let of_type: Vec<&CssPropertyWithConditions> = UA_SCROLLBAR_CSS
1822 .iter()
1823 .filter(|e| e.property.get_type() == ty)
1824 .collect();
1825 assert!(!of_type.is_empty(), "{ty:?} has no entry at all");
1826
1827 let unconditional: Vec<usize> = of_type
1828 .iter()
1829 .enumerate()
1830 .filter(|(_, e)| e.apply_if.as_slice().is_empty())
1831 .map(|(i, _)| i)
1832 .collect();
1833
1834 assert_eq!(
1835 unconditional.len(),
1836 1,
1837 "{ty:?} must have exactly one unconditional fallback, found {}",
1838 unconditional.len()
1839 );
1840 assert_eq!(
1841 unconditional[0],
1842 of_type.len() - 1,
1843 "{ty:?}: the unconditional fallback must come last, otherwise the \
1844 {} rule(s) after it are dead under first-match-wins",
1845 of_type.len() - 1 - unconditional[0]
1846 );
1847 }
1848 }
1849
1850 #[test]
1855 fn default_context_resolves_to_the_classic_light_scrollbar() {
1856 let r = evaluate_ua_scrollbar_css(&DynamicSelectorContext::default());
1857 assert_eq!(r.width, LayoutScrollbarWidth::Auto);
1858 assert_eq!(r.visibility, ScrollbarVisibilityMode::Always);
1859 assert_eq!(r.fade_delay.ms, 0);
1860 assert_eq!(r.fade_duration.ms, 0);
1861 assert_eq!(unwrap_custom(r.color), (CLASSIC_LIGHT_THUMB, CLASSIC_LIGHT_TRACK));
1862 }
1863
1864 #[test]
1865 fn per_os_and_theme_defaults_are_what_the_table_promises() {
1866 let cases: Vec<(OsCondition, ThemeCondition, LayoutScrollbarWidth, ScrollbarVisibilityMode, u32, u32, StyleScrollbarColor)> = vec![
1867 (
1868 OsCondition::MacOS, ThemeCondition::Dark,
1869 LayoutScrollbarWidth::Thin, ScrollbarVisibilityMode::WhenScrolling, 500, 200,
1870 custom_color(ColorU { r: 180, g: 180, b: 180, a: 200 }, ColorU { r: 40, g: 40, b: 40, a: 80 }),
1871 ),
1872 (
1873 OsCondition::MacOS, ThemeCondition::Light,
1874 LayoutScrollbarWidth::Thin, ScrollbarVisibilityMode::WhenScrolling, 500, 200,
1875 custom_color(ColorU { r: 80, g: 80, b: 80, a: 200 }, ColorU { r: 200, g: 200, b: 200, a: 80 }),
1876 ),
1877 (
1878 OsCondition::Windows, ThemeCondition::Dark,
1879 LayoutScrollbarWidth::Auto, ScrollbarVisibilityMode::Always, 0, 0,
1880 custom_color(ColorU { r: 110, g: 110, b: 110, a: 255 }, ColorU { r: 32, g: 32, b: 32, a: 255 }),
1881 ),
1882 (
1883 OsCondition::Windows, ThemeCondition::Light,
1884 LayoutScrollbarWidth::Auto, ScrollbarVisibilityMode::Always, 0, 0,
1885 custom_color(ColorU { r: 130, g: 130, b: 130, a: 255 }, ColorU { r: 241, g: 241, b: 241, a: 255 }),
1886 ),
1887 (
1888 OsCondition::IOS, ThemeCondition::Dark,
1889 LayoutScrollbarWidth::Thin, ScrollbarVisibilityMode::WhenScrolling, 500, 200,
1890 custom_color(ColorU { r: 255, g: 255, b: 255, a: 100 }, ColorU::TRANSPARENT),
1891 ),
1892 (
1893 OsCondition::IOS, ThemeCondition::Light,
1894 LayoutScrollbarWidth::Thin, ScrollbarVisibilityMode::WhenScrolling, 500, 200,
1895 custom_color(ColorU { r: 0, g: 0, b: 0, a: 100 }, ColorU::TRANSPARENT),
1896 ),
1897 (
1898 OsCondition::Android, ThemeCondition::Dark,
1899 LayoutScrollbarWidth::Thin, ScrollbarVisibilityMode::WhenScrolling, 300, 150,
1900 custom_color(ColorU { r: 255, g: 255, b: 255, a: 77 }, ColorU::TRANSPARENT),
1901 ),
1902 (
1903 OsCondition::Android, ThemeCondition::Light,
1904 LayoutScrollbarWidth::Thin, ScrollbarVisibilityMode::WhenScrolling, 300, 150,
1905 custom_color(ColorU { r: 0, g: 0, b: 0, a: 77 }, ColorU::TRANSPARENT),
1906 ),
1907 (
1908 OsCondition::Linux, ThemeCondition::Dark,
1911 LayoutScrollbarWidth::Auto, ScrollbarVisibilityMode::Always, 0, 0,
1912 custom_color(ColorU { r: 100, g: 100, b: 100, a: 255 }, ColorU { r: 45, g: 45, b: 45, a: 255 }),
1913 ),
1914 (
1915 OsCondition::Linux, ThemeCondition::Light,
1916 LayoutScrollbarWidth::Auto, ScrollbarVisibilityMode::Always, 0, 0,
1917 custom_color(CLASSIC_LIGHT_THUMB, CLASSIC_LIGHT_TRACK),
1918 ),
1919 (
1920 OsCondition::Web, ThemeCondition::Dark,
1921 LayoutScrollbarWidth::Auto, ScrollbarVisibilityMode::Always, 0, 0,
1922 custom_color(ColorU { r: 100, g: 100, b: 100, a: 255 }, ColorU { r: 45, g: 45, b: 45, a: 255 }),
1923 ),
1924 ];
1925
1926 for (os, theme, width, visibility, delay, duration, color) in cases {
1927 let r = evaluate_ua_scrollbar_css(&ctx(os, theme.clone()));
1928 assert_eq!(r.width, width, "{os:?}/{theme:?}: width");
1929 assert_eq!(r.visibility, visibility, "{os:?}/{theme:?}: visibility");
1930 assert_eq!(r.fade_delay.ms, delay, "{os:?}/{theme:?}: fade-delay");
1931 assert_eq!(r.fade_duration.ms, duration, "{os:?}/{theme:?}: fade-duration");
1932 assert_eq!(r.color, color, "{os:?}/{theme:?}: color");
1933 }
1934 }
1935
1936 #[test]
1941 fn unrecognised_context_themes_fall_back_instead_of_failing() {
1942 for theme in [ThemeCondition::Custom(AzString::from("")), ThemeCondition::Custom(AzString::from("🎨")), ThemeCondition::SystemPreferred] {
1943 let r = evaluate_ua_scrollbar_css(&ctx(OsCondition::MacOS, theme.clone()));
1945 assert_eq!(r.width, LayoutScrollbarWidth::Thin, "{theme:?}");
1946 assert_eq!(r.visibility, ScrollbarVisibilityMode::WhenScrolling, "{theme:?}");
1947 assert_eq!(
1948 unwrap_custom(r.color),
1949 (CLASSIC_LIGHT_THUMB, CLASSIC_LIGHT_TRACK),
1950 "{theme:?}: must fall back to the unconditional colour"
1951 );
1952 }
1953 }
1954
1955 #[test]
1960 fn apple_as_a_context_os_matches_no_macos_or_ios_rule() {
1961 let r = evaluate_ua_scrollbar_css(&ctx(OsCondition::Apple, ThemeCondition::Dark));
1962 assert_eq!(r.width, LayoutScrollbarWidth::Auto);
1963 assert_eq!(r.visibility, ScrollbarVisibilityMode::Always);
1964 assert_eq!(r.fade_delay.ms, 0);
1965 assert_eq!(r.fade_duration.ms, 0);
1966 }
1967
1968 #[test]
1973 fn overlay_scrollbar_fields_stay_consistent_across_every_os_and_theme() {
1974 for os in all_os() {
1975 for theme in all_themes() {
1976 let r = evaluate_ua_scrollbar_css(&ctx(os, theme.clone()));
1977 let thin = r.width == LayoutScrollbarWidth::Thin;
1978 let overlay = r.visibility == ScrollbarVisibilityMode::WhenScrolling;
1979
1980 assert_eq!(thin, overlay, "{os:?}/{theme:?}: thin/when-scrolling disagree");
1981 assert_eq!(
1982 overlay,
1983 r.fade_delay.ms > 0,
1984 "{os:?}/{theme:?}: an overlay scrollbar needs a fade delay"
1985 );
1986 assert_eq!(
1987 overlay,
1988 r.fade_duration.ms > 0,
1989 "{os:?}/{theme:?}: an overlay scrollbar needs a fade duration"
1990 );
1991 assert!(
1993 matches!(r.color, StyleScrollbarColor::Custom(_)),
1994 "{os:?}/{theme:?}: colour resolved to Auto"
1995 );
1996 }
1997 }
1998 }
1999
2000 #[test]
2004 fn early_break_does_not_change_the_first_match_result() {
2005 for os in all_os() {
2006 for theme in all_themes() {
2007 let c = ctx(os, theme.clone());
2008 let got = evaluate_ua_scrollbar_css(&c);
2009
2010 let mut want_color = None;
2011 let mut want_width = None;
2012 let mut want_vis = None;
2013 let mut want_delay = None;
2014 let mut want_dur = None;
2015 for entry in UA_SCROLLBAR_CSS.iter().filter(|e| e.matches(&c)) {
2016 match &entry.property {
2017 CssProperty::ScrollbarColor(CssPropertyValue::Exact(v)) => {
2018 if want_color.is_none() {
2019 want_color = Some(*v);
2020 }
2021 }
2022 CssProperty::ScrollbarWidth(CssPropertyValue::Exact(v)) => {
2023 if want_width.is_none() {
2024 want_width = Some(*v);
2025 }
2026 }
2027 CssProperty::ScrollbarVisibility(CssPropertyValue::Exact(v)) => {
2028 if want_vis.is_none() {
2029 want_vis = Some(*v);
2030 }
2031 }
2032 CssProperty::ScrollbarFadeDelay(CssPropertyValue::Exact(v)) => {
2033 if want_delay.is_none() {
2034 want_delay = Some(*v);
2035 }
2036 }
2037 CssProperty::ScrollbarFadeDuration(CssPropertyValue::Exact(v)) => {
2038 if want_dur.is_none() {
2039 want_dur = Some(*v);
2040 }
2041 }
2042 _ => {}
2043 }
2044 }
2045
2046 let label = alloc::format!("{os:?}/{theme:?}");
2047 assert_eq!(Some(got.color), want_color, "{label}: color");
2048 assert_eq!(Some(got.width), want_width, "{label}: width");
2049 assert_eq!(Some(got.visibility), want_vis, "{label}: visibility");
2050 assert_eq!(Some(got.fade_delay), want_delay, "{label}: fade-delay");
2051 assert_eq!(Some(got.fade_duration), want_dur, "{label}: fade-duration");
2052 }
2053 }
2054 }
2055
2056 #[test]
2059 fn degenerate_context_values_do_not_panic() {
2060 let hostile = [
2061 (f32::NAN, f32::NAN),
2062 (0.0, 0.0),
2063 (-0.0, -1.0),
2064 (f32::INFINITY, f32::NEG_INFINITY),
2065 (f32::MAX, f32::MIN),
2066 (f32::MIN_POSITIVE, f32::EPSILON),
2067 ];
2068
2069 for (w, h) in hostile {
2070 let c = DynamicSelectorContext {
2071 os: OsCondition::MacOS,
2072 theme: ThemeCondition::Dark,
2073 de_version: u32::MAX,
2074 viewport_width: w,
2075 viewport_height: h,
2076 container_width: h,
2077 container_height: w,
2078 language: AzString::from(""),
2079 ..DynamicSelectorContext::default()
2080 };
2081 let r = evaluate_ua_scrollbar_css(&c);
2082 assert_eq!(r.width, LayoutScrollbarWidth::Thin, "viewport {w}x{h}");
2085 assert_eq!(r.fade_delay.ms, 500, "viewport {w}x{h}");
2086 assert!(matches!(r.color, StyleScrollbarColor::Custom(_)), "viewport {w}x{h}");
2087 }
2088 }
2089
2090 #[test]
2091 fn evaluate_is_deterministic() {
2092 for os in all_os() {
2093 for theme in all_themes() {
2094 let c = ctx(os, theme.clone());
2095 let a = evaluate_ua_scrollbar_css(&c);
2096 let b = evaluate_ua_scrollbar_css(&c);
2097 assert_eq!(a.color, b.color, "{os:?}/{theme:?}");
2098 assert_eq!(a.width, b.width, "{os:?}/{theme:?}");
2099 assert_eq!(a.visibility, b.visibility, "{os:?}/{theme:?}");
2100 assert_eq!(a.fade_delay, b.fade_delay, "{os:?}/{theme:?}");
2101 assert_eq!(a.fade_duration, b.fade_duration, "{os:?}/{theme:?}");
2102 }
2103 }
2104 }
2105}