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