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
334fn select_metrics_of(
342 doc: &BaseDocument,
343 node_id: blitz_traits::node_id::NodeId,
344) -> Option<(usize, f32)> {
345 let node = doc.nodes.get(node_id)?;
346 let element = node.data.downcast_element()?;
347 if element.name.local != local_name!("select") {
348 return None;
349 }
350
351 let widest = crate::traversal::TreeTraverser::new_with_root(doc, node_id)
352 .filter_map(|descendant_id| doc.nodes.get(descendant_id))
353 .filter(|descendant| {
354 descendant
355 .data
356 .is_element_with_tag_name(&local_name!("option"))
357 })
358 .map(|option| option.text_content().trim().chars().count())
359 .max()
360 .unwrap_or(0);
361
362 let rows = element
365 .attr(local_name!("size"))
366 .and_then(|size| size.parse::<f32>().ok())
367 .filter(|rows| *rows >= 1.0)
368 .unwrap_or(if element.attr(local_name!("multiple")).is_some() {
369 4.0
370 } else {
371 1.0
372 });
373
374 Some((widest, rows))
375}
376
377impl BaseDocument {
378 fn select_metrics(&self, node_id: blitz_traits::node_id::NodeId) -> Option<(usize, f32)> {
379 select_metrics_of(self, node_id)
380 }
381
382 fn compute_child_layout_internal(
383 &mut self,
384 node_id: NodeId,
385 inputs: taffy::tree::LayoutInput,
386 block_ctx: Option<&mut BlockContext<'_>>,
387 ) -> taffy::tree::LayoutOutput {
388 #[cfg(feature = "log-phase-times")]
393 layout_counters::note_computed(dom_node_id(node_id));
394
395 let select_metrics = self.select_metrics(dom_node_id(node_id));
398
399 let node = &mut self.nodes[dom_node_id(node_id)];
400
401 let font_styles = node.primary_styles().map(|style| {
402 use style::values::computed::font::LineHeight;
403
404 let font_size = style.clone_font_size().used_size().px();
405 let line_height = match style.clone_line_height() {
406 LineHeight::Normal => font_size * 1.2,
407 LineHeight::Number(num) => font_size * num.0,
408 LineHeight::Length(value) => value.0.px(),
409 };
410
411 (font_size, line_height)
412 });
413 let font_size = font_styles.map(|s| s.0);
414 let resolved_line_height = font_styles.map(|s| s.1);
415
416 match &mut node.data {
417 NodeData::Text(data) => {
418 #[cfg(feature = "tracing")]
421 tracing::error!(
422 node_id = ?dom_node_id(node_id),
423 data = ?data,
424 "Tried to lay out text node individually",
425 );
426
427 #[cfg(not(feature = "tracing"))]
428 let _ = data;
429
430 taffy::LayoutOutput::HIDDEN
431 }
450 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
451 if let Some((widest_label, rows)) = select_metrics {
458 let advance = font_size.unwrap_or(16.0) * 0.6;
459 let line_height = resolved_line_height.unwrap_or(16.0);
460 return compute_leaf_layout(
461 inputs,
462 node.style(),
463 resolve_calc_value,
464 |_known_size, _available_space| taffy::Size {
465 width: widest_label as f32 * advance,
466 height: line_height * rows,
467 },
468 );
469 }
470
471 if *element_data.name.local == *"textarea" {
473 let rows = element_data
474 .attr(local_name!("rows"))
475 .and_then(|val| val.parse::<f32>().ok())
476 .unwrap_or(2.0);
477
478 let cols = element_data
479 .attr(local_name!("cols"))
480 .and_then(|val| val.parse::<f32>().ok());
481
482 let intrinsic_height = resolved_line_height.unwrap_or(16.0) * rows;
483
484 let content_width = node
497 .style()
498 .size
499 .width
500 .maybe_resolve(inputs.parent_size.width, resolve_calc_value)
501 .or(inputs.known_dimensions.width)
502 .or(match inputs.available_space.width {
503 taffy::AvailableSpace::Definite(width) => Some(width),
504 _ => None,
505 })
506 .map(|width| {
507 let inset = node
508 .style()
509 .padding
510 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
511 .horizontal_components()
512 .sum()
513 + node
514 .style()
515 .border
516 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
517 .horizontal_components()
518 .sum();
519 (width - inset).max(0.0)
520 });
521
522 let mut content_height = intrinsic_height;
528 if let Some(width) = content_width.filter(|width| *width > 0.0) {
529 let font_ctx = self.font_ctx.clone();
530 let layout_ctx = &mut self.layout_ctx;
531 let node = &mut self.nodes[dom_node_id(node_id)];
532 if let Some(input) = node
533 .data
534 .downcast_element_mut()
535 .and_then(|el| el.text_input_data_mut())
536 {
537 input.sync_multiline_width(
538 &mut font_ctx.lock().unwrap(),
539 layout_ctx,
540 width,
541 );
542 if let Some(layout) = input.editor.try_layout() {
543 content_height = content_height.max(layout.height());
544 }
545 }
546 }
547
548 let node = &mut self.nodes[dom_node_id(node_id)];
549 let mut output = compute_leaf_layout(
550 inputs,
551 node.style(),
552 resolve_calc_value,
553 |_known_size, _available_space| taffy::Size {
554 width: cols
555 .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
556 .unwrap_or(300.0),
557 height: intrinsic_height,
558 },
559 );
560 output.content_size.height = output.content_size.height.max(content_height);
561 output.content_size.width = output.content_size.width.max(output.size.width);
562 return output;
563 }
564
565 if *element_data.name.local == *"input" {
566 match element_data.attr(local_name!("type")) {
567 Some("hidden") => {
569 node.style_mut().display = Display::None;
570 return taffy::LayoutOutput::HIDDEN;
571 }
572 Some("checkbox") => {
573 return compute_leaf_layout(
574 inputs,
575 node.style(),
576 resolve_calc_value,
577 |_known_size, _available_space| {
578 let width = node.style().size.width.resolve_or_zero(
579 inputs.parent_size.width,
580 resolve_calc_value,
581 );
582 let height = node.style().size.height.resolve_or_zero(
583 inputs.parent_size.height,
584 resolve_calc_value,
585 );
586 let min_size = width.min(height);
587 taffy::Size {
588 width: min_size,
589 height: min_size,
590 }
591 },
592 );
593 }
594 None
602 | Some(
603 "text" | "password" | "email" | "number" | "tel" | "url" | "search",
604 ) => {
605 return compute_leaf_layout(
606 inputs,
607 node.style(),
608 resolve_calc_value,
609 |_known_size, _available_space| taffy::Size {
610 width: match inputs.available_space.width {
611 AvailableSpace::Definite(limit) => limit.min(300.0),
612 AvailableSpace::MinContent => 0.0,
613 AvailableSpace::MaxContent => 300.0,
614 },
615 height: resolved_line_height.unwrap_or(16.0),
616 },
617 );
618 }
619 _ => {}
620 }
621 }
622
623 if is_replaced_element(&element_data.name.local) {
624 let mut attr_size = taffy::Size {
629 width: element_data
630 .attr(local_name!("width"))
631 .and_then(|val| val.parse::<f32>().ok()),
632 height: element_data
633 .attr(local_name!("height"))
634 .and_then(|val| val.parse::<f32>().ok()),
635 };
636
637 let (inherent_size, inherent_ratio) = match &element_data.special_data {
639 SpecialElementData::Image(image_data) => match &**image_data {
640 ImageData::Raster(image) => {
641 let size = taffy::Size {
642 width: image.width as f32,
643 height: image.height as f32,
644 };
645 (size, Some(size.width / size.height))
646 }
647 #[cfg(feature = "svg")]
648 ImageData::Svg(svg) => {
649 if *element_data.name.local == local_name!("svg") {
654 attr_size = taffy::Size {
655 width: svg.resolved_width(inputs.parent_size.width),
656 height: svg.resolved_height(inputs.parent_size.height),
657 };
658 }
659 let (mut width, mut height) = svg.intrinsic_size();
660 if svg.intrinsic_width().is_none()
666 && svg.intrinsic_height().is_none()
667 {
668 if let (
669 Some(ratio),
670 AvailableSpace::Definite(available_width),
671 ) =
672 (svg.viewbox_aspect_ratio(), inputs.available_space.width)
673 {
674 width = available_width;
675 height = available_width / ratio;
676 }
677 }
678 (taffy::Size { width, height }, Some(svg.aspect_ratio()))
679 }
680 ImageData::None => (taffy::Size::ZERO, None),
681 },
682 SpecialElementData::Canvas(_)
687 | SpecialElementData::SubDocument(_)
688 | SpecialElementData::None => {
689 let tag_name = &element_data.name.local;
690 if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
691 (taffy::Size::ZERO, None)
692 } else {
693 let size = taffy::Size {
694 width: attr_size.width.unwrap_or(300.0),
695 height: attr_size.height.unwrap_or(150.0),
696 };
697 let ratio = (*tag_name == local_name!("canvas"))
698 .then(|| size.width / size.height);
699 (size, ratio)
700 }
701 }
702 _ => unreachable!(),
703 };
704
705 let replaced_context = ReplacedContext {
706 inherent_size,
707 attr_size,
708 inherent_ratio,
709 };
710
711 let computed = replaced_measure_function(
712 inputs.known_dimensions,
713 inputs.parent_size,
714 inputs.available_space,
715 &replaced_context,
716 node.style(),
717 inputs.sizing_mode,
718 inputs.axis,
719 );
720
721 return taffy::LayoutOutput {
722 size: computed,
723 content_size: computed,
724 first_baselines: taffy::Point::NONE,
725 top_margin: CollapsibleMarginSet::ZERO,
726 bottom_margin: CollapsibleMarginSet::ZERO,
727 margins_can_collapse_through: false,
728 };
729 }
730
731 if node.flags.is_table_root() {
732 let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
733 .data
734 .downcast_element()
735 .unwrap()
736 .special_data
737 else {
738 panic!("Node marked as table root but doesn't have TableContext");
739 };
740 let context = Arc::clone(context);
741
742 let mut table_wrapper = TableTreeWrapper {
743 doc: self,
744 ctx: context,
745 };
746 let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
747
748 output.content_size.width = output.content_size.width.min(output.size.width);
750 output.content_size.height = output.content_size.height.min(output.size.height);
751
752 return output;
753 }
754
755 if node.flags.is_inline_root() {
756 return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
757 }
758
759 match node.style().display {
761 Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
762 Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
763 Display::Flex => compute_flexbox_layout(self, node_id, inputs),
764 Display::Grid => compute_grid_layout(self, node_id, inputs),
765 Display::None => taffy::LayoutOutput::HIDDEN,
766 }
767 }
768 NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
769
770 _ => taffy::LayoutOutput::HIDDEN,
771 }
772 }
773}
774
775impl TraversePartialTree for BaseDocument {
776 type ChildIter<'a> = RefCellChildIter<'a>;
777
778 fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
779 let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
781 children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
782 }))
783 }
784
785 fn child_count(&self, node_id: NodeId) -> usize {
786 self.node_from_id(node_id)
787 .layout_children
788 .borrow()
789 .as_ref()
790 .map(|c| c.len())
791 .unwrap_or(0)
792 }
793
794 fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
795 taffy_node_id(
796 self.node_from_id(node_id)
797 .layout_children
798 .borrow()
799 .as_ref()
800 .unwrap()[index],
801 )
802 }
803}
804impl TraverseTree for BaseDocument {}
805
806impl LayoutPartialTree for BaseDocument {
807 type CoreContainerStyle<'a>
808 = &'a taffy::Style<Atom>
809 where
810 Self: 'a;
811
812 type CustomIdent = Atom;
813
814 fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
815 self.node_from_id(node_id).style()
816 }
817
818 fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
819 *self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
820 }
821
822 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
823 resolve_calc_value(calc_ptr, parent_size)
824 }
825
826 #[inline(always)]
827 fn compute_child_layout(
828 &mut self,
829 node_id: NodeId,
830 inputs: taffy::LayoutInput,
831 ) -> taffy::LayoutOutput {
832 #[cfg(not(target_arch = "wasm32"))]
833 let probing = layout_panic_probe::enabled();
834 #[cfg(not(target_arch = "wasm32"))]
835 if probing {
836 layout_panic_probe::push(
837 dom_node_id(node_id),
838 self.describe_node_for_panic(dom_node_id(node_id), &inputs),
839 );
840 }
841
842 let output = compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
843 tree.compute_child_layout_internal(node_id, inputs, None)
844 });
845
846 #[cfg(not(target_arch = "wasm32"))]
849 if probing {
850 layout_panic_probe::pop();
851 }
852 output
853 }
854}
855
856impl taffy::CacheTree for BaseDocument {
857 #[inline]
858 fn cache_get(
859 &self,
860 node_id: NodeId,
861 inputs: &taffy::LayoutInput,
862 ) -> Option<taffy::LayoutOutput> {
863 let found = self.node_from_id(node_id).cache().get(inputs);
864 #[cfg(feature = "log-phase-times")]
865 layout_counters::note_lookup(found.is_some());
866 found
867 }
868
869 #[inline]
870 fn cache_store(
871 &mut self,
872 node_id: NodeId,
873 inputs: &taffy::LayoutInput,
874 layout_output: taffy::LayoutOutput,
875 ) {
876 self.node_from_id_mut(node_id)
877 .cache_mut()
878 .store(inputs, layout_output);
879 }
880
881 #[inline]
882 fn cache_clear(&mut self, node_id: NodeId) {
883 self.node_from_id_mut(node_id).cache_release();
888 }
889}
890
891impl taffy::LayoutBlockContainer for BaseDocument {
892 type BlockContainerStyle<'a>
893 = &'a Style<Atom>
894 where
895 Self: 'a;
896
897 type BlockItemStyle<'a>
898 = &'a Style<Atom>
899 where
900 Self: 'a;
901
902 fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
903 self.get_core_container_style(node_id)
904 }
905
906 fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
907 self.get_core_container_style(child_node_id)
908 }
909
910 #[inline(always)]
911 fn compute_block_child_layout(
912 &mut self,
913 node_id: NodeId,
914 inputs: taffy::LayoutInput,
915 block_ctx: Option<&mut BlockContext<'_>>,
916 ) -> taffy::LayoutOutput {
917 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
918 tree.compute_child_layout_internal(node_id, inputs, block_ctx)
919 })
920 }
921}
922
923impl taffy::LayoutFlexboxContainer for BaseDocument {
924 type FlexboxContainerStyle<'a>
925 = &'a Style<Atom>
926 where
927 Self: 'a;
928
929 type FlexboxItemStyle<'a>
930 = &'a Style<Atom>
931 where
932 Self: 'a;
933
934 fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
935 self.get_core_container_style(node_id)
936 }
937
938 fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
939 self.get_core_container_style(child_node_id)
940 }
941}
942
943impl taffy::LayoutGridContainer for BaseDocument {
944 type GridContainerStyle<'a>
945 = &'a Style<Atom>
946 where
947 Self: 'a;
948
949 type GridItemStyle<'a>
950 = &'a Style<Atom>
951 where
952 Self: 'a;
953
954 fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
955 self.get_core_container_style(node_id)
956 }
957
958 fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
959 self.get_core_container_style(child_node_id)
960 }
961
962 fn set_detailed_grid_info(
963 &mut self,
964 node_id: NodeId,
965 detailed_grid_info: taffy::DetailedGridInfo,
966 ) {
967 let node = self.node_from_id_mut(node_id);
968 if let Some(element) = node.element_data_mut() {
969 element.detailed_grid_info = Some(Box::new(detailed_grid_info));
970 }
971 }
972}
973
974impl RoundTree for BaseDocument {
975 fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
976 *self.node_from_id(node_id).unrounded_layout()
977 }
978
979 fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
980 *self.node_from_id_mut(node_id).final_layout_mut() = *layout;
981 }
982}
983
984impl PrintTree for BaseDocument {
985 fn get_debug_label(&self, node_id: NodeId) -> &'static str {
986 let node = &self.node_from_id(node_id);
987
988 match node.data {
989 NodeData::Document(_) => "DOCUMENT",
990 NodeData::Text { .. } => node.node_debug_str().leak(),
992 NodeData::Comment { .. } => "COMMENT",
993 NodeData::DocumentFragment => "FRAGMENT",
994 NodeData::ShadowRoot(_) => "SHADOW ROOT",
995 NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
996 NodeData::Element(_) => {
997 let style = node.style();
998 let display = match style.display {
999 Display::Flex => match style.flex_direction {
1000 FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
1001 FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
1002 },
1003 Display::Grid => "GRID",
1004 Display::Block => "BLOCK",
1005 Display::FlowRoot => "FLOW ROOT",
1006 Display::None => "NONE",
1007 };
1008 format!("{} ({})", node.node_debug_str(), display).leak()
1009 } }
1011 }
1012
1013 fn get_final_layout(&self, node_id: NodeId) -> Layout {
1014 *self.node_from_id(node_id).final_layout()
1015 }
1016}
1017
1018pub struct RefCellChildIter<'a> {
1027 items: Ref<'a, [crate::NodeId]>,
1028 idx: usize,
1029}
1030impl<'a> RefCellChildIter<'a> {
1031 fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
1032 RefCellChildIter { items, idx: 0 }
1033 }
1034}
1035
1036impl Iterator for RefCellChildIter<'_> {
1037 type Item = NodeId;
1038 fn next(&mut self) -> Option<Self::Item> {
1039 self.items.get(self.idx).map(|id| {
1040 self.idx += 1;
1041 taffy_node_id(*id)
1042 })
1043 }
1044}