1use app_units::Au;
6use atomic_refcell::{AtomicRef, AtomicRefCell};
7use style::logical_geometry::Direction;
8use style::properties::ComputedValues;
9use style::values::computed::CSSPixelLength;
10use style::values::computed::length_percentage::CalcLengthPercentage;
11use style::values::specified::align::AlignFlags;
12use style::values::specified::box_::DisplayInside;
13use style::{Atom, Zero};
14use taffy::style_helpers::{TaffyMaxContent, TaffyMinContent};
15use taffy::{AvailableSpace, MaybeMath, RequestedAxis, RunMode};
16
17use super::{
18 SpecificTaffyGridInfo, TaffyContainer, TaffyItemBox, TaffyItemBoxInner, TaffyStyloStyle,
19};
20use crate::cell::ArcRefCell;
21use crate::context::LayoutContext;
22use crate::dom::WeakLayoutBox;
23use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
24use crate::fragment_tree::{
25 BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
26};
27use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize};
28use crate::layout_box_base::IndependentFormattingContextLayoutResult;
29use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
30use crate::sizing::{
31 ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, Size,
32 SizeConstraint,
33};
34use crate::style_ext::{ContentBoxSizesAndPBM, LayoutStyle};
35use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock};
36
37const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX);
38
39fn resolve_content_size(constraint: AvailableSpace, content_sizes: ContentSizes) -> f32 {
40 match constraint {
41 AvailableSpace::Definite(limit) => {
42 let min = content_sizes.min_content.to_f32_px();
43 let max = content_sizes.max_content.to_f32_px();
44 limit.min(max).max(min)
45 },
46 AvailableSpace::MinContent => content_sizes.min_content.to_f32_px(),
47 AvailableSpace::MaxContent => content_sizes.max_content.to_f32_px(),
48 }
49}
50
51#[inline(always)]
52fn with_independent_formatting_context<T>(
53 item: &mut TaffyItemBoxInner,
54 cb: impl FnOnce(&IndependentFormattingContext) -> T,
55) -> T {
56 match item {
57 TaffyItemBoxInner::InFlowBox(context) => cb(context),
58 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abspos_box) => {
59 cb(&AtomicRefCell::borrow(abspos_box).context)
60 },
61 }
62}
63
64struct TaffyContainerContext<'a> {
67 source_child_nodes: &'a [ArcRefCell<TaffyItemBox>],
68 layout_context: &'a LayoutContext<'a>,
69 positioning_context: &'a mut PositioningContext,
70 content_box_size_override: &'a ContainingBlock<'a>,
71 style: &'a ComputedValues,
72 specific_layout_info: Option<SpecificLayoutInfo>,
73
74 child_specific_layout_infos: Vec<Option<SpecificLayoutInfo>>,
76}
77
78struct ChildIter(std::ops::Range<usize>);
79impl Iterator for ChildIter {
80 type Item = taffy::NodeId;
81 fn next(&mut self) -> Option<Self::Item> {
82 self.0.next().map(taffy::NodeId::from)
83 }
84
85 fn size_hint(&self) -> (usize, Option<usize>) {
86 (self.0.len(), Some(self.0.len()))
87 }
88}
89
90impl taffy::TraversePartialTree for TaffyContainerContext<'_> {
91 type ChildIter<'a>
92 = ChildIter
93 where
94 Self: 'a;
95
96 fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> {
97 ChildIter(0..self.source_child_nodes.len())
98 }
99
100 fn child_count(&self, _node_id: taffy::NodeId) -> usize {
101 self.source_child_nodes.len()
102 }
103
104 fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId {
105 taffy::NodeId::from(index)
106 }
107}
108
109impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
110 type CustomIdent = Atom;
111
112 type CoreContainerStyle<'a>
113 = TaffyStyloStyle<&'a ComputedValues>
114 where
115 Self: 'a;
116
117 fn get_core_container_style(&self, _node_id: taffy::NodeId) -> Self::CoreContainerStyle<'_> {
118 TaffyStyloStyle::new(self.style, false )
119 }
120
121 fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) {
122 let id = usize::from(node_id);
123 (*self.source_child_nodes[id]).borrow_mut().taffy_layout = *layout;
124 }
125
126 #[expect(unsafe_code)]
127 fn resolve_calc_value(&self, val: *const (), basis: f32) -> f32 {
128 let calc = unsafe { &*(val as *const CalcLengthPercentage) };
136 calc.resolve(CSSPixelLength::new(basis)).px()
137 }
138
139 fn compute_child_layout(
140 &mut self,
141 node_id: taffy::NodeId,
142 inputs: taffy::LayoutInput,
143 ) -> taffy::LayoutOutput {
144 let mut child = (*self.source_child_nodes[usize::from(node_id)]).borrow_mut();
145 let child = &mut *child;
146
147 let output = with_independent_formatting_context(
148 &mut child.taffy_level_box,
149 |independent_context| -> taffy::LayoutOutput {
150 let containing_block = self.content_box_size_override;
152 let style = independent_context.style();
153
154 let child_containing_block = IndefiniteContainingBlock {
155 size: LogicalVec2 {
156 inline: inputs.parent_size.width.map(Au::from_f32_px),
157 block: inputs.parent_size.height.map(Au::from_f32_px),
158 },
159 style: containing_block.style,
160 };
161
162 let ContentBoxSizesAndPBM {
164 content_box_sizes,
165 pbm,
166 ..
167 } = independent_context
168 .layout_style()
169 .content_box_sizes_and_padding_border_margin(&child_containing_block);
170 let pb_sum = pbm.padding_border_sums.map(|v| v.to_f32_px());
171 let margin_sum = pbm.margin.auto_is(Au::zero).sum().map(|v| v.to_f32_px());
172 let content_box_inset = pb_sum + margin_sum;
173 let content_box_known_dimensions = taffy::Size {
174 width: inputs
175 .known_dimensions
176 .width
177 .map(|width| width - pb_sum.inline),
178 height: inputs
179 .known_dimensions
180 .height
181 .map(|height| height - pb_sum.block),
182 };
183 let preferred_aspect_ratio =
184 independent_context.preferred_aspect_ratio(&pbm.padding_border_sums);
185
186 let tentative_block_size = content_box_known_dimensions
188 .height
189 .map(Au::from_f32_px)
190 .map_or_else(SizeConstraint::default, SizeConstraint::Definite);
191
192 let inline_size = content_box_known_dimensions.width.unwrap_or_else(|| {
194 let constraint_space = ConstraintSpace {
195 block_size: tentative_block_size,
196 style,
197 preferred_aspect_ratio,
198 };
199
200 let result = independent_context
202 .inline_content_sizes(self.layout_context, &constraint_space);
203 let adjusted_available_space = inputs
204 .available_space
205 .width
206 .map_definite_value(|width| width - content_box_inset.inline);
207
208 resolve_content_size(adjusted_available_space, result.sizes)
209 });
210
211 if inputs.run_mode == RunMode::ComputeSize &&
213 inputs.axis == RequestedAxis::Horizontal
214 {
215 return taffy::LayoutOutput::from_outer_size(taffy::Size {
216 width: inline_size + pb_sum.inline,
217 height: 0.0,
219 });
220 }
221
222 let content_box_size_override = ContainingBlock {
223 size: ContainingBlockSize {
224 inline: Au::from_f32_px(inline_size),
225 block: tentative_block_size,
226 },
227 style,
228 };
229
230 let lazy_block_size = match content_box_known_dimensions.height {
231 None => LazySize::new(
232 &content_box_sizes.block,
233 Direction::Block,
234 Size::FitContent,
235 Au::zero,
236 None,
237 independent_context.layout_style().is_table(),
238 ),
239 Some(height) => Au::from_f32_px(height).into(),
240 };
241
242 child.positioning_context = PositioningContext::default();
243 let layout = independent_context.layout(
244 self.layout_context,
245 &mut child.positioning_context,
246 &content_box_size_override,
247 containing_block,
248 preferred_aspect_ratio,
249 &lazy_block_size,
250 );
251
252 child.child_fragments = layout.fragments;
253 self.child_specific_layout_infos[usize::from(node_id)] =
254 layout.specific_layout_info;
255
256 let block_size = lazy_block_size
257 .resolve(|| layout.content_block_size)
258 .to_f32_px();
259
260 let computed_size = taffy::Size {
261 width: inline_size + pb_sum.inline,
262 height: block_size + pb_sum.block,
263 };
264 let size = inputs.known_dimensions.unwrap_or(computed_size);
265
266 taffy::LayoutOutput {
267 size,
268 baselines: taffy::Baselines {
269 first: layout.baselines.first.map(|baseline| {
270 (baseline + pbm.padding.block_start + pbm.border.block_start)
271 .to_f32_px()
272 }),
273 last: layout.baselines.last.map(|baseline| {
274 (baseline + pbm.padding.block_start + pbm.border.block_start)
275 .to_f32_px()
276 }),
277 },
278 ..taffy::LayoutOutput::DEFAULT
279 }
280 },
281 );
282 child.taffy_baselines = output.baselines;
283 output
284 }
285}
286
287impl taffy::LayoutGridContainer for TaffyContainerContext<'_> {
288 type GridContainerStyle<'a>
289 = TaffyStyloStyle<&'a ComputedValues>
290 where
291 Self: 'a;
292
293 type GridItemStyle<'a>
294 = TaffyStyloStyle<AtomicRef<'a, ComputedValues>>
295 where
296 Self: 'a;
297
298 fn get_grid_container_style(
299 &self,
300 _node_id: taffy::prelude::NodeId,
301 ) -> Self::GridContainerStyle<'_> {
302 TaffyStyloStyle::new(self.style, false )
303 }
304
305 fn get_grid_child_style(
306 &self,
307 child_node_id: taffy::prelude::NodeId,
308 ) -> Self::GridItemStyle<'_> {
309 let id = usize::from(child_node_id);
310 let child = (*self.source_child_nodes[id]).borrow();
311 let is_replaced = child.is_in_flow_replaced();
313 let stylo_style = AtomicRef::map(child, |c| &*c.style);
314 TaffyStyloStyle::new(stylo_style, is_replaced)
315 }
316
317 fn set_detailed_grid_info(
318 &mut self,
319 _node_id: taffy::NodeId,
320 specific_layout_info: taffy::DetailedGridInfo<Atom>,
321 ) {
322 self.specific_layout_info = Some(SpecificLayoutInfo::Grid(Box::new(
323 SpecificTaffyGridInfo::from_detailed_grid_layout(specific_layout_info),
324 )));
325 }
326}
327
328impl ComputeInlineContentSizes for TaffyContainer {
329 fn compute_inline_content_sizes(
330 &self,
331 layout_context: &LayoutContext,
332 _constraint_space: &ConstraintSpace,
333 ) -> InlineContentSizesResult {
334 let style = &self.style;
335
336 let max_content_inputs = taffy::LayoutInput {
337 run_mode: taffy::RunMode::ComputeSize,
338 sizing_mode: taffy::SizingMode::InherentSize,
339 axis: taffy::RequestedAxis::Horizontal,
340 vertical_margins_are_collapsible: taffy::Line::FALSE,
341
342 known_dimensions_are_definite: taffy::Size {
343 width: true,
344 height: true,
345 },
346 known_dimensions: taffy::Size::NONE,
347 parent_size: taffy::Size::NONE,
348 available_space: taffy::Size::MAX_CONTENT,
349 };
350
351 let min_content_inputs = taffy::LayoutInput {
352 available_space: taffy::Size::MIN_CONTENT,
353 ..max_content_inputs
354 };
355
356 let containing_block = &ContainingBlock {
357 size: ContainingBlockSize {
358 inline: Au::zero(),
359 block: SizeConstraint::default(),
360 },
361 style,
362 };
363
364 let mut grid_context = TaffyContainerContext {
365 layout_context,
366 positioning_context: &mut PositioningContext::default(),
367 content_box_size_override: containing_block,
368 style,
369 source_child_nodes: &self.children,
370 specific_layout_info: None,
371 child_specific_layout_infos: vec![None; self.children.len()],
372 };
373
374 let (max_content_output, min_content_output) = match style.get_display().inside() {
375 DisplayInside::Grid => {
376 let max_content_output = taffy::compute_grid_layout(
377 &mut grid_context,
378 DUMMY_NODE_ID,
379 max_content_inputs,
380 );
381 let min_content_output = taffy::compute_grid_layout(
382 &mut grid_context,
383 DUMMY_NODE_ID,
384 min_content_inputs,
385 );
386 (max_content_output, min_content_output)
387 },
388 _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"),
389 };
390
391 let pb_sums = self
392 .layout_style()
393 .padding_border_margin(containing_block)
394 .padding_border_sums;
395
396 InlineContentSizesResult {
397 sizes: ContentSizes {
398 max_content: Au::from_f32_px(max_content_output.size.width) - pb_sums.inline,
399 min_content: Au::from_f32_px(min_content_output.size.width) - pb_sums.inline,
400 },
401
402 depends_on_block_constraints: true,
407 }
408 }
409}
410
411impl TaffyContainer {
412 pub(crate) fn layout(
414 &self,
415 layout_context: &LayoutContext,
416 positioning_context: &mut PositioningContext,
417 content_box_size_override: &ContainingBlock,
418 containing_block: &ContainingBlock,
419 ) -> IndependentFormattingContextLayoutResult {
420 let mut container_ctx = TaffyContainerContext {
421 layout_context,
422 positioning_context,
423 content_box_size_override,
424 style: content_box_size_override.style,
425 source_child_nodes: &self.children,
426 specific_layout_info: None,
427 child_specific_layout_infos: vec![None; self.children.len()],
428 };
429
430 let container_style = &content_box_size_override.style;
431 let align_items = container_style.get_align_items();
432 let justify_items = container_style.get_justify_items();
433 let pbm = self.layout_style().padding_border_margin(containing_block);
434
435 let known_dimensions = taffy::Size {
436 width: Some(
437 (content_box_size_override.size.inline + pbm.padding_border_sums.inline)
438 .to_f32_px(),
439 ),
440 height: content_box_size_override
441 .size
442 .block
443 .to_definite()
444 .map(Au::to_f32_px)
445 .maybe_add(pbm.padding_border_sums.block.to_f32_px()),
446 };
447
448 let taffy_containing_block = taffy::Size {
449 width: Some(containing_block.size.inline.to_f32_px()),
450 height: containing_block.size.block.to_definite().map(Au::to_f32_px),
451 };
452
453 let layout_input = taffy::LayoutInput {
454 run_mode: taffy::RunMode::PerformLayout,
455 sizing_mode: taffy::SizingMode::InherentSize,
456 axis: taffy::RequestedAxis::Vertical,
457 vertical_margins_are_collapsible: taffy::Line::FALSE,
458
459 known_dimensions_are_definite: taffy::Size {
460 width: true,
461 height: true,
462 },
463 known_dimensions,
464 parent_size: taffy_containing_block,
465 available_space: taffy_containing_block.map(AvailableSpace::from),
466 };
467
468 let output = match container_ctx.style.get_display().inside() {
469 DisplayInside::Grid => {
470 taffy::compute_grid_layout(&mut container_ctx, DUMMY_NODE_ID, layout_input)
471 },
472 _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"),
473 };
474
475 let fragments: Vec<Fragment> = self
478 .children
479 .iter()
480 .map(|child| (**child).borrow_mut())
481 .enumerate()
482 .map(|(child_id, mut child)| {
483 fn rect_to_physical_sides<T>(rect: taffy::Rect<T>) -> PhysicalSides<T> {
484 PhysicalSides::new(rect.top, rect.right, rect.bottom, rect.left)
485 }
486
487 fn size_and_pos_to_logical_rect<T: Default>(
488 position: taffy::Point<T>,
489 size: taffy::Size<T>,
490 ) -> PhysicalRect<T> {
491 PhysicalRect::new(
492 PhysicalPoint::new(position.x, position.y),
493 PhysicalSize::new(size.width, size.height),
494 )
495 }
496
497 let layout = &child.taffy_layout;
498
499 let padding = rect_to_physical_sides(layout.padding.map(Au::from_f32_px));
500 let border = rect_to_physical_sides(layout.border.map(Au::from_f32_px));
501 let margin = rect_to_physical_sides(layout.margin.map(Au::from_f32_px));
502
503 let content_size = size_and_pos_to_logical_rect(
508 taffy::Point {
509 x: Au::from_f32_px(
510 layout.location.x + layout.padding.left + layout.border.left,
511 ) - pbm.padding.inline_start -
512 pbm.border.inline_start,
513 y: Au::from_f32_px(
514 layout.location.y + layout.padding.top + layout.border.top,
515 ) - pbm.padding.block_start -
516 pbm.border.block_start,
517 },
518 taffy::Size {
519 width: layout.size.width -
520 layout.padding.left -
521 layout.padding.right -
522 layout.border.left -
523 layout.border.right,
524 height: layout.size.height -
525 layout.padding.top -
526 layout.padding.bottom -
527 layout.border.top -
528 layout.border.bottom,
529 }
530 .map(Au::from_f32_px),
531 );
532
533 let child_specific_layout_info: Option<SpecificLayoutInfo> =
534 std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]);
535
536 let fragment = match &mut child.taffy_level_box {
537 TaffyItemBoxInner::InFlowBox(independent_box) => {
538 let mut fragment_info = independent_box.base_fragment_info();
539 fragment_info
540 .flags
541 .insert(FragmentFlags::IS_FLEX_OR_GRID_ITEM);
542 let mut box_fragment = BoxFragment::new(
543 fragment_info,
544 independent_box.style().clone(),
545 std::mem::take(&mut child.child_fragments),
546 content_size,
547 padding,
548 border,
549 margin,
550 child_specific_layout_info,
551 )
552 .with_baselines(Baselines {
553 first: child.taffy_baselines.first.map(|baseline| {
554 Au::from_f32_px(baseline) - padding.top - border.top
555 }),
556 last: child.taffy_baselines.last.map(|baseline| {
557 Au::from_f32_px(baseline) - padding.top - border.top
558 }),
559 });
560
561 child.positioning_context.layout_collected_children(
562 container_ctx.layout_context,
563 &mut box_fragment,
564 );
565
566 child
567 .positioning_context
568 .adjust_static_position_of_hoisted_fragments_with_offset(
569 &box_fragment.content_rect().origin.to_vector(),
570 PositioningContextLength::zero(),
571 );
572 container_ctx
573 .positioning_context
574 .append(std::mem::take(&mut child.positioning_context));
575
576 Fragment::Box(box_fragment.into())
577 },
578 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => {
579 fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags {
580 match value {
581 AlignFlags::AUTO => auto,
582 AlignFlags::NORMAL => AlignFlags::STRETCH,
583 value => value,
584 }
585 }
586
587 let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
588 abs_pos_box.clone(),
589 content_size,
590 LogicalVec2 {
591 inline: resolve_alignment(
592 child.style.get_align_self().0,
593 align_items.0,
594 ),
595 block: resolve_alignment(
596 child.style.get_justify_self().0,
597 justify_items.computed.0.0,
598 ),
599 },
600 container_ctx.style.writing_mode,
601 );
602 let hoisted_fragment = hoisted_box.fragment.clone();
603 container_ctx.positioning_context.push(hoisted_box);
604 Fragment::AbsoluteOrFixedPositionedPlaceholder(hoisted_fragment)
605 },
606 };
607
608 if let TaffyItemBoxInner::InFlowBox(independent_formatting_context) =
609 &child.taffy_level_box
610 {
611 independent_formatting_context
612 .base
613 .set_fragment(fragment.clone());
614 }
615 fragment
616 })
617 .collect();
618
619 IndependentFormattingContextLayoutResult {
620 fragments,
621 content_block_size: Au::from_f32_px(output.size.height) - pbm.padding_border_sums.block,
622 content_inline_size_for_table: None,
623 baselines: Baselines {
624 first: output.baselines.first.map(|baseline| {
625 Au::from_f32_px(baseline) - pbm.padding.block_start - pbm.border.block_start
626 }),
627 last: output.baselines.last.map(|baseline| {
628 Au::from_f32_px(baseline) - pbm.padding.block_start - pbm.border.block_start
629 }),
630 },
631
632 depends_on_block_constraints: true,
637 specific_layout_info: container_ctx.specific_layout_info,
638 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
639 }
640 }
641
642 #[inline]
643 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
644 LayoutStyle::Default(&self.style)
645 }
646
647 pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
648 for child in &self.children {
649 child.borrow_mut().with_base_mut(|base| {
650 base.parent_box.replace(layout_box.clone());
651 });
652 }
653 }
654
655 pub(crate) fn subtree_size(&self) -> usize {
656 self.children
657 .iter()
658 .map(|child| child.borrow().with_base(|base| base.subtree_size()))
659 .sum()
660 }
661}