layout/taffy/
layout.rs

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