1#![allow(dead_code)]
8
9use alloc::{string::String, vec::Vec};
10
11use super::{
12 css_parser::{
13 named_color, px_to_fp, CssValue, Declaration, FixedPoint, Selector, SimpleSelector,
14 Specificity, Stylesheet,
15 },
16 dom::{Document, NodeId, NodeType},
17};
18
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
21pub enum Display {
22 Block,
23 #[default]
24 Inline,
25 InlineBlock,
26 None,
27 Flex,
28 Table,
29 TableRow,
30 TableCell,
31 TableHeaderGroup,
32 TableRowGroup,
33 TableFooterGroup,
34 ListItem,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum Position {
40 #[default]
41 Static,
42 Relative,
43 Absolute,
44 Fixed,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
49pub enum Float {
50 #[default]
51 None,
52 Left,
53 Right,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub enum Clear {
59 #[default]
60 None,
61 Left,
62 Right,
63 Both,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
68pub enum TextAlign {
69 #[default]
70 Left,
71 Center,
72 Right,
73 Justify,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub enum Overflow {
79 #[default]
80 Visible,
81 Hidden,
82 Scroll,
83 Auto,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
88pub enum Visibility {
89 #[default]
90 Visible,
91 Hidden,
92 Collapse,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97pub enum BorderStyle {
98 #[default]
99 None,
100 Solid,
101 Dashed,
102 Dotted,
103 Double,
104 Groove,
105 Ridge,
106 Inset,
107 Outset,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
112pub enum WhiteSpace {
113 #[default]
114 Normal,
115 Nowrap,
116 Pre,
117 PreWrap,
118 PreLine,
119}
120
121#[derive(Debug, Clone)]
123pub struct ComputedStyle {
124 pub display: Display,
125 pub position: Position,
126 pub width: Option<FixedPoint>,
127 pub height: Option<FixedPoint>,
128 pub min_width: FixedPoint,
129 pub min_height: FixedPoint,
130 pub max_width: Option<FixedPoint>,
131 pub max_height: Option<FixedPoint>,
132 pub margin_top: FixedPoint,
133 pub margin_right: FixedPoint,
134 pub margin_bottom: FixedPoint,
135 pub margin_left: FixedPoint,
136 pub padding_top: FixedPoint,
137 pub padding_right: FixedPoint,
138 pub padding_bottom: FixedPoint,
139 pub padding_left: FixedPoint,
140 pub color: u32,
141 pub background_color: u32,
142 pub font_size: FixedPoint,
143 pub font_weight: u16,
144 pub text_align: TextAlign,
145 pub line_height: FixedPoint,
146 pub border_top_width: FixedPoint,
147 pub border_right_width: FixedPoint,
148 pub border_bottom_width: FixedPoint,
149 pub border_left_width: FixedPoint,
150 pub border_top_color: u32,
151 pub border_right_color: u32,
152 pub border_bottom_color: u32,
153 pub border_left_color: u32,
154 pub border_top_style: BorderStyle,
155 pub border_right_style: BorderStyle,
156 pub border_bottom_style: BorderStyle,
157 pub border_left_style: BorderStyle,
158 pub overflow: Overflow,
159 pub visibility: Visibility,
160 pub opacity: u8,
161 pub float: Float,
162 pub clear: Clear,
163 pub z_index: i32,
164 pub text_decoration_underline: bool,
165 pub text_decoration_line_through: bool,
166 pub white_space: WhiteSpace,
167 pub list_style_type: Option<String>,
168}
169
170impl Default for ComputedStyle {
171 fn default() -> Self {
172 Self {
173 display: Display::Inline,
174 position: Position::Static,
175 width: None,
176 height: None,
177 min_width: 0,
178 min_height: 0,
179 max_width: None,
180 max_height: None,
181 margin_top: 0,
182 margin_right: 0,
183 margin_bottom: 0,
184 margin_left: 0,
185 padding_top: 0,
186 padding_right: 0,
187 padding_bottom: 0,
188 padding_left: 0,
189 color: 0xFF000000, background_color: 0x00000000, font_size: px_to_fp(16), font_weight: 400, text_align: TextAlign::Left,
194 line_height: px_to_fp(20), border_top_width: 0,
196 border_right_width: 0,
197 border_bottom_width: 0,
198 border_left_width: 0,
199 border_top_color: 0xFF000000,
200 border_right_color: 0xFF000000,
201 border_bottom_color: 0xFF000000,
202 border_left_color: 0xFF000000,
203 border_top_style: BorderStyle::None,
204 border_right_style: BorderStyle::None,
205 border_bottom_style: BorderStyle::None,
206 border_left_style: BorderStyle::None,
207 overflow: Overflow::Visible,
208 visibility: Visibility::Visible,
209 opacity: 255,
210 float: Float::None,
211 clear: Clear::None,
212 z_index: 0,
213 text_decoration_underline: false,
214 text_decoration_line_through: false,
215 white_space: WhiteSpace::Normal,
216 list_style_type: None,
217 }
218 }
219}
220
221#[derive(Debug)]
223struct MatchedRule {
224 specificity: Specificity,
225 declarations: Vec<Declaration>,
226 important: bool,
227}
228
229pub struct StyleResolver {
231 pub stylesheets: Vec<Stylesheet>,
232 pub ua_stylesheet: Stylesheet,
233}
234
235impl Default for StyleResolver {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl StyleResolver {
242 pub fn new() -> Self {
244 Self {
245 stylesheets: Vec::new(),
246 ua_stylesheet: Self::default_ua_stylesheet(),
247 }
248 }
249
250 pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) {
252 self.stylesheets.push(stylesheet);
253 }
254
255 fn default_ua_stylesheet() -> Stylesheet {
257 use super::css_parser::CssParser;
258 CssParser::parse(
259 "html, body, div, section, article, aside, nav, header, footer, main, address, \
260 blockquote, figure, figcaption, details, summary, ul, ol, dl, pre, fieldset, form, \
261 hgroup, p, h1, h2, h3, h4, h5, h6 { display: block; }
262 li { display: list-item; }
263 table { display: table; }
264 thead { display: table-header-group; }
265 tbody { display: table-row-group; }
266 tfoot { display: table-footer-group; }
267 tr { display: table-row; }
268 td, th { display: table-cell; }
269 head, script, style, link, meta, title { display: none; }
270 b, strong { font-weight: bold; }
271 i, em { font-style: italic; }
272 h1 { font-size: 32px; font-weight: bold; margin-top: 21px; margin-bottom: 21px; }
273 h2 { font-size: 24px; font-weight: bold; margin-top: 19px; margin-bottom: 19px; }
274 h3 { font-size: 18px; font-weight: bold; margin-top: 18px; margin-bottom: 18px; }
275 h4 { font-size: 16px; font-weight: bold; margin-top: 21px; margin-bottom: 21px; }
276 h5 { font-size: 13px; font-weight: bold; margin-top: 22px; margin-bottom: 22px; }
277 h6 { font-size: 10px; font-weight: bold; margin-top: 24px; margin-bottom: 24px; }
278 p { margin-top: 16px; margin-bottom: 16px; }
279 body { margin-top: 8px; margin-right: 8px; margin-bottom: 8px; margin-left: 8px; }
280 ul, ol { margin-top: 16px; margin-bottom: 16px; padding-left: 40px; }
281 a { color: #0000ee; }
282 u { color: inherit; }
283 pre, code { font-family: monospace; }
284 ",
285 )
286 }
287
288 pub fn resolve(
290 &self,
291 doc: &Document,
292 node_id: NodeId,
293 parent_style: Option<&ComputedStyle>,
294 ) -> ComputedStyle {
295 let node = match doc.arena.get(node_id) {
296 Some(n) => n,
297 None => return ComputedStyle::default(),
298 };
299
300 if node.node_type != NodeType::Element {
302 let mut style = ComputedStyle::default();
303 if let Some(ps) = parent_style {
304 Self::inherit(&mut style, ps);
305 }
306 return style;
307 }
308
309 let mut style = ComputedStyle::default();
311
312 let matched = self.cascade(doc, node_id);
314
315 for decl in &matched {
317 Self::apply_declaration(&mut style, decl);
318 }
319
320 if let Some(ps) = parent_style {
322 Self::inherit_if_not_set(&mut style, ps);
323 }
324
325 style
326 }
327
328 fn cascade(&self, doc: &Document, node_id: NodeId) -> Vec<Declaration> {
330 let mut matched_rules: Vec<MatchedRule> = Vec::new();
331
332 self.collect_matching_rules(&self.ua_stylesheet, doc, node_id, &mut matched_rules);
334
335 for ss in &self.stylesheets {
337 self.collect_matching_rules(ss, doc, node_id, &mut matched_rules);
338 }
339
340 matched_rules.sort_by(|a, b| a.specificity.cmp(&b.specificity));
342
343 let mut normal = Vec::new();
345 let mut important = Vec::new();
346 for rule in matched_rules {
347 for decl in rule.declarations {
348 if decl.important {
349 important.push(decl);
350 } else {
351 normal.push(decl);
352 }
353 }
354 }
355 normal.extend(important);
356 normal
357 }
358
359 fn collect_matching_rules(
360 &self,
361 stylesheet: &Stylesheet,
362 doc: &Document,
363 node_id: NodeId,
364 matched: &mut Vec<MatchedRule>,
365 ) {
366 for rule in &stylesheet.rules {
367 for selector in &rule.selectors {
368 if Self::selector_matches(selector, doc, node_id) {
369 matched.push(MatchedRule {
370 specificity: selector.specificity(),
371 declarations: rule.declarations.clone(),
372 important: false,
373 });
374 break; }
376 }
377 }
378 }
379
380 fn selector_matches(selector: &Selector, doc: &Document, node_id: NodeId) -> bool {
382 match selector {
383 Selector::Universal => true,
384 Selector::Tag(tag) => doc.tag_name(node_id) == Some(tag.as_str()),
385 Selector::Id(id) => doc.get_attribute(node_id, "id").as_deref() == Some(id.as_str()),
386 Selector::Class(class) => doc
387 .arena
388 .get(node_id)
389 .and_then(|n| n.element_data.as_ref())
390 .map(|ed| ed.has_class(class))
391 .unwrap_or(false),
392 Selector::Simple(simple) => Self::simple_selector_matches(simple, doc, node_id),
393 Selector::Descendant(parts) => Self::descendant_matches(parts, doc, node_id),
394 Selector::Child(parts) => Self::child_matches(parts, doc, node_id),
395 Selector::Compound(parts) => parts
396 .iter()
397 .all(|p| Self::selector_matches(p, doc, node_id)),
398 }
399 }
400
401 fn simple_selector_matches(sel: &SimpleSelector, doc: &Document, node_id: NodeId) -> bool {
402 if let Some(ref tag) = sel.tag_name {
403 if doc.tag_name(node_id) != Some(tag.as_str()) {
404 return false;
405 }
406 }
407 if let Some(ref id) = sel.id {
408 if doc.get_attribute(node_id, "id").as_deref() != Some(id.as_str()) {
409 return false;
410 }
411 }
412 for class in &sel.classes {
413 let has = doc
414 .arena
415 .get(node_id)
416 .and_then(|n| n.element_data.as_ref())
417 .map(|ed| ed.has_class(class))
418 .unwrap_or(false);
419 if !has {
420 return false;
421 }
422 }
423 true
424 }
425
426 fn descendant_matches(parts: &[Selector], doc: &Document, node_id: NodeId) -> bool {
427 if parts.is_empty() {
428 return false;
429 }
430
431 if !Self::selector_matches(&parts[parts.len() - 1], doc, node_id) {
433 return false;
434 }
435
436 if parts.len() == 1 {
437 return true;
438 }
439
440 let remaining = &parts[..parts.len() - 1];
442 let ancestors = doc.ancestors(node_id);
443 for &ancestor_id in &ancestors {
444 if Self::descendant_matches(remaining, doc, ancestor_id) {
445 return true;
446 }
447 }
448 false
449 }
450
451 fn child_matches(parts: &[Selector], doc: &Document, node_id: NodeId) -> bool {
452 if parts.is_empty() {
453 return false;
454 }
455
456 if !Self::selector_matches(&parts[parts.len() - 1], doc, node_id) {
457 return false;
458 }
459
460 if parts.len() == 1 {
461 return true;
462 }
463
464 if let Some(node) = doc.arena.get(node_id) {
466 if let Some(parent_id) = node.parent {
467 return Self::child_matches(&parts[..parts.len() - 1], doc, parent_id);
468 }
469 }
470 false
471 }
472
473 fn apply_declaration(style: &mut ComputedStyle, decl: &Declaration) {
475 match decl.property.as_str() {
476 "display" => {
477 style.display = match &decl.value {
478 CssValue::Keyword(k) => match k.as_str() {
479 "block" => Display::Block,
480 "inline" => Display::Inline,
481 "inline-block" => Display::InlineBlock,
482 "flex" => Display::Flex,
483 "table" => Display::Table,
484 "table-row" => Display::TableRow,
485 "table-cell" => Display::TableCell,
486 "table-header-group" => Display::TableHeaderGroup,
487 "table-row-group" => Display::TableRowGroup,
488 "table-footer-group" => Display::TableFooterGroup,
489 "list-item" => Display::ListItem,
490 _ => Display::Block,
491 },
492 CssValue::None => Display::None,
493 _ => style.display,
494 };
495 }
496 "position" => {
497 if let CssValue::Keyword(k) = &decl.value {
498 style.position = match k.as_str() {
499 "static" => Position::Static,
500 "relative" => Position::Relative,
501 "absolute" => Position::Absolute,
502 "fixed" => Position::Fixed,
503 _ => style.position,
504 };
505 }
506 }
507 "width" => {
508 style.width = Self::resolve_length(&decl.value);
509 }
510 "height" => {
511 style.height = Self::resolve_length(&decl.value);
512 }
513 "min-width" => {
514 if let Some(v) = Self::resolve_length(&decl.value) {
515 style.min_width = v;
516 }
517 }
518 "min-height" => {
519 if let Some(v) = Self::resolve_length(&decl.value) {
520 style.min_height = v;
521 }
522 }
523 "max-width" => {
524 style.max_width = Self::resolve_length(&decl.value);
525 }
526 "max-height" => {
527 style.max_height = Self::resolve_length(&decl.value);
528 }
529 "margin" => {
530 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
531 style.margin_top = v;
532 style.margin_right = v;
533 style.margin_bottom = v;
534 style.margin_left = v;
535 }
536 }
537 "margin-top" => {
538 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
539 style.margin_top = v;
540 }
541 }
542 "margin-right" => {
543 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
544 style.margin_right = v;
545 }
546 }
547 "margin-bottom" => {
548 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
549 style.margin_bottom = v;
550 }
551 }
552 "margin-left" => {
553 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
554 style.margin_left = v;
555 }
556 }
557 "padding" => {
558 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
559 style.padding_top = v;
560 style.padding_right = v;
561 style.padding_bottom = v;
562 style.padding_left = v;
563 }
564 }
565 "padding-top" => {
566 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
567 style.padding_top = v;
568 }
569 }
570 "padding-right" => {
571 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
572 style.padding_right = v;
573 }
574 }
575 "padding-bottom" => {
576 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
577 style.padding_bottom = v;
578 }
579 }
580 "padding-left" => {
581 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
582 style.padding_left = v;
583 }
584 }
585 "color" => {
586 if let Some(c) = Self::resolve_color(&decl.value) {
587 style.color = c;
588 }
589 }
590 "background-color" | "background" => {
591 if let Some(c) = Self::resolve_color(&decl.value) {
592 style.background_color = c;
593 }
594 }
595 "font-size" => {
596 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
597 style.font_size = v;
598 } else if let CssValue::Keyword(k) = &decl.value {
599 style.font_size = match k.as_str() {
600 "xx-small" => px_to_fp(9),
601 "x-small" => px_to_fp(10),
602 "small" => px_to_fp(13),
603 "medium" => px_to_fp(16),
604 "large" => px_to_fp(18),
605 "x-large" => px_to_fp(24),
606 "xx-large" => px_to_fp(32),
607 _ => style.font_size,
608 };
609 }
610 }
611 "font-weight" => match &decl.value {
612 CssValue::Keyword(k) => {
613 style.font_weight = match k.as_str() {
614 "normal" => 400,
615 "bold" => 700,
616 "lighter" => 100,
617 "bolder" => 900,
618 _ => style.font_weight,
619 };
620 }
621 CssValue::Number(n) => {
622 style.font_weight = (*n as u16).clamp(100, 900);
623 }
624 _ => {}
625 },
626 "text-align" => {
627 if let CssValue::Keyword(k) = &decl.value {
628 style.text_align = match k.as_str() {
629 "left" => TextAlign::Left,
630 "center" => TextAlign::Center,
631 "right" => TextAlign::Right,
632 "justify" => TextAlign::Justify,
633 _ => style.text_align,
634 };
635 }
636 }
637 "line-height" => {
638 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
639 style.line_height = v;
640 }
641 }
642 "border-width" => {
643 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
644 style.border_top_width = v;
645 style.border_right_width = v;
646 style.border_bottom_width = v;
647 style.border_left_width = v;
648 }
649 }
650 "border-color" => {
651 if let Some(c) = Self::resolve_color(&decl.value) {
652 style.border_top_color = c;
653 style.border_right_color = c;
654 style.border_bottom_color = c;
655 style.border_left_color = c;
656 }
657 }
658 "border-style" => {
659 if let CssValue::Keyword(k) = &decl.value {
660 let bs = Self::parse_border_style(k);
661 style.border_top_style = bs;
662 style.border_right_style = bs;
663 style.border_bottom_style = bs;
664 style.border_left_style = bs;
665 }
666 }
667 "border" => {
668 if let Some(c) = Self::resolve_color(&decl.value) {
671 style.border_top_color = c;
672 style.border_right_color = c;
673 style.border_bottom_color = c;
674 style.border_left_color = c;
675 }
676 if let Some(v) = Self::resolve_length_or_zero(&decl.value) {
677 style.border_top_width = v;
678 style.border_right_width = v;
679 style.border_bottom_width = v;
680 style.border_left_width = v;
681 }
682 }
683 "overflow" => {
684 if let CssValue::Keyword(k) = &decl.value {
685 style.overflow = match k.as_str() {
686 "hidden" => Overflow::Hidden,
687 "scroll" => Overflow::Scroll,
688 "auto" => Overflow::Auto,
689 _ => Overflow::Visible,
690 };
691 }
692 }
693 "visibility" => {
694 if let CssValue::Keyword(k) = &decl.value {
695 style.visibility = match k.as_str() {
696 "hidden" => Visibility::Hidden,
697 "collapse" => Visibility::Collapse,
698 _ => Visibility::Visible,
699 };
700 }
701 }
702 "opacity" => {
703 if let CssValue::Number(n) = &decl.value {
704 style.opacity = *n as u8;
705 }
706 }
707 "float" => {
708 if let CssValue::Keyword(k) = &decl.value {
709 style.float = match k.as_str() {
710 "left" => Float::Left,
711 "right" => Float::Right,
712 _ => Float::None,
713 };
714 }
715 }
716 "clear" => {
717 if let CssValue::Keyword(k) = &decl.value {
718 style.clear = match k.as_str() {
719 "left" => Clear::Left,
720 "right" => Clear::Right,
721 "both" => Clear::Both,
722 _ => Clear::None,
723 };
724 }
725 }
726 "z-index" => {
727 if let CssValue::Number(n) = &decl.value {
728 style.z_index = *n;
729 }
730 }
731 "text-decoration" => {
732 if let CssValue::Keyword(k) = &decl.value {
733 match k.as_str() {
734 "underline" => style.text_decoration_underline = true,
735 "line-through" => style.text_decoration_line_through = true,
736 "none" => {
737 style.text_decoration_underline = false;
738 style.text_decoration_line_through = false;
739 }
740 _ => {}
741 }
742 }
743 }
744 "white-space" => {
745 if let CssValue::Keyword(k) = &decl.value {
746 style.white_space = match k.as_str() {
747 "nowrap" => WhiteSpace::Nowrap,
748 "pre" => WhiteSpace::Pre,
749 "pre-wrap" => WhiteSpace::PreWrap,
750 "pre-line" => WhiteSpace::PreLine,
751 _ => WhiteSpace::Normal,
752 };
753 }
754 }
755 "list-style-type" => {
756 if let CssValue::Keyword(k) = &decl.value {
757 style.list_style_type = Some(k.clone());
758 }
759 }
760 "font-style" | "font-family" => {
762 }
764 _ => {
765 }
767 }
768 }
769
770 fn resolve_length(value: &CssValue) -> Option<FixedPoint> {
771 match value {
772 CssValue::Length(v, _) => Some(*v),
773 CssValue::Number(0) => Some(0),
774 CssValue::Auto => None,
775 CssValue::None => None,
776 CssValue::Percentage(v) => Some(*v), _ => None,
778 }
779 }
780
781 fn resolve_length_or_zero(value: &CssValue) -> Option<FixedPoint> {
782 match value {
783 CssValue::Length(v, _) => Some(*v),
784 CssValue::Number(n) => Some(px_to_fp(*n)),
785 CssValue::Percentage(v) => Some(*v),
786 _ => None,
787 }
788 }
789
790 fn resolve_color(value: &CssValue) -> Option<u32> {
791 match value {
792 CssValue::Color(c) => Some(*c),
793 CssValue::Keyword(k) => named_color(k),
794 _ => None,
795 }
796 }
797
798 fn parse_border_style(s: &str) -> BorderStyle {
799 match s {
800 "solid" => BorderStyle::Solid,
801 "dashed" => BorderStyle::Dashed,
802 "dotted" => BorderStyle::Dotted,
803 "double" => BorderStyle::Double,
804 "groove" => BorderStyle::Groove,
805 "ridge" => BorderStyle::Ridge,
806 "inset" => BorderStyle::Inset,
807 "outset" => BorderStyle::Outset,
808 "none" => BorderStyle::None,
809 _ => BorderStyle::None,
810 }
811 }
812
813 fn inherit(style: &mut ComputedStyle, parent: &ComputedStyle) {
815 style.color = parent.color;
816 style.font_size = parent.font_size;
817 style.font_weight = parent.font_weight;
818 style.line_height = parent.line_height;
819 style.text_align = parent.text_align;
820 style.visibility = parent.visibility;
821 style.white_space = parent.white_space;
822 style.list_style_type = parent.list_style_type.clone();
823 }
824
825 fn inherit_if_not_set(style: &mut ComputedStyle, parent: &ComputedStyle) {
827 if style.color == 0xFF000000 && parent.color != 0xFF000000 {
830 style.color = parent.color;
831 }
832 if style.font_size == px_to_fp(16) && parent.font_size != px_to_fp(16) {
834 style.font_size = parent.font_size;
835 }
836 if style.line_height == px_to_fp(20) && parent.line_height != px_to_fp(20) {
838 style.line_height = parent.line_height;
839 }
840 if style.text_align == TextAlign::Left && parent.text_align != TextAlign::Left {
842 style.text_align = parent.text_align;
843 }
844 if style.visibility == Visibility::Visible && parent.visibility != Visibility::Visible {
846 style.visibility = parent.visibility;
847 }
848 if style.white_space == WhiteSpace::Normal && parent.white_space != WhiteSpace::Normal {
850 style.white_space = parent.white_space;
851 }
852 if style.list_style_type.is_none() {
854 style.list_style_type = parent.list_style_type.clone();
855 }
856 }
857}
858
859#[cfg(test)]
860mod tests {
861 #[allow(unused_imports)]
862 use alloc::vec;
863
864 use super::{
865 super::{css_parser::CssParser, tree_builder::TreeBuilder},
866 *,
867 };
868
869 fn resolve_first_p(html: &str, css: &str) -> ComputedStyle {
870 let doc = TreeBuilder::build(html);
871 let stylesheet = CssParser::parse(css);
872 let mut resolver = StyleResolver::new();
873 resolver.add_stylesheet(stylesheet);
874 let ps = doc.get_elements_by_tag_name("p");
875 assert!(!ps.is_empty(), "No <p> found");
876 resolver.resolve(&doc, ps[0], None)
877 }
878
879 #[test]
880 fn test_default_style() {
881 let style = ComputedStyle::default();
882 assert_eq!(style.display, Display::Inline);
883 assert_eq!(style.color, 0xFF000000);
884 assert_eq!(style.font_size, px_to_fp(16));
885 }
886
887 #[test]
888 fn test_ua_block_display() {
889 let doc = TreeBuilder::build("<div>hello</div>");
890 let resolver = StyleResolver::new();
891 let divs = doc.get_elements_by_tag_name("div");
892 let style = resolver.resolve(&doc, divs[0], None);
893 assert_eq!(style.display, Display::Block);
894 }
895
896 #[test]
897 fn test_ua_heading_font_size() {
898 let doc = TreeBuilder::build("<h1>Title</h1>");
899 let resolver = StyleResolver::new();
900 let h1s = doc.get_elements_by_tag_name("h1");
901 let style = resolver.resolve(&doc, h1s[0], None);
902 assert_eq!(style.font_size, px_to_fp(32));
903 assert_eq!(style.font_weight, 700);
904 }
905
906 #[test]
907 fn test_ua_body_margin() {
908 let doc = TreeBuilder::build("<body></body>");
909 let resolver = StyleResolver::new();
910 let bodies = doc.get_elements_by_tag_name("body");
911 let style = resolver.resolve(&doc, bodies[0], None);
912 assert_eq!(style.margin_top, px_to_fp(8));
913 }
914
915 #[test]
916 fn test_color_override() {
917 let style = resolve_first_p("<p>text</p>", "p { color: #ff0000; }");
918 assert_eq!(style.color, 0xFFFF0000);
919 }
920
921 #[test]
922 fn test_display_none() {
923 let doc = TreeBuilder::build("<head><title>T</title></head>");
924 let resolver = StyleResolver::new();
925 let titles = doc.get_elements_by_tag_name("title");
926 if !titles.is_empty() {
927 let style = resolver.resolve(&doc, titles[0], None);
928 assert_eq!(style.display, Display::None);
929 }
930 }
931
932 #[test]
933 fn test_background_color() {
934 let style = resolve_first_p("<p>text</p>", "p { background-color: #00ff00; }");
935 assert_eq!(style.background_color, 0xFF00FF00);
936 }
937
938 #[test]
939 fn test_padding() {
940 let style = resolve_first_p("<p>text</p>", "p { padding: 10px; }");
941 assert_eq!(style.padding_top, px_to_fp(10));
942 assert_eq!(style.padding_right, px_to_fp(10));
943 assert_eq!(style.padding_bottom, px_to_fp(10));
944 assert_eq!(style.padding_left, px_to_fp(10));
945 }
946
947 #[test]
948 fn test_margin_individual() {
949 let style = resolve_first_p("<p>text</p>", "p { margin-left: 20px; }");
950 assert_eq!(style.margin_left, px_to_fp(20));
951 }
952
953 #[test]
954 fn test_font_weight_bold() {
955 let doc = TreeBuilder::build("<b>bold</b>");
956 let resolver = StyleResolver::new();
957 let bs = doc.get_elements_by_tag_name("b");
958 let style = resolver.resolve(&doc, bs[0], None);
959 assert_eq!(style.font_weight, 700);
960 }
961
962 #[test]
963 fn test_width_height() {
964 let style = resolve_first_p("<p>text</p>", "p { width: 200px; height: 100px; }");
965 assert_eq!(style.width, Some(px_to_fp(200)));
966 assert_eq!(style.height, Some(px_to_fp(100)));
967 }
968
969 #[test]
970 fn test_text_align() {
971 let style = resolve_first_p("<p>text</p>", "p { text-align: center; }");
972 assert_eq!(style.text_align, TextAlign::Center);
973 }
974
975 #[test]
976 fn test_float() {
977 let style = resolve_first_p("<p>text</p>", "p { float: left; }");
978 assert_eq!(style.float, Float::Left);
979 }
980
981 #[test]
982 fn test_position() {
983 let style = resolve_first_p("<p>text</p>", "p { position: absolute; }");
984 assert_eq!(style.position, Position::Absolute);
985 }
986
987 #[test]
988 fn test_border_style() {
989 let style = resolve_first_p("<p>text</p>", "p { border-style: solid; }");
990 assert_eq!(style.border_top_style, BorderStyle::Solid);
991 }
992
993 #[test]
994 fn test_overflow() {
995 let style = resolve_first_p("<p>text</p>", "p { overflow: hidden; }");
996 assert_eq!(style.overflow, Overflow::Hidden);
997 }
998
999 #[test]
1000 fn test_z_index() {
1001 let style = resolve_first_p("<p>text</p>", "p { z-index: 10; }");
1002 assert_eq!(style.z_index, 10);
1003 }
1004
1005 #[test]
1006 fn test_specificity_ordering() {
1007 let style = resolve_first_p(
1008 "<p id=\"x\">text</p>",
1009 "p { color: #ff0000; } #x { color: #0000ff; }",
1010 );
1011 assert_eq!(style.color, 0xFF0000FF);
1013 }
1014
1015 #[test]
1016 fn test_named_color_resolution() {
1017 let style = resolve_first_p("<p>text</p>", "p { background-color: red; }");
1018 assert_eq!(style.background_color, 0xFFFF0000);
1019 }
1020
1021 #[test]
1022 fn test_class_selector_match() {
1023 let doc = TreeBuilder::build("<p class=\"highlight\">text</p>");
1024 let stylesheet = CssParser::parse(".highlight { color: #00ff00; }");
1025 let mut resolver = StyleResolver::new();
1026 resolver.add_stylesheet(stylesheet);
1027 let ps = doc.get_elements_by_tag_name("p");
1028 let style = resolver.resolve(&doc, ps[0], None);
1029 assert_eq!(style.color, 0xFF00FF00);
1030 }
1031
1032 #[test]
1033 fn test_visibility_hidden() {
1034 let style = resolve_first_p("<p>text</p>", "p { visibility: hidden; }");
1035 assert_eq!(style.visibility, Visibility::Hidden);
1036 }
1037
1038 #[test]
1039 fn test_white_space() {
1040 let style = resolve_first_p("<p>text</p>", "p { white-space: pre; }");
1041 assert_eq!(style.white_space, WhiteSpace::Pre);
1042 }
1043
1044 #[test]
1045 fn test_text_decoration() {
1046 let style = resolve_first_p("<p>text</p>", "p { text-decoration: underline; }");
1047 assert!(style.text_decoration_underline);
1048 }
1049
1050 #[test]
1051 fn test_default_resolver() {
1052 let _r = StyleResolver::default();
1053 }
1054}