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 construct;
269pub(crate) mod damage;
270pub(crate) mod inline;
271pub(crate) mod list;
272pub(crate) mod replaced;
273pub(crate) mod table;
274
275use self::replaced::{ReplacedContext, is_replaced_element, replaced_measure_function};
276use self::table::TableTreeWrapper;
277
278pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
279 let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
280 let result = calc.resolve(CSSPixelLength::new(parent_size));
281 result.px()
282}
283
284impl BaseDocument {
285 fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
286 &self.nodes[dom_node_id(node_id)]
287 }
288 fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
289 &mut self.nodes[dom_node_id(node_id)]
290 }
291
292 #[cfg(not(target_arch = "wasm32"))]
296 fn describe_node_for_panic(
297 &self,
298 node_id: blitz_traits::node_id::NodeId,
299 inputs: &taffy::LayoutInput,
300 ) -> String {
301 let Some(node) = self.nodes.get(node_id) else {
302 return format!("node {node_id} (gone)");
303 };
304 let Some(element) = node.data.downcast_element() else {
305 return format!("node {node_id} <{:?}>", node.data.kind());
306 };
307 let attr = |name: &str| -> Option<&str> {
308 element
309 .attrs
310 .iter()
311 .find(|a| a.name.local.as_ref() == name)
312 .map(|a| a.value.as_ref())
313 };
314 format!(
318 "node {node_id} <{}{}{}> known={:?}x{:?} avail={:?}x{:?} mode={:?}/{:?}",
319 element.name.local,
320 attr("id").map(|v| format!(" id={v}")).unwrap_or_default(),
321 attr("class")
322 .map(|v| format!(" class=\"{}\"", &v[..v.len().min(160)]))
323 .unwrap_or_default(),
324 inputs.known_dimensions.width,
325 inputs.known_dimensions.height,
326 inputs.available_space.width,
327 inputs.available_space.height,
328 inputs.run_mode,
329 inputs.axis,
330 )
331 }
332}
333
334impl BaseDocument {
335 fn compute_child_layout_internal(
336 &mut self,
337 node_id: NodeId,
338 inputs: taffy::tree::LayoutInput,
339 block_ctx: Option<&mut BlockContext<'_>>,
340 ) -> taffy::tree::LayoutOutput {
341 #[cfg(feature = "log-phase-times")]
346 layout_counters::note_computed(dom_node_id(node_id));
347 let node = &mut self.nodes[dom_node_id(node_id)];
348
349 let font_styles = node.primary_styles().map(|style| {
350 use style::values::computed::font::LineHeight;
351
352 let font_size = style.clone_font_size().used_size().px();
353 let line_height = match style.clone_line_height() {
354 LineHeight::Normal => font_size * 1.2,
355 LineHeight::Number(num) => font_size * num.0,
356 LineHeight::Length(value) => value.0.px(),
357 };
358
359 (font_size, line_height)
360 });
361 let font_size = font_styles.map(|s| s.0);
362 let resolved_line_height = font_styles.map(|s| s.1);
363
364 match &mut node.data {
365 NodeData::Text(data) => {
366 #[cfg(feature = "tracing")]
369 tracing::error!(
370 node_id = ?dom_node_id(node_id),
371 data = ?data,
372 "Tried to lay out text node individually",
373 );
374
375 #[cfg(not(feature = "tracing"))]
376 let _ = data;
377
378 taffy::LayoutOutput::HIDDEN
379 }
398 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
399 if *element_data.name.local == *"textarea" {
401 let rows = element_data
402 .attr(local_name!("rows"))
403 .and_then(|val| val.parse::<f32>().ok())
404 .unwrap_or(2.0);
405
406 let cols = element_data
407 .attr(local_name!("cols"))
408 .and_then(|val| val.parse::<f32>().ok());
409
410 let intrinsic_height = resolved_line_height.unwrap_or(16.0) * rows;
411
412 let content_width = node
425 .style()
426 .size
427 .width
428 .maybe_resolve(inputs.parent_size.width, resolve_calc_value)
429 .or(inputs.known_dimensions.width)
430 .or(match inputs.available_space.width {
431 taffy::AvailableSpace::Definite(width) => Some(width),
432 _ => None,
433 })
434 .map(|width| {
435 let inset = node
436 .style()
437 .padding
438 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
439 .horizontal_components()
440 .sum()
441 + node
442 .style()
443 .border
444 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
445 .horizontal_components()
446 .sum();
447 (width - inset).max(0.0)
448 });
449
450 let mut content_height = intrinsic_height;
456 if let Some(width) = content_width.filter(|width| *width > 0.0) {
457 let font_ctx = self.font_ctx.clone();
458 let layout_ctx = &mut self.layout_ctx;
459 let node = &mut self.nodes[dom_node_id(node_id)];
460 if let Some(input) = node
461 .data
462 .downcast_element_mut()
463 .and_then(|el| el.text_input_data_mut())
464 {
465 input.sync_multiline_width(
466 &mut font_ctx.lock().unwrap(),
467 layout_ctx,
468 width,
469 );
470 if let Some(layout) = input.editor.try_layout() {
471 content_height = content_height.max(layout.height());
472 }
473 }
474 }
475
476 let node = &mut self.nodes[dom_node_id(node_id)];
477 let mut output = compute_leaf_layout(
478 inputs,
479 node.style(),
480 resolve_calc_value,
481 |_known_size, _available_space| taffy::Size {
482 width: cols
483 .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
484 .unwrap_or(300.0),
485 height: intrinsic_height,
486 },
487 );
488 output.content_size.height = output.content_size.height.max(content_height);
489 output.content_size.width = output.content_size.width.max(output.size.width);
490 return output;
491 }
492
493 if *element_data.name.local == *"input" {
494 match element_data.attr(local_name!("type")) {
495 Some("hidden") => {
497 node.style_mut().display = Display::None;
498 return taffy::LayoutOutput::HIDDEN;
499 }
500 Some("checkbox") => {
501 return compute_leaf_layout(
502 inputs,
503 node.style(),
504 resolve_calc_value,
505 |_known_size, _available_space| {
506 let width = node.style().size.width.resolve_or_zero(
507 inputs.parent_size.width,
508 resolve_calc_value,
509 );
510 let height = node.style().size.height.resolve_or_zero(
511 inputs.parent_size.height,
512 resolve_calc_value,
513 );
514 let min_size = width.min(height);
515 taffy::Size {
516 width: min_size,
517 height: min_size,
518 }
519 },
520 );
521 }
522 None | Some("text" | "password" | "email" | "tel" | "url" | "search") => {
523 return compute_leaf_layout(
524 inputs,
525 node.style(),
526 resolve_calc_value,
527 |_known_size, _available_space| taffy::Size {
528 width: match inputs.available_space.width {
529 AvailableSpace::Definite(limit) => limit.min(300.0),
530 AvailableSpace::MinContent => 0.0,
531 AvailableSpace::MaxContent => 300.0,
532 },
533 height: resolved_line_height.unwrap_or(16.0),
534 },
535 );
536 }
537 _ => {}
538 }
539 }
540
541 if is_replaced_element(&element_data.name.local) {
542 let mut attr_size = taffy::Size {
547 width: element_data
548 .attr(local_name!("width"))
549 .and_then(|val| val.parse::<f32>().ok()),
550 height: element_data
551 .attr(local_name!("height"))
552 .and_then(|val| val.parse::<f32>().ok()),
553 };
554
555 let (inherent_size, inherent_ratio) = match &element_data.special_data {
557 SpecialElementData::Image(image_data) => match &**image_data {
558 ImageData::Raster(image) => {
559 let size = taffy::Size {
560 width: image.width as f32,
561 height: image.height as f32,
562 };
563 (size, Some(size.width / size.height))
564 }
565 #[cfg(feature = "svg")]
566 ImageData::Svg(svg) => {
567 if *element_data.name.local == local_name!("svg") {
572 attr_size = taffy::Size {
573 width: svg.resolved_width(inputs.parent_size.width),
574 height: svg.resolved_height(inputs.parent_size.height),
575 };
576 }
577 let (mut width, mut height) = svg.intrinsic_size();
578 if svg.intrinsic_width().is_none()
584 && svg.intrinsic_height().is_none()
585 {
586 if let (
587 Some(ratio),
588 AvailableSpace::Definite(available_width),
589 ) =
590 (svg.viewbox_aspect_ratio(), inputs.available_space.width)
591 {
592 width = available_width;
593 height = available_width / ratio;
594 }
595 }
596 (taffy::Size { width, height }, Some(svg.aspect_ratio()))
597 }
598 ImageData::None => (taffy::Size::ZERO, None),
599 },
600 SpecialElementData::Canvas(_)
605 | SpecialElementData::SubDocument(_)
606 | SpecialElementData::None => {
607 let tag_name = &element_data.name.local;
608 if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
609 (taffy::Size::ZERO, None)
610 } else {
611 let size = taffy::Size {
612 width: attr_size.width.unwrap_or(300.0),
613 height: attr_size.height.unwrap_or(150.0),
614 };
615 let ratio = (*tag_name == local_name!("canvas"))
616 .then(|| size.width / size.height);
617 (size, ratio)
618 }
619 }
620 _ => unreachable!(),
621 };
622
623 let replaced_context = ReplacedContext {
624 inherent_size,
625 attr_size,
626 inherent_ratio,
627 };
628
629 let computed = replaced_measure_function(
630 inputs.known_dimensions,
631 inputs.parent_size,
632 inputs.available_space,
633 &replaced_context,
634 node.style(),
635 inputs.sizing_mode,
636 inputs.axis,
637 );
638
639 return taffy::LayoutOutput {
640 size: computed,
641 content_size: computed,
642 first_baselines: taffy::Point::NONE,
643 top_margin: CollapsibleMarginSet::ZERO,
644 bottom_margin: CollapsibleMarginSet::ZERO,
645 margins_can_collapse_through: false,
646 };
647 }
648
649 if node.flags.is_table_root() {
650 let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
651 .data
652 .downcast_element()
653 .unwrap()
654 .special_data
655 else {
656 panic!("Node marked as table root but doesn't have TableContext");
657 };
658 let context = Arc::clone(context);
659
660 let mut table_wrapper = TableTreeWrapper {
661 doc: self,
662 ctx: context,
663 };
664 let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
665
666 output.content_size.width = output.content_size.width.min(output.size.width);
668 output.content_size.height = output.content_size.height.min(output.size.height);
669
670 return output;
671 }
672
673 if node.flags.is_inline_root() {
674 return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
675 }
676
677 match node.style().display {
679 Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
680 Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
681 Display::Flex => compute_flexbox_layout(self, node_id, inputs),
682 Display::Grid => compute_grid_layout(self, node_id, inputs),
683 Display::None => taffy::LayoutOutput::HIDDEN,
684 }
685 }
686 NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
687
688 _ => taffy::LayoutOutput::HIDDEN,
689 }
690 }
691}
692
693impl TraversePartialTree for BaseDocument {
694 type ChildIter<'a> = RefCellChildIter<'a>;
695
696 fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
697 let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
699 children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
700 }))
701 }
702
703 fn child_count(&self, node_id: NodeId) -> usize {
704 self.node_from_id(node_id)
705 .layout_children
706 .borrow()
707 .as_ref()
708 .map(|c| c.len())
709 .unwrap_or(0)
710 }
711
712 fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
713 taffy_node_id(
714 self.node_from_id(node_id)
715 .layout_children
716 .borrow()
717 .as_ref()
718 .unwrap()[index],
719 )
720 }
721}
722impl TraverseTree for BaseDocument {}
723
724impl LayoutPartialTree for BaseDocument {
725 type CoreContainerStyle<'a>
726 = &'a taffy::Style<Atom>
727 where
728 Self: 'a;
729
730 type CustomIdent = Atom;
731
732 fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
733 self.node_from_id(node_id).style()
734 }
735
736 fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
737 *self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
738 }
739
740 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
741 resolve_calc_value(calc_ptr, parent_size)
742 }
743
744 #[inline(always)]
745 fn compute_child_layout(
746 &mut self,
747 node_id: NodeId,
748 inputs: taffy::LayoutInput,
749 ) -> taffy::LayoutOutput {
750 #[cfg(not(target_arch = "wasm32"))]
751 let probing = layout_panic_probe::enabled();
752 #[cfg(not(target_arch = "wasm32"))]
753 if probing {
754 layout_panic_probe::push(
755 dom_node_id(node_id),
756 self.describe_node_for_panic(dom_node_id(node_id), &inputs),
757 );
758 }
759
760 let output = compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
761 tree.compute_child_layout_internal(node_id, inputs, None)
762 });
763
764 #[cfg(not(target_arch = "wasm32"))]
767 if probing {
768 layout_panic_probe::pop();
769 }
770 output
771 }
772}
773
774impl taffy::CacheTree for BaseDocument {
775 #[inline]
776 fn cache_get(
777 &self,
778 node_id: NodeId,
779 inputs: &taffy::LayoutInput,
780 ) -> Option<taffy::LayoutOutput> {
781 let found = self.node_from_id(node_id).cache().get(inputs);
782 #[cfg(feature = "log-phase-times")]
783 layout_counters::note_lookup(found.is_some());
784 found
785 }
786
787 #[inline]
788 fn cache_store(
789 &mut self,
790 node_id: NodeId,
791 inputs: &taffy::LayoutInput,
792 layout_output: taffy::LayoutOutput,
793 ) {
794 self.node_from_id_mut(node_id)
795 .cache_mut()
796 .store(inputs, layout_output);
797 }
798
799 #[inline]
800 fn cache_clear(&mut self, node_id: NodeId) {
801 self.node_from_id_mut(node_id).cache_release();
806 }
807}
808
809impl taffy::LayoutBlockContainer for BaseDocument {
810 type BlockContainerStyle<'a>
811 = &'a Style<Atom>
812 where
813 Self: 'a;
814
815 type BlockItemStyle<'a>
816 = &'a Style<Atom>
817 where
818 Self: 'a;
819
820 fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
821 self.get_core_container_style(node_id)
822 }
823
824 fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
825 self.get_core_container_style(child_node_id)
826 }
827
828 #[inline(always)]
829 fn compute_block_child_layout(
830 &mut self,
831 node_id: NodeId,
832 inputs: taffy::LayoutInput,
833 block_ctx: Option<&mut BlockContext<'_>>,
834 ) -> taffy::LayoutOutput {
835 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
836 tree.compute_child_layout_internal(node_id, inputs, block_ctx)
837 })
838 }
839}
840
841impl taffy::LayoutFlexboxContainer for BaseDocument {
842 type FlexboxContainerStyle<'a>
843 = &'a Style<Atom>
844 where
845 Self: 'a;
846
847 type FlexboxItemStyle<'a>
848 = &'a Style<Atom>
849 where
850 Self: 'a;
851
852 fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
853 self.get_core_container_style(node_id)
854 }
855
856 fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
857 self.get_core_container_style(child_node_id)
858 }
859}
860
861impl taffy::LayoutGridContainer for BaseDocument {
862 type GridContainerStyle<'a>
863 = &'a Style<Atom>
864 where
865 Self: 'a;
866
867 type GridItemStyle<'a>
868 = &'a Style<Atom>
869 where
870 Self: 'a;
871
872 fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
873 self.get_core_container_style(node_id)
874 }
875
876 fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
877 self.get_core_container_style(child_node_id)
878 }
879
880 fn set_detailed_grid_info(
881 &mut self,
882 node_id: NodeId,
883 detailed_grid_info: taffy::DetailedGridInfo,
884 ) {
885 let node = self.node_from_id_mut(node_id);
886 if let Some(element) = node.element_data_mut() {
887 element.detailed_grid_info = Some(Box::new(detailed_grid_info));
888 }
889 }
890}
891
892impl RoundTree for BaseDocument {
893 fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
894 *self.node_from_id(node_id).unrounded_layout()
895 }
896
897 fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
898 *self.node_from_id_mut(node_id).final_layout_mut() = *layout;
899 }
900}
901
902impl PrintTree for BaseDocument {
903 fn get_debug_label(&self, node_id: NodeId) -> &'static str {
904 let node = &self.node_from_id(node_id);
905
906 match node.data {
907 NodeData::Document(_) => "DOCUMENT",
908 NodeData::Text { .. } => node.node_debug_str().leak(),
910 NodeData::Comment { .. } => "COMMENT",
911 NodeData::DocumentFragment => "FRAGMENT",
912 NodeData::ShadowRoot(_) => "SHADOW ROOT",
913 NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
914 NodeData::Element(_) => {
915 let style = node.style();
916 let display = match style.display {
917 Display::Flex => match style.flex_direction {
918 FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
919 FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
920 },
921 Display::Grid => "GRID",
922 Display::Block => "BLOCK",
923 Display::FlowRoot => "FLOW ROOT",
924 Display::None => "NONE",
925 };
926 format!("{} ({})", node.node_debug_str(), display).leak()
927 } }
929 }
930
931 fn get_final_layout(&self, node_id: NodeId) -> Layout {
932 *self.node_from_id(node_id).final_layout()
933 }
934}
935
936pub struct RefCellChildIter<'a> {
945 items: Ref<'a, [crate::NodeId]>,
946 idx: usize,
947}
948impl<'a> RefCellChildIter<'a> {
949 fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
950 RefCellChildIter { items, idx: 0 }
951 }
952}
953
954impl Iterator for RefCellChildIter<'_> {
955 type Item = NodeId;
956 fn next(&mut self) -> Option<Self::Item> {
957 self.items.get(self.idx).map(|id| {
958 self.idx += 1;
959 taffy_node_id(*id)
960 })
961 }
962}