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