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