1use core::any::TypeId;
26
27use style::Atom;
28use style::properties::ComputedValues;
29use taffy::tree::{Cache, Layout, LayoutInput, LayoutOutput, NodeId};
30use taffy::{
31 CacheTree, LayoutBlockContainer, LayoutFlexboxContainer, LayoutGridContainer,
32 LayoutPartialTree, TraversePartialTree, compute_block_layout, compute_cached_layout,
33 compute_flexbox_layout, compute_grid_layout, compute_hidden_layout, compute_leaf_layout,
34 compute_root_layout,
35};
36
37use kozan_primitives::geometry::{Point, Size};
38
39use kozan_primitives::arena::Storage;
40
41use crate::TextData;
42use crate::data_storage::DataStorage;
43use crate::dom::document::Document;
44use crate::dom::element_data::ElementData;
45use crate::dom::node::{NodeMeta, NodeType};
46use crate::layout::algo::shared;
47use crate::layout::box_model::is_stacking_context;
48use crate::layout::context::LayoutContext;
49use crate::layout::fragment::{
50 BoxFragmentData, ChildFragment, Fragment, OverflowClip, PhysicalInsets, TextFragmentData,
51};
52use crate::layout::node_data::LayoutNodeData;
53use crate::layout::result::{EscapedMargins, LayoutResult};
54use crate::tree::{self, TreeData};
55
56impl Document {
61 #[inline]
63 pub(crate) fn is_text_node(&self, index: u32) -> bool {
64 self.meta
65 .get(index)
66 .is_some_and(|m| m.flags.node_type() == NodeType::Text)
67 }
68
69 pub(crate) fn text_content_ref(&self, index: u32) -> Option<&str> {
71 let meta = self.meta.get(index)?;
72 if meta.data_type_id != TypeId::of::<TextData>() {
73 return None;
74 }
75 let data = unsafe { self.data.get::<TextData>(index) };
76 Some(&data.content)
77 }
78
79 pub(crate) fn flush_styles_to_layout(&mut self, index: u32, force_clear: bool) {
87 if !self.is_text_node(index) {
88 if let Some(cv) = self.computed_style(index) {
89 let new_style = shared::computed_to_taffy_item_style(&cv);
90 if let Some(data) = self.layout.get_mut(index) {
91 if force_clear {
92 data.style = new_style;
93 data.clear_cache();
94 } else if data.style != new_style {
95 data.style = new_style;
97 data.clear_cache();
98 self.clear_ancestor_caches(index);
99 }
100 }
102 } else {
103 if let Some(data) = self.layout.get_mut(index) {
105 data.clear_cache();
106 }
107 }
108 } else if force_clear {
109 if let Some(data) = self.layout.get_mut(index) {
112 data.clear_cache();
113 }
114 }
115
116 let dom_children = unsafe { tree::ops::children(&self.tree, index) };
117 for child in dom_children {
118 self.flush_styles_to_layout(child, force_clear);
119 }
120 }
121
122 fn clear_ancestor_caches(&mut self, index: u32) {
124 let mut current = index;
125 loop {
126 let parent = match self.tree.get(current) {
127 Some(td) if td.parent != crate::id::INVALID => td.parent,
128 _ => break,
129 };
130 if let Some(pd) = self.layout.get_mut(parent) {
131 pd.clear_cache();
132 }
133 current = parent;
134 }
135 }
136
137 pub(crate) fn resolve_layout_children(&mut self, index: u32) {
142 if self.is_text_node(index) {
143 if let Some(data) = self.layout.get_mut(index) {
144 data.layout_children = Some(Vec::new());
145 }
146 return;
147 }
148
149 let dom_children = unsafe { tree::ops::children(&self.tree, index) };
150 let mut layout_children = Vec::new();
151
152 for child in dom_children {
153 if !self.is_text_node(child) {
155 let display = self
156 .layout
157 .get(child)
158 .map_or(taffy::Display::None, |d| d.style.display);
159 if display == taffy::Display::None {
160 continue;
161 }
162 }
163
164 if let Some(data) = self.layout.get_mut(child) {
165 data.layout_parent = Some(index);
166 }
167 layout_children.push(child);
168 self.resolve_layout_children(child);
169 }
170
171 if let Some(data) = self.layout.get_mut(index) {
172 data.layout_children = Some(layout_children);
173 }
174 }
175
176 pub(crate) fn set_root_viewport_style(&mut self, root: u32) {
182 let lp = crate::styling::taffy_bridge::convert::length_percentage(
183 &style::values::computed::LengthPercentage::new_percent(
184 style::values::computed::Percentage(1.0),
185 ),
186 );
187 let full: taffy::Dimension = lp.into();
188 if let Some(data) = self.layout.get_mut(root) {
189 data.style.display = taffy::Display::Block;
190 data.style.size.width = full;
191 data.style.size.height = full;
192 }
193 }
194
195 fn apply_rtl_swap_recursive(&mut self, index: u32) {
197 let children = self
198 .layout
199 .get(index)
200 .and_then(|d| d.layout_children.clone())
201 .unwrap_or_default();
202
203 for &child in &children {
204 let is_abs = self
205 .layout
206 .get(child)
207 .is_some_and(|d| d.style.position == taffy::Position::Absolute);
208
209 if is_abs {
210 if let Some(parent_cv) = self.computed_style(index) {
211 let dir = shared::InlineDirection::from_style(&parent_cv);
212 if let Some(data) = self.layout.get_mut(child) {
213 dir.swap_absolute_insets(&mut data.style);
214 }
215 }
216 }
217 }
218
219 for child in children {
220 self.apply_rtl_swap_recursive(child);
221 }
222 }
223
224 pub fn resolve_layout(
234 &mut self,
235 root: u32,
236 available_width: Option<f32>,
237 available_height: Option<f32>,
238 ctx: &LayoutContext,
239 ) -> LayoutResult {
240 self.resolve_layout_dirty(root, available_width, available_height, ctx, true)
241 }
242
243 pub fn resolve_layout_dirty(
248 &mut self,
249 root: u32,
250 available_width: Option<f32>,
251 available_height: Option<f32>,
252 ctx: &LayoutContext,
253 layout_dirty: bool,
254 ) -> LayoutResult {
255 self.flush_styles_to_layout(root, layout_dirty);
257
258 self.resolve_layout_children(root);
260
261 self.set_root_viewport_style(root);
263
264 self.apply_rtl_swap_recursive(root);
266
267 let available_space = taffy::Size {
269 width: available_width.map_or(
270 taffy::AvailableSpace::MaxContent,
271 taffy::AvailableSpace::Definite,
272 ),
273 height: available_height.map_or(
274 taffy::AvailableSpace::MaxContent,
275 taffy::AvailableSpace::Definite,
276 ),
277 };
278 {
279 let mut view = DocumentLayoutView::from_document(self, ctx);
280 view.compute_layout(root, available_space);
281 }
282
283 build_fragment_from_document(self, ctx, root)
285 }
286}
287
288pub(crate) struct DocumentLayoutView<'a> {
301 layout: &'a mut Storage<LayoutNodeData>,
302 meta: &'a Storage<NodeMeta>,
303 tree: &'a Storage<TreeData>,
304 element_data: &'a Storage<ElementData>,
305 data: &'a DataStorage,
306 ctx: &'a LayoutContext<'a>,
307}
308
309impl<'a> DocumentLayoutView<'a> {
310 fn from_document(doc: &'a mut Document, ctx: &'a LayoutContext<'a>) -> Self {
311 Self {
312 layout: &mut doc.layout,
313 meta: &doc.meta,
314 tree: &doc.tree,
315 element_data: &doc.element_data,
316 data: &doc.data,
317 ctx,
318 }
319 }
320
321 fn compute_layout(&mut self, root: u32, available_space: taffy::Size<taffy::AvailableSpace>) {
322 let root_node = NodeId::from(root as u64);
323 compute_root_layout(self, root_node, available_space);
324 }
325
326 #[inline]
327 fn is_text_node(&self, index: u32) -> bool {
328 self.meta
329 .get(index)
330 .is_some_and(|m| m.flags.node_type() == NodeType::Text)
331 }
332
333 fn text_content_ref(&self, index: u32) -> Option<&str> {
334 let meta = self.meta.get(index)?;
335 if meta.data_type_id != std::any::TypeId::of::<TextData>() {
336 return None;
337 }
338 let data = unsafe { self.data.get::<TextData>(index) };
339 Some(&data.content)
340 }
341
342 fn computed_style(&self, index: u32) -> Option<servo_arc::Arc<ComputedValues>> {
343 let ed = self.element_data.get(index)?;
344 let data = ed.stylo_data.borrow();
345 if data.has_styles() {
346 Some(data.styles.primary().clone())
347 } else {
348 None
349 }
350 }
351}
352
353pub(crate) struct ChildIter {
358 ids: Vec<u32>,
359 pos: usize,
360}
361
362impl Iterator for ChildIter {
363 type Item = NodeId;
364 fn next(&mut self) -> Option<Self::Item> {
365 if self.pos < self.ids.len() {
366 let id = self.ids[self.pos];
367 self.pos += 1;
368 Some(NodeId::from(id as u64))
369 } else {
370 None
371 }
372 }
373}
374
375impl TraversePartialTree for DocumentLayoutView<'_> {
376 type ChildIter<'c>
377 = ChildIter
378 where
379 Self: 'c;
380
381 fn child_ids(&self, parent: NodeId) -> Self::ChildIter<'_> {
382 let idx = u64::from(parent) as u32;
383 let ids = self
384 .layout
385 .get(idx)
386 .and_then(|d| d.layout_children.as_ref())
387 .cloned()
388 .unwrap_or_default();
389 ChildIter { ids, pos: 0 }
390 }
391
392 fn child_count(&self, parent: NodeId) -> usize {
393 let idx = u64::from(parent) as u32;
394 self.layout
395 .get(idx)
396 .and_then(|d| d.layout_children.as_ref())
397 .map_or(0, |c| c.len())
398 }
399
400 fn get_child_id(&self, parent: NodeId, index: usize) -> NodeId {
401 let idx = u64::from(parent) as u32;
402 let child = self
403 .layout
404 .get(idx)
405 .and_then(|d| d.layout_children.as_ref())
406 .and_then(|c| c.get(index))
407 .copied()
408 .expect("child index out of bounds");
409 NodeId::from(child as u64)
410 }
411}
412
413impl LayoutPartialTree for DocumentLayoutView<'_> {
418 type CoreContainerStyle<'a>
419 = &'a taffy::Style<Atom>
420 where
421 Self: 'a;
422 type CustomIdent = Atom;
423
424 fn get_core_container_style(&self, node: NodeId) -> Self::CoreContainerStyle<'_> {
425 let idx = u64::from(node) as u32;
426 &self.layout.get(idx).expect("missing layout data").style
427 }
428
429 fn set_unrounded_layout(&mut self, node: NodeId, layout: &Layout) {
430 let idx = u64::from(node) as u32;
431 if let Some(data) = self.layout.get_mut(idx) {
432 data.unrounded_layout = *layout;
433 }
434 }
435
436 fn compute_child_layout(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
437 let idx = u64::from(node) as u32;
438 let display = self
439 .layout
440 .get(idx)
441 .map_or(taffy::Display::None, |d| d.style.display);
442
443 if display == taffy::Display::None {
444 return compute_hidden_layout(self, node);
445 }
446
447 compute_cached_layout(self, node, inputs, |view, node, inputs| {
448 view.compute_uncached_child_layout(node, inputs, display)
449 })
450 }
451}
452
453impl DocumentLayoutView<'_> {
454 fn compute_uncached_child_layout(
459 &mut self,
460 node: NodeId,
461 inputs: LayoutInput,
462 display: taffy::Display,
463 ) -> LayoutOutput {
464 let idx = u64::from(node) as u32;
465 let is_text = self.is_text_node(idx);
466 let has_children = self
467 .layout
468 .get(idx)
469 .and_then(|d| d.layout_children.as_ref())
470 .is_some_and(|c: &Vec<u32>| !c.is_empty());
471
472 if is_text && !has_children {
473 return self.layout_text_leaf(node, inputs, idx);
474 }
475
476 let is_replaced = self
477 .layout
478 .get(idx)
479 .is_some_and(|d| d.style.item_is_replaced);
480
481 if is_replaced {
482 return self.layout_replaced(node, inputs, idx);
483 }
484
485 self.layout_container(node, inputs, display, idx)
486 }
487
488 fn layout_text_leaf(&mut self, _node: NodeId, inputs: LayoutInput, idx: u32) -> LayoutOutput {
489 let parent_idx = self.tree.get(idx).map_or(crate::id::INVALID, |t| t.parent);
490 let parent_style = if parent_idx != crate::id::INVALID {
491 self.computed_style(parent_idx)
492 } else {
493 None
494 }
495 .unwrap_or_else(|| crate::styling::initial_values_arc().clone());
496
497 let text = self.text_content_ref(idx).map(str::to_string);
498 let style = &self.layout.get(idx).expect("missing layout data").style;
499 let measurer = self.ctx.text_measurer;
500
501 compute_leaf_layout(
502 inputs,
503 style,
504 |_val, _basis| 0.0,
505 |known_dimensions, available_space| {
506 compute_text_measure(
507 known_dimensions,
508 available_space,
509 text.as_deref(),
510 measurer,
511 &parent_style,
512 )
513 },
514 )
515 }
516
517 fn layout_replaced(&mut self, _node: NodeId, inputs: LayoutInput, idx: u32) -> LayoutOutput {
518 let style = &self.layout.get(idx).expect("missing layout data").style;
519 compute_leaf_layout(
520 inputs,
521 style,
522 |_val, _basis| 0.0,
523 |known_dimensions, _available_space| taffy::Size {
524 width: known_dimensions.width.unwrap_or(0.0),
525 height: known_dimensions.height.unwrap_or(0.0),
526 },
527 )
528 }
529
530 fn layout_container(
531 &mut self,
532 node: NodeId,
533 inputs: LayoutInput,
534 display: taffy::Display,
535 idx: u32,
536 ) -> LayoutOutput {
537 match display {
538 taffy::Display::Flex => compute_flexbox_layout(self, node, inputs),
539 taffy::Display::Grid => compute_grid_layout(self, node, inputs),
540 taffy::Display::Block => compute_block_layout(self, node, inputs),
541 _ => {
542 let style = &self.layout.get(idx).expect("missing layout data").style;
543 compute_leaf_layout(
544 inputs,
545 style,
546 |_val, _basis| 0.0,
547 |known, _avail| taffy::Size {
548 width: known.width.unwrap_or(0.0),
549 height: known.height.unwrap_or(0.0),
550 },
551 )
552 }
553 }
554 }
555}
556
557fn compute_text_measure(
566 known_dimensions: taffy::Size<Option<f32>>,
567 available_space: taffy::Size<taffy::AvailableSpace>,
568 text: Option<&str>,
569 measurer: &dyn crate::layout::inline::measurer::TextMeasurer,
570 parent_style: &ComputedValues,
571) -> taffy::Size<f32> {
572 let Some(text) = text else {
573 return taffy::Size {
574 width: known_dimensions.width.unwrap_or(0.0),
575 height: known_dimensions.height.unwrap_or(0.0),
576 };
577 };
578
579 use crate::layout::inline::font_system::FontQuery;
580 let query = FontQuery::from_computed(parent_style);
581
582 let max_w = match available_space.width {
583 taffy::AvailableSpace::Definite(w) => Some(w),
584 taffy::AvailableSpace::MaxContent => None,
585 taffy::AvailableSpace::MinContent => Some(0.0),
586 };
587
588 let text_metrics = measurer.shape_text(text, &query);
589 let fm = measurer.query_metrics(&query);
590
591 let text_width = if let Some(mw) = max_w {
592 text_metrics.width.min(mw)
593 } else {
594 text_metrics.width
595 };
596
597 let lh = crate::layout::inline::measurer::resolve_line_height(
598 &parent_style.clone_line_height(),
599 query.font_size,
600 &fm,
601 );
602
603 taffy::Size {
604 width: known_dimensions.width.unwrap_or(text_width),
605 height: known_dimensions.height.unwrap_or(lh),
606 }
607}
608
609impl CacheTree for DocumentLayoutView<'_> {
614 fn cache_get(
615 &self,
616 node: NodeId,
617 known_dimensions: taffy::Size<Option<f32>>,
618 available_space: taffy::Size<taffy::AvailableSpace>,
619 run_mode: taffy::tree::RunMode,
620 ) -> Option<LayoutOutput> {
621 let idx = u64::from(node) as u32;
622 self.layout
623 .get(idx)?
624 .cache
625 .get(known_dimensions, available_space, run_mode)
626 }
627
628 fn cache_store(
629 &mut self,
630 node: NodeId,
631 known_dimensions: taffy::Size<Option<f32>>,
632 available_space: taffy::Size<taffy::AvailableSpace>,
633 run_mode: taffy::tree::RunMode,
634 layout_output: LayoutOutput,
635 ) {
636 let idx = u64::from(node) as u32;
637 if let Some(data) = self.layout.get_mut(idx) {
638 data.cache
639 .store(known_dimensions, available_space, run_mode, layout_output);
640 }
641 }
642
643 fn cache_clear(&mut self, node: NodeId) {
644 let idx = u64::from(node) as u32;
645 if let Some(data) = self.layout.get_mut(idx) {
646 data.cache = Cache::new();
647 }
648 }
649}
650
651impl LayoutFlexboxContainer for DocumentLayoutView<'_> {
656 type FlexboxContainerStyle<'a>
657 = &'a taffy::Style<Atom>
658 where
659 Self: 'a;
660 type FlexboxItemStyle<'a>
661 = &'a taffy::Style<Atom>
662 where
663 Self: 'a;
664
665 fn get_flexbox_container_style(&self, node: NodeId) -> Self::FlexboxContainerStyle<'_> {
666 let idx = u64::from(node) as u32;
667 &self.layout.get(idx).expect("missing layout data").style
668 }
669
670 fn get_flexbox_child_style(&self, child: NodeId) -> Self::FlexboxItemStyle<'_> {
671 let idx = u64::from(child) as u32;
672 &self.layout.get(idx).expect("missing layout data").style
673 }
674}
675
676impl LayoutGridContainer for DocumentLayoutView<'_> {
677 type GridContainerStyle<'a>
678 = &'a taffy::Style<Atom>
679 where
680 Self: 'a;
681 type GridItemStyle<'a>
682 = &'a taffy::Style<Atom>
683 where
684 Self: 'a;
685
686 fn get_grid_container_style(&self, node: NodeId) -> Self::GridContainerStyle<'_> {
687 let idx = u64::from(node) as u32;
688 &self.layout.get(idx).expect("missing layout data").style
689 }
690
691 fn get_grid_child_style(&self, child: NodeId) -> Self::GridItemStyle<'_> {
692 let idx = u64::from(child) as u32;
693 &self.layout.get(idx).expect("missing layout data").style
694 }
695}
696
697impl LayoutBlockContainer for DocumentLayoutView<'_> {
698 type BlockContainerStyle<'a>
699 = &'a taffy::Style<Atom>
700 where
701 Self: 'a;
702 type BlockItemStyle<'a>
703 = &'a taffy::Style<Atom>
704 where
705 Self: 'a;
706
707 fn get_block_container_style(&self, node: NodeId) -> Self::BlockContainerStyle<'_> {
708 let idx = u64::from(node) as u32;
709 &self.layout.get(idx).expect("missing layout data").style
710 }
711
712 fn get_block_child_style(&self, child: NodeId) -> Self::BlockItemStyle<'_> {
713 let idx = u64::from(child) as u32;
714 &self.layout.get(idx).expect("missing layout data").style
715 }
716}
717
718pub(crate) fn build_fragment_from_document(
727 doc: &Document,
728 ctx: &LayoutContext,
729 root: u32,
730) -> LayoutResult {
731 build_fragment_recursive(doc, ctx, root)
732}
733
734fn overflow_clip_from_style(overflow: style::computed_values::overflow_x::T) -> OverflowClip {
736 use style::computed_values::overflow_x::T;
737 match overflow {
738 T::Visible => OverflowClip::Visible,
739 T::Hidden | T::Clip => OverflowClip::Hidden,
740 T::Scroll => OverflowClip::Scroll,
741 T::Auto => OverflowClip::Auto,
742 }
743}
744
745fn build_fragment_recursive(doc: &Document, ctx: &LayoutContext, index: u32) -> LayoutResult {
746 let layout_data = doc.layout.get(index).expect("missing layout data");
747 let layout = &layout_data.unrounded_layout;
748 let is_text = doc.is_text_node(index);
749
750 let style = if is_text {
752 let parent_idx = doc.tree.get(index).map_or(crate::id::INVALID, |t| t.parent);
753 if parent_idx != crate::id::INVALID {
754 doc.computed_style(parent_idx)
755 .unwrap_or_else(|| crate::styling::initial_values_arc().clone())
756 } else {
757 crate::styling::initial_values_arc().clone()
758 }
759 } else {
760 doc.computed_style(index)
761 .unwrap_or_else(|| crate::styling::initial_values_arc().clone())
762 };
763
764 let dom_node = Some(index);
765
766 let border = PhysicalInsets {
767 top: layout.border.top,
768 right: layout.border.right,
769 bottom: layout.border.bottom,
770 left: layout.border.left,
771 };
772 let padding = PhysicalInsets {
773 top: layout.padding.top,
774 right: layout.padding.right,
775 bottom: layout.padding.bottom,
776 left: layout.padding.left,
777 };
778
779 let children_ids = layout_data.layout_children.clone().unwrap_or_default();
781 let mut children = Vec::with_capacity(children_ids.len());
782
783 for &child_id in &children_ids {
784 let child_layout = &doc
785 .layout
786 .get(child_id)
787 .expect("missing layout data")
788 .unrounded_layout;
789 let child_result = build_fragment_recursive(doc, ctx, child_id);
790
791 children.push(ChildFragment {
792 offset: Point::new(child_layout.location.x, child_layout.location.y),
793 fragment: child_result.fragment,
794 });
795 }
796
797 let size = Size::new(layout.size.width, layout.size.height);
798
799 let scrollable_overflow = {
802 let mut max_w = 0.0_f32;
803 let mut max_h = 0.0_f32;
804 for child in &children {
805 max_w = max_w.max(child.offset.x + child.fragment.size.width);
806 max_h = max_h.max(child.offset.y + child.fragment.size.height);
807 }
808 Size::new(max_w, max_h)
809 };
810
811 let display = layout_data.style.display;
813 let dir = shared::InlineDirection::from_style(&style);
814 let child_positions: Vec<_> = children_ids
815 .iter()
816 .map(|&id| {
817 doc.layout
818 .get(id)
819 .expect("missing layout data for child during RTL fixup")
820 .style
821 .position
822 })
823 .collect();
824 dir.fixup_children(
825 display,
826 &mut children,
827 &child_positions,
828 border.left,
829 border.right,
830 padding.left,
831 padding.right,
832 size.width,
833 );
834
835 let has_children = !children.is_empty();
837
838 let fragment = if is_text && !has_children {
839 let text: Option<std::sync::Arc<str>> = doc.text_content_ref(index).map(Into::into);
840 let text_len = text.as_ref().map_or(0, |t| t.len());
841
842 use crate::layout::inline::font_system::FontQuery;
843 let query = FontQuery::from_computed(&style);
844 let color_abs = style.clone_color();
845 let color_rgba = [
846 (color_abs.components.0 * 255.0).round().clamp(0.0, 255.0) as u8,
847 (color_abs.components.1 * 255.0).round().clamp(0.0, 255.0) as u8,
848 (color_abs.components.2 * 255.0).round().clamp(0.0, 255.0) as u8,
849 (color_abs.alpha * 255.0).round().clamp(0.0, 255.0) as u8,
850 ];
851 let shaped_runs = if let Some(ref t) = text {
852 ctx.text_measurer.shape_glyphs(t, &query, color_rgba)
853 } else {
854 Vec::new()
855 };
856
857 let metrics = ctx.text_measurer.font_metrics(query.font_size);
858 let lh = crate::layout::inline::measurer::resolve_line_height(
859 &style.clone_line_height(),
860 query.font_size,
861 &metrics,
862 );
863 let font_height = metrics.ascent + metrics.descent;
864 let half_leading = ((lh - font_height) / 2.0).max(0.0);
865 let baseline = metrics.ascent + half_leading;
866
867 Fragment::new_text_styled(
868 size,
869 TextFragmentData {
870 text_range: 0..text_len,
871 baseline,
872 text,
873 shaped_runs,
874 },
875 style,
876 dom_node,
877 )
878 } else {
879 Fragment::new_box_styled(
880 size,
881 BoxFragmentData {
882 children,
883 padding,
884 border,
885 scrollable_overflow,
886 is_stacking_context: is_stacking_context(&style),
887 overflow_x: overflow_clip_from_style(style.clone_overflow_x()),
888 overflow_y: overflow_clip_from_style(style.clone_overflow_y()),
889 },
890 style,
891 dom_node,
892 )
893 };
894
895 LayoutResult {
896 fragment,
897 intrinsic_sizes: None,
898 escaped_margins: EscapedMargins::default(),
899 }
900}
901
902#[cfg(test)]
903mod tests {
904 use super::*;
905 use crate::dom::traits::{Element, HasHandle};
906 use crate::layout::inline::FontSystem;
907
908 #[test]
909 fn resolve_layout_produces_fragment() {
910 let mut doc = Document::new();
911 doc.init_body();
912 doc.set_viewport(800.0, 600.0);
913 doc.recalc_styles();
914
915 let measurer = FontSystem::new();
916 let ctx = LayoutContext {
917 text_measurer: &measurer,
918 };
919
920 let root = doc.root_index();
921 let result = doc.resolve_layout(root, Some(800.0), Some(600.0), &ctx);
922 assert!(result.fragment.size.width > 0.0);
923 assert!(result.fragment.size.height > 0.0);
924 }
925
926 #[test]
927 fn text_node_gets_layout() {
928 let mut doc = Document::new();
929 doc.init_body();
930 doc.set_viewport(800.0, 600.0);
931
932 let text = doc.create_text("Hello");
933 doc.body().append(text.handle());
934 doc.recalc_styles();
935
936 let measurer = FontSystem::new();
937 let ctx = LayoutContext {
938 text_measurer: &measurer,
939 };
940
941 let root = doc.root_index();
942 let result = doc.resolve_layout(root, Some(800.0), Some(600.0), &ctx);
943 assert!(result.fragment.size.width > 0.0);
944 }
945
946 #[test]
947 fn display_none_excluded_from_layout() {
948 let mut doc = Document::new();
949 doc.init_body();
950 doc.set_viewport(800.0, 600.0);
951
952 doc.add_stylesheet(".hidden { display: none; }");
954
955 let div = doc.create::<crate::HtmlDivElement>();
956 div.set_class_name("hidden");
957 doc.body().append(div.handle());
958 doc.recalc_styles();
959
960 let body_idx = doc.body;
961 doc.flush_styles_to_layout(doc.root_index(), false);
962 doc.resolve_layout_children(doc.root_index());
963
964 let body_children = doc
966 .layout
967 .get(body_idx)
968 .and_then(|d| d.layout_children.as_ref())
969 .map(|c| c.len())
970 .unwrap_or(0);
971 assert_eq!(body_children, 0);
972 }
973
974 #[test]
975 fn is_text_node_works() {
976 let doc = Document::new();
977 assert!(!doc.is_text_node(doc.root_index()));
979 }
980
981 #[test]
982 fn layout_children_set_for_text() {
983 let mut doc = Document::new();
984 doc.init_body();
985 doc.set_viewport(800.0, 600.0);
986
987 let text = doc.create_text("Hello");
988 let text_idx = text.handle().raw().index();
989 doc.body().append(text.handle());
990 doc.recalc_styles();
991
992 doc.flush_styles_to_layout(doc.root_index(), false);
993 doc.resolve_layout_children(doc.root_index());
994
995 let text_children = doc
997 .layout
998 .get(text_idx)
999 .and_then(|d| d.layout_children.as_ref())
1000 .map(|c| c.len());
1001 assert_eq!(text_children, Some(0));
1002 }
1003}