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 mod cache;
270pub(crate) mod construct;
271pub(crate) mod damage;
272pub(crate) mod inline;
273pub(crate) mod list;
274pub(crate) mod replaced;
275#[cfg(feature = "svg")]
276pub(crate) mod svg_geometry;
277pub(crate) mod table;
278
279use self::replaced::{ReplacedContext, is_replaced_element, replaced_measure_function};
280use self::table::TableTreeWrapper;
281
282pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
283 let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
284 let result = calc.resolve(CSSPixelLength::new(parent_size));
285 result.px()
286}
287
288impl BaseDocument {
289 fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
290 &self.nodes[dom_node_id(node_id)]
291 }
292 fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
293 &mut self.nodes[dom_node_id(node_id)]
294 }
295
296 #[cfg(not(target_arch = "wasm32"))]
300 fn describe_node_for_panic(
301 &self,
302 node_id: blitz_traits::node_id::NodeId,
303 inputs: &taffy::LayoutInput,
304 ) -> String {
305 let Some(node) = self.nodes.get(node_id) else {
306 return format!("node {node_id} (gone)");
307 };
308 let Some(element) = node.data.downcast_element() else {
309 return format!("node {node_id} <{:?}>", node.data.kind());
310 };
311 let attr = |name: &str| -> Option<&str> {
312 element
313 .attrs
314 .iter()
315 .find(|a| a.name.local.as_ref() == name)
316 .map(|a| a.value.as_ref())
317 };
318 format!(
322 "node {node_id} <{}{}{}> known={:?}x{:?} avail={:?}x{:?} mode={:?}/{:?}",
323 element.name.local,
324 attr("id").map(|v| format!(" id={v}")).unwrap_or_default(),
325 attr("class")
326 .map(|v| format!(" class=\"{}\"", &v[..v.len().min(160)]))
327 .unwrap_or_default(),
328 inputs.known_dimensions.width,
329 inputs.known_dimensions.height,
330 inputs.available_space.width,
331 inputs.available_space.height,
332 inputs.run_mode,
333 inputs.axis,
334 )
335 }
336}
337
338fn select_metrics_of(
355 doc: &BaseDocument,
356 node_id: blitz_traits::node_id::NodeId,
357) -> Option<(usize, f32)> {
358 let node = doc.nodes.get(node_id)?;
359 let element = node.data.downcast_element()?;
360 if element.name.local != local_name!("select") {
361 return None;
362 }
363
364 let widest = doc
365 .select_options(node_id)
366 .into_iter()
367 .map(|option_id| doc.option_label(option_id).chars().count())
368 .max()
369 .unwrap_or(0);
370
371 let rows = element
374 .attr(local_name!("size"))
375 .and_then(|size| size.parse::<f32>().ok())
376 .filter(|rows| *rows >= 1.0)
377 .unwrap_or(if element.attr(local_name!("multiple")).is_some() {
378 4.0
379 } else {
380 1.0
381 });
382
383 Some((widest, rows))
384}
385
386impl BaseDocument {
387 fn select_metrics(&self, node_id: blitz_traits::node_id::NodeId) -> Option<(usize, f32)> {
388 select_metrics_of(self, node_id)
389 }
390
391 fn compute_child_layout_internal(
392 &mut self,
393 node_id: NodeId,
394 inputs: taffy::tree::LayoutInput,
395 block_ctx: Option<&mut BlockContext<'_>>,
396 ) -> taffy::tree::LayoutOutput {
397 #[cfg(feature = "log-phase-times")]
402 layout_counters::note_computed(dom_node_id(node_id));
403
404 if inputs.run_mode == taffy::RunMode::PerformLayout
405 && dom_node_id(node_id) == self.root_element().id
406 {
407 self.abspos_normal_layout = true;
408 }
409
410 let select_metrics = self.select_metrics(dom_node_id(node_id));
413
414 let node = &mut self.nodes[dom_node_id(node_id)];
415
416 let font_styles = node.primary_styles().map(|style| {
417 use style::values::computed::font::LineHeight;
418
419 let font_size = style.clone_font_size().used_size().px();
420 let line_height = match style.clone_line_height() {
421 LineHeight::Normal => font_size * 1.2,
422 LineHeight::Number(num) => font_size * num.0,
423 LineHeight::Length(value) => value.0.px(),
424 };
425
426 (font_size, line_height)
427 });
428 let font_size = font_styles.map(|s| s.0);
429 let resolved_line_height = font_styles.map(|s| s.1);
430
431 match &mut node.data {
432 NodeData::Text(data) => {
433 #[cfg(feature = "tracing")]
436 tracing::error!(
437 node_id = ?dom_node_id(node_id),
438 data = ?data,
439 "Tried to lay out text node individually",
440 );
441
442 #[cfg(not(feature = "tracing"))]
443 let _ = data;
444
445 taffy::LayoutOutput::HIDDEN
446 }
465 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
466 if let Some((widest_label, rows)) = select_metrics {
473 let advance = font_size.unwrap_or(16.0) * 0.6;
474 let line_height = resolved_line_height.unwrap_or(16.0);
475 return compute_leaf_layout(
476 inputs,
477 node.style(),
478 resolve_calc_value,
479 |_known_size, _available_space| taffy::Size {
480 width: widest_label as f32 * advance,
481 height: line_height * rows,
482 },
483 );
484 }
485
486 if *element_data.name.local == *"textarea" {
488 let rows = element_data
489 .attr(local_name!("rows"))
490 .and_then(|val| val.parse::<f32>().ok())
491 .unwrap_or(2.0);
492
493 let cols = element_data
494 .attr(local_name!("cols"))
495 .and_then(|val| val.parse::<f32>().ok());
496
497 let intrinsic_height = resolved_line_height.unwrap_or(16.0) * rows;
498
499 let content_width = node
512 .style()
513 .size
514 .width
515 .maybe_resolve(inputs.parent_size.width, resolve_calc_value)
516 .or(inputs.known_dimensions.width)
517 .or(match inputs.available_space.width {
518 taffy::AvailableSpace::Definite(width) => Some(width),
519 _ => None,
520 })
521 .map(|width| {
522 let inset = node
523 .style()
524 .padding
525 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
526 .horizontal_components()
527 .sum()
528 + node
529 .style()
530 .border
531 .resolve_or_zero(inputs.parent_size, resolve_calc_value)
532 .horizontal_components()
533 .sum();
534 (width - inset).max(0.0)
535 });
536
537 let mut content_height = intrinsic_height;
543 if let Some(width) = content_width.filter(|width| *width > 0.0) {
544 let font_ctx = self.font_ctx.clone();
545 let layout_ctx = &mut self.layout_ctx;
546 let node = &mut self.nodes[dom_node_id(node_id)];
547 if let Some(input) = node
548 .data
549 .downcast_element_mut()
550 .and_then(|el| el.text_input_data_mut())
551 {
552 input.sync_multiline_width(
553 &mut font_ctx.lock().unwrap(),
554 layout_ctx,
555 width,
556 );
557 if let Some(layout) = input.editor.try_layout() {
558 content_height = content_height.max(layout.height());
559 }
560 }
561 }
562
563 let node = &mut self.nodes[dom_node_id(node_id)];
564 let mut output = compute_leaf_layout(
565 inputs,
566 node.style(),
567 resolve_calc_value,
568 |_known_size, _available_space| taffy::Size {
569 width: cols
570 .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
571 .unwrap_or(300.0),
572 height: intrinsic_height,
573 },
574 );
575 output.content_size.height = output.content_size.height.max(content_height);
576 output.content_size.width = output.content_size.width.max(output.size.width);
577 return output;
578 }
579
580 if *element_data.name.local == *"input" {
581 match element_data.attr(local_name!("type")) {
582 Some("hidden") => {
584 node.style_mut().display = Display::None;
585 return taffy::LayoutOutput::HIDDEN;
586 }
587 Some("checkbox") => {
588 return compute_leaf_layout(
589 inputs,
590 node.style(),
591 resolve_calc_value,
592 |_known_size, _available_space| {
593 let width = node.style().size.width.resolve_or_zero(
594 inputs.parent_size.width,
595 resolve_calc_value,
596 );
597 let height = node.style().size.height.resolve_or_zero(
598 inputs.parent_size.height,
599 resolve_calc_value,
600 );
601 let min_size = width.min(height);
602 taffy::Size {
603 width: min_size,
604 height: min_size,
605 }
606 },
607 );
608 }
609 None
617 | Some(
618 "text" | "password" | "email" | "number" | "tel" | "url" | "search",
619 ) => {
620 return compute_leaf_layout(
621 inputs,
622 node.style(),
623 resolve_calc_value,
624 |_known_size, _available_space| taffy::Size {
625 width: match inputs.available_space.width {
626 AvailableSpace::Definite(limit) => limit.min(300.0),
627 AvailableSpace::MinContent => 0.0,
628 AvailableSpace::MaxContent => 300.0,
629 },
630 height: resolved_line_height.unwrap_or(16.0),
631 },
632 );
633 }
634 _ => {}
635 }
636 }
637
638 if is_replaced_element(&element_data.name.local) {
639 #[cfg_attr(not(feature = "svg"), allow(unused_mut))]
646 let mut attr_size = taffy::Size {
647 width: element_data
648 .attr(local_name!("width"))
649 .and_then(|val| val.parse::<f32>().ok()),
650 height: element_data
651 .attr(local_name!("height"))
652 .and_then(|val| val.parse::<f32>().ok()),
653 };
654
655 let (inherent_size, inherent_ratio) = match &element_data.special_data {
657 SpecialElementData::Image(image_data) => match &**image_data {
658 ImageData::Raster(image) => {
659 let size = taffy::Size {
660 width: image.width as f32,
661 height: image.height as f32,
662 };
663 (size, Some(size.width / size.height))
664 }
665 #[cfg(feature = "svg")]
666 ImageData::Svg(svg) => {
667 if *element_data.name.local == local_name!("svg") {
672 attr_size = taffy::Size {
673 width: svg.resolved_width(inputs.parent_size.width),
674 height: svg.resolved_height(inputs.parent_size.height),
675 };
676 }
677 let (mut width, mut height) = svg.intrinsic_size();
678 if svg.intrinsic_width().is_none()
684 && svg.intrinsic_height().is_none()
685 {
686 if let (
687 Some(ratio),
688 AvailableSpace::Definite(available_width),
689 ) =
690 (svg.viewbox_aspect_ratio(), inputs.available_space.width)
691 {
692 width = available_width;
693 height = available_width / ratio;
694 }
695 }
696 (taffy::Size { width, height }, Some(svg.aspect_ratio()))
697 }
698 ImageData::None => (taffy::Size::ZERO, None),
699 },
700 SpecialElementData::Canvas(_)
705 | SpecialElementData::SubDocument(_)
706 | SpecialElementData::None => {
707 let tag_name = &element_data.name.local;
708 if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
709 (taffy::Size::ZERO, None)
710 } else {
711 let size = taffy::Size {
712 width: attr_size.width.unwrap_or(300.0),
713 height: attr_size.height.unwrap_or(150.0),
714 };
715 let ratio = (*tag_name == local_name!("canvas"))
716 .then(|| size.width / size.height);
717 (size, ratio)
718 }
719 }
720 _ => unreachable!(),
721 };
722
723 let replaced_context = ReplacedContext {
724 inherent_size,
725 attr_size,
726 inherent_ratio,
727 };
728
729 let computed = replaced_measure_function(
730 inputs.known_dimensions,
731 inputs.parent_size,
732 inputs.available_space,
733 &replaced_context,
734 node.style(),
735 inputs.sizing_mode,
736 inputs.axis,
737 );
738
739 return taffy::LayoutOutput {
740 size: computed,
741 content_size: computed,
742 first_baselines: taffy::Point::NONE,
743 top_margin: CollapsibleMarginSet::ZERO,
744 bottom_margin: CollapsibleMarginSet::ZERO,
745 margins_can_collapse_through: false,
746 };
747 }
748
749 if node.flags.is_table_root() {
750 let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
751 .data
752 .downcast_element()
753 .unwrap()
754 .special_data
755 else {
756 panic!("Node marked as table root but doesn't have TableContext");
757 };
758 let context = Arc::clone(context);
759
760 let mut table_wrapper = TableTreeWrapper {
761 doc: self,
762 ctx: context,
763 };
764 let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
765
766 output.content_size.width = output.content_size.width.min(output.size.width);
768 output.content_size.height = output.content_size.height.min(output.size.height);
769
770 return output;
771 }
772
773 if node.flags.is_inline_root() {
774 return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
775 }
776
777 match node.style().display {
779 Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
780 Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
781 Display::Flex => compute_flexbox_layout(self, node_id, inputs),
782 Display::Grid => compute_grid_layout(self, node_id, inputs),
783 Display::None => taffy::LayoutOutput::HIDDEN,
784 }
785 }
786 NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
787
788 _ => taffy::LayoutOutput::HIDDEN,
789 }
790 }
791}
792
793impl TraversePartialTree for BaseDocument {
794 type ChildIter<'a> = RefCellChildIter<'a>;
795
796 fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
797 let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
799 children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
800 }))
801 }
802
803 fn child_count(&self, node_id: NodeId) -> usize {
804 self.node_from_id(node_id)
805 .layout_children
806 .borrow()
807 .as_ref()
808 .map(|c| c.len())
809 .unwrap_or(0)
810 }
811
812 fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
813 taffy_node_id(
814 self.node_from_id(node_id)
815 .layout_children
816 .borrow()
817 .as_ref()
818 .unwrap()[index],
819 )
820 }
821}
822impl TraverseTree for BaseDocument {}
823
824impl LayoutPartialTree for BaseDocument {
825 type CoreContainerStyle<'a>
826 = &'a taffy::Style<Atom>
827 where
828 Self: 'a;
829
830 type CustomIdent = Atom;
831
832 fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
833 self.node_from_id(node_id).style()
834 }
835
836 fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
837 let id = dom_node_id(node_id);
838 if self.abspos_candidate_ids.contains(&id) {
839 self.abspos_written.insert(id);
840 }
841 *self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
842 }
843
844 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
845 resolve_calc_value(calc_ptr, parent_size)
846 }
847
848 #[inline(always)]
849 fn compute_child_layout(
850 &mut self,
851 node_id: NodeId,
852 inputs: taffy::LayoutInput,
853 ) -> taffy::LayoutOutput {
854 #[cfg(not(target_arch = "wasm32"))]
855 let probing = layout_panic_probe::enabled();
856 #[cfg(not(target_arch = "wasm32"))]
857 if probing {
858 layout_panic_probe::push(
859 dom_node_id(node_id),
860 self.describe_node_for_panic(dom_node_id(node_id), &inputs),
861 );
862 }
863
864 let output = compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
865 tree.compute_child_layout_internal(node_id, inputs, None)
866 });
867
868 #[cfg(not(target_arch = "wasm32"))]
871 if probing {
872 layout_panic_probe::pop();
873 }
874 output
875 }
876}
877
878impl taffy::CacheTree for BaseDocument {
879 #[inline]
880 fn cache_get(
881 &self,
882 node_id: NodeId,
883 inputs: &taffy::LayoutInput,
884 ) -> Option<taffy::LayoutOutput> {
885 let found = self.node_from_id(node_id).cache().get(inputs);
886 #[cfg(feature = "log-phase-times")]
887 layout_counters::note_lookup(found.is_some());
888 found
889 }
890
891 #[inline]
892 fn cache_store(
893 &mut self,
894 node_id: NodeId,
895 inputs: &taffy::LayoutInput,
896 layout_output: taffy::LayoutOutput,
897 ) {
898 let node = self.node_from_id_mut(node_id);
899 let independent_container = matches!(node.style().display, Display::Flex | Display::Grid)
900 && !node.flags.is_inline_root()
901 && !node.flags.is_table_root();
902 if independent_container {
903 node.cache_mut().store_intrinsic(inputs, layout_output);
904 } else {
905 node.cache_mut().store(inputs, layout_output);
906 }
907 }
908
909 #[inline]
910 fn cache_clear(&mut self, node_id: NodeId) {
911 self.node_from_id_mut(node_id).cache_release();
916 }
917}
918
919impl taffy::LayoutBlockContainer for BaseDocument {
920 type BlockContainerStyle<'a>
921 = &'a Style<Atom>
922 where
923 Self: 'a;
924
925 type BlockItemStyle<'a>
926 = &'a Style<Atom>
927 where
928 Self: 'a;
929
930 fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
931 self.get_core_container_style(node_id)
932 }
933
934 fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
935 self.get_core_container_style(child_node_id)
936 }
937
938 #[inline(always)]
939 fn compute_block_child_layout(
940 &mut self,
941 node_id: NodeId,
942 inputs: taffy::LayoutInput,
943 block_ctx: Option<&mut BlockContext<'_>>,
944 ) -> taffy::LayoutOutput {
945 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
946 tree.compute_child_layout_internal(node_id, inputs, block_ctx)
947 })
948 }
949}
950
951impl taffy::LayoutFlexboxContainer for BaseDocument {
952 type FlexboxContainerStyle<'a>
953 = &'a Style<Atom>
954 where
955 Self: 'a;
956
957 type FlexboxItemStyle<'a>
958 = &'a Style<Atom>
959 where
960 Self: 'a;
961
962 fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
963 self.get_core_container_style(node_id)
964 }
965
966 fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
967 self.get_core_container_style(child_node_id)
968 }
969}
970
971impl taffy::LayoutGridContainer for BaseDocument {
972 type GridContainerStyle<'a>
973 = &'a Style<Atom>
974 where
975 Self: 'a;
976
977 type GridItemStyle<'a>
978 = &'a Style<Atom>
979 where
980 Self: 'a;
981
982 fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
983 self.get_core_container_style(node_id)
984 }
985
986 fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
987 self.get_core_container_style(child_node_id)
988 }
989
990 fn set_detailed_grid_info(
991 &mut self,
992 node_id: NodeId,
993 detailed_grid_info: taffy::DetailedGridInfo,
994 ) {
995 let node = self.node_from_id_mut(node_id);
996 if let Some(element) = node.element_data_mut() {
997 element.detailed_grid_info = Some(Box::new(detailed_grid_info));
998 }
999 }
1000}
1001
1002impl RoundTree for BaseDocument {
1003 fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
1004 *self.node_from_id(node_id).unrounded_layout()
1005 }
1006
1007 fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
1008 *self.node_from_id_mut(node_id).final_layout_mut() = *layout;
1009 }
1010}
1011
1012impl PrintTree for BaseDocument {
1013 fn get_debug_label(&self, node_id: NodeId) -> &'static str {
1014 let node = &self.node_from_id(node_id);
1015
1016 match node.data {
1017 NodeData::Document(_) => "DOCUMENT",
1018 NodeData::Text { .. } => node.node_debug_str().leak(),
1020 NodeData::Comment { .. } => "COMMENT",
1021 NodeData::DocumentFragment => "FRAGMENT",
1022 NodeData::ShadowRoot(_) => "SHADOW ROOT",
1023 NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
1024 NodeData::Element(_) => {
1025 let style = node.style();
1026 let display = match style.display {
1027 Display::Flex => match style.flex_direction {
1028 FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
1029 FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
1030 },
1031 Display::Grid => "GRID",
1032 Display::Block => "BLOCK",
1033 Display::FlowRoot => "FLOW ROOT",
1034 Display::None => "NONE",
1035 };
1036 format!("{} ({})", node.node_debug_str(), display).leak()
1037 } }
1039 }
1040
1041 fn get_final_layout(&self, node_id: NodeId) -> Layout {
1042 *self.node_from_id(node_id).final_layout()
1043 }
1044}
1045
1046pub struct RefCellChildIter<'a> {
1055 items: Ref<'a, [crate::NodeId]>,
1056 idx: usize,
1057}
1058impl<'a> RefCellChildIter<'a> {
1059 fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
1060 RefCellChildIter { items, idx: 0 }
1061 }
1062}
1063
1064impl Iterator for RefCellChildIter<'_> {
1065 type Item = NodeId;
1066 fn next(&mut self) -> Option<Self::Item> {
1067 self.items.get(self.idx).map(|id| {
1068 self.idx += 1;
1069 taffy_node_id(*id)
1070 })
1071 }
1072}