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