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 = node_and_style_info
96                    .node
97                    .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 inline_size = contents.fallback_inline_size(writing_mode);
224                let block_size = ratio.compute_dependent_size(Direction::Block, inline_size);
225                Some(block_size.into())
226            },
227            _ => None,
228        }
229    }
230
231    pub(crate) fn outer_inline_content_sizes(
232        &self,
233        layout_context: &LayoutContext,
234        containing_block: &IndefiniteContainingBlock,
235        auto_minimum: &LogicalVec2<Au>,
236        auto_block_size_stretches_to_containing_block: bool,
237    ) -> InlineContentSizesResult {
238        sizing::outer_inline(
239            &self.base,
240            &self.layout_style(),
241            containing_block,
242            auto_minimum,
243            auto_block_size_stretches_to_containing_block,
244            self.is_replaced(),
245            true, /* establishes_containing_block */
246            |padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),
247            |constraint_space| self.inline_content_sizes(layout_context, constraint_space),
248            |preferred_aspect_ratio| self.tentative_block_content_size(preferred_aspect_ratio),
249        )
250    }
251
252    pub(crate) fn repair_style(
253        &mut self,
254        context: &SharedStyleContext,
255        node: &ServoThreadSafeLayoutNode,
256        new_style: &Arc<ComputedValues>,
257    ) {
258        self.base.repair_style(new_style);
259        match &mut self.contents {
260            IndependentFormattingContextContents::Replaced(_, widget) => {
261                if let Some(widget) = widget {
262                    let node = node
263                        .with_pseudo(PseudoElement::ServoAnonymousBox)
264                        .expect("Should always be able to construct info for anonymous boxes.");
265                    widget.borrow_mut().repair_style(context, &node, new_style);
266                }
267            },
268            IndependentFormattingContextContents::Flow(block_formatting_context) => {
269                block_formatting_context.repair_style(node, new_style);
270            },
271            IndependentFormattingContextContents::Flex(flex_container) => {
272                flex_container.repair_style(new_style)
273            },
274            IndependentFormattingContextContents::Grid(taffy_container) => {
275                taffy_container.repair_style(new_style)
276            },
277            IndependentFormattingContextContents::Table(table) => {
278                table.repair_style(context, new_style)
279            },
280        }
281    }
282
283    #[inline]
284    pub(crate) fn is_block_container(&self) -> bool {
285        matches!(self.contents, IndependentFormattingContextContents::Flow(_))
286    }
287
288    #[inline]
289    pub(crate) fn is_replaced(&self) -> bool {
290        matches!(
291            self.contents,
292            IndependentFormattingContextContents::Replaced(_, _)
293        )
294    }
295
296    #[inline]
297    pub(crate) fn is_table(&self) -> bool {
298        matches!(
299            &self.contents,
300            IndependentFormattingContextContents::Table(_)
301        )
302    }
303
304    fn layout_without_caching(
305        &self,
306        layout_context: &LayoutContext,
307        positioning_context: &mut PositioningContext,
308        containing_block_for_children: &ContainingBlock,
309        containing_block: &ContainingBlock,
310        preferred_aspect_ratio: Option<AspectRatio>,
311        lazy_block_size: &LazySize,
312    ) -> CacheableLayoutResult {
313        match &self.contents {
314            IndependentFormattingContextContents::Replaced(replaced, widget) => {
315                let mut replaced_layout = replaced.layout(
316                    layout_context,
317                    containing_block_for_children,
318                    preferred_aspect_ratio,
319                    &self.base,
320                    lazy_block_size,
321                );
322                if let Some(widget) = widget {
323                    let mut widget_layout = widget.borrow().layout(
324                        layout_context,
325                        positioning_context,
326                        containing_block_for_children,
327                        containing_block_for_children,
328                        None,
329                        &LazySize::intrinsic(),
330                    );
331                    replaced_layout
332                        .fragments
333                        .append(&mut widget_layout.fragments);
334                }
335                replaced_layout
336            },
337            IndependentFormattingContextContents::Flow(bfc) => bfc.layout(
338                layout_context,
339                positioning_context,
340                containing_block_for_children,
341            ),
342            IndependentFormattingContextContents::Flex(fc) => fc.layout(
343                layout_context,
344                positioning_context,
345                containing_block_for_children,
346                lazy_block_size,
347            ),
348            IndependentFormattingContextContents::Grid(fc) => fc.layout(
349                layout_context,
350                positioning_context,
351                containing_block_for_children,
352                containing_block,
353            ),
354            IndependentFormattingContextContents::Table(table) => table.layout(
355                layout_context,
356                positioning_context,
357                containing_block_for_children,
358                containing_block,
359            ),
360        }
361    }
362
363    #[servo_tracing::instrument(name = "IndependentFormattingContext::layout", skip_all)]
364    pub(crate) fn layout(
365        &self,
366        layout_context: &LayoutContext,
367        positioning_context: &mut PositioningContext,
368        containing_block_for_children: &ContainingBlock,
369        containing_block: &ContainingBlock,
370        preferred_aspect_ratio: Option<AspectRatio>,
371        lazy_block_size: &LazySize,
372    ) -> CacheableLayoutResult {
373        if let Some(cache) = self.base.cached_layout_result.borrow().as_ref() {
374            let cache = &**cache;
375            if cache.containing_block_for_children_size.inline ==
376                containing_block_for_children.size.inline &&
377                (cache.containing_block_for_children_size.block ==
378                    containing_block_for_children.size.block ||
379                    !cache.result.depends_on_block_constraints)
380            {
381                positioning_context.append(cache.positioning_context.clone());
382                return cache.result.clone();
383            }
384            #[cfg(feature = "tracing")]
385            tracing::debug!(
386                name: "IndependentFormattingContext::layout cache miss",
387                cached = ?cache.containing_block_for_children_size,
388                required = ?containing_block_for_children.size,
389            );
390        }
391
392        let mut child_positioning_context = PositioningContext::default();
393        let result = self.layout_without_caching(
394            layout_context,
395            &mut child_positioning_context,
396            containing_block_for_children,
397            containing_block,
398            preferred_aspect_ratio,
399            lazy_block_size,
400        );
401
402        *self.base.cached_layout_result.borrow_mut() =
403            Some(Box::new(CacheableLayoutResultAndInputs {
404                result: result.clone(),
405                positioning_context: child_positioning_context.clone(),
406                containing_block_for_children_size: containing_block_for_children.size.clone(),
407            }));
408        positioning_context.append(child_positioning_context);
409
410        result
411    }
412
413    #[inline]
414    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
415        match &self.contents {
416            IndependentFormattingContextContents::Replaced(replaced, _) => {
417                replaced.layout_style(&self.base)
418            },
419            IndependentFormattingContextContents::Flow(fc) => fc.layout_style(&self.base),
420            IndependentFormattingContextContents::Flex(fc) => fc.layout_style(),
421            IndependentFormattingContextContents::Grid(fc) => fc.layout_style(),
422            IndependentFormattingContextContents::Table(fc) => fc.layout_style(None),
423        }
424    }
425
426    #[inline]
427    pub(crate) fn preferred_aspect_ratio(
428        &self,
429        padding_border_sums: &LogicalVec2<Au>,
430    ) -> Option<AspectRatio> {
431        match &self.contents {
432            IndependentFormattingContextContents::Replaced(replaced, _) => {
433                replaced.preferred_aspect_ratio(self.style(), padding_border_sums)
434            },
435            // TODO: support preferred aspect ratios on non-replaced boxes.
436            _ => None,
437        }
438    }
439
440    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
441        match &self.contents {
442            IndependentFormattingContextContents::Replaced(_, widget) => {
443                if let Some(widget) = widget {
444                    widget.borrow_mut().base.parent_box.replace(layout_box);
445                }
446            },
447            IndependentFormattingContextContents::Flow(contents) => {
448                contents.attached_to_tree(layout_box)
449            },
450            IndependentFormattingContextContents::Flex(contents) => {
451                contents.attached_to_tree(layout_box)
452            },
453            IndependentFormattingContextContents::Grid(contents) => {
454                contents.attached_to_tree(layout_box)
455            },
456            IndependentFormattingContextContents::Table(contents) => {
457                contents.attached_to_tree(layout_box)
458            },
459        }
460    }
461}
462
463impl ComputeInlineContentSizes for IndependentFormattingContextContents {
464    fn compute_inline_content_sizes(
465        &self,
466        layout_context: &LayoutContext,
467        constraint_space: &ConstraintSpace,
468    ) -> InlineContentSizesResult {
469        match self {
470            Self::Replaced(inner, _) => {
471                inner.compute_inline_content_sizes(layout_context, constraint_space)
472            },
473            Self::Flow(inner) => inner
474                .contents
475                .compute_inline_content_sizes(layout_context, constraint_space),
476            Self::Flex(inner) => {
477                inner.compute_inline_content_sizes(layout_context, constraint_space)
478            },
479            Self::Grid(inner) => {
480                inner.compute_inline_content_sizes(layout_context, constraint_space)
481            },
482            Self::Table(inner) => {
483                inner.compute_inline_content_sizes(layout_context, constraint_space)
484            },
485        }
486    }
487}