layout/
formatting_contexts.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 malloc_size_of_derive::MallocSizeOf;
7use script::layout_dom::{ServoLayoutElement, ServoThreadSafeLayoutNode};
8use servo_arc::Arc;
9use style::context::SharedStyleContext;
10use style::logical_geometry::Direction;
11use style::properties::ComputedValues;
12use style::selector_parser::PseudoElement;
13
14use crate::context::LayoutContext;
15use crate::dom_traversal::{Contents, NodeAndStyleInfo};
16use crate::flexbox::FlexContainer;
17use crate::flow::BlockFormattingContext;
18use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags};
19use crate::layout_box_base::{
20    CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase,
21};
22use crate::positioned::PositioningContext;
23use crate::replaced::ReplacedContents;
24use crate::sizing::{
25    self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize,
26};
27use crate::style_ext::{AspectRatio, DisplayInside, LayoutStyle};
28use crate::table::Table;
29use crate::taffy::TaffyContainer;
30use crate::{
31    ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, LogicalVec2, PropagatedBoxTreeData,
32};
33
34/// <https://drafts.csswg.org/css-display/#independent-formatting-context>
35#[derive(Debug, MallocSizeOf)]
36pub(crate) struct IndependentFormattingContext {
37    pub base: LayoutBoxBase,
38    // Private so that code outside of this module cannot match variants.
39    // It should go through methods instead.
40    contents: IndependentFormattingContextContents,
41}
42
43#[derive(Debug, MallocSizeOf)]
44pub(crate) enum IndependentFormattingContextContents {
45    Replaced(ReplacedContents),
46    Flow(BlockFormattingContext),
47    Flex(FlexContainer),
48    Grid(TaffyContainer),
49    Table(Table),
50    // Other layout modes go here
51}
52
53/// The baselines of a layout or a [`crate::fragment_tree::BoxFragment`]. Some layout
54/// uses the first and some layout uses the last.
55#[derive(Clone, Copy, Debug, Default, MallocSizeOf)]
56pub(crate) struct Baselines {
57    pub first: Option<Au>,
58    pub last: Option<Au>,
59}
60
61impl Baselines {
62    pub(crate) fn offset(&self, block_offset: Au) -> Baselines {
63        Self {
64            first: self.first.map(|first| first + block_offset),
65            last: self.last.map(|last| last + block_offset),
66        }
67    }
68}
69
70impl IndependentFormattingContext {
71    pub(crate) fn new(base: LayoutBoxBase, contents: IndependentFormattingContextContents) -> Self {
72        Self { base, contents }
73    }
74
75    pub fn construct(
76        context: &LayoutContext,
77        node_and_style_info: &NodeAndStyleInfo,
78        display_inside: DisplayInside,
79        contents: Contents,
80        propagated_data: PropagatedBoxTreeData,
81    ) -> Self {
82        let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into();
83
84        match contents {
85            Contents::NonReplaced(non_replaced_contents) => {
86                let contents = match display_inside {
87                    DisplayInside::Flow { is_list_item } |
88                    DisplayInside::FlowRoot { is_list_item } => {
89                        IndependentFormattingContextContents::Flow(
90                            BlockFormattingContext::construct(
91                                context,
92                                node_and_style_info,
93                                non_replaced_contents,
94                                propagated_data,
95                                is_list_item,
96                            ),
97                        )
98                    },
99                    DisplayInside::Grid => {
100                        IndependentFormattingContextContents::Grid(TaffyContainer::construct(
101                            context,
102                            node_and_style_info,
103                            non_replaced_contents,
104                            propagated_data,
105                        ))
106                    },
107                    DisplayInside::Flex => {
108                        IndependentFormattingContextContents::Flex(FlexContainer::construct(
109                            context,
110                            node_and_style_info,
111                            non_replaced_contents,
112                            propagated_data,
113                        ))
114                    },
115                    DisplayInside::Table => {
116                        let table_grid_style = context
117                            .style_context
118                            .stylist
119                            .style_for_anonymous::<ServoLayoutElement>(
120                                &context.style_context.guards,
121                                &PseudoElement::ServoTableGrid,
122                                &node_and_style_info.style,
123                            );
124                        base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
125                        IndependentFormattingContextContents::Table(Table::construct(
126                            context,
127                            node_and_style_info,
128                            table_grid_style,
129                            non_replaced_contents,
130                            propagated_data,
131                        ))
132                    },
133                };
134                Self {
135                    base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()),
136                    contents,
137                }
138            },
139            Contents::Replaced(contents) => {
140                base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
141                Self {
142                    base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()),
143                    contents: IndependentFormattingContextContents::Replaced(contents),
144                }
145            },
146        }
147    }
148
149    #[inline]
150    pub fn style(&self) -> &Arc<ComputedValues> {
151        &self.base.style
152    }
153
154    #[inline]
155    pub fn base_fragment_info(&self) -> BaseFragmentInfo {
156        self.base.base_fragment_info
157    }
158
159    pub(crate) fn inline_content_sizes(
160        &self,
161        layout_context: &LayoutContext,
162        constraint_space: &ConstraintSpace,
163    ) -> InlineContentSizesResult {
164        self.base
165            .inline_content_sizes(layout_context, constraint_space, &self.contents)
166    }
167
168    /// Computes the tentative intrinsic block sizes that may be needed while computing
169    /// the intrinsic inline sizes. Therefore, this ignores the values of the sizing
170    /// properties in both axes.
171    /// A return value of `None` indicates that there is no suitable tentative intrinsic
172    /// block size, so intrinsic keywords in the block sizing properties will be ignored,
173    /// possibly resulting in an indefinite [`SizeConstraint`] for computing the intrinsic
174    /// inline sizes and laying out the contents.
175    /// A return value of `Some` indicates that intrinsic keywords in the block sizing
176    /// properties will be resolved as the contained value, guaranteeing a definite amount
177    /// for computing the intrinsic inline sizes and laying out the contents.
178    pub(crate) fn tentative_block_content_size(
179        &self,
180        preferred_aspect_ratio: Option<AspectRatio>,
181    ) -> Option<ContentSizes> {
182        // See <https://github.com/w3c/csswg-drafts/issues/12333> regarding the difference
183        // in behavior for the replaced and non-replaced cases.
184        match &self.contents {
185            IndependentFormattingContextContents::Replaced(contents) => {
186                // For replaced elements with no ratio, the returned value doesn't matter.
187                let ratio = preferred_aspect_ratio?;
188                let writing_mode = self.style().writing_mode;
189                let inline_size = contents.fallback_inline_size(writing_mode);
190                let block_size = ratio.compute_dependent_size(Direction::Block, inline_size);
191                Some(block_size.into())
192            },
193            _ => None,
194        }
195    }
196
197    pub(crate) fn outer_inline_content_sizes(
198        &self,
199        layout_context: &LayoutContext,
200        containing_block: &IndefiniteContainingBlock,
201        auto_minimum: &LogicalVec2<Au>,
202        auto_block_size_stretches_to_containing_block: bool,
203    ) -> InlineContentSizesResult {
204        sizing::outer_inline(
205            &self.layout_style(),
206            containing_block,
207            auto_minimum,
208            auto_block_size_stretches_to_containing_block,
209            self.is_replaced(),
210            true, /* establishes_containing_block */
211            |padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),
212            |constraint_space| self.inline_content_sizes(layout_context, constraint_space),
213            |preferred_aspect_ratio| self.tentative_block_content_size(preferred_aspect_ratio),
214        )
215    }
216
217    pub(crate) fn repair_style(
218        &mut self,
219        context: &SharedStyleContext,
220        node: &ServoThreadSafeLayoutNode,
221        new_style: &Arc<ComputedValues>,
222    ) {
223        self.base.repair_style(new_style);
224        match &mut self.contents {
225            IndependentFormattingContextContents::Replaced(..) => {},
226            IndependentFormattingContextContents::Flow(block_formatting_context) => {
227                block_formatting_context.repair_style(node, new_style);
228            },
229            IndependentFormattingContextContents::Flex(flex_container) => {
230                flex_container.repair_style(new_style)
231            },
232            IndependentFormattingContextContents::Grid(taffy_container) => {
233                taffy_container.repair_style(new_style)
234            },
235            IndependentFormattingContextContents::Table(table) => {
236                table.repair_style(context, new_style)
237            },
238        }
239    }
240
241    #[inline]
242    pub(crate) fn is_block_container(&self) -> bool {
243        matches!(self.contents, IndependentFormattingContextContents::Flow(_))
244    }
245
246    #[inline]
247    pub(crate) fn is_replaced(&self) -> bool {
248        matches!(
249            self.contents,
250            IndependentFormattingContextContents::Replaced(_)
251        )
252    }
253
254    #[inline]
255    pub(crate) fn is_table(&self) -> bool {
256        matches!(
257            &self.contents,
258            IndependentFormattingContextContents::Table(_)
259        )
260    }
261
262    #[allow(clippy::too_many_arguments)]
263    fn layout_without_caching(
264        &self,
265        layout_context: &LayoutContext,
266        positioning_context: &mut PositioningContext,
267        containing_block_for_children: &ContainingBlock,
268        containing_block: &ContainingBlock,
269        preferred_aspect_ratio: Option<AspectRatio>,
270        lazy_block_size: &LazySize,
271    ) -> CacheableLayoutResult {
272        match &self.contents {
273            IndependentFormattingContextContents::Replaced(replaced) => replaced.layout(
274                layout_context,
275                containing_block_for_children,
276                preferred_aspect_ratio,
277                &self.base,
278                lazy_block_size,
279            ),
280            IndependentFormattingContextContents::Flow(bfc) => bfc.layout(
281                layout_context,
282                positioning_context,
283                containing_block_for_children,
284            ),
285            IndependentFormattingContextContents::Flex(fc) => fc.layout(
286                layout_context,
287                positioning_context,
288                containing_block_for_children,
289                lazy_block_size,
290            ),
291            IndependentFormattingContextContents::Grid(fc) => fc.layout(
292                layout_context,
293                positioning_context,
294                containing_block_for_children,
295                containing_block,
296            ),
297            IndependentFormattingContextContents::Table(table) => table.layout(
298                layout_context,
299                positioning_context,
300                containing_block_for_children,
301                containing_block,
302            ),
303        }
304    }
305
306    #[servo_tracing::instrument(name = "IndependentFormattingContext::layout", skip_all)]
307    #[allow(clippy::too_many_arguments)]
308    pub(crate) fn layout(
309        &self,
310        layout_context: &LayoutContext,
311        positioning_context: &mut PositioningContext,
312        containing_block_for_children: &ContainingBlock,
313        containing_block: &ContainingBlock,
314        preferred_aspect_ratio: Option<AspectRatio>,
315        lazy_block_size: &LazySize,
316    ) -> CacheableLayoutResult {
317        if let Some(cache) = self.base.cached_layout_result.borrow().as_ref() {
318            let cache = &**cache;
319            if cache.containing_block_for_children_size.inline ==
320                containing_block_for_children.size.inline &&
321                (cache.containing_block_for_children_size.block ==
322                    containing_block_for_children.size.block ||
323                    !cache.result.depends_on_block_constraints)
324            {
325                positioning_context.append(cache.positioning_context.clone());
326                return cache.result.clone();
327            }
328            #[cfg(feature = "tracing")]
329            tracing::debug!(
330                name: "IndependentFormattingContext::layout cache miss",
331                cached = ?cache.containing_block_for_children_size,
332                required = ?containing_block_for_children.size,
333            );
334        }
335
336        let mut child_positioning_context = PositioningContext::default();
337        let result = self.layout_without_caching(
338            layout_context,
339            &mut child_positioning_context,
340            containing_block_for_children,
341            containing_block,
342            preferred_aspect_ratio,
343            lazy_block_size,
344        );
345
346        *self.base.cached_layout_result.borrow_mut() =
347            Some(Box::new(CacheableLayoutResultAndInputs {
348                result: result.clone(),
349                positioning_context: child_positioning_context.clone(),
350                containing_block_for_children_size: containing_block_for_children.size.clone(),
351            }));
352        positioning_context.append(child_positioning_context);
353
354        result
355    }
356
357    #[inline]
358    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
359        match &self.contents {
360            IndependentFormattingContextContents::Replaced(fc) => fc.layout_style(&self.base),
361            IndependentFormattingContextContents::Flow(fc) => fc.layout_style(&self.base),
362            IndependentFormattingContextContents::Flex(fc) => fc.layout_style(),
363            IndependentFormattingContextContents::Grid(fc) => fc.layout_style(),
364            IndependentFormattingContextContents::Table(fc) => fc.layout_style(None),
365        }
366    }
367
368    #[inline]
369    pub(crate) fn preferred_aspect_ratio(
370        &self,
371        padding_border_sums: &LogicalVec2<Au>,
372    ) -> Option<AspectRatio> {
373        match &self.contents {
374            IndependentFormattingContextContents::Replaced(replaced) => {
375                replaced.preferred_aspect_ratio(self.style(), padding_border_sums)
376            },
377            // TODO: support preferred aspect ratios on non-replaced boxes.
378            _ => None,
379        }
380    }
381}
382
383impl ComputeInlineContentSizes for IndependentFormattingContextContents {
384    fn compute_inline_content_sizes(
385        &self,
386        layout_context: &LayoutContext,
387        constraint_space: &ConstraintSpace,
388    ) -> InlineContentSizesResult {
389        match self {
390            Self::Replaced(inner) => {
391                inner.compute_inline_content_sizes(layout_context, constraint_space)
392            },
393            Self::Flow(inner) => inner
394                .contents
395                .compute_inline_content_sizes(layout_context, constraint_space),
396            Self::Flex(inner) => {
397                inner.compute_inline_content_sizes(layout_context, constraint_space)
398            },
399            Self::Grid(inner) => {
400                inner.compute_inline_content_sizes(layout_context, constraint_space)
401            },
402            Self::Table(inner) => {
403                inner.compute_inline_content_sizes(layout_context, constraint_space)
404            },
405        }
406    }
407}