Skip to main content

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::LayoutNode;
7use malloc_size_of_derive::MallocSizeOf;
8use script::layout_dom::{ServoDangerousStyleElement, ServoLayoutNode};
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::{IndependentFormattingContextLayoutResult, LayoutBoxBase};
22use crate::positioned::PositioningContext;
23use crate::replaced::ReplacedContents;
24use crate::sizing::{
25    self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize,
26};
27use crate::style_ext::{AspectRatio, Display, DisplayInside, LayoutStyle};
28use crate::table::Table;
29use crate::taffy::TaffyContainer;
30use crate::{
31    ArcRefCell, ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, LogicalVec2,
32    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    /// Data that was originally propagated down to this [`IndependentFormattingContext`]
43    /// during creation. This is used during incremental layout.
44    pub propagated_data: PropagatedBoxTreeData,
45}
46
47#[derive(Debug, MallocSizeOf)]
48pub(crate) enum IndependentFormattingContextContents {
49    // Additionally to the replaced contents, replaced boxes may have an inner widget.
50    Replaced(
51        ReplacedContents,
52        Option<ArcRefCell<IndependentFormattingContext>>,
53    ),
54    Flow(BlockFormattingContext),
55    Flex(FlexContainer),
56    Grid(TaffyContainer),
57    Table(Table),
58    // Other layout modes go here
59}
60
61/// The baselines of a layout or a [`crate::fragment_tree::BoxFragment`]. Some layout
62/// uses the first and some layout uses the last.
63#[derive(Clone, Copy, Debug, Default, MallocSizeOf)]
64pub(crate) struct Baselines {
65    pub first: Option<Au>,
66    pub last: Option<Au>,
67}
68
69impl Baselines {
70    pub(crate) fn offset(&self, block_offset: Au) -> Baselines {
71        Self {
72            first: self.first.map(|first| first + block_offset),
73            last: self.last.map(|last| last + block_offset),
74        }
75    }
76}
77
78impl IndependentFormattingContext {
79    pub(crate) fn new(
80        base: LayoutBoxBase,
81        contents: IndependentFormattingContextContents,
82        propagated_data: PropagatedBoxTreeData,
83    ) -> Self {
84        Self {
85            base,
86            contents,
87            propagated_data,
88        }
89    }
90
91    pub(crate) fn rebuild(
92        &mut self,
93        layout_context: &LayoutContext,
94        node_and_style_info: &NodeAndStyleInfo,
95    ) {
96        let contents = Contents::for_element(node_and_style_info.node, layout_context);
97        let display = match Display::from(node_and_style_info.style.get_box().display) {
98            Display::None | Display::Contents => {
99                unreachable!("Should never try to rebuild IndependentFormattingContext with no box")
100            },
101            Display::GeneratingBox(display) => display.used_value_for_contents(&contents),
102        };
103        self.contents = Self::construct_contents(
104            layout_context,
105            node_and_style_info,
106            &mut self.base.base_fragment_info,
107            display.display_inside(),
108            contents,
109            self.propagated_data,
110        );
111
112        self.base.clear_fragments_and_dirty_fragment_cache();
113        *self.base.cached_inline_content_size.borrow_mut() = None;
114        self.base.repair_style(&node_and_style_info.style);
115    }
116
117    pub(crate) fn construct(
118        context: &LayoutContext,
119        node_and_style_info: &NodeAndStyleInfo,
120        display_inside: DisplayInside,
121        contents: Contents,
122        propagated_data: PropagatedBoxTreeData,
123    ) -> Self {
124        let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into();
125        let contents = Self::construct_contents(
126            context,
127            node_and_style_info,
128            &mut base_fragment_info,
129            display_inside,
130            contents,
131            propagated_data,
132        );
133        Self {
134            base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()),
135            contents,
136            propagated_data,
137        }
138    }
139
140    fn construct_contents(
141        context: &LayoutContext,
142        node_and_style_info: &NodeAndStyleInfo,
143        base_fragment_info: &mut BaseFragmentInfo,
144        display_inside: DisplayInside,
145        contents: Contents,
146        propagated_data: PropagatedBoxTreeData,
147    ) -> IndependentFormattingContextContents {
148        let non_replaced_contents = match contents {
149            Contents::Replaced(contents) => {
150                base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
151
152                // Some replaced elements can have inner widgets, e.g. `<video controls>`.
153                let node = node_and_style_info.node;
154                let widget = (node.pseudo_element_chain().is_empty() &&
155                    node.is_root_of_user_agent_widget())
156                .then(|| {
157                    let widget_info = node_and_style_info
158                        .with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
159                        .expect("Should always be able to construct info for anonymous boxes.");
160                    // Use a block formatting context for the widget, since the display inside is always flow.
161                    let widget_contents = IndependentFormattingContextContents::Flow(
162                        BlockFormattingContext::construct(
163                            context,
164                            &widget_info,
165                            NonReplacedContents::OfElement,
166                            propagated_data,
167                            false, /* is_list_item */
168                        ),
169                    );
170                    let widget_base = LayoutBoxBase::new((&widget_info).into(), widget_info.style);
171                    ArcRefCell::new(IndependentFormattingContext::new(
172                        widget_base,
173                        widget_contents,
174                        propagated_data,
175                    ))
176                });
177
178                return IndependentFormattingContextContents::Replaced(contents, widget);
179            },
180            Contents::Widget(non_replaced_contents) => {
181                base_fragment_info.flags.insert(FragmentFlags::IS_WIDGET);
182                non_replaced_contents
183            },
184            Contents::NonReplaced(non_replaced_contents) => non_replaced_contents,
185        };
186
187        match display_inside {
188            DisplayInside::Flow { is_list_item } | DisplayInside::FlowRoot { is_list_item } => {
189                IndependentFormattingContextContents::Flow(BlockFormattingContext::construct(
190                    context,
191                    node_and_style_info,
192                    non_replaced_contents,
193                    propagated_data,
194                    is_list_item,
195                ))
196            },
197            DisplayInside::Grid => {
198                IndependentFormattingContextContents::Grid(TaffyContainer::construct(
199                    context,
200                    node_and_style_info,
201                    non_replaced_contents,
202                    propagated_data,
203                ))
204            },
205            DisplayInside::Flex => {
206                IndependentFormattingContextContents::Flex(FlexContainer::construct(
207                    context,
208                    node_and_style_info,
209                    non_replaced_contents,
210                    propagated_data,
211                ))
212            },
213            DisplayInside::Table => {
214                let table_grid_style = context
215                    .style_context
216                    .stylist
217                    .style_for_anonymous::<ServoDangerousStyleElement>(
218                        &context.style_context.guards,
219                        &PseudoElement::ServoTableGrid,
220                        &node_and_style_info.style,
221                    );
222                base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
223                IndependentFormattingContextContents::Table(Table::construct(
224                    context,
225                    node_and_style_info,
226                    table_grid_style,
227                    non_replaced_contents,
228                    propagated_data,
229                ))
230            },
231        }
232    }
233
234    #[inline]
235    pub fn style(&self) -> &Arc<ComputedValues> {
236        &self.base.style
237    }
238
239    #[inline]
240    pub fn base_fragment_info(&self) -> BaseFragmentInfo {
241        self.base.base_fragment_info
242    }
243
244    pub(crate) fn inline_content_sizes(
245        &self,
246        layout_context: &LayoutContext,
247        constraint_space: &ConstraintSpace,
248    ) -> InlineContentSizesResult {
249        self.base
250            .inline_content_sizes(layout_context, constraint_space, &self.contents)
251    }
252
253    /// Computes the tentative intrinsic block sizes that may be needed while computing
254    /// the intrinsic inline sizes. Therefore, this ignores the values of the sizing
255    /// properties in both axes.
256    /// A return value of `None` indicates that there is no suitable tentative intrinsic
257    /// block size, so intrinsic keywords in the block sizing properties will be ignored,
258    /// possibly resulting in an indefinite [`SizeConstraint`] for computing the intrinsic
259    /// inline sizes and laying out the contents.
260    /// A return value of `Some` indicates that intrinsic keywords in the block sizing
261    /// properties will be resolved as the contained value, guaranteeing a definite amount
262    /// for computing the intrinsic inline sizes and laying out the contents.
263    pub(crate) fn tentative_block_content_size(
264        &self,
265        preferred_aspect_ratio: Option<AspectRatio>,
266        inline_stretch_size: Au,
267    ) -> Option<ContentSizes> {
268        let result = self.tentative_block_content_size_with_dependency(
269            preferred_aspect_ratio,
270            inline_stretch_size,
271        );
272        Some(result?.0)
273    }
274
275    /// Same as [`Self::tentative_block_content_size()`], but if there is a tentative intrinsic
276    /// block size, it also includes a bool which will be true if the former depends on
277    /// the provided `inline_stretch_size`.
278    pub(crate) fn tentative_block_content_size_with_dependency(
279        &self,
280        preferred_aspect_ratio: Option<AspectRatio>,
281        inline_stretch_size: Au,
282    ) -> Option<(ContentSizes, bool)> {
283        // See <https://github.com/w3c/csswg-drafts/issues/12333> regarding the difference
284        // in behavior for the replaced and non-replaced cases.
285        match &self.contents {
286            IndependentFormattingContextContents::Replaced(contents, _) => {
287                // For replaced elements with no ratio, the returned value doesn't matter.
288                let ratio = preferred_aspect_ratio?;
289                let writing_mode = self.style().writing_mode;
290                let natural_sizes = contents.logical_natural_sizes(writing_mode);
291                let (block_size, depends_on_inline_stretch_size) =
292                    match (natural_sizes.block, natural_sizes.inline) {
293                        (Some(block_size), None) => (block_size, false),
294                        (_, Some(inline_size)) => (
295                            ratio.compute_dependent_size(Direction::Block, inline_size),
296                            false,
297                        ),
298                        (None, None) => (
299                            ratio.compute_dependent_size(Direction::Block, inline_stretch_size),
300                            true,
301                        ),
302                    };
303                Some((block_size.into(), depends_on_inline_stretch_size))
304            },
305            _ => None,
306        }
307    }
308
309    pub(crate) fn outer_inline_content_sizes(
310        &self,
311        layout_context: &LayoutContext,
312        containing_block: &IndefiniteContainingBlock,
313        auto_minimum: &LogicalVec2<Au>,
314        auto_block_size_stretches_to_containing_block: bool,
315    ) -> InlineContentSizesResult {
316        sizing::outer_inline(
317            &self.base,
318            &self.layout_style(),
319            containing_block,
320            auto_minimum,
321            auto_block_size_stretches_to_containing_block,
322            self.is_replaced(),
323            true, /* establishes_containing_block */
324            |padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),
325            |constraint_space| self.inline_content_sizes(layout_context, constraint_space),
326            |preferred_aspect_ratio| {
327                self.tentative_block_content_size(preferred_aspect_ratio, Au(0))
328            },
329        )
330    }
331
332    pub(crate) fn repair_style(
333        &mut self,
334        context: &SharedStyleContext,
335        node: &ServoLayoutNode,
336        new_style: &Arc<ComputedValues>,
337    ) {
338        self.base.repair_style(new_style);
339        match &mut self.contents {
340            IndependentFormattingContextContents::Replaced(_, widget) => {
341                if let Some(widget) = widget {
342                    let node = node
343                        .with_pseudo(PseudoElement::ServoAnonymousBox)
344                        .expect("Should always be able to construct info for anonymous boxes.");
345                    widget.borrow_mut().repair_style(context, &node, new_style);
346                }
347            },
348            IndependentFormattingContextContents::Flow(block_formatting_context) => {
349                block_formatting_context.repair_style(context, node, new_style);
350            },
351            IndependentFormattingContextContents::Flex(flex_container) => {
352                flex_container.repair_style(new_style)
353            },
354            IndependentFormattingContextContents::Grid(taffy_container) => {
355                taffy_container.repair_style(new_style)
356            },
357            IndependentFormattingContextContents::Table(table) => {
358                table.repair_style(context, new_style)
359            },
360        }
361    }
362
363    #[inline]
364    pub(crate) fn is_block_container(&self) -> bool {
365        matches!(self.contents, IndependentFormattingContextContents::Flow(_))
366    }
367
368    #[inline]
369    pub(crate) fn is_replaced(&self) -> bool {
370        matches!(
371            self.contents,
372            IndependentFormattingContextContents::Replaced(_, _)
373        )
374    }
375
376    #[inline]
377    pub(crate) fn is_table(&self) -> bool {
378        matches!(
379            &self.contents,
380            IndependentFormattingContextContents::Table(_)
381        )
382    }
383
384    #[inline]
385    pub(crate) fn is_grid(&self) -> bool {
386        matches!(
387            &self.contents,
388            IndependentFormattingContextContents::Grid(_)
389        )
390    }
391
392    #[servo_tracing::instrument(
393        name = "IndependentFormattingContext::layout_without_caching",
394        skip_all
395    )]
396    fn layout_without_caching(
397        &self,
398        layout_context: &LayoutContext,
399        positioning_context: &mut PositioningContext,
400        containing_block_for_children: &ContainingBlock,
401        containing_block: &ContainingBlock,
402        preferred_aspect_ratio: Option<AspectRatio>,
403        lazy_block_size: &LazySize,
404    ) -> IndependentFormattingContextLayoutResult {
405        match &self.contents {
406            IndependentFormattingContextContents::Replaced(replaced, widget) => {
407                let mut replaced_layout = replaced.layout(
408                    layout_context,
409                    containing_block_for_children,
410                    preferred_aspect_ratio,
411                    &self.base,
412                    lazy_block_size,
413                );
414                if let Some(widget) = widget {
415                    let mut widget_layout = widget.borrow().layout(
416                        layout_context,
417                        positioning_context,
418                        containing_block_for_children,
419                        containing_block_for_children,
420                        None,
421                        &LazySize::intrinsic(),
422                    );
423                    replaced_layout
424                        .fragments
425                        .append(&mut widget_layout.fragments);
426                }
427                replaced_layout
428            },
429            IndependentFormattingContextContents::Flow(bfc) => bfc.layout(
430                layout_context,
431                positioning_context,
432                containing_block_for_children,
433            ),
434            IndependentFormattingContextContents::Flex(fc) => fc.layout(
435                layout_context,
436                positioning_context,
437                containing_block_for_children,
438                lazy_block_size,
439            ),
440            IndependentFormattingContextContents::Grid(fc) => fc.layout(
441                layout_context,
442                positioning_context,
443                containing_block_for_children,
444                containing_block,
445            ),
446            IndependentFormattingContextContents::Table(table) => table.layout(
447                layout_context,
448                positioning_context,
449                containing_block_for_children,
450                containing_block,
451            ),
452        }
453    }
454
455    pub(crate) fn layout_and_is_cached(
456        &self,
457        layout_context: &LayoutContext,
458        positioning_context: &mut PositioningContext,
459        containing_block_for_children: &ContainingBlock,
460        containing_block: &ContainingBlock,
461        preferred_aspect_ratio: Option<AspectRatio>,
462        lazy_block_size: &LazySize,
463    ) -> (IndependentFormattingContextLayoutResult, bool) {
464        if let Some(cached_layout_result) = self
465            .base
466            .cached_independent_formatting_context_layout_if_applicable(
467                positioning_context,
468                containing_block_for_children,
469            )
470        {
471            return (cached_layout_result, true);
472        }
473
474        #[cfg(feature = "tracing")]
475        tracing::debug!(
476            name: "IndependentFormattingContext::layout cache miss",
477            required = ?containing_block_for_children.size,
478        );
479        let mut child_positioning_context = PositioningContext::default();
480        let result = self.layout_without_caching(
481            layout_context,
482            &mut child_positioning_context,
483            containing_block_for_children,
484            containing_block,
485            preferred_aspect_ratio,
486            lazy_block_size,
487        );
488        self.base.cache_independent_formatting_context_layout(
489            containing_block_for_children,
490            &child_positioning_context,
491            &result,
492        );
493        positioning_context.append(child_positioning_context);
494        (result, false)
495    }
496
497    pub(crate) fn layout(
498        &self,
499        layout_context: &LayoutContext,
500        positioning_context: &mut PositioningContext,
501        containing_block_for_children: &ContainingBlock,
502        containing_block: &ContainingBlock,
503        preferred_aspect_ratio: Option<AspectRatio>,
504        lazy_block_size: &LazySize,
505    ) -> IndependentFormattingContextLayoutResult {
506        self.layout_and_is_cached(
507            layout_context,
508            positioning_context,
509            containing_block_for_children,
510            containing_block,
511            preferred_aspect_ratio,
512            lazy_block_size,
513        )
514        .0
515    }
516
517    #[inline]
518    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
519        match &self.contents {
520            IndependentFormattingContextContents::Replaced(replaced, _) => {
521                replaced.layout_style(&self.base)
522            },
523            IndependentFormattingContextContents::Flow(fc) => fc.layout_style(&self.base),
524            IndependentFormattingContextContents::Flex(fc) => fc.layout_style(),
525            IndependentFormattingContextContents::Grid(fc) => fc.layout_style(),
526            IndependentFormattingContextContents::Table(fc) => fc.layout_style(None),
527        }
528    }
529
530    #[inline]
531    pub(crate) fn preferred_aspect_ratio(
532        &self,
533        padding_border_sums: &LogicalVec2<Au>,
534    ) -> Option<AspectRatio> {
535        match &self.contents {
536            IndependentFormattingContextContents::Replaced(replaced, _) => {
537                replaced.preferred_aspect_ratio(self.style(), padding_border_sums)
538            },
539            // TODO: support preferred aspect ratios on non-replaced boxes.
540            _ => None,
541        }
542    }
543
544    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
545        match &self.contents {
546            IndependentFormattingContextContents::Replaced(_, widget) => {
547                if let Some(widget) = widget {
548                    widget.borrow_mut().base.parent_box.replace(layout_box);
549                }
550            },
551            IndependentFormattingContextContents::Flow(contents) => {
552                contents.attached_to_tree(layout_box)
553            },
554            IndependentFormattingContextContents::Flex(contents) => {
555                contents.attached_to_tree(layout_box)
556            },
557            IndependentFormattingContextContents::Grid(contents) => {
558                contents.attached_to_tree(layout_box)
559            },
560            IndependentFormattingContextContents::Table(contents) => {
561                contents.attached_to_tree(layout_box)
562            },
563        }
564    }
565}
566
567impl ComputeInlineContentSizes for IndependentFormattingContextContents {
568    fn compute_inline_content_sizes(
569        &self,
570        layout_context: &LayoutContext,
571        constraint_space: &ConstraintSpace,
572    ) -> InlineContentSizesResult {
573        match self {
574            Self::Replaced(inner, _) => {
575                inner.compute_inline_content_sizes(layout_context, constraint_space)
576            },
577            Self::Flow(inner) => inner
578                .contents
579                .compute_inline_content_sizes(layout_context, constraint_space),
580            Self::Flex(inner) => {
581                inner.compute_inline_content_sizes(layout_context, constraint_space)
582            },
583            Self::Grid(inner) => {
584                inner.compute_inline_content_sizes(layout_context, constraint_space)
585            },
586            Self::Table(inner) => {
587                inner.compute_inline_content_sizes(layout_context, constraint_space)
588            },
589        }
590    }
591}