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