1use crate::node::{ImageData, NodeData, SpecialElementData};
8use crate::{document::BaseDocument, dom_node_id, node::Node, taffy_node_id};
9use markup5ever::local_name;
10use std::cell::Ref;
11use std::sync::Arc;
12use style::Atom;
13use style::values::computed::CSSPixelLength;
14use style::values::computed::length_percentage::CalcLengthPercentage;
15use taffy::{
16 BlockContext, CollapsibleMarginSet, FlexDirection, LayoutPartialTree, MaybeResolve, NodeId,
17 ResolveOrZero, RoundTree, Style, TraversePartialTree, TraverseTree, compute_block_layout,
18 compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_leaf_layout,
19 prelude::*,
20};
21
22#[cfg(not(target_arch = "wasm32"))]
37pub(crate) mod layout_panic_probe {
38 use std::cell::RefCell;
39 use std::sync::OnceLock;
40
41 thread_local! {
42 static IN_FLIGHT: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
43 static INNERMOST: std::cell::Cell<Option<blitz_traits::node_id::NodeId>> =
46 const { std::cell::Cell::new(None) };
47 }
48
49 pub(crate) fn enabled() -> bool {
50 static ENABLED: OnceLock<bool> = OnceLock::new();
51 *ENABLED.get_or_init(|| {
52 let on = std::env::var_os("BLITZ_TRACE_LAYOUT_PANIC").is_some();
53 if on {
54 install_hook();
55 }
56 on
57 })
58 }
59
60 fn install_hook() {
64 let previous = std::panic::take_hook();
65 std::panic::set_hook(Box::new(move |info| {
66 IN_FLIGHT.with(|stack| {
67 let stack = stack.borrow();
68 if stack.is_empty() {
69 eprintln!("[blitz-layout-panic] no layout in flight on this thread");
70 } else {
71 eprintln!("[blitz-layout-panic] innermost first:");
72 for entry in stack.iter().rev().take(12) {
73 eprintln!("[blitz-layout-panic] {entry}");
74 }
75 eprintln!("[blitz-layout-panic] ({} deep)", stack.len());
76 }
77 });
78 previous(info);
79 }));
80 }
81
82 const RUNAWAY_DEPTH: usize = 512;
85
86 pub(crate) fn innermost_node() -> Option<blitz_traits::node_id::NodeId> {
89 INNERMOST.with(std::cell::Cell::get)
90 }
91
92 pub(crate) fn push(node_id: blitz_traits::node_id::NodeId, description: String) {
93 INNERMOST.with(|cell| cell.set(Some(node_id)));
94 IN_FLIGHT.with(|stack| {
95 let mut stack = stack.borrow_mut();
96 stack.push(description);
97 if stack.len() == RUNAWAY_DEPTH {
98 eprintln!(
105 "[blitz-layout-panic] runaway: {RUNAWAY_DEPTH} nested layouts, innermost first:"
106 );
107 for entry in stack.iter().rev().take(24) {
108 eprintln!("[blitz-layout-panic] {entry}");
109 }
110 }
111 });
112 }
113
114 pub(crate) fn pop() {
115 IN_FLIGHT.with(|stack| {
116 stack.borrow_mut().pop();
117 });
118 }
119}
120
121#[cfg(feature = "log-phase-times")]
128pub mod layout_counters {
129 use blitz_traits::node_id::NodeId;
130 use std::cell::Cell;
131
132 thread_local! {
133 static ACTIVE: Cell<bool> = const { Cell::new(false) };
134 static COMPUTED: Cell<u64> = const { Cell::new(0) };
135 static CACHES_CLEARED: Cell<u64> = const { Cell::new(0) };
136 static LOOKUPS: Cell<u64> = const { Cell::new(0) };
137 static HITS: Cell<u64> = const { Cell::new(0) };
138 static DISTINCT: std::cell::RefCell<std::collections::HashMap<NodeId, u32>> =
142 std::cell::RefCell::new(std::collections::HashMap::new());
143 }
144
145 pub(crate) fn begin(active: bool) {
147 ACTIVE.with(|enabled| enabled.set(active));
148 if !active {
149 return;
150 }
151 COMPUTED.with(|count| count.set(0));
152 CACHES_CLEARED.with(|count| count.set(0));
153 LOOKUPS.with(|count| count.set(0));
154 HITS.with(|count| count.set(0));
155 DISTINCT.with(|seen| seen.borrow_mut().clear());
156 }
157
158 #[inline(always)]
159 fn active() -> bool {
160 ACTIVE.with(Cell::get)
161 }
162
163 pub(crate) fn note_computed(node_id: NodeId) {
164 if !active() {
165 return;
166 }
167 COMPUTED.with(|count| count.set(count.get() + 1));
168 DISTINCT.with(|seen| {
169 *seen.borrow_mut().entry(node_id).or_insert(0u32) += 1;
170 });
171 }
172
173 pub(crate) fn worst_offenders(limit: usize) -> Vec<(NodeId, u32)> {
180 DISTINCT.with(|seen| {
181 let mut rows: Vec<(NodeId, u32)> = seen
182 .borrow()
183 .iter()
184 .map(|(id, count)| (*id, *count))
185 .collect();
186 rows.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
187 rows.truncate(limit);
188 rows
189 })
190 }
191
192 pub(crate) fn note_cache_cleared() {
193 if !active() {
194 return;
195 }
196 CACHES_CLEARED.with(|count| count.set(count.get() + 1));
197 }
198
199 pub(crate) fn note_lookup(hit: bool) {
200 if !active() {
201 return;
202 }
203 LOOKUPS.with(|count| count.set(count.get() + 1));
204 if hit {
205 HITS.with(|count| count.set(count.get() + 1));
206 }
207 }
208
209 #[derive(Clone, Copy)]
213 pub struct LayoutCounts {
214 pub computed: u64,
215 pub distinct: usize,
216 pub caches_cleared: u64,
217 pub lookups: u64,
218 pub hits: u64,
219 }
220
221 impl LayoutCounts {
222 const ZERO: Self = Self {
223 computed: 0,
224 distinct: 0,
225 caches_cleared: 0,
226 lookups: 0,
227 hits: 0,
228 };
229 }
230
231 thread_local! {
232 static LAST: Cell<LayoutCounts> = const { Cell::new(LayoutCounts::ZERO) };
236 }
237
238 #[must_use]
240 pub fn last() -> LayoutCounts {
241 LAST.with(Cell::get)
242 }
243
244 pub fn take() -> LayoutCounts {
246 if !active() {
247 LAST.with(|last| last.set(LayoutCounts::ZERO));
248 return LayoutCounts::ZERO;
249 }
250 let counts = LayoutCounts {
251 computed: COMPUTED.with(|count| count.replace(0)),
252 distinct: DISTINCT.with(|seen| {
253 let mut seen = seen.borrow_mut();
254 let len = seen.len();
255 seen.clear();
256 len
257 }),
258 caches_cleared: CACHES_CLEARED.with(|count| count.replace(0)),
259 lookups: LOOKUPS.with(|count| count.replace(0)),
260 hits: HITS.with(|count| count.replace(0)),
261 };
262 ACTIVE.with(|active| active.set(false));
263 LAST.with(|last| last.set(counts));
264 counts
265 }
266}
267
268pub(crate) mod abspos;
269pub(crate) mod construct;
270pub(crate) mod damage;
271pub(crate) mod inline;
272pub(crate) mod list;
273pub(crate) mod replaced;
274#[cfg(feature = "svg")]
275pub(crate) mod svg_geometry;
276pub(crate) mod table;
277
278use self::replaced::{ReplacedContext, is_replaced_element, replaced_measure_function};
279use self::table::TableTreeWrapper;
280
281pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
282 let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
283 let result = calc.resolve(CSSPixelLength::new(parent_size));
284 result.px()
285}
286
287impl BaseDocument {
288 fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
289 &self.nodes[dom_node_id(node_id)]
290 }
291 fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
292 &mut self.nodes[dom_node_id(node_id)]
293 }
294
295 #[cfg(not(target_arch = "wasm32"))]
299 fn describe_node_for_panic(
300 &self,
301 node_id: blitz_traits::node_id::NodeId,
302 inputs: &taffy::LayoutInput,
303 ) -> String {
304 let Some(node) = self.nodes.get(node_id) else {
305 return format!("node {node_id} (gone)");
306 };
307 let Some(element) = node.data.downcast_element() else {
308 return format!("node {node_id} <{:?}>", node.data.kind());
309 };
310 let attr = |name: &str| -> Option<&str> {
311 element
312 .attrs
313 .iter()
314 .find(|a| a.name.local.as_ref() == name)
315 .map(|a| a.value.as_ref())
316 };
317 format!(
321 "node {node_id} <{}{}{}> known={:?}x{:?} avail={:?}x{:?} mode={:?}/{:?}",
322 element.name.local,
323 attr("id").map(|v| format!(" id={v}")).unwrap_or_default(),
324 attr("class")
325 .map(|v| format!(" class=\"{}\"", &v[..v.len().min(160)]))
326 .unwrap_or_default(),
327 inputs.known_dimensions.width,
328 inputs.known_dimensions.height,
329 inputs.available_space.width,
330 inputs.available_space.height,
331 inputs.run_mode,
332 inputs.axis,
333 )
334 }
335}
336
337fn select_metrics_of(
354 doc: &BaseDocument,
355 node_id: blitz_traits::node_id::NodeId,
356) -> Option<(usize, f32)> {
357 let node = doc.nodes.get(node_id)?;
358 let element = node.data.downcast_element()?;
359 if element.name.local != local_name!("select") {
360 return None;
361 }
362
363 let widest = doc
364 .select_options(node_id)
365 .into_iter()
366 .map(|option_id| doc.option_label(option_id).chars().count())
367 .max()
368 .unwrap_or(0);
369
370 let rows = element
373 .attr(local_name!("size"))
374 .and_then(|size| size.parse::<f32>().ok())
375 .filter(|rows| *rows >= 1.0)
376 .unwrap_or(if element.attr(local_name!("multiple")).is_some() {
377 4.0
378 } else {
379 1.0
380 });
381
382 Some((widest, rows))
383}
384
385impl BaseDocument {
386 fn select_metrics(&self, node_id: blitz_traits::node_id::NodeId) -> Option<(usize, f32)> {
387 select_metrics_of(self, node_id)
388 }
389
390 fn compute_child_layout_internal(
391 &mut self,
392 node_id: NodeId,
393 inputs: taffy::tree::LayoutInput,
394 block_ctx: Option<&mut BlockContext<'_>>,
395 ) -> taffy::tree::LayoutOutput {
396 #[cfg(feature = "log-phase-times")]
401 layout_counters::note_computed(dom_node_id(node_id));
402
403 if inputs.run_mode == taffy::RunMode::PerformLayout
404 && dom_node_id(node_id) == self.root_element().id
405 {
406 self.abspos_normal_layout = true;
407 }
408
409 let select_metrics = self.select_metrics(dom_node_id(node_id));
412
413 let node = &mut self.nodes[dom_node_id(node_id)];
414
415 let font_styles = node.primary_styles().map(|style| {
416 use style::values::computed::font::LineHeight;
417
418 let font_size = style.clone_font_size().used_size().px();
419 let line_height = match style.clone_line_height() {
420 LineHeight::Normal => font_size * 1.2,
421 LineHeight::Number(num) => font_size * num.0,
422 LineHeight::Length(value) => value.0.px(),
423 };
424
425 (font_size, line_height)
426 });
427 let font_size = font_styles.map(|s| s.0);
428 let resolved_line_height = font_styles.map(|s| s.1);
429
430 match &mut node.data {
431 NodeData::Text(data) => {
432 #[cfg(feature = "tracing")]
435 tracing::error!(
436 node_id = ?dom_node_id(node_id),
437 data = ?data,
438 "Tried to lay out text node individually",
439 );
440
441 #[cfg(not(feature = "tracing"))]
442 let _ = data;
443
444 taffy::LayoutOutput::HIDDEN
445 }
464 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
465 if let Some((widest_label, rows)) = select_metrics {
472 let advance = font_size.unwrap_or(16.0) * 0.6;
473 let line_height = resolved_line_height.unwrap_or(16.0);
474 return compute_leaf_layout(
475 inputs,
476 node.style(),
477 resolve_calc_value,
478 |_known_size, _available_space| taffy::Size {
479 width: widest_label as f32 * advance,
480 height: line_height * rows,
481 },
482 );
483 }
484
485 if *element_data.name.local == *"textarea" {
487 let rows = element_data
488 .attr(local_name!("rows"))
489 .and_then(|val| val.parse::<f32>().ok())
490 .unwrap_or(2.0);
491
492 let cols = element_data
493 .attr(local_name!("cols"))
494 .and_then(|val| val.parse::<f32>().ok());
495
496 let intrinsic_height = resolved_line_height.unwrap_or(16.0) * rows;
497
498 let content_width = node
511 .style()
512 .size
513 .width
514 .maybe_resolve(inputs.parent_size.width, resolve_calc_value)
515 .or(inputs.known_dimensions.width)
516 .or(match inputs.available_space.width {
517 taffy::AvailableSpace::Definite(width) => Some(width),
518 _ => None,
519 })
520 .map(|width| {
521 let inset = node
522 .style()
523 .padding
524 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
525 .horizontal_components()
526 .sum()
527 + node
528 .style()
529 .border
530 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
531 .horizontal_components()
532 .sum();
533 (width - inset).max(0.0)
534 });
535
536 let mut content_height = intrinsic_height;
542 if let Some(width) = content_width.filter(|width| *width > 0.0) {
543 let font_ctx = self.font_ctx.clone();
544 let layout_ctx = &mut self.layout_ctx;
545 let node = &mut self.nodes[dom_node_id(node_id)];
546 if let Some(input) = node
547 .data
548 .downcast_element_mut()
549 .and_then(|el| el.text_input_data_mut())
550 {
551 input.sync_multiline_width(
552 &mut font_ctx.lock().unwrap(),
553 layout_ctx,
554 width,
555 );
556 if let Some(layout) = input.editor.try_layout() {
557 content_height = content_height.max(layout.height());
558 }
559 }
560 }
561
562 let node = &mut self.nodes[dom_node_id(node_id)];
563 let mut output = compute_leaf_layout(
564 inputs,
565 node.style(),
566 resolve_calc_value,
567 |_known_size, _available_space| taffy::Size {
568 width: cols
569 .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
570 .unwrap_or(300.0),
571 height: intrinsic_height,
572 },
573 );
574 output.content_size.height = output.content_size.height.max(content_height);
575 output.content_size.width = output.content_size.width.max(output.size.width);
576 return output;
577 }
578
579 if *element_data.name.local == *"input" {
580 match element_data.attr(local_name!("type")) {
581 Some("hidden") => {
583 node.style_mut().display = Display::None;
584 return taffy::LayoutOutput::HIDDEN;
585 }
586 Some("checkbox") => {
587 return compute_leaf_layout(
588 inputs,
589 node.style(),
590 resolve_calc_value,
591 |_known_size, _available_space| {
592 let width = node.style().size.width.resolve_or_zero(
593 inputs.parent_size.width,
594 resolve_calc_value,
595 );
596 let height = node.style().size.height.resolve_or_zero(
597 inputs.parent_size.height,
598 resolve_calc_value,
599 );
600 let min_size = width.min(height);
601 taffy::Size {
602 width: min_size,
603 height: min_size,
604 }
605 },
606 );
607 }
608 None
616 | Some(
617 "text" | "password" | "email" | "number" | "tel" | "url" | "search",
618 ) => {
619 return compute_leaf_layout(
620 inputs,
621 node.style(),
622 resolve_calc_value,
623 |_known_size, _available_space| taffy::Size {
624 width: match inputs.available_space.width {
625 AvailableSpace::Definite(limit) => limit.min(300.0),
626 AvailableSpace::MinContent => 0.0,
627 AvailableSpace::MaxContent => 300.0,
628 },
629 height: resolved_line_height.unwrap_or(16.0),
630 },
631 );
632 }
633 _ => {}
634 }
635 }
636
637 if is_replaced_element(&element_data.name.local) {
638 #[cfg_attr(not(feature = "svg"), allow(unused_mut))]
645 let mut attr_size = taffy::Size {
646 width: element_data
647 .attr(local_name!("width"))
648 .and_then(|val| val.parse::<f32>().ok()),
649 height: element_data
650 .attr(local_name!("height"))
651 .and_then(|val| val.parse::<f32>().ok()),
652 };
653
654 let (inherent_size, inherent_ratio) = match &element_data.special_data {
656 SpecialElementData::Image(image_data) => match &**image_data {
657 ImageData::Raster(image) => {
658 let size = taffy::Size {
659 width: image.width as f32,
660 height: image.height as f32,
661 };
662 (size, Some(size.width / size.height))
663 }
664 #[cfg(feature = "svg")]
665 ImageData::Svg(svg) => {
666 if *element_data.name.local == local_name!("svg") {
671 attr_size = taffy::Size {
672 width: svg.resolved_width(inputs.parent_size.width),
673 height: svg.resolved_height(inputs.parent_size.height),
674 };
675 }
676 let (mut width, mut height) = svg.intrinsic_size();
677 if svg.intrinsic_width().is_none()
683 && svg.intrinsic_height().is_none()
684 {
685 if let (
686 Some(ratio),
687 AvailableSpace::Definite(available_width),
688 ) =
689 (svg.viewbox_aspect_ratio(), inputs.available_space.width)
690 {
691 width = available_width;
692 height = available_width / ratio;
693 }
694 }
695 (taffy::Size { width, height }, Some(svg.aspect_ratio()))
696 }
697 ImageData::None => (taffy::Size::ZERO, None),
698 },
699 SpecialElementData::Canvas(_)
704 | SpecialElementData::SubDocument(_)
705 | SpecialElementData::None => {
706 let tag_name = &element_data.name.local;
707 if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
708 (taffy::Size::ZERO, None)
709 } else {
710 let size = taffy::Size {
711 width: attr_size.width.unwrap_or(300.0),
712 height: attr_size.height.unwrap_or(150.0),
713 };
714 let ratio = (*tag_name == local_name!("canvas"))
715 .then(|| size.width / size.height);
716 (size, ratio)
717 }
718 }
719 _ => unreachable!(),
720 };
721
722 let replaced_context = ReplacedContext {
723 inherent_size,
724 attr_size,
725 inherent_ratio,
726 };
727
728 let computed = replaced_measure_function(
729 inputs.known_dimensions,
730 inputs.parent_size,
731 inputs.available_space,
732 &replaced_context,
733 node.style(),
734 inputs.sizing_mode,
735 inputs.axis,
736 );
737
738 return taffy::LayoutOutput {
739 size: computed,
740 content_size: computed,
741 first_baselines: taffy::Point::NONE,
742 top_margin: CollapsibleMarginSet::ZERO,
743 bottom_margin: CollapsibleMarginSet::ZERO,
744 margins_can_collapse_through: false,
745 };
746 }
747
748 if node.flags.is_table_root() {
749 let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
750 .data
751 .downcast_element()
752 .unwrap()
753 .special_data
754 else {
755 panic!("Node marked as table root but doesn't have TableContext");
756 };
757 let context = Arc::clone(context);
758
759 let mut table_wrapper = TableTreeWrapper {
760 doc: self,
761 ctx: context,
762 };
763 let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
764
765 output.content_size.width = output.content_size.width.min(output.size.width);
767 output.content_size.height = output.content_size.height.min(output.size.height);
768
769 return output;
770 }
771
772 if node.flags.is_inline_root() {
773 return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
774 }
775
776 match node.style().display {
778 Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
779 Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
780 Display::Flex => compute_flexbox_layout(self, node_id, inputs),
781 Display::Grid => compute_grid_layout(self, node_id, inputs),
782 Display::None => taffy::LayoutOutput::HIDDEN,
783 }
784 }
785 NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
786
787 _ => taffy::LayoutOutput::HIDDEN,
788 }
789 }
790}
791
792impl TraversePartialTree for BaseDocument {
793 type ChildIter<'a> = RefCellChildIter<'a>;
794
795 fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
796 let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
798 children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
799 }))
800 }
801
802 fn child_count(&self, node_id: NodeId) -> usize {
803 self.node_from_id(node_id)
804 .layout_children
805 .borrow()
806 .as_ref()
807 .map(|c| c.len())
808 .unwrap_or(0)
809 }
810
811 fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
812 taffy_node_id(
813 self.node_from_id(node_id)
814 .layout_children
815 .borrow()
816 .as_ref()
817 .unwrap()[index],
818 )
819 }
820}
821impl TraverseTree for BaseDocument {}
822
823impl LayoutPartialTree for BaseDocument {
824 type CoreContainerStyle<'a>
825 = &'a taffy::Style<Atom>
826 where
827 Self: 'a;
828
829 type CustomIdent = Atom;
830
831 fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
832 self.node_from_id(node_id).style()
833 }
834
835 fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
836 let id = dom_node_id(node_id);
837 if self.abspos_candidate_ids.contains(&id) {
838 self.abspos_written.insert(id);
839 }
840 *self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
841 }
842
843 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
844 resolve_calc_value(calc_ptr, parent_size)
845 }
846
847 #[inline(always)]
848 fn compute_child_layout(
849 &mut self,
850 node_id: NodeId,
851 inputs: taffy::LayoutInput,
852 ) -> taffy::LayoutOutput {
853 #[cfg(not(target_arch = "wasm32"))]
854 let probing = layout_panic_probe::enabled();
855 #[cfg(not(target_arch = "wasm32"))]
856 if probing {
857 layout_panic_probe::push(
858 dom_node_id(node_id),
859 self.describe_node_for_panic(dom_node_id(node_id), &inputs),
860 );
861 }
862
863 let output = compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
864 tree.compute_child_layout_internal(node_id, inputs, None)
865 });
866
867 #[cfg(not(target_arch = "wasm32"))]
870 if probing {
871 layout_panic_probe::pop();
872 }
873 output
874 }
875}
876
877impl taffy::CacheTree for BaseDocument {
878 #[inline]
879 fn cache_get(
880 &self,
881 node_id: NodeId,
882 inputs: &taffy::LayoutInput,
883 ) -> Option<taffy::LayoutOutput> {
884 let found = self.node_from_id(node_id).cache().get(inputs);
885 #[cfg(feature = "log-phase-times")]
886 layout_counters::note_lookup(found.is_some());
887 found
888 }
889
890 #[inline]
891 fn cache_store(
892 &mut self,
893 node_id: NodeId,
894 inputs: &taffy::LayoutInput,
895 layout_output: taffy::LayoutOutput,
896 ) {
897 self.node_from_id_mut(node_id)
898 .cache_mut()
899 .store(inputs, layout_output);
900 }
901
902 #[inline]
903 fn cache_clear(&mut self, node_id: NodeId) {
904 self.node_from_id_mut(node_id).cache_release();
909 }
910}
911
912impl taffy::LayoutBlockContainer for BaseDocument {
913 type BlockContainerStyle<'a>
914 = &'a Style<Atom>
915 where
916 Self: 'a;
917
918 type BlockItemStyle<'a>
919 = &'a Style<Atom>
920 where
921 Self: 'a;
922
923 fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
924 self.get_core_container_style(node_id)
925 }
926
927 fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
928 self.get_core_container_style(child_node_id)
929 }
930
931 #[inline(always)]
932 fn compute_block_child_layout(
933 &mut self,
934 node_id: NodeId,
935 inputs: taffy::LayoutInput,
936 block_ctx: Option<&mut BlockContext<'_>>,
937 ) -> taffy::LayoutOutput {
938 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
939 tree.compute_child_layout_internal(node_id, inputs, block_ctx)
940 })
941 }
942}
943
944impl taffy::LayoutFlexboxContainer for BaseDocument {
945 type FlexboxContainerStyle<'a>
946 = &'a Style<Atom>
947 where
948 Self: 'a;
949
950 type FlexboxItemStyle<'a>
951 = &'a Style<Atom>
952 where
953 Self: 'a;
954
955 fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
956 self.get_core_container_style(node_id)
957 }
958
959 fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
960 self.get_core_container_style(child_node_id)
961 }
962}
963
964impl taffy::LayoutGridContainer for BaseDocument {
965 type GridContainerStyle<'a>
966 = &'a Style<Atom>
967 where
968 Self: 'a;
969
970 type GridItemStyle<'a>
971 = &'a Style<Atom>
972 where
973 Self: 'a;
974
975 fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
976 self.get_core_container_style(node_id)
977 }
978
979 fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
980 self.get_core_container_style(child_node_id)
981 }
982
983 fn set_detailed_grid_info(
984 &mut self,
985 node_id: NodeId,
986 detailed_grid_info: taffy::DetailedGridInfo,
987 ) {
988 let node = self.node_from_id_mut(node_id);
989 if let Some(element) = node.element_data_mut() {
990 element.detailed_grid_info = Some(Box::new(detailed_grid_info));
991 }
992 }
993}
994
995impl RoundTree for BaseDocument {
996 fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
997 *self.node_from_id(node_id).unrounded_layout()
998 }
999
1000 fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
1001 *self.node_from_id_mut(node_id).final_layout_mut() = *layout;
1002 }
1003}
1004
1005impl PrintTree for BaseDocument {
1006 fn get_debug_label(&self, node_id: NodeId) -> &'static str {
1007 let node = &self.node_from_id(node_id);
1008
1009 match node.data {
1010 NodeData::Document(_) => "DOCUMENT",
1011 NodeData::Text { .. } => node.node_debug_str().leak(),
1013 NodeData::Comment { .. } => "COMMENT",
1014 NodeData::DocumentFragment => "FRAGMENT",
1015 NodeData::ShadowRoot(_) => "SHADOW ROOT",
1016 NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
1017 NodeData::Element(_) => {
1018 let style = node.style();
1019 let display = match style.display {
1020 Display::Flex => match style.flex_direction {
1021 FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
1022 FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
1023 },
1024 Display::Grid => "GRID",
1025 Display::Block => "BLOCK",
1026 Display::FlowRoot => "FLOW ROOT",
1027 Display::None => "NONE",
1028 };
1029 format!("{} ({})", node.node_debug_str(), display).leak()
1030 } }
1032 }
1033
1034 fn get_final_layout(&self, node_id: NodeId) -> Layout {
1035 *self.node_from_id(node_id).final_layout()
1036 }
1037}
1038
1039pub struct RefCellChildIter<'a> {
1048 items: Ref<'a, [crate::NodeId]>,
1049 idx: usize,
1050}
1051impl<'a> RefCellChildIter<'a> {
1052 fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
1053 RefCellChildIter { items, idx: 0 }
1054 }
1055}
1056
1057impl Iterator for RefCellChildIter<'_> {
1058 type Item = NodeId;
1059 fn next(&mut self) -> Option<Self::Item> {
1060 self.items.get(self.idx).map(|id| {
1061 self.idx += 1;
1062 taffy_node_id(*id)
1063 })
1064 }
1065}