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::CacheableLayoutResult;
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_independant_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
84impl taffy::TraversePartialTree for TaffyContainerContext<'_> {
85 type ChildIter<'a>
86 = ChildIter
87 where
88 Self: 'a;
89
90 fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> {
91 ChildIter(0..self.source_child_nodes.len())
92 }
93
94 fn child_count(&self, _node_id: taffy::NodeId) -> usize {
95 self.source_child_nodes.len()
96 }
97
98 fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId {
99 taffy::NodeId::from(index)
100 }
101}
102
103impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
104 type CustomIdent = Atom;
105
106 type CoreContainerStyle<'a>
107 = TaffyStyloStyle<&'a ComputedValues>
108 where
109 Self: 'a;
110
111 fn get_core_container_style(&self, _node_id: taffy::NodeId) -> Self::CoreContainerStyle<'_> {
112 TaffyStyloStyle::new(self.style, false )
113 }
114
115 fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) {
116 let id = usize::from(node_id);
117 (*self.source_child_nodes[id]).borrow_mut().taffy_layout = *layout;
118 }
119
120 #[expect(unsafe_code)]
121 fn resolve_calc_value(&self, val: *const (), basis: f32) -> f32 {
122 let calc = unsafe { &*(val as *const CalcLengthPercentage) };
130 calc.resolve(CSSPixelLength::new(basis)).px()
131 }
132
133 fn compute_child_layout(
134 &mut self,
135 node_id: taffy::NodeId,
136 inputs: taffy::LayoutInput,
137 ) -> taffy::LayoutOutput {
138 let mut child = (*self.source_child_nodes[usize::from(node_id)]).borrow_mut();
139 let child = &mut *child;
140
141 with_independant_formatting_context(
142 &mut child.taffy_level_box,
143 |independent_context| -> taffy::LayoutOutput {
144 let containing_block = &self.content_box_size_override;
146 let style = independent_context.style();
147
148 let pbm = independent_context
150 .layout_style()
151 .padding_border_margin(containing_block);
152 let pb_sum = pbm.padding_border_sums.map(|v| v.to_f32_px());
153 let margin_sum = pbm.margin.auto_is(Au::zero).sum().map(|v| v.to_f32_px());
154 let content_box_inset = pb_sum + margin_sum;
155 let content_box_known_dimensions = taffy::Size {
156 width: inputs
157 .known_dimensions
158 .width
159 .map(|width| width - pb_sum.inline),
160 height: inputs
161 .known_dimensions
162 .height
163 .map(|height| height - pb_sum.block),
164 };
165 let preferred_aspect_ratio =
166 independent_context.preferred_aspect_ratio(&pbm.padding_border_sums);
167
168 let tentative_block_size = content_box_known_dimensions
170 .height
171 .map(Au::from_f32_px)
172 .map_or_else(SizeConstraint::default, SizeConstraint::Definite);
173
174 let inline_size = content_box_known_dimensions.width.unwrap_or_else(|| {
176 let constraint_space = ConstraintSpace {
177 block_size: tentative_block_size,
178 style,
179 preferred_aspect_ratio,
180 };
181
182 let result = independent_context
184 .inline_content_sizes(self.layout_context, &constraint_space);
185 let adjusted_available_space = inputs
186 .available_space
187 .width
188 .map_definite_value(|width| width - content_box_inset.inline);
189
190 resolve_content_size(adjusted_available_space, result.sizes)
191 });
192
193 if inputs.run_mode == RunMode::ComputeSize &&
195 inputs.axis == RequestedAxis::Horizontal
196 {
197 return taffy::LayoutOutput::from_outer_size(taffy::Size {
198 width: inline_size + pb_sum.inline,
199 height: 0.0,
201 });
202 }
203
204 let content_box_size_override = ContainingBlock {
205 size: ContainingBlockSize {
206 inline: Au::from_f32_px(inline_size),
207 block: tentative_block_size,
208 },
209 style,
210 };
211
212 let lazy_block_size = match content_box_known_dimensions.height {
213 None => LazySize::intrinsic(),
215 Some(height) => Au::from_f32_px(height).into(),
216 };
217
218 child.positioning_context = PositioningContext::default();
219 let layout = independent_context.layout(
220 self.layout_context,
221 &mut child.positioning_context,
222 &content_box_size_override,
223 containing_block,
224 preferred_aspect_ratio,
225 &lazy_block_size,
226 );
227
228 child.child_fragments = layout.fragments;
229 self.child_specific_layout_infos[usize::from(node_id)] =
230 layout.specific_layout_info;
231
232 let block_size = lazy_block_size
233 .resolve(|| layout.content_block_size)
234 .to_f32_px();
235
236 let computed_size = taffy::Size {
237 width: inline_size + pb_sum.inline,
238 height: block_size + pb_sum.block,
239 };
240 let size = inputs.known_dimensions.unwrap_or(computed_size);
241
242 taffy::LayoutOutput {
243 size,
244 first_baselines: taffy::Point {
245 x: None,
246 y: layout.baselines.first.map(|au| au.to_f32_px()),
247 },
248 ..taffy::LayoutOutput::DEFAULT
249 }
250 },
251 )
252 }
253}
254
255impl taffy::LayoutGridContainer for TaffyContainerContext<'_> {
256 type GridContainerStyle<'a>
257 = TaffyStyloStyle<&'a ComputedValues>
258 where
259 Self: 'a;
260
261 type GridItemStyle<'a>
262 = TaffyStyloStyle<AtomicRef<'a, ComputedValues>>
263 where
264 Self: 'a;
265
266 fn get_grid_container_style(
267 &self,
268 _node_id: taffy::prelude::NodeId,
269 ) -> Self::GridContainerStyle<'_> {
270 TaffyStyloStyle::new(self.style, false )
271 }
272
273 fn get_grid_child_style(
274 &self,
275 child_node_id: taffy::prelude::NodeId,
276 ) -> Self::GridItemStyle<'_> {
277 let id = usize::from(child_node_id);
278 let child = (*self.source_child_nodes[id]).borrow();
279 let is_replaced = child.is_in_flow_replaced();
281 let stylo_style = AtomicRef::map(child, |c| &*c.style);
282 TaffyStyloStyle::new(stylo_style, is_replaced)
283 }
284
285 fn set_detailed_grid_info(
286 &mut self,
287 _node_id: taffy::NodeId,
288 specific_layout_info: taffy::DetailedGridInfo,
289 ) {
290 self.specific_layout_info = Some(SpecificLayoutInfo::Grid(Box::new(
291 SpecificTaffyGridInfo::from_detailed_grid_layout(specific_layout_info),
292 )));
293 }
294}
295
296impl ComputeInlineContentSizes for TaffyContainer {
297 fn compute_inline_content_sizes(
298 &self,
299 layout_context: &LayoutContext,
300 _constraint_space: &ConstraintSpace,
301 ) -> InlineContentSizesResult {
302 let style = &self.style;
303
304 let max_content_inputs = taffy::LayoutInput {
305 run_mode: taffy::RunMode::ComputeSize,
306 sizing_mode: taffy::SizingMode::InherentSize,
307 axis: taffy::RequestedAxis::Horizontal,
308 vertical_margins_are_collapsible: taffy::Line::FALSE,
309
310 known_dimensions: taffy::Size::NONE,
311 parent_size: taffy::Size::NONE,
312 available_space: taffy::Size::MAX_CONTENT,
313 };
314
315 let min_content_inputs = taffy::LayoutInput {
316 available_space: taffy::Size::MIN_CONTENT,
317 ..max_content_inputs
318 };
319
320 let containing_block = &ContainingBlock {
321 size: ContainingBlockSize {
322 inline: Au::zero(),
323 block: SizeConstraint::default(),
324 },
325 style,
326 };
327
328 let mut grid_context = TaffyContainerContext {
329 layout_context,
330 positioning_context: &mut PositioningContext::default(),
331 content_box_size_override: containing_block,
332 style,
333 source_child_nodes: &self.children,
334 specific_layout_info: None,
335 child_specific_layout_infos: vec![None; self.children.len()],
336 };
337
338 let (max_content_output, min_content_output) = match style.clone_display().inside() {
339 DisplayInside::Grid => {
340 let max_content_output = taffy::compute_grid_layout(
341 &mut grid_context,
342 DUMMY_NODE_ID,
343 max_content_inputs,
344 );
345 let min_content_output = taffy::compute_grid_layout(
346 &mut grid_context,
347 DUMMY_NODE_ID,
348 min_content_inputs,
349 );
350 (max_content_output, min_content_output)
351 },
352 _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"),
353 };
354
355 let pb_sums = self
356 .layout_style()
357 .padding_border_margin(containing_block)
358 .padding_border_sums;
359
360 InlineContentSizesResult {
361 sizes: ContentSizes {
362 max_content: Au::from_f32_px(max_content_output.size.width) - pb_sums.inline,
363 min_content: Au::from_f32_px(min_content_output.size.width) - pb_sums.inline,
364 },
365
366 depends_on_block_constraints: true,
371 }
372 }
373}
374
375impl TaffyContainer {
376 pub(crate) fn layout(
378 &self,
379 layout_context: &LayoutContext,
380 positioning_context: &mut PositioningContext,
381 content_box_size_override: &ContainingBlock,
382 containing_block: &ContainingBlock,
383 ) -> CacheableLayoutResult {
384 let mut container_ctx = TaffyContainerContext {
385 layout_context,
386 positioning_context,
387 content_box_size_override,
388 style: content_box_size_override.style,
389 source_child_nodes: &self.children,
390 specific_layout_info: None,
391 child_specific_layout_infos: vec![None; self.children.len()],
392 };
393
394 let container_style = &content_box_size_override.style;
395 let align_items = container_style.clone_align_items();
396 let justify_items = container_style.clone_justify_items();
397 let pbm = self.layout_style().padding_border_margin(containing_block);
398
399 let known_dimensions = taffy::Size {
400 width: Some(
401 (content_box_size_override.size.inline + pbm.padding_border_sums.inline)
402 .to_f32_px(),
403 ),
404 height: content_box_size_override
405 .size
406 .block
407 .to_definite()
408 .map(Au::to_f32_px)
409 .maybe_add(pbm.padding_border_sums.block.to_f32_px()),
410 };
411
412 let taffy_containing_block = taffy::Size {
413 width: Some(containing_block.size.inline.to_f32_px()),
414 height: containing_block.size.block.to_definite().map(Au::to_f32_px),
415 };
416
417 let layout_input = taffy::LayoutInput {
418 run_mode: taffy::RunMode::PerformLayout,
419 sizing_mode: taffy::SizingMode::InherentSize,
420 axis: taffy::RequestedAxis::Vertical,
421 vertical_margins_are_collapsible: taffy::Line::FALSE,
422
423 known_dimensions,
424 parent_size: taffy_containing_block,
425 available_space: taffy_containing_block.map(AvailableSpace::from),
426 };
427
428 let output = match container_ctx.style.clone_display().inside() {
429 DisplayInside::Grid => {
430 taffy::compute_grid_layout(&mut container_ctx, DUMMY_NODE_ID, layout_input)
431 },
432 _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"),
433 };
434
435 let fragments: Vec<Fragment> = self
438 .children
439 .iter()
440 .map(|child| (**child).borrow_mut())
441 .enumerate()
442 .map(|(child_id, mut child)| {
443 fn rect_to_physical_sides<T>(rect: taffy::Rect<T>) -> PhysicalSides<T> {
444 PhysicalSides::new(rect.top, rect.right, rect.bottom, rect.left)
445 }
446
447 fn size_and_pos_to_logical_rect<T: Default>(
448 position: taffy::Point<T>,
449 size: taffy::Size<T>,
450 ) -> PhysicalRect<T> {
451 PhysicalRect::new(
452 PhysicalPoint::new(position.x, position.y),
453 PhysicalSize::new(size.width, size.height),
454 )
455 }
456
457 let layout = &child.taffy_layout;
458
459 let padding = rect_to_physical_sides(layout.padding.map(Au::from_f32_px));
460 let border = rect_to_physical_sides(layout.border.map(Au::from_f32_px));
461 let margin = rect_to_physical_sides(layout.margin.map(Au::from_f32_px));
462
463 let content_size = size_and_pos_to_logical_rect(
468 taffy::Point {
469 x: Au::from_f32_px(
470 layout.location.x + layout.padding.left + layout.border.left,
471 ) - pbm.padding.inline_start -
472 pbm.border.inline_start,
473 y: Au::from_f32_px(
474 layout.location.y + layout.padding.top + layout.border.top,
475 ) - pbm.padding.block_start -
476 pbm.border.block_start,
477 },
478 taffy::Size {
479 width: layout.size.width -
480 layout.padding.left -
481 layout.padding.right -
482 layout.border.left -
483 layout.border.right,
484 height: layout.size.height -
485 layout.padding.top -
486 layout.padding.bottom -
487 layout.border.top -
488 layout.border.bottom,
489 }
490 .map(Au::from_f32_px),
491 );
492
493 let child_specific_layout_info: Option<SpecificLayoutInfo> =
494 std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]);
495
496 let fragment = match &mut child.taffy_level_box {
497 TaffyItemBoxInner::InFlowBox(independent_box) => {
498 let mut fragment_info = independent_box.base_fragment_info();
499 fragment_info
500 .flags
501 .insert(FragmentFlags::IS_FLEX_OR_GRID_ITEM);
502 let mut box_fragment = BoxFragment::new(
503 fragment_info,
504 independent_box.style().clone(),
505 std::mem::take(&mut child.child_fragments),
506 content_size,
507 padding,
508 border,
509 margin,
510 child_specific_layout_info,
511 )
512 .with_baselines(Baselines {
513 first: output.first_baselines.y.map(Au::from_f32_px),
514 last: None,
515 });
516
517 child.positioning_context.layout_collected_children(
518 container_ctx.layout_context,
519 &mut box_fragment,
520 );
521 child
522 .positioning_context
523 .adjust_static_position_of_hoisted_fragments_with_offset(
524 &box_fragment.content_rect().origin.to_vector(),
525 PositioningContextLength::zero(),
526 );
527 container_ctx
528 .positioning_context
529 .append(std::mem::take(&mut child.positioning_context));
530
531 Fragment::Box(ArcRefCell::new(box_fragment))
532 },
533 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => {
534 fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags {
535 match value {
536 AlignFlags::AUTO => auto,
537 AlignFlags::NORMAL => AlignFlags::STRETCH,
538 value => value,
539 }
540 }
541
542 let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
543 abs_pos_box.clone(),
544 PhysicalRect::from_size(PhysicalSize::new(
545 Au::from_f32_px(output.size.width),
546 Au::from_f32_px(output.size.height),
547 )),
548 LogicalVec2 {
549 inline: resolve_alignment(
550 child.style.clone_align_self().0,
551 align_items.0,
552 ),
553 block: resolve_alignment(
554 child.style.clone_justify_self().0,
555 justify_items.computed.0.0,
556 ),
557 },
558 container_ctx.style.writing_mode,
559 );
560 let hoisted_fragment = hoisted_box.fragment.clone();
561 container_ctx.positioning_context.push(hoisted_box);
562 Fragment::AbsoluteOrFixedPositioned(hoisted_fragment)
563 },
564 };
565
566 if let TaffyItemBoxInner::InFlowBox(independent_formatting_context) =
567 &child.taffy_level_box
568 {
569 independent_formatting_context
570 .base
571 .set_fragment(fragment.clone());
572 }
573 fragment
574 })
575 .collect();
576
577 CacheableLayoutResult {
578 fragments,
579 content_block_size: Au::from_f32_px(output.size.height) - pbm.padding_border_sums.block,
580 content_inline_size_for_table: None,
581 baselines: Baselines::default(),
582
583 depends_on_block_constraints: true,
588 specific_layout_info: container_ctx.specific_layout_info,
589 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
590 }
591 }
592
593 #[inline]
594 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
595 LayoutStyle::Default(&self.style)
596 }
597
598 pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
599 for child in &self.children {
600 child.borrow_mut().with_base_mut(|base| {
601 base.parent_box.replace(layout_box.clone());
602 });
603 }
604 }
605}