1use super::*;
4
5
6pub fn style_tree<'a>(root: &'a Node, stylesheet: &StyleSheet) -> StyledNode<'a> {
18 let rule_index = super::rule_index::RuleIndex::build(&stylesheet.rules);
21 let mut ancestors = Vec::new();
22 style_tree_with_ancestors_vars(
23 root,
24 stylesheet,
25 Some(&rule_index),
26 &mut ancestors,
27 None,
28 None,
29 &alloc::rc::Rc::new(BTreeMap::new()),
30 &alloc::rc::Rc::new(BTreeMap::new()),
31 &BTreeMap::new(),
32 )
33 .0
34}
35
36fn html_legacy_dimension_to_css(raw: &str) -> Option<String> {
40 let s = raw.trim();
41 if s.is_empty() {
42 return None;
43 }
44 if let Some(pct) = s.strip_suffix('%') {
45 let pct = pct.trim();
46 return pct.parse::<f32>().ok().map(|_| alloc::format!("{}%", pct));
47 }
48 let digits: String = s.chars().take_while(|c| c.is_ascii_digit()).collect();
49 if digits.is_empty() {
50 return None;
51 }
52 Some(alloc::format!("{}px", digits))
53}
54
55#[derive(Debug, Clone, Copy)]
56pub(crate) struct AncestorContext<'a> {
57 pub(crate) node: &'a Node,
58 pub(crate) sibling_index: Option<usize>,
59 pub(crate) sibling_count: Option<usize>,
60}
61
62#[inline]
63fn insert_ua_pairs(specified_values: &mut BTreeMap<String, String>, pairs: &[(&'static str, &'static str)]) {
64 for &(k, v) in pairs {
65 specified_values.insert(String::from(k), String::from(v));
66 }
67}
68
69type AppliedDecl = (bool, Option<usize>, (u32, u32, u32), usize, String);
71
72pub(crate) const BOX_SHORTHANDS: &[(&str, [&str; 4])] = &[
75 ("margin", ["margin-top", "margin-right", "margin-bottom", "margin-left"]),
76 ("padding", ["padding-top", "padding-right", "padding-bottom", "padding-left"]),
77 (
78 "border-width",
79 ["border-top-width", "border-right-width", "border-bottom-width", "border-left-width"],
80 ),
81 ("inset", ["top", "right", "bottom", "left"]),
82];
83
84fn decl_outranks(a: &AppliedDecl, b: &AppliedDecl) -> bool {
86 if a.0 != b.0 {
87 return a.0;
88 }
89 if a.0 {
90 let ar = a.1.map(|l| -(l as i64)).unwrap_or(i64::MIN);
91 let br = b.1.map(|l| -(l as i64)).unwrap_or(i64::MIN);
92 ar > br || (ar == br && (a.2 > b.2 || (a.2 == b.2 && a.3 >= b.3)))
93 } else {
94 let ar = a.1.map(|l| l as i64).unwrap_or(i64::MAX);
95 let br = b.1.map(|l| l as i64).unwrap_or(i64::MAX);
96 ar > br || (ar == br && (a.2 > b.2 || (a.2 == b.2 && a.3 >= b.3)))
97 }
98}
99
100fn drop_overridden_longhands(
112 applied: &mut BTreeMap<String, AppliedDecl>,
113 specified_values: &mut BTreeMap<String, String>,
114) {
115 for (short, longs) in BOX_SHORTHANDS {
116 let Some(sh) = applied.get(*short) else {
117 continue;
118 };
119 let sh_rank: AppliedDecl = (sh.0, sh.1, sh.2, sh.3, String::new());
120 for long in longs {
121 specified_values.remove(*long);
122 let long_loses = applied.get(*long).is_some_and(|l| !decl_outranks(l, &sh_rank));
123 if long_loses {
124 applied.remove(*long);
125 }
126 }
127 }
128}
129
130pub(crate) fn compute_styles<'a>(
131 root: &'a Node,
132 stylesheet: &StyleSheet,
133 ancestors: &[AncestorContext<'a>],
134 sibling_index: Option<usize>,
135 sibling_count: Option<usize>,
136 active_pseudo_states: &[&str],
137) -> BTreeMap<String, String> {
138 let (vals, _, _, _) = compute_styles_with_vars(
139 root,
140 stylesheet,
141 None,
142 ancestors,
143 sibling_index,
144 sibling_count,
145 active_pseudo_states,
146 &alloc::rc::Rc::new(BTreeMap::new()),
147 &BTreeMap::new(),
148 );
149 vals
150}
151
152pub(crate) fn compute_styles_with_vars<'a>(
157 root: &'a Node,
158 stylesheet: &StyleSheet,
159 rule_index: Option<&super::rule_index::RuleIndex>,
160 ancestors: &[AncestorContext<'a>],
161 sibling_index: Option<usize>,
162 sibling_count: Option<usize>,
163 active_pseudo_states: &[&str],
164 parent_vars: &alloc::rc::Rc<BTreeMap<String, String>>,
165 counters_in: &BTreeMap<String, i64>,
166) -> (
167 BTreeMap<String, String>,
168 alloc::rc::Rc<BTreeMap<String, String>>,
169 BTreeMap<String, i64>,
170 alloc::collections::BTreeSet<String>,
171) {
172 compute_styles_with_vars_stack(
173 root,
174 stylesheet,
175 rule_index,
176 ancestors,
177 sibling_index,
178 sibling_count,
179 active_pseudo_states,
180 parent_vars,
181 counters_in,
182 None,
183 )
184}
185
186pub(crate) fn compute_styles_with_vars_stack<'a>(
187 root: &'a Node,
188 stylesheet: &StyleSheet,
189 rule_index: Option<&super::rule_index::RuleIndex>,
190 ancestors: &[AncestorContext<'a>],
191 sibling_index: Option<usize>,
192 sibling_count: Option<usize>,
193 active_pseudo_states: &[&str],
194 parent_vars: &alloc::rc::Rc<BTreeMap<String, String>>,
195 counters_in: &BTreeMap<String, i64>,
196 counter_stack: Option<&crate::os_lib::css::counter_scope::CounterScopeStack>,
197) -> (
198 BTreeMap<String, String>,
199 alloc::rc::Rc<BTreeMap<String, String>>,
200 BTreeMap<String, i64>,
201 alloc::collections::BTreeSet<String>,
202) {
203 let mut specified_values = BTreeMap::new();
204 let mut css_vars: alloc::rc::Rc<BTreeMap<String, String>> = parent_vars.clone();
212 let mut counters_out = if counters_in.is_empty() {
213 BTreeMap::new()
214 } else {
215 counters_in.clone()
216 };
217 let mut counters_reset_names = alloc::collections::BTreeSet::new();
218
219 match &root.node_type {
220 NodeType::Element {
221 tag_name,
222 attributes,
223 classes: _,
224 id: _,
225 } => {
226 match tag_name.as_str() {
227 "h1" => {
228 insert_ua_pairs(&mut specified_values, &[
229 ("display", "block"),
230 ("font-size", "32"),
231 ("font-weight", "bold"),
232 ("margin-top", "21"),
233 ("margin-bottom", "21"),
234 ]);
235 }
236 "h2" => {
237 insert_ua_pairs(&mut specified_values, &[
238 ("display", "block"),
239 ("font-size", "24"),
240 ("font-weight", "bold"),
241 ("margin-top", "20"),
242 ("margin-bottom", "20"),
243 ]);
244 }
245 "h3" => {
246 insert_ua_pairs(&mut specified_values, &[
247 ("display", "block"),
248 ("font-size", "18"),
249 ("font-weight", "bold"),
250 ("margin-top", "18"),
251 ("margin-bottom", "18"),
252 ]);
253 }
254 "p" => {
255 insert_ua_pairs(&mut specified_values, &[
256 ("display", "block"),
257 ("margin-top", "16"),
258 ("margin-bottom", "16"),
259 ]);
260 }
261 "body" => {
262 insert_ua_pairs(&mut specified_values, &[
263 ("display", "block"),
264 ("margin", "8"),
265 ]);
266 }
267 "ul" | "ol" => {
268 insert_ua_pairs(&mut specified_values, &[
269 ("display", "block"),
270 ("margin-left", "20px"),
271 ]);
272 let is_ol = tag_name.eq_ignore_ascii_case("ol");
284 let start_attr = if is_ol {
285 attributes.get("start").and_then(|s| s.trim().parse::<i64>().ok())
286 } else {
287 None
288 };
289 let reversed = is_ol && attributes.contains_key("reversed");
296 let start_val = if reversed {
297 let li_count = root
298 .children
299 .iter()
300 .filter(|c| {
301 matches!(&c.node_type, NodeType::Element { tag_name, .. } if tag_name.eq_ignore_ascii_case("li"))
302 })
303 .count() as i64;
304 start_attr.unwrap_or(li_count) + 1
305 } else {
306 start_attr.map(|n| n - 1).unwrap_or(0)
307 };
308 specified_values.insert(
309 String::from("counter-reset"),
310 alloc::format!("list-item {start_val}"),
311 );
312 }
313 "li" => {
314 specified_values.insert(String::from("display"), String::from("block"));
315 let parent_reversed = ancestors.last().is_some_and(|a| {
321 matches!(&a.node.node_type, NodeType::Element { tag_name, attributes, .. }
322 if tag_name.eq_ignore_ascii_case("ol") && attributes.contains_key("reversed"))
323 });
324 let incr = if parent_reversed { "list-item -1" } else { "list-item" };
325 specified_values
326 .insert(String::from("counter-increment"), String::from(incr));
327 }
328 "pre" => {
329 insert_ua_pairs(&mut specified_values, &[
330 ("display", "block"),
331 ("white-space", "pre"),
335 ("font-family", "monospace"),
336 ("background-color", "#f6f8fa"),
337 ("color", "#24292e"),
338 ("border-color", "#d0d7de"),
339 ("border-style", "solid"),
340 ("border-width", "1px"),
341 ("border-radius", "6px"),
342 ("padding", "12px"),
343 ("margin-top", "10px"),
344 ("margin-bottom", "10px"),
345 ]);
346 }
347 "code" => {
348 insert_ua_pairs(&mut specified_values, &[
349 ("display", "inline"),
350 ("background-color", "#f0f0f0"),
351 ("color", "#d63384"),
352 ("padding", "2px 4px"),
353 ("border-radius", "3px"),
354 ]);
355 }
356 "blockquote" => {
357 insert_ua_pairs(&mut specified_values, &[
358 ("display", "block"),
359 ("margin-left", "20px"),
360 ("margin-right", "20px"),
361 ("margin-top", "10px"),
362 ("margin-bottom", "10px"),
363 ("border-left", "4px solid #BD93F9"),
364 ]);
365 }
366 "div" | "html" | "form" | "header" | "footer" | "main" | "section" | "nav"
367 | "hr" | "article" | "aside" | "figure" | "figcaption" | "fieldset" | "legend"
368 | "dl" | "dt" | "dd" | "details" | "summary" => {
369 specified_values.insert(String::from("display"), String::from("block"));
370 }
371 "dialog" => {
376 specified_values.insert(
377 String::from("display"),
378 if attributes.contains_key("open") {
379 String::from("block")
380 } else {
381 String::from("none")
382 },
383 );
384 }
385 "select" | "video" | "audio" | "iframe" | "svg" | "canvas" => {
388 specified_values.insert(String::from("display"), String::from("inline-block"));
389 }
390 "center" => {
391 insert_ua_pairs(&mut specified_values, &[
392 ("display", "block"),
393 ("text-align", "center"),
394 ]);
395 }
396 "img" => {
397 specified_values.insert(String::from("display"), String::from("inline-block"));
398 }
399 "a" => {
400 insert_ua_pairs(&mut specified_values, &[
401 ("display", "inline"),
402 ("text-decoration", "underline"),
403 ]);
404 if attributes.contains_key("href") {
408 specified_values.insert(String::from("color"), String::from("-webkit-link"));
409 }
410 }
411 "strong" | "b" => {
412 insert_ua_pairs(&mut specified_values, &[
413 ("display", "inline"),
414 ("font-weight", "bold"),
415 ]);
416 }
417 "em" | "i" => {
418 insert_ua_pairs(&mut specified_values, &[
419 ("display", "inline"),
420 ("font-style", "italic"),
421 ]);
422 }
423 "u" => {
424 insert_ua_pairs(&mut specified_values, &[
425 ("display", "inline"),
426 ("text-decoration", "underline"),
427 ]);
428 }
429 "s" | "del" | "strike" => {
430 insert_ua_pairs(&mut specified_values, &[
431 ("display", "inline"),
432 ("text-decoration", "line-through"),
433 ]);
434 }
435 "input" | "textarea" | "button" => {
436 insert_ua_pairs(&mut specified_values, &[
437 ("display", "inline-block"),
438 ("border-radius", "6px"),
439 ]);
440 }
441 "table" => {
442 insert_ua_pairs(&mut specified_values, &[
443 ("display", "table"),
444 ("border-color", "#6272A4"),
445 ]);
446 }
447 "thead" | "tbody" | "tfoot" => {
448 specified_values
449 .insert(String::from("display"), String::from("table-row-group"));
450 }
451 "tr" => {
452 specified_values.insert(String::from("display"), String::from("table-row"));
453 }
454 "td" => {
455 insert_ua_pairs(&mut specified_values, &[
456 ("display", "table-cell"),
457 ("padding", "4px 8px"),
458 ]);
459 }
460 "th" => {
461 insert_ua_pairs(&mut specified_values, &[
462 ("display", "table-cell"),
463 ("padding", "4px 8px"),
464 ("font-weight", "bold"),
465 ("text-align", "center"),
466 ]);
467 }
468 "caption" => {
469 insert_ua_pairs(&mut specified_values, &[
470 ("display", "block"),
471 ("text-align", "center"),
472 ]);
473 }
474 "head" | "title" | "meta" | "link" | "style" | "script" | "template"
477 | "noscript" => {
478 specified_values.insert(String::from("display"), String::from("none"));
479 }
480 _ => {
481 specified_values.insert(String::from("display"), String::from("inline"));
482 }
483 }
484
485 if matches!(
496 tag_name.as_str(),
497 "img" | "iframe" | "video" | "embed" | "object" | "canvas"
498 ) {
499 if let Some(w) = attributes.get("width").and_then(|v| html_legacy_dimension_to_css(v)) {
500 specified_values.insert(String::from("width"), w);
501 }
502 if let Some(h) = attributes.get("height").and_then(|v| html_legacy_dimension_to_css(v)) {
503 specified_values.insert(String::from("height"), h);
504 }
505 }
506
507 if attributes.contains_key("hidden") {
512 specified_values.insert(String::from("display"), String::from("none"));
513 }
514
515 if attributes.contains_key("popover") && !attributes.contains_key("_popover_open") {
522 specified_values.insert(String::from("display"), String::from("none"));
523 }
524
525 if let Some(parent_ctx) = ancestors.last() {
527 if let NodeType::Element { tag_name: parent_tag, attributes: parent_attrs, .. } = &parent_ctx.node.node_type {
528 if parent_tag == "details" && !parent_attrs.contains_key("open") {
529 let mut first_summary_idx = None;
531 for (idx, child) in parent_ctx.node.children.iter().enumerate() {
532 if let NodeType::Element { tag_name: child_tag, .. } = &child.node_type {
533 if child_tag == "summary" {
534 first_summary_idx = Some(idx);
535 break;
536 }
537 }
538 }
539
540 let is_first_summary = if let Some(fs_idx) = first_summary_idx {
541 sibling_index == Some(fs_idx)
542 } else {
543 false
544 };
545
546 if !is_first_summary {
547 specified_values.insert(String::from("display"), String::from("none"));
548 }
549 }
550 }
551 }
552
553 if attributes.contains_key("inert") {
562 specified_values.insert(String::from("pointer-events"), String::from("none"));
563 }
564
565 if let Some(dv) = attributes.get("dir").map(|v| v.trim().to_lowercase()) {
571 if dv == "ltr" || dv == "rtl" {
572 specified_values.insert(String::from("direction"), dv);
573 }
574 }
575
576 let mut applied: BTreeMap<String, AppliedDecl> = BTreeMap::new();
579
580 let node_ctx = crate::os_lib::css::selector::NodeMatchContext::new(
587 root,
588 ancestors,
589 sibling_index,
590 );
591
592 let rule_orders: alloc::vec::Vec<usize> = match &root.node_type {
598 NodeType::Element {
599 tag_name,
600 classes,
601 id,
602 ..
603 } => {
604 if let Some(idx) = rule_index {
605 idx.candidates(tag_name.as_str(), id.as_deref(), classes.as_slice())
606 .unwrap_or_else(|_| (0..stylesheet.rules.len()).collect())
607 } else {
608 (0..stylesheet.rules.len()).collect()
609 }
610 }
611 _ => alloc::vec::Vec::new(),
613 };
614
615 for rule_order in rule_orders {
616 let rule = &stylesheet.rules[rule_order];
617 if let Some(q) = &rule.media_query {
624 if !crate::os_lib::css::media_query_matches(q) {
625 continue;
626 }
627 }
628 if let Some(q) = &rule.container_query {
638 let mut cw = crate::os_lib::layout::CONTAINER_HINT_W
639 .load(core::sync::atomic::Ordering::Relaxed);
640 let mut ch = crate::os_lib::layout::CONTAINER_HINT_H
641 .load(core::sync::atomic::Ordering::Relaxed);
642 if cw <= 0 {
643 cw = crate::os_lib::layout::VIEWPORT_HINT_W
644 .load(core::sync::atomic::Ordering::Relaxed);
645 }
646 if ch <= 0 {
647 ch = crate::os_lib::layout::VIEWPORT_HINT_H
648 .load(core::sync::atomic::Ordering::Relaxed);
649 }
650 let size = crate::os_lib::css::container_query::ContainerSize {
651 width: cw,
652 height: ch,
653 };
654 let all_ok = q.split("&&").all(|one| {
656 let cond = one.split_once('|').map(|(_, c)| c).unwrap_or(one);
659 crate::os_lib::css::container_query::condition_matches(cond, size)
660 });
661 if !all_ok {
662 continue;
663 }
664 }
665 let mut matched_spec: Option<(u32, u32, u32)> = None;
666 let mut matched_pseudo_el: Option<PseudoElement> = None;
667 for selector in &rule.selectors {
668 if matches_selector(
669 selector,
670 root,
671 ancestors,
672 sibling_index,
673 sibling_count,
674 active_pseudo_states,
675 &node_ctx,
676 ) {
677 let spec = selector_specificity(selector);
678 if matched_spec.is_none_or(|cur| spec > cur) {
679 matched_spec = Some(spec);
680 matched_pseudo_el = selector_pseudo_element(selector);
681 }
682 }
683 }
684 if let Some(spec) = matched_spec {
685 for decl in &rule.declarations {
694 let key: Option<alloc::borrow::Cow<'_, str>> = match matched_pseudo_el {
695 Some(PseudoElement::Before) => {
699 Some(alloc::borrow::Cow::Owned(alloc::format!("x-before-{}", decl.name)))
700 }
701 Some(PseudoElement::After) => {
702 Some(alloc::borrow::Cow::Owned(alloc::format!("x-after-{}", decl.name)))
703 }
704 Some(PseudoElement::Marker) => {
705 matches!(decl.name.as_str(), "color" | "font-size" | "content")
706 .then(|| alloc::borrow::Cow::Owned(alloc::format!("x-marker-{}", decl.name)))
707 }
708 Some(PseudoElement::Placeholder) => {
709 (decl.name == "color").then(|| alloc::borrow::Cow::Borrowed("x-placeholder-color"))
710 }
711 Some(PseudoElement::FirstLine) => matches!(
712 decl.name.as_str(),
713 "color" | "font-size" | "font-weight" | "font-style"
714 )
715 .then(|| alloc::borrow::Cow::Owned(alloc::format!("x-first-line-{}", decl.name))),
716 Some(PseudoElement::FirstLetter) => matches!(
717 decl.name.as_str(),
718 "color" | "font-size" | "font-weight" | "font-style"
719 )
720 .then(|| alloc::borrow::Cow::Owned(alloc::format!("x-first-letter-{}", decl.name))),
721 Some(PseudoElement::Selection) => matches!(
722 decl.name.as_str(),
723 "color" | "background-color" | "background"
724 )
725 .then(|| alloc::borrow::Cow::Owned(alloc::format!("x-selection-{}", decl.name))),
726 None => Some(alloc::borrow::Cow::Borrowed(decl.name.as_str())),
727 };
728 let Some(key) = key else {
729 continue;
730 };
731 let should_replace = match applied.get(key.as_ref()) {
732 Some((cur_imp, cur_layer, cur_spec, cur_order, _)) => {
733 if decl.important != *cur_imp {
734 decl.important } else if decl.important {
736 let cur_rank = cur_layer.map(|l| -(l as i64)).unwrap_or(i64::MIN);
738 let new_rank = rule.layer.map(|l| -(l as i64)).unwrap_or(i64::MIN);
739 new_rank > cur_rank
740 || (new_rank == cur_rank
741 && (spec > *cur_spec
742 || (spec == *cur_spec && rule_order >= *cur_order)))
743 } else {
744 let cur_rank = cur_layer.map(|l| l as i64).unwrap_or(i64::MAX);
747 let new_rank = rule.layer.map(|l| l as i64).unwrap_or(i64::MAX);
748 new_rank > cur_rank
749 || (new_rank == cur_rank
750 && (spec > *cur_spec
751 || (spec == *cur_spec && rule_order >= *cur_order)))
752 }
753 }
754 None => true,
755 };
756 if should_replace {
757 applied.insert(
761 key.into_owned(),
762 (decl.important, rule.layer, spec, rule_order, decl.value.clone()),
763 );
764 }
765 }
766 }
767 }
768 drop_overridden_longhands(&mut applied, &mut specified_values);
773 let mut important_keys: alloc::collections::BTreeSet<String> =
774 alloc::collections::BTreeSet::new();
775 for (k, (is_important, _, _, _, _)) in &applied {
776 if *is_important {
777 important_keys.insert(k.clone());
778 }
779 }
780 for (k, (_, _, _, _, v)) in applied {
781 if k.starts_with("--") {
782 alloc::rc::Rc::make_mut(&mut css_vars).insert(k, v);
783 } else {
784 let resolved = if v.contains("var(") {
785 resolve_css_var(&v, &css_vars)
786 } else {
787 v
788 };
789 let resolved = if resolved.contains("env(") {
790 resolve_css_env(&resolved)
791 } else {
792 resolved
793 };
794 specified_values.insert(k, resolved);
795 }
796 }
797
798 if let Some(style_attr) = attributes.get("style") {
799 for decl in style_attr.split(';') {
800 let mut parts = decl.splitn(2, ':');
801 if let (Some(k), Some(v)) = (parts.next(), parts.next()) {
802 let k_trim = k.trim().to_string();
803 let raw_value = v.trim();
804 let (v_trim, inline_important) = match raw_value.rfind('!') {
806 Some(pos)
807 if raw_value
808 .get(pos + 1..)
809 .unwrap_or("")
810 .trim()
811 .eq_ignore_ascii_case("important") =>
812 {
813 (
814 String::from(raw_value.get(..pos).unwrap_or("").trim()),
815 true,
816 )
817 }
818 _ => (String::from(raw_value), false),
819 };
820 if v_trim.is_empty() && !k_trim.starts_with("--") {
824 continue;
825 }
826 if !k_trim.is_empty() {
827 if important_keys.contains(&k_trim) && !inline_important {
830 continue;
831 }
832 if k_trim.starts_with("--") {
833 alloc::rc::Rc::make_mut(&mut css_vars).insert(k_trim, v_trim);
834 } else {
835 let resolved = if v_trim.contains("var(") {
836 resolve_css_var(&v_trim, &css_vars)
837 } else {
838 v_trim
839 };
840 let resolved = if resolved.contains("env(") {
841 resolve_css_env(&resolved)
842 } else {
843 resolved
844 };
845 if let Some((_, longs)) =
848 BOX_SHORTHANDS.iter().find(|(sh, _)| *sh == k_trim.as_str())
849 {
850 for long in longs {
851 if !important_keys.contains(*long) {
852 specified_values.remove(*long);
853 }
854 }
855 }
856 specified_values.insert(k_trim, resolved);
857 }
858 }
859 }
860 }
861 }
862 expand_border_shorthands(&mut specified_values);
863 expand_place_shorthands(&mut specified_values);
864 expand_grid_template_shorthand(&mut specified_values);
865 expand_text_emphasis_shorthand(&mut specified_values);
866 expand_font_shorthand(&mut specified_values);
867 expand_background_shorthand(&mut specified_values);
868
869 counters_reset_names = apply_counter_decls(&specified_values, &mut counters_out);
872 let mut stack_buf;
873 let current_stack = if let Some(st) = counter_stack {
874 stack_buf = st.clone();
875 crate::os_lib::css::counter_scope::apply_counter_decls_to_stack(&specified_values, &mut stack_buf);
876 Some(&stack_buf)
877 } else {
878 None
879 };
880 let quotes_pairs = specified_values
884 .get("quotes")
885 .and_then(|v| parse_quotes_pairs(v));
886 let quote_depth = ancestors
892 .iter()
893 .filter(|a| {
894 matches!(&a.node.node_type, NodeType::Element { tag_name, .. } if tag_name == "q")
895 })
896 .count();
897 for key in ["x-before-content", "x-after-content", "x-marker-content"] {
898 if let Some(raw) = specified_values.get(key).cloned() {
899 if key != "x-marker-content"
902 && matches!(raw.trim().to_ascii_lowercase().as_str(), "none" | "normal")
903 {
904 specified_values.remove(key);
905 continue;
906 }
907 let resolved = crate::os_lib::css::selector::parse_content_value_with_stack(
908 &raw,
909 attributes,
910 &counters_out,
911 current_stack,
912 quotes_pairs.as_deref(),
913 quote_depth,
914 &stylesheet.counter_styles,
915 );
916 specified_values.insert(String::from(key), resolved);
917 }
918 }
919 }
920 NodeType::Text(_) => {
921 specified_values.insert(String::from("display"), String::from("inline"));
922 }
923 }
924 (specified_values, css_vars, counters_out, counters_reset_names)
925}
926
927pub(crate) const INHERITED_PROPS: &[&str] = &[
929 "color",
930 "-webkit-text-fill-color",
931 "font-family",
939 "font-size",
940 "font-weight",
941 "font-style",
942 "line-height",
943 "text-align",
944 "letter-spacing",
945 "word-spacing",
946 "white-space",
947 "text-transform",
948 "list-style-type",
949 "list-style-image",
950 "list-style-position",
951 "list-style",
952 "tab-size",
953 "direction",
954 "visibility",
955 "text-indent",
960 "border-collapse",
964 "text-emphasis-style",
966 "text-emphasis-color",
967 "cursor",
969 "word-break",
971 "overflow-wrap",
972 "word-wrap",
973];
974
975pub(crate) fn style_tree_with_ancestors<'a>(
976 root: &'a Node,
977 stylesheet: &StyleSheet,
978 ancestors: &[AncestorContext<'a>],
979 sibling_index: Option<usize>,
980 sibling_count: Option<usize>,
981) -> StyledNode<'a> {
982 let mut stack = ancestors.to_vec();
983 style_tree_with_ancestors_vars(
984 root,
985 stylesheet,
986 None,
987 &mut stack,
988 sibling_index,
989 sibling_count,
990 &alloc::rc::Rc::new(BTreeMap::new()),
991 &alloc::rc::Rc::new(BTreeMap::new()),
992 &BTreeMap::new(),
993 )
994 .0
995}
996
997pub(crate) fn style_tree_with_ancestors_vars<'a>(
1001 root: &'a Node,
1002 stylesheet: &StyleSheet,
1003 rule_index: Option<&super::rule_index::RuleIndex>,
1004 ancestors_stack: &mut Vec<AncestorContext<'a>>,
1005 sibling_index: Option<usize>,
1006 sibling_count: Option<usize>,
1007 parent_vars: &alloc::rc::Rc<BTreeMap<String, String>>,
1008 parent_inherited: &alloc::rc::Rc<BTreeMap<String, String>>,
1009 counters_in: &BTreeMap<String, i64>,
1010) -> (StyledNode<'a>, BTreeMap<String, i64>, alloc::collections::BTreeSet<String>) {
1011 let stack_init = crate::os_lib::css::counter_scope::CounterScopeStack::from_map(counters_in);
1012 let (node, counters_out, resets, _final_stack) = style_tree_with_ancestors_vars_stack(
1013 root,
1014 stylesheet,
1015 rule_index,
1016 ancestors_stack,
1017 sibling_index,
1018 sibling_count,
1019 parent_vars,
1020 parent_inherited,
1021 counters_in,
1022 &stack_init,
1023 );
1024 (node, counters_out, resets)
1025}
1026
1027pub(crate) fn style_tree_with_ancestors_vars_stack<'a>(
1028 root: &'a Node,
1029 stylesheet: &StyleSheet,
1030 rule_index: Option<&super::rule_index::RuleIndex>,
1031 ancestors_stack: &mut Vec<AncestorContext<'a>>,
1032 sibling_index: Option<usize>,
1033 sibling_count: Option<usize>,
1034 parent_vars: &alloc::rc::Rc<BTreeMap<String, String>>,
1035 parent_inherited: &alloc::rc::Rc<BTreeMap<String, String>>,
1036 counters_in: &BTreeMap<String, i64>,
1037 counter_stack_in: &crate::os_lib::css::counter_scope::CounterScopeStack,
1038) -> (
1039 StyledNode<'a>,
1040 BTreeMap<String, i64>,
1041 alloc::collections::BTreeSet<String>,
1042 crate::os_lib::css::counter_scope::CounterScopeStack,
1043) {
1044 let (mut specified_values, css_vars, counters_after_self, counters_reset_self) = compute_styles_with_vars_stack(
1045 root,
1046 stylesheet,
1047 rule_index,
1048 ancestors_stack.as_slice(),
1049 sibling_index,
1050 sibling_count,
1051 &[],
1052 parent_vars,
1053 counters_in,
1054 Some(counter_stack_in),
1055 );
1056
1057 resolve_all_shorthand(&mut specified_values);
1060 strip_css_wide_keywords(&mut specified_values);
1066
1067 for (k, v) in parent_inherited.iter() {
1071 if !specified_values.contains_key(k) {
1072 specified_values.insert(k.clone(), v.clone());
1073 }
1074 }
1075 expand_logical_box_shorthands(&mut specified_values);
1082 resolve_logical_properties(&mut specified_values);
1085 resolve_current_color(&mut specified_values);
1088 let (uses_hover, uses_focus, uses_active) = match &root.node_type {
1100 NodeType::Element {
1101 tag_name,
1102 classes,
1103 id,
1104 ..
1105 } => {
1106 if let Some(idx) = rule_index {
1107 match idx.candidates(tag_name.as_str(), id.as_deref(), classes.as_slice()) {
1108 Ok(cands) => match idx.states_of(&cands) {
1109 Some(m) => (
1110 m & super::rule_index::STATE_HOVER != 0,
1111 m & super::rule_index::STATE_FOCUS != 0,
1112 m & super::rule_index::STATE_ACTIVE != 0,
1113 ),
1114 None => (true, true, true),
1115 },
1116 Err(_) => (true, true, true),
1117 }
1118 } else {
1119 (true, true, true)
1120 }
1121 }
1122 _ => (false, false, false),
1125 };
1126
1127 let hover_opt = if !uses_hover {
1128 None
1129 } else {
1130 let (hover_values_raw, _, _, _) = compute_styles_with_vars(
1131 root,
1132 stylesheet,
1133 rule_index,
1134 ancestors_stack.as_slice(),
1135 sibling_index,
1136 sibling_count,
1137 &["hover"],
1138 &css_vars,
1139 &counters_after_self,
1140 );
1141 let mut has_hover_diff = false;
1142 for (k, v) in &hover_values_raw {
1143 if specified_values.get(k) != Some(v) {
1144 has_hover_diff = true;
1145 break;
1146 }
1147 }
1148 if has_hover_diff {
1149 Some(hover_values_raw)
1150 } else {
1151 None
1152 }
1153 };
1154
1155 let focus_opt = if !uses_focus {
1156 None
1157 } else {
1158 let (focus_values_raw, _, _, _) = compute_styles_with_vars(
1159 root,
1160 stylesheet,
1161 rule_index,
1162 ancestors_stack.as_slice(),
1163 sibling_index,
1164 sibling_count,
1165 &["focus"],
1166 &css_vars,
1167 &counters_after_self,
1168 );
1169 let mut has_focus_diff = false;
1170 for (k, v) in &focus_values_raw {
1171 if specified_values.get(k) != Some(v) {
1172 has_focus_diff = true;
1173 break;
1174 }
1175 }
1176 if has_focus_diff {
1177 Some(focus_values_raw)
1178 } else {
1179 None
1180 }
1181 };
1182
1183 let active_opt = if !uses_active {
1184 None
1185 } else {
1186 let (active_values_raw, _, _, _) = compute_styles_with_vars(
1187 root,
1188 stylesheet,
1189 rule_index,
1190 ancestors_stack.as_slice(),
1191 sibling_index,
1192 sibling_count,
1193 &["active"],
1194 &css_vars,
1195 &counters_after_self,
1196 );
1197 let mut has_active_diff = false;
1198 for (k, v) in &active_values_raw {
1199 if specified_values.get(k) != Some(v) {
1200 has_active_diff = true;
1201 break;
1202 }
1203 }
1204 if has_active_diff {
1205 Some(active_values_raw)
1206 } else {
1207 None
1208 }
1209 };
1210
1211 let mut has_any_new_inherited = false;
1213 for k in INHERITED_PROPS {
1214 if let Some(v) = specified_values.get(*k) {
1215 if parent_inherited.get(*k) != Some(v) {
1216 has_any_new_inherited = true;
1217 break;
1218 }
1219 }
1220 }
1221 let inherited_for_children = if !has_any_new_inherited {
1222 parent_inherited.clone()
1223 } else {
1224 let mut map = BTreeMap::new();
1225 for k in INHERITED_PROPS {
1226 if let Some(v) = specified_values.get(*k) {
1227 map.insert(String::from(*k), v.clone());
1228 }
1229 }
1230 alloc::rc::Rc::new(map)
1231 };
1232
1233 ancestors_stack.push(AncestorContext {
1234 node: root,
1235 sibling_index,
1236 sibling_count,
1237 });
1238 let child_count = root.children.len();
1239 let mut counters_running = counters_after_self;
1240 let mut stack_running = counter_stack_in.clone();
1241 let pushed_resets_self = crate::os_lib::css::counter_scope::apply_counter_decls_to_stack(&specified_values, &mut stack_running);
1242 let mut children = alloc::vec::Vec::with_capacity(child_count);
1243 for (idx, child) in root.children.iter().enumerate() {
1244 let counters_before_child = if counters_running.is_empty() {
1245 None
1246 } else {
1247 Some(counters_running.clone())
1248 };
1249 let (child_styled, counters_after_child, reset_names_in_child, stack_after_child) =
1250 style_tree_with_ancestors_vars_stack(
1251 child,
1252 stylesheet,
1253 rule_index,
1254 ancestors_stack,
1255 Some(idx),
1256 Some(child_count),
1257 &css_vars,
1258 &inherited_for_children,
1259 &counters_running,
1260 &stack_running,
1261 );
1262 counters_running = counters_after_child;
1263 stack_running = stack_after_child;
1264 if !reset_names_in_child.is_empty() {
1265 if let Some(before) = &counters_before_child {
1266 for name in &reset_names_in_child {
1267 match before.get(name) {
1268 Some(v) => {
1269 counters_running.insert(name.clone(), *v);
1270 }
1271 None => {
1272 counters_running.remove(name);
1273 }
1274 }
1275 }
1276 } else {
1277 for name in &reset_names_in_child {
1278 counters_running.remove(name);
1279 }
1280 }
1281 }
1282 children.push(child_styled);
1283 }
1284 ancestors_stack.pop();
1285
1286 for name in &pushed_resets_self {
1287 stack_running.pop_reset(name);
1288 }
1289
1290 (
1291 StyledNode {
1292 node: root,
1293 specified_values,
1294 hover_values: hover_opt,
1295 focus_values: focus_opt,
1296 active_values: active_opt,
1297 css_vars: css_vars.clone(),
1298 children,
1299 edges_cache: Default::default(),
1300 },
1301 counters_running,
1302 counters_reset_self,
1303 stack_running,
1304 )
1305}
1306